Files
news/docs/user_guide.md
T
2026-07-18 15:51:01 +08:00

713 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# A 股 Deep Research 私有投研平台 — 用户手册
版本:v1.0 | 最后更新:2026-06-17
---
## 目录
1. [项目概述](#1-项目概述)
2. [环境准备](#2-环境准备)
3. [全链路一键运行](#3-全链路一键运行)
4. [分模块使用](#4-分模块使用)
- [M1 新闻抓取](#m1-新闻抓取)
- [M2 正文提取](#m2-正文提取)
- [M3 三层去重](#m3-三层去重)
- [M4 投资事件抽取](#m4-投资事件抽取)
- [M5 向量化](#m5-向量化)
- [M6 Qdrant 入库与检索](#m6-qdrant-入库与检索)
5. [定时任务部署](#5-定时任务部署)
6. [MCP 服务与 AI Agent](#6-mcp-服务与-ai-agent)
7. [投研分析模板](#7-投研分析模板)
8. [附录](#8-附录)
---
## 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 获取代码
```bash
git clone <repo> /home/pi/news
cd /home/pi/news
```
### 2.3 安装依赖
```bash
uv sync
```
### 2.4 配置环境变量
```bash
cp .env.example .env
# 编辑 .env,填写:
# DEEPSEEK_API_KEY=sk-xxx (M4 LLM 调用)
# DASHSCOPE_API_KEY=sk-xxx (M5/M8 嵌入调用)
```
### 2.5 验证安装
```bash
uv run pytest -m "not integration" # 应显示 186 passed
```
### 2.6 统一 CLI `a-share`
所有操作通过 `a-share` 命令完成,替代之前的 8 个脚本入口:
```bash
# 模块操作
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**:
```bash
uv run a-share pipeline --once
```
(等效于旧版 `uv run python -m scripts.run_scheduler --once`,`a-share` 命令更短更好记)
```bash
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)
只执行部分步骤:
```bash
uv run python -m scripts.run_scheduler --once --steps crawler,llm,qdrant
```
---
## 4. 分模块使用
### M1 新闻抓取
从 5 个 A 股财经源抓取文章。
```bash
# 抓取全部启用源
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 | 第一财经 | 是 | 新闻频道 |
**新增新闻源**:一条命令完成分析+配置:
```bash
# 自动分析 + 自动写入 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 步:
1. Crawl4AI 抓取首页(自动判断 JS 渲染)
2. 提取所有站内 `<a href>` 链接
3. 过滤导航/功能页(about/download/member 等)
4. 按 URL 路径模式聚类
5. 数字 ID 模式优先排序(文章特征)
6. 自动生成正则
7. 输出 yaml 配置 + `--add` 自动追加到 `sources.yaml`
`--add` 写入时自动:空行分隔、序号递增、`# ---- N. 名称 ----` 注释头。
**验证新源**:
```bash
uv run a-share crawl --source wallstreetcn
```
**同一网站多个入口**:使用 `--extra` 添加额外频道,共用同一个正则:
```bash
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):
```bash
uv run a-share add-entry wallstreetcn https://xxx.com/news/新频道
```
自动去重:已存在的 URL 跳过。
### M2 正文提取
从 M1 抓取的 HTML 中提取标准化 Article(title/content/时间/作者)。
```bash
# 处理今日所有源
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):
```json
{
"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。
```bash
# 处理今日
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 从去重文章中抽取结构化投资事件。
```bash
# 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):
```json
{
"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 向量化。
```bash
# 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 模式)**:
```bash
uv run a-share pipeline --cninfo-once
```
包含 9 步: 公告抓取(关注公司) → 调研筛选 → 互动问答 → 正文提取 → PDF 富化 → 去重 → LLM 抽取 → 向量化 → Qdrant 入库。
**单独操作**:
```bash
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 中每家公司生成综合日报:
```bash
uv run a-share stock-report
```
报告内容: AI 要点分析 / 近 7 日公告 / 相关新闻 / 互动问答。
上传路径: `echart/research/{YYYYMMDD}/{代码}_{名称}_个股日报_{日期}.html`
### 公告关注列表
只关注特定公司公告,日报中高亮显示:
```bash
# 管理关注列表
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 本地知识库。
**入库**:
```bash
# 默认本地文件模式(无需 Docker)
uv run python -m scripts.run_qdrant_ingest --date 20260616
# 重建 collection
uv run python -m scripts.run_qdrant_ingest --recreate
```
**Python 检索**:
```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](#a-qdrant-部署模式)。
---
## 5. 定时任务部署
将 M1→M6 全链路注册为系统服务,每天 7:00 / 12:00 / 18:00 / 22:00 自动执行。
### 安装(一次性)
```bash
sudo cp scripts/a-share-research.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable a-share-research
```
### 日常操作
```bash
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 # 文件日志
```
修改执行时间和配置:
```bash
# 编辑 .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
1. 打开 Cherry Studio → 设置 → MCP 服务器 → 添加
2. 填入:
```json
{
"mcpServers": {
"a-share-research": {
"command": "uv",
"args": ["run", "python", "-m", "scripts.run_mcp_server"],
"cwd": "/home/pi/news"
}
}
}
```
3. 点击启用。Cherry Studio 会自动启动 MCP 服务进程(stdio 模式)。
### 6.2 连接 Claude Code
编辑项目根目录 `.mcp.json`:
```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 模式)
```bash
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:如何从零重建整个知识库?**
```bash
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
```
---
—— 用户手册结束 ——