

有同学问我怎么用AI进行新闻挖掘A股概念股, 这里写一写。
在A股市场混,最怕的是什么?不是大盘跳水,而是 “消息跟不上” 和**“逻辑理不清”**。
你一定经历过这样的场景:早盘某条新闻刷屏——比如“高层推进新型工业化”,你看着这几个字发呆,心里想:这到底是利好机器人?还是利好工业软件?还是利好数控机床?等你还在纠结概念板块的时候,资金已经把龙头封死了。
信息差,就是交易的核心壁垒。
为了消灭这个信息差,今天我利用 Streamlit + Akshare + LangChain + 智谱 GLM-4,写了一个 “AI 财经新闻概念挖掘终端” 。它能自动读取实时新闻,并在 1 秒钟内提炼出核心概念,甚至直接给出相关的龙头股代码。
今天就来复盘这个项目的核心逻辑与代码实现。
一、 需求起源:从“人肉阅读”到“AI 推理”
传统的看新闻方式属于“人肉过滤”。我们需要打开财联社、同花顺,一条条看,还要在脑子里检索知识库:
这个过程效率极低,且高度依赖个人的知识储备。
而大模型(LLM)最擅长的就是语义理解与逻辑推理。我的目标很简单:让 AI 帮我读新闻,然后把“新闻文本”直接转化为“交易逻辑”。
为了把这个想法落地,我设计了三层架构:
akshare 获取A股的实时财经新闻数据。streamlit 快速构建一个左侧列表、右侧详情的 Web 界面,模拟专业交易终端。langchain 调用智谱 AI 的 glm-4-flash 模型,进行概念提取与个股映射。
做量化,数据是灵魂。Akshare 是目前开源界最好的财经数据接口之一。在代码中,我们只需要一行函数就能获取最新的财经新闻:
def get_news_data():
return ak.stock_info_global_cls()这个函数返回的 DataFrame 包含了标题、发布时间、内容等字段,构成了我们分析的原始素材。
虽然用命令行(CLI)也能跑,但为了沉浸式的体验,我使用了 Streamlit。关键点在于利用 st.session_state 来记录用户点击的是哪一条新闻,从而实现左侧点击、右侧刷新的联动效果。
这是整个项目最核心的部分。如何让 AI 不仅仅是“总结新闻”,而是去“挖掘概念”?
我设计了一个结构化的 Prompt(提示词):
prompt = ChatPromptTemplate.from_messages([
("system", "你是一位专业的财经证券分析师。请阅读用户提供的财经新闻,完成以下任务:\n"
"1. **概念识别**:分析该新闻涉及的核心产业链概念(例如:Robotaxi, CPO, 创新药等)。\n"
"2. **个股挖掘**:根据概念,列出3-5只A股或港股中最相关的龙头个股名称,并用一句话解释关联理由。\n\n"
"输出格式请使用 Markdown,清晰分级。"),
("user", "新闻标题:{title}\n\n新闻内容:{content}\n\n请开始分析。")
])注意这里有两个技巧:
glm-4-flash。为什么选它?因为新闻分析对延迟很敏感,Flash 版本速度快且免费,完全能满足这种逻辑推理的需求,性价比极高。通过 chain = prompt | llm | StrOutputParser() 这一行经典的 LangChain 链式调用,数据就流动了起来。
写这个工具的初衷,并非是为了让 AI 直接代替我做交易决策。股市变幻莫测,只有逻辑才是恒定的。
这个工具的价值在于,它充当了交易员的“外骨骼”:
在量化与主观交易的融合道路上,Python + AI 已经成为了不可或缺的武器。如果你也对用代码武装投资感兴趣,不妨动手试试这段代码。
import streamlit as st
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
import akshare as ak
# ================= 配置部分 =================
st.set_page_config(
page_title="AI 财经新闻概念挖掘终端",
page_icon="📰",
layout="wide"
)
# ================= 数据获取层 =================
def get_news_data():
return ak.stock_info_global_cls()
def app():
# ================= 界面布局 =================
st.title("🤖 AI 新闻概念与个股挖掘")
api_key = 'XXXXXX'
# ================= 主程序逻辑 =================
# 加载数据
news_df = get_news_data()
# 初始化 Session State 用于存储选中的新闻
if 'selected_idx' not in st.session_state:
st.session_state.selected_idx = 0
# 布局:左侧新闻列表,右侧详情与分析
col_list, col_detail = st.columns([3, 7])
with col_list:
st.subheader("📰 实时新闻流")
# 显示新闻列表
for idx, row in news_df.iterrows():
# 简单的卡片样式
with st.container():
# 高亮选中项
border_color = "#2563eb" if idx == st.session_state.selected_idx else "#e2e8f0"
# 点击事件
if st.button(
f"**{row['标题']}**\n\n`{row['发布日期']} {row['发布时间']}`",
key=f"news_{idx}",
use_container_width=True,
help="点击查看分析"
):
st.session_state.selected_idx = idx
st.rerun() # 重新运行以更新右侧视图
st.markdown(f"<div style='border-bottom: 2px solid {border_color}; margin-bottom: 10px;'></div>",
unsafe_allow_html=True)
with col_detail:
# 获取当前选中的新闻
current_news = news_df.iloc[st.session_state.selected_idx]
st.markdown("---")
# 1. 展示新闻原文
st.subheader(f"📌 {current_news['标题']}")
st.caption(f"发布时间:{current_news['发布日期']} {current_news['发布时间']}")
st.info(current_news['内容'])
# 2. AI 分析按钮
st.markdown("### 🧠 AI 深度分析")
if st.button("✨ 开始分析:提取概念 & 挖掘个股", type="primary", use_container_width=True):
with st.spinner("GLM-4-Flash 正在阅读新闻并进行逻辑推理..."):
try:
# 初始化 LLM
llm = ChatOpenAI(
api_key=api_key,
base_url="https://open.bigmodel.cn/api/paas/v4/",
model="glm-4-flash",
temperature=0.3 # 降低温度以获得更精确的分析
)
# 构建 Prompt
prompt = ChatPromptTemplate.from_messages([
("system", "你是一位专业的财经证券分析师。请阅读用户提供的财经新闻,完成以下任务:\n"
"1. **概念识别**:分析该新闻涉及的核心产业链概念(例如:Robotaxi, CPO, 创新药等)。\n"
"2. **个股挖掘**:根据概念,列出3-5只A股或港股中最相关的龙头个股名称,并用一句话解释关联理由。\n\n"
"输出格式请使用 Markdown,清晰分级。"),
("user", "新闻标题:{title}\n\n新闻内容:{content}\n\n请开始分析。")
])
chain = prompt | llm | StrOutputParser()
# 调用模型
analysis_result = chain.invoke({
"title": current_news['标题'],
"content": current_news['内容']
})
# 展示结果
st.success("分析完成!")
st.markdown(analysis_result)
# 为了方便用户复制
with st.expander("查看原始输出 JSON"):
st.text(analysis_result)
except Exception as e:
st.error(f"分析过程出错: {e}")
st.error("请检查 API Key 或网络连接。")
if __name__ == "__main__":
#st.set_page_config(layout="wide")
app()如果我的分享对你有所帮助, 不吝啬给个点赞呗