Files
news/docs/user-guide.md
simon ff911cf6f7 feat: Token Plan 迁移与 .env 热加载,并修复日报 AI 摘要为空
Token Plan 迁移 / 配置热加载:
- configs/llm_models.yaml: 各场景切到 Token Plan(deepseek-v4.1-flash / qwen3.6-flash)
- 新增 configs/runtime_env.py: .env 按 (mtime_ns, size) 热加载并同步 os.environ,
  统一 env_get 取值;llm / embedding / vectorstore / mcp / pipeline 改用 env_get
- configs/loader.py / scripts/run_scheduler.py 等配套调整
- 新增 tests/test_hot_reload.py

日报 AI 摘要为空修复(2026-09-25):
- 根因: 推理模型的 reasoning token 与正文共用 max_tokens, 预算 1500 被"思考"
  占满 -> text_tokens=0 / finish_reason=length, 摘要静默为空且不重试
- daily_report 场景新增 max_tokens(默认 4000, YAML 保存即热生效);
  LLMConfig 支持可选 max_tokens; 分块预算 800 -> 2000
- _llm_call 拆出 _call_once, 正文为空时自动加倍预算重试(上限 16000),
  用尽才降级返回空串; 网络异常重试语义不变
- docs/user-guide.md 新增 FAQ; continuation.md 记录本次排查
- 已重跑 2026-09-25 日报(report_id=357)补回 466 字摘要

测试: 相关用例 56 passed(test_hot_reload 12 passed);
      ruff 无新增问题; 3 个 crawler 既有失败与本改动无关
2026-09-25 11:13:37 +08:00

22 KiB
Raw Permalink Blame History

A 股 Deep Research 用户手册

版本:v2.0 | 最后更新:2026-08-22


目录

  1. 项目概述
  2. 环境准备
  3. 快速开始
  4. 统一 CLI 参考
  5. 全链路与 Pipeline
  6. 分模块使用
  7. 定时任务与 systemd
  8. MCP 服务
  9. 日报系统
  10. 环境变量参考
  11. 常见问题

1. 项目概述

A 股 Deep Research 是一个私有化 A 股投研辅助平台,自动完成:

财经网站抓取 → 正文提取 → 去重 → LLM 投资事件抽取 → 向量化 → 知识库检索 → MCP 服务

核心能力:

  • 从 14 个财经新闻源自动抓取(含 cninfo 公告)
  • 23 种投资事件类型自动抽取(利好/利空/重要度)
  • 1024 维语义向量检索(Qdrant 本地文件模式)
  • MCP 协议接入 Cherry Studio / Claude Code
  • 每日自动生成结构化日报并入库 MySQL

定位:研究辅助与知识管理平台,非交易系统、非预测工具、非投资顾问。

技术栈:Python 3.11 / Crawl4AI / GNE / DeepSeek / Qwen / DashScope / Qdrant / APScheduler / MCP


2. 环境准备

2.1 前置条件

  • Python 3.11
  • uv 包管理器
# macOS
brew install uv

# Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

2.2 安装

cd /home/pi/news
uv sync

# 可选:本地 BGE-M3 嵌入(离线,无需 API key)
uv sync --extra local-embedding

# 可选:Playwright 浏览器(仅 JS 渲染源抓取需要)
uv run python -m playwright install chromium

2.3 配置

cp .env.example .env
# 编辑 .env,至少填写:
#   DASHSCOPE_API_KEY=sk-xxx    (百炼 LLM + Embedding)
#   DEEPSEEK_API_KEY=sk-xxx     (DeepSeek LLM,可选)

2.4 验证

uv run pytest -m "not integration"   # 应显示 225+ passed
uv run a-share status                 # 查看数据状态

3. 快速开始

3.1 三条命令入门

# 1. 抓取全网新闻,跑通全链路
uv run a-share pipeline --once

# 2. 语义检索
uv run a-share search "宁德时代固态电池"

# 3. 查看数据总览
uv run a-share status

3.2 全链路 + 日报

# 跑全链路末尾生成日报
uv run a-share pipeline --once --report

# 或单独生成日报(已有数据时)
uv run a-share report --date 20260822

3.3 cninfo 公告管道

# 全链路:公告抓取 → 提取 → PDF → 去重 → LLM → 入库
uv run a-share pipeline --cninfo-once

# 或分步
uv run a-share cninfo                 # 只抓公告
uv run a-share cninfo --enrich-pdf    # 下载 PDF 补充正文

4. 统一 CLI 参考

全部操作通过 a-share 命令完成。子命令速查:

子命令 功能 常用参数
crawl M1 新闻抓取 --source cls
extract M2 正文提取 --date 20260822
dedup M3 三层去重 --date 20260822 --reset
events M4 LLM 事件抽取 --provider qwen --limit 5
embed M5 向量化 --provider local-bge
ingest M6 Qdrant 入库 --date 20260822 --recreate
pipeline 全链路/定时守护 --once --resume --report
search 语义检索 --source --stock --sentiment
status 数据总览 无参数
report 生成日报 --date 20260822
report-import 历史日报入库 --date 20260616 --type finance
cninfo 公告/调研/互动易 --enrich-pdf
stock-report 个股日报 --no-upload
watchlist 关注列表管理 add/remove/list
discover 站点分析 url --name --add
add-entry 追加多频道入口 source_id url...

4.1 a-share crawl — M1 抓取

uv run a-share crawl                     # 抓取全部启用源
uv run a-share crawl --source cls        # 单源调试
uv run a-share crawl --no-save           # 试跑,不写文件

4.2 a-share extract — M2 提取

uv run a-share extract --date 20260822            # 指定日期
uv run a-share extract --source sina --date ...   # 单源

产物:data/processed/{source}/{date}/{url_hash}.json (Article)

4.3 a-share dedup — M3 去重

uv run a-share dedup --date 20260822       # 增量去重
uv run a-share dedup --date 20260822 --reset  # 重建指纹库

产物:data/deduped/{date}/uniques/*.json + data/dedup/fingerprints.sqlite3

4.4 a-share events — M4 LLM 抽取

uv run a-share events --date 20260822                    # 默认读 configs/llm_models.yaml
uv run a-share events --provider qwen --model qwen-plus  # 临时覆盖
uv run a-share events --limit 5                          # 小批量调试
uv run a-share events --concurrency 5                    # 调并发(默认 3)

产物:data/events/{date}/{url_hash}.json (ExtractedEvent)

4.5 a-share embed — M5 向量化

uv run a-share embed --date 20260822
uv run a-share embed --provider local-bge                # 本地 BGE-M3

产物:data/embeddings/{date}/{url_hash}.json (1024 维向量)

4.6 a-share ingest — M6 入库

uv run a-share ingest --date 20260822
uv run a-share ingest --recreate                         # 重建 collection

4.7 a-share search — 语义检索

# 基础检索
uv run a-share search "宁德时代固态电池"

# 结构化过滤
uv run a-share search "政策" --source cls --sentiment positive
uv run a-share search "减持" --stock 300750 --min-importance 3
uv run a-share search "芯片" --industry 半导体 --top 5

4.8 a-share status — 数据总览

uv run a-share status

输出各层统计(M1→M6 数据量) + 今日高重要度事件 TOP 10 + systemd 服务状态。

4.9 a-share discover — 站点分析

# 自动分析首页,输出 yaml 建议
uv run a-share discover https://wallstreetcn.com/news/global

# 自动写入 sources.yaml
uv run a-share discover https://example.com/news --name 某某财经 --add

# 多频道入口
uv run a-share discover https://example.com/news \
  --extra https://example.com/tech \
  --extra https://example.com/market \
  --name 某某财经 --add

4.10 a-share add-entry — 追加入口

uv run a-share add-entry wallstreetcn https://xxx.com/news/新频道

4.11 a-share watchlist — 关注列表

uv run a-share watchlist add 300750 宁德时代 --note "动力电池龙头"
uv run a-share watchlist list
uv run a-share watchlist remove 000001

关注列表存储在 configs/watchlist.yaml,用于 cninfo 公告过滤和个股日报。

4.12 a-share cninfo — 公告管道

uv run a-share cninfo                  # 抓取关注公司公告+调研+互动易
uv run a-share cninfo --enrich-pdf     # 下载 PDF 补充正文
uv run a-share cninfo --pdf-limit 50   # 限制 PDF 处理数量

4.13 a-share stock-report — 个股日报

uv run a-share stock-report             # 为关注列表中每家公司生成日报
uv run a-share stock-report --no-upload # 仅生成,不上传

4.14 a-share report — 日报生成

uv run a-share report --date 20260822   # 生成指定日期日报并入库 MySQL

日报内容:新闻联播摘要 + 高重要度新闻 + 公告调研 + AI 要点分析。

4.15 a-share report-import — 历史日报导入

uv run a-share report-import                        # 全量导入(幂等)
uv run a-share report-import --date 20260616        # 只导入指定日期
uv run a-share report-import --type intl            # 只导入 intl 日报
uv run a-share report-import --force                # 覆盖已存在

5. 全链路与 Pipeline

5.1 一次性执行

# 全链路 M1→M6
uv run a-share pipeline --once

# 全链路 + 日报
uv run a-share pipeline --once --report

# 指定步骤
uv run a-share pipeline --once --steps crawler,extractor,dedup

# cninfo 全链路
uv run a-share pipeline --cninfo-once

5.2 增量处理与断点续跑

增量跳过:M2/M4/M5 产物已存在时自动跳过,不重复调用 API/计费。使用 --force 参数可强制全量重建。

# pipeline 断点续跑:从上次失败步骤继续
uv run a-share pipeline --once --resume

# 断点续跑 + 日报(中断后直接重跑同一条命令即可)
uv run a-share pipeline --once --resume --report

断点状态文件:data/pipeline/state.json(按日期隔离,记录每步骤 ok/failed + 耗时)

5.3 全链路耗时参考(100 篇文章)

步骤 耗时 说明
crawler ~3 min 含 Playwright 浏览器渲染
xwlb ~1 s 新闻联播 API
extractor ~24 s GNE 正文提取
dedup ~2 s 三层去重
llm ~45 s DeepSeek 事件抽取
embedding ~10 s DashScope 向量化
qdrant ~4 s 写入知识库

5.4 步骤超时配置

优先级:TIMEOUT_{NAME} 环境变量 > PIPELINE_STEP_TIMEOUT > 硬编码默认值

# .env 中设置
PIPELINE_STEP_TIMEOUT=1800    # 全局兜底
TIMEOUT_CRAWLER=900           # 单步精确控制
TIMEOUT_LLM=900
TIMEOUT_DEDUP=300

6. 分模块使用

6.1 M1 — 新闻抓取

新闻源:14 个(13 Web + 1 API:新闻联播),配置文件 configs/sources.yaml

抓取分流:

条件 引擎 特点
js_render=false 且无 wait_for httpx 直连 快,不受反爬影响
js_render=true 或有 wait_for Playwright 支持 JS 渲染

新增新闻源:

# 自动分析 → 输出 yaml 建议
uv run a-share discover https://example.com/news

# 自动分析 + 写入 sources.yaml
uv run a-share discover https://example.com/news --name 某某财经 --add

# 验证新源
uv run a-share crawl --source example

为已有源追加多频道入口:

uv run a-share add-entry wallstreetcn https://xxx.com/news/china https://xxx.com/news/tech

6.2 M2 — 正文提取

输入:data/raw/{source}/{date}/*.html 输出:data/processed/{source}/{date}/{url_hash}.json (Article)

uv run a-share extract --date 20260822
uv run a-share extract --source cls --date 20260822

6.3 M3 — 三层去重

层 算法 说明
L1 URL Hash 完全相同 URL
L2 Content Hash 标准化正文完全一致
L3 SimHash 汉明距离 ≤ 3(默认)
uv run a-share dedup --date 20260822
uv run a-share dedup --reset    # 重建指纹库

6.4 M4 — 投资事件抽取

23 种事件类型:业绩预告/业绩快报/财报披露/合作签约/投资并购/重大合同/产品发布/技术突破/监管处罚/诉讼仲裁/股东减持/股东增持/回购/分红/高管变动/资产重组/停牌复牌/ST警示/退市风险/宏观政策/行业政策/国际局势/其他

LLM 配置:configs/llm_models.yaml(4 场景独立配置),支持 DeepSeek 和 Qwen

uv run a-share events --date 20260822
uv run a-share events --provider qwen --model qwen-plus
uv run a-share events --limit 5 --concurrency 3

6.5 M5 — 向量化

两种模式:

模式 命令 要求
DashScope 远程 默认 DASHSCOPE_API_KEY
本地 BGE-M3 --provider local-bge uv sync --extra local-embedding
uv run a-share embed --date 20260822
uv run a-share embed --provider local-bge --date 20260822

6.6 M6 — Qdrant 知识库

默认本地文件模式(零依赖,ARM64 兼容),数据在 data/qdrant_storage/

uv run a-share ingest --date 20260822
uv run a-share ingest --recreate    # 重建 collection

Python 检索示例:

from vectorstore import VectorStore, SearchFilter, make_qdrant_client

c = make_qdrant_client()
store = VectorStore(c)

hits = store.query(
    query_vector=my_vector,
    top_k=10,
    filter=SearchFilter(
        source_id="cls",
        importance_min=3,
        sentiment="positive",
        stock_codes=["300750"],
    ),
)
store.close()

6.7 cninfo 公告管道

cninfo(巨潮资讯网)是独立的 A 股公告/调研/互动易抓取管道,与新闻抓取分开调度。

# 全链路一条命令
uv run a-share pipeline --cninfo-once

# 分步操作
uv run a-share cninfo                 # 只抓公告(关注公司)
uv run a-share cninfo --enrich-pdf    # PDF 正文补充
uv run a-share extract --source cninfo --date 20260822  # 正文提取

7. 定时任务与 systemd

7.1 安装 systemd 服务

sudo cp scripts/a-share-research.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable a-share-research

7.2 日常操作

sudo systemctl start a-share-research        # 启动
sudo systemctl stop a-share-research         # 停止
sudo systemctl restart a-share-research      # 重启
sudo systemctl status a-share-research       # 查看状态

# 查看日志
journalctl -u a-share-research -f            # 实时系统日志
tail -f logs/scheduler.log                   # 文件日志

7.3 调度时间表

时间 步骤
06:30 cninfo 公告管道
07:00 crawler→xwlb→extractor→dedup→llm→embedding→qdrant→日报
07:30 个股日报
12:00 crawler→xwlb→extractor→dedup→llm→embedding→qdrant
18:00 crawler→xwlb→extractor→dedup→llm→embedding→qdrant
22:00 crawler→xwlb→extractor→dedup→llm→embedding→qdrant

7.4 修改调度时间(无需重启)

# 编辑 .env 中的 SCHEDULE_TIMES,格式: HH:MM,HH:MM,...
nano /home/pi/news/.env
# 例: SCHEDULE_TIMES=08:00,14:00,20:00

# 保存即生效:常驻调度器每 30s 比对一次,自动重新注册定时任务
# 日志确认:grep "定时任务已同步" /home/pi/news/logs/scheduler.log

配置热加载:.env(模型 / Key / 端点 / 超时 / DB / 调度时间)与 configs/llm_models.yaml(各场景 provider / model / 温度)都是改完保存即生效, 不需要 systemctl restart。调度时间最长 30s 生效,其余配置约 2s 生效。

只有两种情况需要重启:

  1. 部署/更新了 Python 代码本身;
  2. 正在执行中的那一步(子进程)会继续用旧配置跑完,下一步才用新配置。

7.5 前台守护模式(调试)

uv run a-share pipeline
# Ctrl+C 退出

8. MCP 服务

8.1 连接 Cherry Studio

Cherry Studio → 设置 → MCP 服务器 → 添加:

{
  "mcpServers": {
    "a-share-research": {
      "command": "uv",
      "args": ["run", "python", "-m", "scripts.run_mcp_server"],
      "cwd": "/home/pi/news"
    }
  }
}

8.2 连接 Claude Code

编辑项目根目录 .mcp.json:

{
  "mcpServers": {
    "a-share-research": {
      "command": "uv",
      "args": ["run", "python", "-m", "scripts.run_mcp_server"],
      "cwd": "/home/pi/news"
    }
  }
}

8.3 5 个 MCP 工具

工具 参数 示例
search_news query, top_k search_news("宁德时代固态电池")
search_company_news query, company, top_k search_company_news("价格", company="贵州茅台")
search_industry_news query, industry, top_k search_industry_news("政策", industry="半导体")
search_stock_events query, stock_code, top_k search_stock_events("重大事件", stock_code="300750")
search_sentiment_trend query, sentiment, top_k search_sentiment_trend("AI算力", sentiment="all")

📄 工具完整说明(参数/返回格式/降级策略/工作原理/调试):见 docs/mcp_tools.md

8.4 调试(SSE 模式)

uv run python -m scripts.run_mcp_server --sse 8765
# 浏览器访问 http://<host>:8765/sse

8.5 工作原理

用户输入自然语言查询
    → DashScope 嵌入(1024 维)
    → Qdrant 余弦相似度检索
    → 按过滤条件筛选
    → Markdown 格式化返回

9. 日报系统

9.1 日报生成

uv run a-share report --date 20260822

日报内容:新闻联播摘要 + 近 30 小时高重要度新闻 + 近 15 日公告/调研 + AI 要点分析。

入库目标:MySQL myquant 库,表 news_report(主表) + news_event(事件明细)。

9.2 数据库表结构

详见 docs/db-schema.md(供 API/前端对接)。

9.3 历史日报导入

# 一次性导入 178 份历史日报 HTML
uv run a-share report-import

# 或按日期/类型筛选
uv run a-share report-import --date 20260616 --type finance
uv run a-share report-import --force       # 覆盖已存在

9.4 个股日报

uv run a-share stock-report

为 configs/watchlist.yaml 中每家公司生成综合日报(AI 要点 + 公告 + 新闻 + 互动问答)。


10. 环境变量参考

10.1 LLM 与 Embedding

变量 默认值 说明
LLM_PROVIDER deepseek 默认 LLM 提供商
DEEPSEEK_API_KEY — DeepSeek API Key
DEEPSEEK_BASE_URL https://api.deepseek.com DeepSeek API 地址
DASHSCOPE_API_KEY — 百炼 API Key(LLM Qwen + Embedding 共用)
QWEN_BASE_URL https://dashscope.aliyuncs.com/compatible-mode/v1 Qwen API 地址
EMBEDDING_PROVIDER dashscope 嵌入提供商
LLM_RETRY_TIMES 3 AI 摘要调用失败重试次数
LLM_RETRY_BACKOFF_SEC 2.0 指数退避基数(秒)

LLM 场景配置:configs/llm_models.yaml 优先级高于环境变量,每个场景(事件抽取/日报摘要/个股分析/嵌入)可独立指定 provider 和 model。

10.2 调度与超时

变量 默认值 说明
SCHEDULE_TIMES 07:00,12:00,18:00,22:00 定时任务时间
CNINFO_SCHEDULE_TIME 06:30 cninfo 公告时间
STOCK_REPORT_TIME 07:30 个股日报时间
STOCK_REPORT_DAYS 15 个股日报回溯天数
PIPELINE_STEP_TIMEOUT — 全局步骤超时(秒)
TIMEOUT_CRAWLER — 抓取步骤超时
TIMEOUT_LLM — LLM 步骤超时
TIMEOUT_DEDUP — 去重步骤超时
TIMEOUT_EMBEDDING — 向量化步骤超时
TIMEOUT_QDRANT — Qdrant 步骤超时
TIMEOUT_XWLB — 新闻联播步骤超时

10.3 Qdrant

变量 默认值 说明
QDRANT_HOST localhost Qdrant 服务地址
QDRANT_PORT 6333 Qdrant 端口
QDRANT_COLLECTION a_share_news Collection 名

10.4 日报入库

变量 默认值 说明
NEWS_DB_HOST 127.0.0.1 日报 MySQL 主机
NEWS_DB_PORT 13306 日报 MySQL 端口
NEWS_DB_USER myquant 数据库用户
NEWS_DB_PASSWORD — 数据库密码
NEWS_DB_NAME myquant 数据库名
REPORT_HISTORY_DIR data/reports_history 历史日报目录

10.5 cninfo

变量 默认值 说明
CNINFO_PDF_BASE http://static.cninfo.com.cn PDF 基础 URL

11. 常见问题

Q: 某源抓取 0 篇文章?

检查 configs/sources.yaml 中该源的 article_url_pattern 正则是否匹配真实链接格式。先用 uv run a-share crawl --source <id> 单源调试。

Q: M2 提取的正文是模板文本(如"郑重声明")?

提取器已内置关键词黑名单自动过滤。若遇到新模板,可在 extractor/parser.py 的 _BOILERPLATE_PATTERNS 追加。

Q: M4 调用 LLM 报错?

检查 .env 中 DASHSCOPE_API_KEY 是否填写。可用 --provider qwen 切换到百炼测试。模型名缺失时直接报错,检查 configs/llm_models.yaml 中 event_extraction 场景的 model 字段。

Q: 日报没有 AI 摘要(ai_summary 为空)?

先查 logs/scheduler.log 是否有 AI 摘要可能被截断: ... finish_reason=length 实际输出 0 字符。根因通常是推理模型的 reasoning token 与正文共用 max_tokens:预算过小时"思考"占满配额,正文一个字都没有。处理办法:

  1. 调大 configs/llm_models.yaml 中 daily_report.max_tokens(默认 4000,YAML 保存即热生效,无需重启);
  2. 代码已内置兜底:正文为空时自动加倍预算重试(上限 16000),仍失败才降级为无摘要;
  3. 补生成某天摘要:uv run a-share report --date <YYYYMMDD>(按 (report_date, report_type, file_name) 幂等 upsert,不会新增记录)。

注意:report 步骤由调度器进程内执行(scheduler/pipeline.py),改动 Python 代码后需 sudo systemctl restart a-share-research 才会生效;只改 YAML / .env 则无需重启。

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 a-share ingest --date <date> 入库。

Q: 如何从零重建知识库?

rm -rf data/raw data/processed data/dedup data/deduped data/events data/embeddings data/qdrant_storage
uv run a-share pipeline --once

Q: 如何避免重复调用 LLM/Embedding API?

M2/M4/M5 默认增量处理:产物已存在即跳过(2026-08-12 新增)。中断后重跑:

uv run a-share pipeline --once --resume --report

从断点继续,已成功的步骤不会重复执行。

Q: 树莓派 Qdrant Docker 启动崩溃?

树莓派 ARM64 内核使用 16KB 内存页,Qdrant 官方 Docker 镜像内置的 jemalloc 仅支持 4KB 页。使用本地文件模式(默认)即可,无需 Docker。

Q: 如何新增新闻源?

uv run a-share discover https://example.com/news --name 某某财经 --add
uv run a-share crawl --source 某某财经    # 验证

Q: 服务器 .env 和本地 .env 有何不同?

  • 生产 pi5:NEWS_DB_HOST=192.168.1.10(直连 DB 隧道)
  • Mac 本地:NEWS_DB_HOST=127.0.0.1(通过 ssh 隧道)
  • 两处 .env 不同,勿互相覆盖

—— 用户手册结束 ——