20 KiB
A 股 Deep Research 私有投研平台 — 用户手册
版本:v1.0 | 最后更新:2026-06-17
目录
1. 项目概述
A 股 Deep Research 是一个私有化投研平台,自动完成财经新闻抓取 → 正文提取 → 事件抽取 → 向量化 → 语义检索的完整链路,最终通过 MCP 协议接入 Cherry Studio / Claude Code 等 AI 客户端,实现"用自然语言提问,获得结构化研究报告"。
核心数据流:
财经网站 → 抓取(HTML+Markdown) → 正文提取 → 去重
→ LLM 抽取(股票/公司/行业/情绪/事件类型)
→ Embedding 向量化(1024维)
→ Qdrant 知识库(语义检索,2ms 级)
→ MCP 服务 → AI Agent 深度研究
技术栈:Python 3.11 / Crawl4AI / GNE / DeepSeek / DashScope / Qdrant / APScheduler / MCP
部署位置:树莓派 5(ARM64),/home/pi/news/
2. 环境准备
2.1 前置条件
- Python 3.11
- uv(包管理器):
brew install uv(macOS) 或curl -LsSf https://astral.sh/uv/install.sh | sh(Linux) - Playwright 浏览器(仅 M1 抓取需要):
uv run python -m playwright install chromium
2.2 获取代码
git clone <repo> /home/pi/news
cd /home/pi/news
2.3 安装依赖
uv sync
2.4 配置环境变量
cp .env.example .env
# 编辑 .env,填写:
# DEEPSEEK_API_KEY=sk-xxx (M4 LLM 调用)
# DASHSCOPE_API_KEY=sk-xxx (M5/M8 嵌入调用)
2.5 验证安装
uv run pytest -m "not integration" # 应显示 186 passed
2.6 统一 CLI a-share
所有操作通过 a-share 命令完成,替代之前的 8 个脚本入口:
# 模块操作
uv run a-share crawl # M1 抓取全部源
uv run a-share crawl --source cls # 单源
uv run a-share extract --date 20260616 # M2 提取
uv run a-share dedup --date 20260616 # M3 去重
uv run a-share events --date 20260616 # M4 LLM 抽取
uv run a-share events --provider qwen # 切换 LLM
uv run a-share embed --date 20260616 # M5 向量化
uv run a-share ingest --date 20260616 # M6 入库
# 全链路
uv run a-share pipeline --once # 立即执行一次全链路
# 检索(直接查 Qdrant,无需写代码或配 Cherry Studio)
uv run a-share search "宁德时代固态电池"
uv run a-share search "政策" --source cls --sentiment positive
uv run a-share search "风险" --stock 001212 --min-importance 3
# 状态总览
uv run a-share status
子命令速查:
| 命令 | 功能 | 常用参数 |
|---|---|---|
a-share crawl |
M1 抓取 | --source cls |
a-share extract |
M2 提取 | --date 20260616 |
a-share dedup |
M3 去重 | --date 20260616 --reset |
a-share events |
M4 LLM 抽取 | --provider qwen --limit 5 |
a-share embed |
M5 向量化 | --model text-embedding-v3 |
a-share ingest |
M6 入库 | --date 20260616 --recreate |
a-share pipeline |
全链路 | --once --report |
a-share discover |
站点分析 | --name 中文名 --add --extra URL |
a-share add-entry |
追加入口 | source_id url1 url2 ... |
a-share report |
生成日报 | --date 20260616 --no-upload |
a-share search |
检索知识库 | --source --stock --sentiment |
a-share status |
数据总览 | 无参数 |
3. 全链路一键运行
一条命令跑通 M1→M6:
uv run a-share pipeline --once
(等效于旧版 uv run python -m scripts.run_scheduler --once,a-share 命令更短更好记)
uv run python -m scripts.run_scheduler --once
全链路耗时约 4-5 分钟(100 篇文章):
| 步骤 | 耗时 | 说明 |
|---|---|---|
| crawler | ~3 分钟 | 含浏览器渲染,最耗时 |
| extractor | ~24 秒 | GNE 正文提取 |
| dedup | ~2 秒 | 三层去重 |
| llm | ~45 秒 | DeepSeek 事件抽取 |
| embedding | ~10 秒 | DashScope 向量化 |
| qdrant | ~4 秒 | 写入知识库 |
产物路径:
data/raw/— 原始 HTML + Markdown(M1)data/processed/— 提取后的 Article(M2)data/deduped/— 去重后唯一文章(M3)data/events/— 结构化投资事件(M4)data/embeddings/— 1024 维向量(M5)data/qdrant_storage/— Qdrant 知识库(M6)
只执行部分步骤:
uv run python -m scripts.run_scheduler --once --steps crawler,llm,qdrant
4. 分模块使用
M1 新闻抓取
从 5 个 A 股财经源抓取文章。
# 抓取全部启用源
uv run python -m scripts.run_crawler
# 抓取单个源
uv run python -m scripts.run_crawler --source cls
# 试跑不写文件
uv run python -m scripts.run_crawler --no-save
配置文件:configs/sources.yaml
当前支持的源:
| ID | 名称 | JS 渲染 | 说明 |
|---|---|---|---|
| cls | 财联社 | 是 | 深度频道 |
| eastmoney | 东方财富 | 是 | 首页 |
| sina | 新浪财经 | 否 | 静态页面,速度快 |
| stcn | 证券时报 | 是 | 要闻列表 |
| yicai | 第一财经 | 是 | 新闻频道 |
新增新闻源:一条命令完成分析+配置:
# 自动分析 + 自动写入 sources.yaml(推荐)
uv run a-share discover https://wallstreetcn.com/news/global --name 华尔街见闻 --add
# 仅预览,不写入
uv run a-share discover https://example.com/news/
discover 自动完成 7 步:
- Crawl4AI 抓取首页(自动判断 JS 渲染)
- 提取所有站内
<a href>链接 - 过滤导航/功能页(about/download/member 等)
- 按 URL 路径模式聚类
- 数字 ID 模式优先排序(文章特征)
- 自动生成正则
- 输出 yaml 配置 +
--add自动追加到sources.yaml
--add 写入时自动:空行分隔、序号递增、# ---- N. 名称 ---- 注释头。
验证新源:
uv run a-share crawl --source wallstreetcn
同一网站多个入口:使用 --extra 添加额外频道,共用同一个正则:
uv run a-share discover https://wallstreetcn.com/news/global \
--extra https://wallstreetcn.com/news/china \
--extra https://wallstreetcn.com/news/tech \
--name 华尔街见闻 --add
生成的 yaml 自动包含 extra_homepages 字段。抓取时依次访问所有入口,自动去重。
为已有源追加额外入口(无需重新 discover):
uv run a-share add-entry wallstreetcn https://xxx.com/news/新频道
自动去重:已存在的 URL 跳过。
M2 正文提取
从 M1 抓取的 HTML 中提取标准化 Article(title/content/时间/作者)。
# 处理今日所有源
uv run python -m scripts.run_extractor
# 指定日期
uv run python -m scripts.run_extractor --date 20260616
# 单源
uv run python -m scripts.run_extractor --source sina --date 20260616
输入:data/raw/{source}/{YYYYMMDD}/*.html
输出:data/processed/{source}/{YYYYMMDD}/{url_hash}.json
输出格式(Article):
{
"source_id": "sina",
"url": "https://finance.sina.com.cn/...",
"url_hash": "abc123...",
"title": "宁德时代发布新一代麒麟电池",
"content": "财联社6月15日电...",
"publish_time": "2026-06-15T14:30:00",
"author": "记者 张三",
"word_count": 1234
}
M3 三层去重
对 M2 输出做 URL Hash → 内容 Hash → SimHash 三层去重,指纹持久化到 SQLite。
# 处理今日
uv run python -m scripts.run_dedup
# 指定日期
uv run python -m scripts.run_dedup --date 20260616
# 重建指纹库
uv run python -m scripts.run_dedup --reset
# 调 SimHash 阈值(默认 3)
uv run python -m scripts.run_dedup --simhash-threshold 5
输入:data/processed/{source}/{YYYYMMDD}/*.json
输出:
data/deduped/{YYYYMMDD}/uniques/{url_hash}.json— 唯一文章data/deduped/{YYYYMMDD}/duplicates.jsonl— 重复记录data/dedup/fingerprints.sqlite3— 指纹库(跨日累积)
M4 投资事件抽取
调用 DeepSeek/Qwen 从去重文章中抽取结构化投资事件。
# DeepSeek(默认)
uv run python -m scripts.run_event_extraction --date 20260616
# Qwen(百炼)
uv run python -m scripts.run_event_extraction --provider qwen --model qwen-plus
# 联调小批量
uv run python -m scripts.run_event_extraction --limit 5
# 调整并发(默认 3)
uv run python -m scripts.run_event_extraction --concurrency 5
输入:data/deduped/{YYYYMMDD}/uniques/*.json
输出:data/events/{YYYYMMDD}/{url_hash}.json
输出格式(ExtractedEvent):
{
"source_id": "cls",
"url": "https://...",
"title": "宁德时代签订100GWh供货协议",
"event": {
"stock_codes": ["300750.SZ"],
"company_names": ["宁德时代"],
"industries": ["动力电池"],
"sentiment": "positive",
"importance": 5,
"event_type": "重大合同",
"summary": "宁德时代签5年100GWh协议,金额超1500亿"
},
"provider": "deepseek",
"model": "deepseek-chat"
}
23 种事件类型:业绩预告/业绩快报/财报披露/合作签约/投资并购/重大合同/产品发布/技术突破/监管处罚/诉讼仲裁/股东减持/股东增持/回购/分红/高管变动/资产重组/停牌复牌/ST警示/退市风险/宏观政策/行业政策/国际局势/其他
M5 向量化
将 M4 事件文本用 DashScope text-embedding-v3(1024 维)或本地 BGE-M3 向量化。
# DashScope(默认)
uv run python -m scripts.run_embedding --date 20260616
# 强制覆盖模型
uv run python -m scripts.run_embedding --provider dashscope --model text-embedding-v3
# 本地 BGE-M3(需先 uv sync --extra local-embedding)
uv run python -m scripts.run_embedding --provider local-bge
输入:data/events/{YYYYMMDD}/*.json
输出:data/embeddings/{YYYYMMDD}/{url_hash}.json(含 1024 维向量)
cninfo 公告抓取
cninfo(巨潮资讯网)是独立的 A 股公告抓取管道,与新闻抓取分开调度(每天 08:00)。
全链路(一条命令, watchlist 模式):
uv run a-share pipeline --cninfo-once
包含 9 步: 公告抓取(关注公司) → 调研筛选 → 互动问答 → 正文提取 → PDF 富化 → 去重 → LLM 抽取 → 向量化 → Qdrant 入库。
单独操作:
uv run a-share cninfo --watchlist # 仅公告(关注公司)
uv run a-share cninfo --research # 仅调研(关注公司)
uv run a-share cninfo --irm # 仅互动问答(关注公司)
uv run a-share cninfo --enrich-pdf # PDF→Markdown
**分步操作**:
```bash
# 抓取公告
uv run a-share cninfo --days 1
uv run a-share cninfo --start 2026-06-17 --end 2026-06-19 --max-pages 50
# PDF 正文 (MarkItDown → Markdown)
uv run a-share cninfo --enrich-pdf --pdf-limit 50
个股日报(关注列表)
为 watchlist 中每家公司生成综合日报:
uv run a-share stock-report
报告内容: AI 要点分析 / 近 7 日公告 / 相关新闻 / 互动问答。
上传路径: echart/research/{YYYYMMDD}/{代码}_{名称}_个股日报_{日期}.html
公告关注列表
只关注特定公司公告,日报中高亮显示:
# 管理关注列表
uv run a-share watchlist add 300750 宁德时代 --note "动力电池龙头"
uv run a-share watchlist add 000001 平安银行
uv run a-share watchlist list # 查看关注列表
uv run a-share watchlist remove 000001 # 移除
# 只抓关注公司公告
uv run a-share cninfo --watchlist
# 日报效果: 关注公司事件置顶 + ⭐ 标记
关注列表存储在 configs/watchlist.yaml。
.env 配置:
| 变量 | 默认 | 说明 |
|---|---|---|
CNINFO_ENABLED |
true |
是否启用 |
CNINFO_DAYS_BACK |
1 |
每次抓最近 N 天 |
CNINFO_MAX_PAGES |
20 |
最大页数(×30=600 条/次) |
CNINFO_API_BASE |
http://www.cninfo.com.cn/new |
API 地址 |
CNINFO_PDF_BASE |
http://static.cninfo.com.cn |
PDF 地址 |
.env 注意: 值后面不能跟 # 注释。python-dotenv 会错误地把 # 后内容当成值。注释必须独占一行。
M6 Qdrant 入库与检索
将 M5 向量 + M4 事件标签写入 Qdrant 本地知识库。
入库:
# 默认本地文件模式(无需 Docker)
uv run python -m scripts.run_qdrant_ingest --date 20260616
# 重建 collection
uv run python -m scripts.run_qdrant_ingest --recreate
Python 检索:
from vectorstore import VectorStore, SearchFilter, make_qdrant_client
c = make_qdrant_client()
store = VectorStore(c)
# 基础语义检索
hits = store.query(query_vector=probe["vector"], top_k=10)
for h in hits:
print(h.short_summary())
# 结构化过滤
hits = store.query(
query_vector=probe["vector"],
top_k=10,
filter=SearchFilter(
source_id="cls", # 按源
importance_min=3, # 影响程度 >= 3
sentiment="positive", # 利好
stock_codes=["300750"], # 按股票代码
industries=["动力电池"], # 按行业
publish_date_from="2026-06-01", # 时间范围
),
)
store.close()
Qdrant 部署说明见附录 A。
5. 定时任务部署
将 M1→M6 全链路注册为系统服务,每天 7:00 / 12:00 / 18:00 / 22:00 自动执行。
安装(一次性)
sudo cp scripts/a-share-research.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable a-share-research
日常操作
sudo systemctl start a-share-research # 启动
sudo systemctl stop a-share-research # 停止
sudo systemctl restart a-share-research # 重启
sudo systemctl status a-share-research # 查看状态(含 PID)
# 查看日志
journalctl -u a-share-research -f # 实时系统日志
tail -f logs/scheduler.log # 文件日志
修改执行时间和配置:
# 编辑 .env 中的 SCHEDULE_TIMES
nano /home/pi/news/.env
# 格式: HH:MM,HH:MM,... (24 小时制,逗号分隔)
# 例: SCHEDULE_TIMES=08:00,14:00,20:00
# 重启服务使新配置生效
sudo systemctl restart a-share-research
# 验证新配置
grep "已注册定时任务" /home/pi/news/logs/scheduler.log | tail -4
6. MCP 服务与 AI Agent
6.1 连接 Cherry Studio
- 打开 Cherry Studio → 设置 → MCP 服务器 → 添加
- 填入:
{
"mcpServers": {
"a-share-research": {
"command": "uv",
"args": ["run", "python", "-m", "scripts.run_mcp_server"],
"cwd": "/home/pi/news"
}
}
}
- 点击启用。Cherry Studio 会自动启动 MCP 服务进程(stdio 模式)。
6.2 连接 Claude Code
编辑项目根目录 .mcp.json:
{
"mcpServers": {
"a-share-research": {
"command": "uv",
"args": ["run", "python", "-m", "scripts.run_mcp_server"],
"cwd": "/home/pi/news"
}
}
}
6.3 5 个 MCP 工具
| 工具 | 使用示例 |
|---|---|
search_news |
search_news("宁德时代固态电池进展") |
search_company_news |
search_company_news("价格调整", company="贵州茅台") |
search_industry_news |
search_industry_news("最新政策", industry="半导体") |
search_stock_events |
search_stock_events("重大事件", stock_code="300750") |
search_sentiment_trend |
search_sentiment_trend("AI算力", sentiment="all") |
6.4 调试(HTTP SSE 模式)
uv run python -m scripts.run_mcp_server --sse 8765
# 浏览器访问 http://<host>:8765/sse
6.5 工作原理
用户输入自然语言查询
→ DashScope 嵌入(1024 维)
→ Qdrant 余弦相似度检索(2.3ms)
→ 按过滤条件筛选(公司/行业/代码/情绪)
→ Markdown 格式化返回
7. 投研分析模板
在 Cherry Studio 中粘贴 docs/agent_prompt.md 的内容作为 System Prompt,然后可以使用以下提问模式:
公司深度分析
分析宁德时代(300750)近 30 天的利好利空变化,并给出主要投资逻辑演化过程。
行业分析
分析动力电池行业最近的政策动向和竞争格局变化。
风险排查
帮我梳理中旗新材(001212)近期面临的主要风险点。
事件追踪
过去一周 A 股市场有哪些重大监管处罚事件?
情绪趋势
近一个月 AI 算力概念的情绪变化趋势如何?
8. 附录
A. Qdrant 部署模式
本项目支持两种 Qdrant 运行模式:
| 本地文件模式(默认) | Docker Server 模式 | |
|---|---|---|
| 类比 | SQLite | PostgreSQL |
| 进程 | 无独立进程 | 容器 a_share_qdrant |
| 端口 | 不监听 | 6333/6334 |
| 并发 | 单进程 | 多客户端 |
| 数据位置 | data/qdrant_storage/ |
Docker volume |
| 适用场景 | 树莓派/单机 | x86 生产/Cherry Studio 远程 |
| ARM64 兼容 | ✅ | ❌(jemalloc 16K 页崩溃) |
B. 环境变量完整列表
| 变量 | 默认值 | 说明 |
|---|---|---|
LLM_PROVIDER |
deepseek |
M4 LLM 提供商 |
DEEPSEEK_API_KEY |
- | DeepSeek API Key |
DASHSCOPE_API_KEY |
- | 百炼 API Key(M5/M8) |
EMBEDDING_PROVIDER |
dashscope |
M5 嵌入提供商 |
SCHEDULE_TIMES |
07:00,12:00,18:00,22:00 |
M7 定时时间 |
QDRANT_COLLECTION |
a_share_news |
M6 Collection 名 |
LOG_LEVEL |
INFO |
日志级别 |
C. 日志文件
| 文件 | 对应模块 |
|---|---|
logs/crawler.log |
M1 抓取 |
logs/extractor.log |
M2 提取 |
logs/dedup.log |
M3 去重 |
logs/llm.log |
M4 LLM |
logs/embedding.log |
M5 向量化 |
logs/qdrant.log |
M6 入库 |
logs/scheduler.log |
M7 调度 |
logs/mcp_server.log |
M8 MCP |
日志自动轮转(10MB/文件,保留 5 份)。
D. 项目目录结构
news/
├── crawler/ M1 新闻抓取
├── extractor/ M2 正文提取
├── dedup/ M3 三层去重
├── llm/ M4 事件抽取
├── embedding/ M5 向量化
├── vectorstore/ M6 Qdrant 客户端
├── scheduler/ M7 定时任务
├── mcp_server/ M8 MCP 服务
├── configs/ 配置文件(sources.yaml)
├── prompts/ LLM Prompt 模板
├── docs/ 文档与 Agent Prompt
├── scripts/ 运行脚本(每个模块一个入口)
├── tests/ pytest 测试
├── data/ 数据产物(各层按日期组织)
├── logs/ 运行日志
├── pyproject.toml 依赖定义
└── .env.example 环境变量模板
E. 常见问题
Q:M1 某源抓取 0 篇文章?
编辑 configs/sources.yaml,检查该源的 article_url_pattern 正则是否匹配真实链接格式。可先用 uv run python -m scripts.run_crawler --source <id> 单源调试。
Q:M2 提取的正文不是新闻内容(而是"郑重声明"等模板文本)?
这是已知的 M2.2 模板兜底问题,提取器已内置关键词黑名单自动过滤。若遇到新模板,可在 extractor/parser.py 的 _BOILERPLATE_PATTERNS 追加。
Q:M4 调用 LLM 报错?
检查 .env 中 DEEPSEEK_API_KEY 是否填写。可用 --provider qwen 切换到百炼测试。
Q:Qdrant 搜索不到结果?
检查是否已入库:uv run python -c "from vectorstore import VectorStore, make_qdrant_client; c=make_qdrant_client(); s=VectorStore(c); print(s.count()); s.close()"。若 count=0,运行 uv run python -m scripts.run_qdrant_ingest --date <date> 入库。
Q:如何从零重建整个知识库?
rm -rf data/raw data/processed data/dedup data/deduped data/events data/embeddings data/qdrant_storage
uv run python -m scripts.run_scheduler --once
—— 用户手册结束 ——