# 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
```

---

—— 用户手册结束 ——
