# 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 /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. 提取所有站内 `` 链接 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://: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 ` 单源调试。 **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 ` 入库。 **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 ``` --- —— 用户手册结束 ——