docs: 文档清理与重构 — 统一为 3 个核心文档
- 删除 5 个过时/残留文档(project_plan/agent_prompt/optimization_plan/report_db_design/deploy/README) - 新建 docs/architecture.md(项目架构:11 包职责+数据模型+配置+产物) - 重写 docs/user-guide.md(CLI 全量+增量/断点续跑+MCP+FAQ) - 重写 README.md(精简入口+文档索引) - 更新 continuation.md(追加本次记录) - 更新 .gitignore(排除 data/* 运行产物)
This commit is contained in:
@@ -0,0 +1,636 @@
|
||||
# 项目架构文档
|
||||
|
||||
> 版本:v2.0 | 最后更新:2026-08-22
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [整体架构](#1-整体架构)
|
||||
2. [数据流全景](#2-数据流全景)
|
||||
3. [包职责与导出 API](#3-包职责与导出-api)
|
||||
4. [核心数据模型](#4-核心数据模型)
|
||||
5. [配置体系](#5-配置体系)
|
||||
6. [产物目录结构](#6-产物目录结构)
|
||||
7. [运行环境](#7-运行环境)
|
||||
|
||||
---
|
||||
|
||||
## 1. 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 统一 CLI (a_share_cli) │
|
||||
│ crawl | extract | dedup | events | embed | ingest | pipeline │
|
||||
│ search | status | report | cninfo | watchlist | discover │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────────┼──────────────────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌──────────┐ ┌────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ crawler │──▶│extractor │──▶│ dedup │──▶│ llm │──▶│embedding │
|
||||
│ (M1) │ │ (M2) │ │ (M3) │ │ (M4) │ │ (M5) │
|
||||
└─────────┘ └──────────┘ └────────┘ └──────────┘ └─────┬────┘
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
│vectorstore│
|
||||
│ (M6) │
|
||||
└─────┬────┘
|
||||
│
|
||||
┌──────────────────────────────────────┤
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────┐ ┌──────────┐
|
||||
│ mcp_server│ │scheduler │
|
||||
│ (M8) │ │ (M7) │
|
||||
└──────────┘ └─────┬────┘
|
||||
│
|
||||
┌─────────────────┤
|
||||
▼ ▼
|
||||
┌──────────┐ ┌──────────────┐
|
||||
│ reporter │ │stock_reporter │
|
||||
│ (日报) │ │ (个股日报) │
|
||||
└─────┬────┘ └──────────────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│report_db │ │report_ │ │ MySQL │
|
||||
│ (M10) │ │ import │ │(myquant) │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
**11 个包**(按管道顺序):
|
||||
|
||||
| 序号 | 包 | 模块 | 概述 |
|
||||
|------|-----|------|------|
|
||||
| 1 | `crawler/` | M1 | 新闻抓取:httpx 静态直连 + Playwright JS 渲染 + cninfo 公告 API |
|
||||
| 2 | `extractor/` | M2 | GNE 中文新闻正文提取,输出 Article |
|
||||
| 3 | `dedup/` | M3 | 三层去重:URL Hash → Content Hash → SimHash |
|
||||
| 4 | `llm/` | M4 | DeepSeek/Qwen 投资事件抽取,输出 ExtractedEvent |
|
||||
| 5 | `embedding/` | M5 | DashScope 远程 / 本地 BGE-M3 向量化 |
|
||||
| 6 | `vectorstore/` | M6 | Qdrant 本地文件模式,语义检索 |
|
||||
| 7 | `scheduler/` | M7 | APScheduler 定时任务 + pipeline 编排 + 日报生成 |
|
||||
| 8 | `mcp_server/` | M8 | MCP 5 工具,供 Cherry Studio/Claude Code 调用 |
|
||||
| 9 | `a_share_cli/` | CLI | 统一命令行入口(argparse),所有子命令 |
|
||||
| 10 | `report_db/` | M10 | 日报 MySQL 结构化入库(pymysql) |
|
||||
| 11 | `report_import/` | M10 | 历史日报 HTML 解析与批量导入 |
|
||||
|
||||
辅助目录:
|
||||
|
||||
| 目录 | 用途 |
|
||||
|------|------|
|
||||
| `configs/` | sources.yaml(新闻源)、watchlist.yaml(关注列表)、llm_models.yaml(LLM 场景配置)、loader.py |
|
||||
| `prompts/` | 4 个 LLM Prompt 模板(event_extraction/company_analysis/industry_analysis/risk_analysis) |
|
||||
| `scripts/` | 独立运行脚本(每个模块一个入口) + systemd 服务文件 |
|
||||
| `tests/` | pytest 测试(225+ passed) |
|
||||
| `api/` | 占位(空包,预留给未来 API 服务) |
|
||||
| `app/` | 空目录(预留) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据流全景
|
||||
|
||||
```
|
||||
财经网站 / cninfo API
|
||||
│
|
||||
▼
|
||||
┌───────────┐
|
||||
│ M1 抓取 │ → data/raw/{source}/{YYYYMMDD}/*.html + index.jsonl
|
||||
└─────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌───────────┐
|
||||
│ M2 提取 │ → data/processed/{source}/{YYYYMMDD}/{url_hash}.json (Article)
|
||||
└─────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌───────────┐
|
||||
│ M3 去重 │ → data/deduped/{YYYYMMDD}/uniques/{url_hash}.json
|
||||
└─────┬─────┘ data/dedup/fingerprints.sqlite3 (指纹库)
|
||||
│
|
||||
▼
|
||||
┌───────────┐
|
||||
│ M4 LLM │ → data/events/{YYYYMMDD}/{url_hash}.json (ExtractedEvent)
|
||||
└─────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌───────────┐
|
||||
│ M5 向量化 │ → data/embeddings/{YYYYMMDD}/{url_hash}.json (1024 维向量)
|
||||
└─────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌───────────┐
|
||||
│ M6 入库 │ → data/qdrant_storage/ (Qdrant 本地文件)
|
||||
└─────┬─────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
▼ ▼
|
||||
┌──────┐ ┌──────┐
|
||||
│ MCP │ │ 日报 │
|
||||
│ 检索 │ │ 生成 │
|
||||
└──────┘ └──┬───┘
|
||||
│
|
||||
▼
|
||||
┌────────┐
|
||||
│ MySQL │ → news_report / news_event (myquant 库)
|
||||
└────────┘
|
||||
```
|
||||
|
||||
**关键设计**:
|
||||
|
||||
- **增量处理**:M2/M4/M5 产物存在即跳过(2026-08-12 新增),`--force` 全量重建
|
||||
- **断点续跑**:`pipeline --once --resume` 从上次失败步骤继续,状态文件 `data/pipeline/state.json`
|
||||
- **去重多源记录**:M3 指纹库 `source_ids` 列记录同一篇新闻的多个来源
|
||||
|
||||
---
|
||||
|
||||
## 3. 包职责与导出 API
|
||||
|
||||
### 3.1 `crawler/` — M1 新闻抓取
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `engine.py` | Crawl4AI 异步引擎:httpx 静态直连(js_render=false) / Playwright(js_render=true) |
|
||||
| `config.py` | 加载 `configs/sources.yaml` |
|
||||
| `models.py` | SourceConfig / CrawlerConfig / CrawlResult / ArticleLink / CninfoItem / CrawlStage |
|
||||
| `storage.py` | 保存 raw HTML + index.jsonl,URL 去重 |
|
||||
| `cninfo.py` | cninfo 公告/调研/互动易 API 抓取 |
|
||||
|
||||
**公开 API**:
|
||||
|
||||
```python
|
||||
from crawler import (
|
||||
crawl_all, crawl_source, extract_article_links,
|
||||
load_crawler_config,
|
||||
SourceConfig, CrawlerConfig, CrawlResult, CrawlStage, ArticleLink, CninfoItem,
|
||||
)
|
||||
```
|
||||
|
||||
**抓取分流规则**:
|
||||
|
||||
| 条件 | 引擎 | 特点 |
|
||||
|------|------|------|
|
||||
| `js_render=False` 且无 `wait_for` | httpx 直连 | 快,不触发反爬 |
|
||||
| `js_render=True` 或有 `wait_for` | Playwright | 支持 JS 渲染 |
|
||||
|
||||
**新闻源**:14 个(13 Web + 1 API:新闻联播),见 `configs/sources.yaml`
|
||||
|
||||
### 3.2 `extractor/` — M2 正文提取
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `parser.py` | GNE 中文正文提取,模板文本过滤 |
|
||||
| `models.py` | Article / ExtractError |
|
||||
|
||||
**公开 API**:
|
||||
|
||||
```python
|
||||
from extractor import (
|
||||
extract_article, Article, ExtractError, MIN_CONTENT_LENGTH, SOURCE_NAME_MAP,
|
||||
)
|
||||
```
|
||||
|
||||
**Article 模型**(M2 输出,M3+ 输入的唯一格式):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `source_id` | str | M1 源 ID |
|
||||
| `url` | str | 文章 URL |
|
||||
| `url_hash` | str | SHA1 前 16 位,主键 |
|
||||
| `title` | str | 标题 |
|
||||
| `content` | str | 清理后纯文本正文 |
|
||||
| `author` | str\|None | 作者 |
|
||||
| `publish_time` | datetime\|None | 标准化发布时间 |
|
||||
| `word_count` | int | 中文字符数 |
|
||||
| `item_type` | str\|None | cninfo 类型:announcement/research/irm |
|
||||
|
||||
### 3.3 `dedup/` — M3 三层去重
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `deduper.py` | Deduper 主类:check / ingest / stats |
|
||||
| `hasher.py` | SimHash 64 位 + 汉明距离 + content_hash |
|
||||
| `store.py` | FingerprintStore,SQLite 持久化 |
|
||||
| `models.py` | DedupLayer / DedupResult / DedupStats / Fingerprint |
|
||||
|
||||
**公开 API**:
|
||||
|
||||
```python
|
||||
from dedup import (
|
||||
Deduper, article_to_fingerprint, FingerprintStore,
|
||||
DedupLayer, DedupResult, DedupStats, Fingerprint,
|
||||
simhash64, hamming, content_hash, normalize_content,
|
||||
DEFAULT_HAMMING_THRESHOLD, DEFAULT_TIME_WINDOW_DAYS,
|
||||
)
|
||||
```
|
||||
|
||||
**三层去重逻辑**:
|
||||
|
||||
| 层 | 算法 | 命中条件 |
|
||||
|----|------|----------|
|
||||
| L1 URL | URL Hash (SHA1) | 完全相同 URL |
|
||||
| L2 Content | 标准化正文 SHA1 | 正文完全一致 |
|
||||
| L3 SimHash | 64 位 SimHash + 汉明距离 | 距离 ≤ 3 (默认) |
|
||||
|
||||
### 3.4 `llm/` — M4 投资事件抽取
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `client.py` | OpenAI 兼容客户端,指数退避重试,LLMConfig 加载 |
|
||||
| `extractor.py` | Prompt 模板加载,JSON 解析,extract_event / extract_event_async |
|
||||
| `models.py` | EventExtraction / ExtractedEvent / Sentiment / LLMCallError / EVENT_TYPES |
|
||||
|
||||
**公开 API**:
|
||||
|
||||
```python
|
||||
from llm import (
|
||||
extract_event, extract_event_async, parse_event_json,
|
||||
PromptTemplate, load_llm_config, LLMConfig,
|
||||
make_sync_client, make_async_client,
|
||||
EventExtraction, ExtractedEvent, Sentiment, LLMCallError,
|
||||
EVENT_TYPES, MAX_CONTENT_CHARS, MAX_IMPORTANCE, MIN_IMPORTANCE,
|
||||
SCENE_EVENT_EXTRACTION, SCENE_DAILY_REPORT, SCENE_STOCK_REPORT,
|
||||
)
|
||||
```
|
||||
|
||||
**ExtractedEvent 模型**(M4 落盘格式):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `source_id` | str | 主源 |
|
||||
| `url` | str | 原文 URL |
|
||||
| `url_hash` | str | SHA1 前 16 位 |
|
||||
| `title` | str | 标题 |
|
||||
| `publish_time` | datetime\|None | 发布时间 |
|
||||
| `sources` | list[str] | 全部来源(去重合并) |
|
||||
| `event` | EventExtraction | LLM 抽取结果 |
|
||||
| `provider` | str | deepseek / qwen |
|
||||
| `model` | str | 模型名 |
|
||||
| `attempts` | int | LLM 实际调用次数(含重试) |
|
||||
|
||||
**EventExtraction**(LLM 输出 JSON 结构):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `stock_codes` | list[str] | 6 位代码,可带 .SH/.SZ/.BJ |
|
||||
| `company_names` | list[str] | 公司中文简称 |
|
||||
| `industries` | list[str] | 行业(申万二级) |
|
||||
| `sentiment` | Sentiment | positive/neutral/negative |
|
||||
| `importance` | int | 1-5 重要程度 |
|
||||
| `event_type` | str | 23 种事件类型之一 |
|
||||
| `summary` | str | 一句话摘要(≤200 字) |
|
||||
|
||||
**23 种事件类型**:业绩预告/业绩快报/财报披露/合作签约/投资并购/重大合同/产品发布/技术突破/监管处罚/诉讼仲裁/股东减持/股东增持/回购/分红/高管变动/资产重组/停牌复牌/ST警示/退市风险/宏观政策/行业政策/国际局势/其他
|
||||
|
||||
**LLM 场景配置**(`configs/llm_models.yaml`):
|
||||
|
||||
| 场景 | 用途 | 调用方 |
|
||||
|------|------|--------|
|
||||
| `event_extraction` | 投资事件抽取(M4) | llm/extractor.py |
|
||||
| `daily_report` | 日报 AI 摘要 | scheduler/reporter.py |
|
||||
| `stock_report` | 个股 AI 要点 | scheduler/stock_reporter.py |
|
||||
| `embedding` | 文本向量化(M5) | embedding/factory.py |
|
||||
|
||||
### 3.5 `embedding/` — M5 向量化
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `base.py` | EmbeddingProvider / AsyncEmbeddingProvider ABC + compose_text |
|
||||
| `remote.py` | DashScope 远程 Embedding(OpenAI 兼容) |
|
||||
| `local.py` | 本地 BGE-M3(可选,需 `uv sync --extra local-embedding`) |
|
||||
| `factory.py` | resolve_provider_type / make_sync_provider / make_async_provider |
|
||||
| `models.py` | EmbeddingProviderType / EmbeddingResult / EmbeddingError |
|
||||
|
||||
**公开 API**:
|
||||
|
||||
```python
|
||||
from embedding import (
|
||||
make_sync_provider, make_async_provider, resolve_provider_type,
|
||||
compose_text, EmbeddingProvider, AsyncEmbeddingProvider,
|
||||
EmbeddingResult, EmbeddingError, EmbeddingProviderType,
|
||||
DashScopeEmbeddingProvider, DashScopeAsyncEmbeddingProvider,
|
||||
DASHSCOPE_DEFAULT_DIM, DASHSCOPE_DEFAULT_MODEL, DASHSCOPE_BATCH_LIMIT,
|
||||
MAX_TEXT_CHARS,
|
||||
)
|
||||
```
|
||||
|
||||
**向量维度**:1024(DashScope `text-embedding-v3` / 本地 BGE-M3)
|
||||
|
||||
**文本组装**:`compose_text()` 统一策略——标题 + 正文 + 事件摘要,最大 8000 字符
|
||||
|
||||
### 3.6 `vectorstore/` — M6 Qdrant 知识库
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `client.py` | VectorStore 封装(init/upsert/query/count/info) + make_qdrant_client 工厂 |
|
||||
| `models.py` | SearchFilter / SearchResult / CollectionInfo |
|
||||
|
||||
**公开 API**:
|
||||
|
||||
```python
|
||||
from vectorstore import (
|
||||
VectorStore, make_qdrant_client,
|
||||
SearchFilter, SearchResult, CollectionInfo,
|
||||
DEFAULT_COLLECTION, DEFAULT_VECTOR_DIM,
|
||||
)
|
||||
```
|
||||
|
||||
**Qdrant 模式**:默认本地文件模式(`data/qdrant_storage/`),零依赖,ARM64 兼容
|
||||
|
||||
**SearchFilter 支持**:source_id / stock_codes / company_names / industries / sentiment / importance_min / event_types / publish_date_from / publish_date_to
|
||||
|
||||
### 3.7 `scheduler/` — M7 定时任务与 Pipeline
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `pipeline.py` | run_pipeline / run_step,编排 M1→M6,断点续跑 |
|
||||
| `reporter.py` | 日报生成(收集→AI 摘要→MySQL 入库) |
|
||||
| `stock_reporter.py` | 个股日报生成(关注列表) |
|
||||
|
||||
**公开 API**:
|
||||
|
||||
```python
|
||||
from scheduler import (
|
||||
run_pipeline, run_step, PipelineResult, StepResult,
|
||||
STEP_COMMANDS, STEP_TIMEOUTS,
|
||||
)
|
||||
```
|
||||
|
||||
**Pipeline 步骤**:
|
||||
|
||||
| 步骤名 | 中文 | 默认超时 | 说明 |
|
||||
|--------|------|----------|------|
|
||||
| `crawler` | M1 新闻抓取 | 900s | 13 源,含 Playwright |
|
||||
| `xwlb` | M1 新闻联播 | 60s | 纯 HTTP API |
|
||||
| `extractor` | M2 正文提取 | 300s | GNE 提取 |
|
||||
| `dedup` | M3 新闻去重 | 120s | 三层去重 |
|
||||
| `llm` | M4 LLM 抽取 | 900s | API 调用 |
|
||||
| `embedding` | M5 向量化 | 300s | DashScope |
|
||||
| `qdrant` | M6 Qdrant 入库 | 300s | 本地文件写入 |
|
||||
| `report` | 日报生成 | 30s | 仅 07:00 执行 |
|
||||
| `cninfo_crawl` | cninfo 公告抓取 | 900s | 06:30 执行 |
|
||||
| `cninfo_extract` | cninfo 正文提取 | 300s | — |
|
||||
| `cninfo_pdf` | cninfo PDF 补充 | — | — |
|
||||
|
||||
**超时优先级**:`TIMEOUT_{NAME}` 环境变量 > `PIPELINE_STEP_TIMEOUT` > 硬编码默认值 > 1800s
|
||||
|
||||
**调度时间表**:
|
||||
|
||||
| 时间 | 步骤 |
|
||||
|------|------|
|
||||
| 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 |
|
||||
|
||||
### 3.8 `mcp_server/` — M8 MCP 服务
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `tools.py` | FastMCP 服务器,5 个 MCP 工具 |
|
||||
|
||||
**5 个 MCP 工具**:
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `search_news` | query, top_k | 通用语义检索 |
|
||||
| `search_company_news` | query, company, top_k | 按公司过滤 |
|
||||
| `search_industry_news` | query, industry, top_k | 按行业过滤 |
|
||||
| `search_stock_events` | query, stock_code, top_k | 按股票代码过滤 |
|
||||
| `search_sentiment_trend` | query, sentiment, top_k | 情绪趋势 + 统计 |
|
||||
|
||||
**启动方式**:stdio 模式(Cherry Studio/Claude Code 自动管理进程)或 SSE 模式(调试:`--sse 8765`)
|
||||
|
||||
### 3.9 `a_share_cli/` — 统一 CLI
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `main.py` | argparse 子命令路由,所有命令行操作 |
|
||||
|
||||
**全部子命令**:
|
||||
|
||||
| 命令 | 功能 | 关键参数 |
|
||||
|------|------|----------|
|
||||
| `crawl` | M1 抓取 | `--source`, `--no-save` |
|
||||
| `extract` | M2 提取 | `--date`, `--source` |
|
||||
| `dedup` | M3 去重 | `--date`, `--reset` |
|
||||
| `events` | M4 LLM 抽取 | `--date`, `--provider`, `--model`, `--limit`, `--concurrency` |
|
||||
| `embed` | M5 向量化 | `--date`, `--provider`, `--model` |
|
||||
| `ingest` | M6 入库 | `--date`, `--recreate` |
|
||||
| `pipeline` | 全链路/守护 | `--once`, `--resume`, `--steps`, `--report`, `--cninfo-once` |
|
||||
| `search` | 检索知识库 | `query`, `--top`, `--source`, `--sentiment`, `--stock`, `--industry`, `--min-importance` |
|
||||
| `status` | 数据总览 | 无参数 |
|
||||
| `report` | 生成日报 | `--date`, `--no-upload` |
|
||||
| `report-import` | 历史日报导入 | `--dir`, `--date`, `--type`, `--force` |
|
||||
| `cninfo` | 公告抓取 | `--no-save`, `--enrich-pdf`, `--pdf-limit` |
|
||||
| `stock-report` | 个股日报 | `--no-upload` |
|
||||
| `watchlist` | 关注列表管理 | `add`/`remove`/`list` |
|
||||
| `discover` | 站点分析 | `url`, `--name`, `--extra`, `--add` |
|
||||
| `add-entry` | 追加入口 | `source`, `urls...` |
|
||||
|
||||
### 3.10 `report_db/` — M10 日报 DB 层
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `db.py` | 连接/事务/建表/保存/查询 |
|
||||
| `models.py` | EventRow / ReportData (Pydantic) |
|
||||
| `schema.py` | DDL(CREATE TABLE) |
|
||||
|
||||
**公开 API**:
|
||||
|
||||
```python
|
||||
from report_db import (
|
||||
connect, init_schema, save_report, load_db_config,
|
||||
exists_report, fetch_report,
|
||||
EventRow, ReportData,
|
||||
)
|
||||
```
|
||||
|
||||
### 3.11 `report_import/` — M10 历史日报导入
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `parser.py` | BeautifulSoup 解析 finance/intl 历史 HTML |
|
||||
| `importer.py` | 批量导入,幂等,ImportStats 统计 |
|
||||
|
||||
**公开 API**:
|
||||
|
||||
```python
|
||||
from report_import import (
|
||||
import_history, ImportStats,
|
||||
parse_report, parse_finance_report, parse_intl_report, ReportParseError,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心数据模型
|
||||
|
||||
### 4.1 管道数据模型关系
|
||||
|
||||
```
|
||||
CrawlResult (M1) ──→ Article (M2) ──→ DedupResult (M3)
|
||||
│
|
||||
▼
|
||||
ExtractedEvent (M4)
|
||||
│
|
||||
▼
|
||||
EmbeddingResult (M5)
|
||||
│
|
||||
▼
|
||||
SearchResult (M6)
|
||||
```
|
||||
|
||||
### 4.2 日报数据模型
|
||||
|
||||
```
|
||||
ReportData (Pydantic)
|
||||
├── report_date: date
|
||||
├── report_type: str (finance | intl)
|
||||
├── file_name: str
|
||||
├── generated_at: datetime
|
||||
├── ai_summary: str | None
|
||||
├── stats: dict (JSON)
|
||||
└── events: list[EventRow]
|
||||
├── section: str (xwlb | news | cninfo | intl)
|
||||
├── rank: int
|
||||
├── importance: int | None
|
||||
├── event_type: str | None
|
||||
├── title: str
|
||||
├── summary: str | None
|
||||
├── sentiment: str | None
|
||||
└── source: str | None
|
||||
```
|
||||
|
||||
### 4.3 全部 Pydantic 模型清单
|
||||
|
||||
| 包 | 模型 | 用途 |
|
||||
|----|------|------|
|
||||
| crawler | SourceConfig | 单个新闻源配置 |
|
||||
| crawler | CrawlerConfig | 全局抓取配置 |
|
||||
| crawler | CrawlerSettings | 抓取设置(并发/重试/UA) |
|
||||
| crawler | CrawlResult | 单次抓取结果 |
|
||||
| crawler | ArticleLink | 列表页发现的链接 |
|
||||
| crawler | CninfoItem | 公告/调研/互动易条目 |
|
||||
| extractor | Article | 标准化文章(M2 输出) |
|
||||
| dedup | Fingerprint | 指纹记录(SQLite) |
|
||||
| dedup | DedupResult | 判重结果 |
|
||||
| dedup | DedupStats | 指纹库统计 |
|
||||
| llm | EventExtraction | LLM 输出 JSON 结构 |
|
||||
| llm | ExtractedEvent | M4 落盘格式(文章+事件) |
|
||||
| embedding | EmbeddingResult | 向量化结果 |
|
||||
| vectorstore | SearchFilter | 检索过滤条件 |
|
||||
| vectorstore | SearchResult | 单条检索结果 |
|
||||
| vectorstore | CollectionInfo | Qdrant Collection 信息 |
|
||||
| scheduler | StepResult | 单步执行结果 |
|
||||
| scheduler | PipelineResult | 全链路执行结果 |
|
||||
| report_db | EventRow | 日报事件行 |
|
||||
| report_db | ReportData | 完整日报数据 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置体系
|
||||
|
||||
### 5.1 配置文件
|
||||
|
||||
| 文件 | 格式 | 用途 | 热更新 |
|
||||
|------|------|------|--------|
|
||||
| `configs/sources.yaml` | YAML | 14 个新闻源配置 | 每次抓取重读 |
|
||||
| `configs/llm_models.yaml` | YAML | 4 个 LLM 场景配置 | 每次调用重读 |
|
||||
| `configs/watchlist.yaml` | YAML | cninfo 公告关注列表 | 每次操作重读 |
|
||||
| `.env` | dotenv | API Key + 调度/超时/DB 配置 | 重启服务生效 |
|
||||
|
||||
### 5.2 配置优先级(LLM 场景)
|
||||
|
||||
```
|
||||
CLI 显式参数 (--provider / --model)
|
||||
↓
|
||||
configs/llm_models.yaml scenes.<场景>.xxx
|
||||
↓
|
||||
.env 环境变量 (LLM_PROVIDER / DEEPSEEK_MODEL 等)
|
||||
↓
|
||||
代码内置默认值 (仅超时/温度等参数)
|
||||
```
|
||||
|
||||
> **模型名**无内置兜底:缺失即报错,绝不静默使用错误模型。
|
||||
|
||||
### 5.3 关键环境变量
|
||||
|
||||
| 变量 | 用途 | 生产值示例 |
|
||||
|------|------|-----------|
|
||||
| `DASHSCOPE_API_KEY` | 百炼 LLM + Embedding | sk-xxx |
|
||||
| `DEEPSEEK_API_KEY` | DeepSeek LLM | sk-xxx |
|
||||
| `SCHEDULE_TIMES` | 调度时间 | `07:00,12:00,18:00,22:00` |
|
||||
| `PIPELINE_STEP_TIMEOUT` | 全局超时 | `1800` |
|
||||
| `NEWS_DB_HOST` | 日报 DB 主机 | `192.168.1.10`(pi5) / `127.0.0.1`(Mac) |
|
||||
| `NEWS_DB_PORT` | 日报 DB 端口 | `13306` |
|
||||
|
||||
---
|
||||
|
||||
## 6. 产物目录结构
|
||||
|
||||
```
|
||||
data/
|
||||
├── raw/ # M1 原始抓取
|
||||
│ ├── cls/{YYYYMMDD}/ # 按源分目录
|
||||
│ │ ├── index.jsonl # 文章列表(CrawlResult 摘要)
|
||||
│ │ └── *.html # 原始 HTML
|
||||
│ ├── eastmoney/{YYYYMMDD}/
|
||||
│ ├── ...
|
||||
│ └── cninfo/{YYYYMMDD}/ # 公告独立目录
|
||||
│
|
||||
├── processed/ # M2 正文提取
|
||||
│ ├── cls/{YYYYMMDD}/
|
||||
│ │ └── {url_hash}.json # Article
|
||||
│ └── ...
|
||||
│
|
||||
├── dedup/ # M3 指纹库(跨日)
|
||||
│ └── fingerprints.sqlite3
|
||||
│
|
||||
├── deduped/ # M3 去重产物
|
||||
│ └── {YYYYMMDD}/
|
||||
│ ├── uniques/
|
||||
│ │ └── {url_hash}.json # 唯一文章
|
||||
│ ├── duplicates.jsonl # 重复记录
|
||||
│ └── sources.json # 多源映射
|
||||
│
|
||||
├── events/ # M4 事件抽取
|
||||
│ └── {YYYYMMDD}/
|
||||
│ └── {url_hash}.json # ExtractedEvent
|
||||
│
|
||||
├── embeddings/ # M5 向量化
|
||||
│ └── {YYYYMMDD}/
|
||||
│ └── {url_hash}.json # 1024 维向量 + 事件
|
||||
│
|
||||
├── qdrant_storage/ # M6 Qdrant 本地文件
|
||||
│
|
||||
├── pipeline/ # 断点续跑状态
|
||||
│ └── state.json
|
||||
│
|
||||
└── reports_history/ # 历史日报 HTML(可选)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 运行环境
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| Python | 3.11 |
|
||||
| 包管理器 | uv |
|
||||
| 虚拟环境 | `.venv/` |
|
||||
| 生产服务器 | 树莓派 5 (ARM64), `pi@192.168.1.160` |
|
||||
| 数据库 | MySQL/MariaDB (myquant 库), 通过 `pi@192.168.1.10` autossh 隧道 |
|
||||
| 系统服务 | systemd `a-share-research` + `a-share-db-tunnel` |
|
||||
| 测试 | pytest (asyncio_mode=auto, integration 标记默认跳过) |
|
||||
| 静态检查 | ruff + mypy |
|
||||
| LLM 模型 | `deepseek-v4-flash`(锁定,不可修改) |
|
||||
|
||||
---
|
||||
|
||||
—— 架构文档结束 ——
|
||||
@@ -0,0 +1,125 @@
|
||||
# 日报结构化入库:数据库表结构与数据契约
|
||||
|
||||
> 版本:v1.0 | 2026-08-03
|
||||
> 用途:供 API / 前端对接读取日报数据。表位于 MySQL `myquant` 库,表前缀 `news_`。
|
||||
> 连接:`192.168.1.10:13306`(pi 上 autossh 隧道 → doorcome.cn:3306 MariaDB 10.11),用户 `myquant`(密码在服务器 `.env` 的 `NEWS_DB_PASSWORD`)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 表结构
|
||||
|
||||
### 1.1 news_report(日报主表,一行 = 一份日报)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| id | BIGINT UNSIGNED PK | 自增主键 |
|
||||
| report_date | DATE | 日报日期 |
|
||||
| report_type | VARCHAR(16) | `finance`=A 股日报 / `intl`=国际财经日报 |
|
||||
| file_name | VARCHAR(160) | 历史文件源文件名;**新生成日报为空字符串 `""`** |
|
||||
| generated_at | DATETIME | 生成时间 |
|
||||
| ai_summary | TEXT | AI 摘要全文(含换行,按条目分行) |
|
||||
| stats | JSON | 数据总览统计快照(见第 3 节),可为 NULL |
|
||||
| created_at | DATETIME | 入库时间 |
|
||||
|
||||
唯一键:`(report_date, report_type, file_name)` —— 历史同一天多次生成(intl 一日 3 次)保留多行;新生成日报 `file_name=''` 每天每类型仅一行,重复生成覆盖。
|
||||
|
||||
### 1.2 news_event(日报事件明细,一行 = 一条事件)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| id | BIGINT UNSIGNED PK | 自增主键 |
|
||||
| report_id | BIGINT UNSIGNED | FK → news_report.id |
|
||||
| section | VARCHAR(16) | 板块:`xwlb`=新闻联播 / `news`=财经新闻 / `cninfo`=公告调研 / `intl`=国际重要事件 |
|
||||
| rank | INT | 板块内序号(1 起) |
|
||||
| importance | INT NULL | 重要度 1-5 |
|
||||
| event_type | VARCHAR(64) NULL | 事件类型(如 宏观经济/地缘政治/新闻联播/公告) |
|
||||
| title | VARCHAR(512) | 标题 |
|
||||
| summary | TEXT NULL | 摘要/正文 |
|
||||
| sentiment | VARCHAR(8) NULL | `positive` / `negative` / `neutral` |
|
||||
| source | VARCHAR(64) NULL | 来源(如 `cls`、`investinglive.com`) |
|
||||
| url | VARCHAR(512) NULL | 原文链接(新闻联播为空) |
|
||||
| created_at | DATETIME | 入库时间 |
|
||||
|
||||
索引:`idx_report_section (report_id, section)`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据契约
|
||||
|
||||
- **幂等语义**:同一 `(report_date, report_type, file_name)` 重复写入会覆盖主表并全量替换事件(DELETE + INSERT),不会产生重复行。
|
||||
- **取最新**:同一天存在多份时(历史 intl 一日 3 次),前端按 `generated_at` 取最新;新日报 `file_name=''` 每天唯一。
|
||||
- **板块差异**:finance 日报含 `xwlb`+`news`+`cninfo` 三板块;intl 日报仅 `intl` 板块。前端按 `section` 过滤展示。
|
||||
- **历史覆盖范围**:2026-06-16 ~ 2026-08-03,共 177 行(finance 49 + intl 128;finance 少 1 因为两个目录存在同名文件被幂等合并)。事件总计 4222 条。
|
||||
|
||||
---
|
||||
|
||||
## 3. stats JSON 结构
|
||||
|
||||
`news_report.stats` 为数据总览快照,前端自行解析。finance 与 intl 的 key 集合不同:
|
||||
|
||||
| key | finance | intl | 内容 |
|
||||
| --- | --- | --- | --- |
|
||||
| `pipeline` | ✅ | ✅ | M1→M6 管道各环节数量:`{label: 数量}` |
|
||||
| `sources` | ✅ | — | 各新闻源文章数:`{源名: 数量}` |
|
||||
| `news` | ✅ | — | 新闻统计:`{total, hi_threshold, sentiments, importances, event_types}` |
|
||||
| `cninfo` | ✅ | — | 公告调研统计:`{total, hi_threshold, by_day, announcement, research, irm}` |
|
||||
| `xwlb` | ✅ | — | 联播统计:`{total, date}`(有数据时才有) |
|
||||
| `sentiment` | ✅ | ✅ | 情绪分布(历史文件为图例文本列表;新生成在 `news.sentiments`) |
|
||||
| `importance` | ✅ | ✅ | 重要度分布:`[{重要度, 数量}, ...]` |
|
||||
| `event_types` | ✅ | ✅ | 事件类型 TOP:`[{事件类型, 数量}, ...]` |
|
||||
| `source_dist` | — | ✅ | 文章来源分布:`[{来源, 文章数}, ...]` |
|
||||
|
||||
> 历史文件与新生成日报的 stats 结构存在差异(历史为 HTML 解析快照,新生成为结构化组装),前端建议按 key 防御性读取。
|
||||
|
||||
### 3.1 口径说明(重要,避免误解)
|
||||
|
||||
`stats` 内各数字口径不同,请勿直接互相比较:
|
||||
|
||||
| 字段 | 口径 |
|
||||
| --- | --- |
|
||||
| `pipeline.raw_total` | **日报日期当天**抓取的文章数(`data/raw/{src}/{date}/index.jsonl` 中 `stage=article 且 success` 的条目)。`raw_by_source` 是各源明细,**其和 = raw_total**;当天未抓取/无文章的源显示 0 |
|
||||
| `pipeline.raw_total_24h` | 最近 24 小时内**抓取**(按 `fetched_at`)的文章数;`raw_by_source_24h` 为各源明细,和 = raw_total_24h。当天 07:00 抓取的数据其值 ≈ raw_total(并非"24h 内发布的新闻",raw 层无发布时间的可靠字段) |
|
||||
| `pipeline.proc / deduped / dups / emb_count / qdrant_count` | 抽取 / 去重后 / 重复 / 向量化 / Qdrant 总量(`qdrant_count` 为全量累计,非当天) |
|
||||
| `news.total` | **过去 30 小时窗口内**经 LLM 抽取的新闻事件数。**≠ raw_total**:raw 是抓取的文章数,news 是抽取后的事件数(会有过滤/合并),两者不可互相验证 |
|
||||
| `news.importances` | `{重要度等级(1-5): 事件数}`,**各等级之和 = news.total** |
|
||||
| `news.sentiments` | `{情绪: 事件数}`(positive/negative/neutral),和 = news.total |
|
||||
| `news.event_types` | `{事件类型: 事件数}`(TOP 10) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 常用查询示例(API 实现参考)
|
||||
|
||||
```sql
|
||||
-- 某类型日报列表(取每天最新一份)
|
||||
SELECT r.* FROM news_report r
|
||||
JOIN (
|
||||
SELECT report_date, report_type, MAX(generated_at) AS g
|
||||
FROM news_report GROUP BY report_date, report_type
|
||||
) t ON r.report_date = t.report_date AND r.report_type = t.report_type
|
||||
AND r.generated_at = t.g
|
||||
WHERE r.report_type = 'finance' AND r.report_date >= '2026-07-01'
|
||||
ORDER BY r.report_date DESC;
|
||||
|
||||
-- 某日报的全部事件(按板块)
|
||||
SELECT section, rank, importance, event_type, title, summary, sentiment, source, url
|
||||
FROM news_event WHERE report_id = ? ORDER BY section, rank;
|
||||
|
||||
-- 最近 N 天重要事件聚合(跨日报检索)
|
||||
SELECT e.* FROM news_event e
|
||||
JOIN news_report r ON r.id = e.report_id
|
||||
WHERE r.report_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
|
||||
AND e.importance >= 4
|
||||
ORDER BY e.importance DESC, r.report_date DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 相关命令(数据生产侧)
|
||||
|
||||
```bash
|
||||
uv run a-share report --date YYYYMMDD # 生成当日日报并入库(finance)
|
||||
uv run a-share report-import # 历史 HTML 全量解析入库(幂等)
|
||||
uv run a-share report-import --date YYYYMMDD --type intl
|
||||
```
|
||||
|
||||
代码:`report_db/`(连接/写入)、`report_import/`(历史解析/导入)、`scheduler/reporter.py`(日报生成)。
|
||||
@@ -0,0 +1,752 @@
|
||||
# A 股 Deep Research 用户手册
|
||||
|
||||
> 版本:v2.0 | 最后更新:2026-08-22
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [项目概述](#1-项目概述)
|
||||
2. [环境准备](#2-环境准备)
|
||||
3. [快速开始](#3-快速开始)
|
||||
4. [统一 CLI 参考](#4-统一-cli-参考)
|
||||
5. [全链路与 Pipeline](#5-全链路与-pipeline)
|
||||
6. [分模块使用](#6-分模块使用)
|
||||
7. [定时任务与 systemd](#7-定时任务与-systemd)
|
||||
8. [MCP 服务](#8-mcp-服务)
|
||||
9. [日报系统](#9-日报系统)
|
||||
10. [环境变量参考](#10-环境变量参考)
|
||||
11. [常见问题](#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 包管理器
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install uv
|
||||
|
||||
# Linux
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
### 2.2 安装
|
||||
|
||||
```bash
|
||||
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 配置
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# 编辑 .env,至少填写:
|
||||
# DASHSCOPE_API_KEY=sk-xxx (百炼 LLM + Embedding)
|
||||
# DEEPSEEK_API_KEY=sk-xxx (DeepSeek LLM,可选)
|
||||
```
|
||||
|
||||
### 2.4 验证
|
||||
|
||||
```bash
|
||||
uv run pytest -m "not integration" # 应显示 225+ passed
|
||||
uv run a-share status # 查看数据状态
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 快速开始
|
||||
|
||||
### 3.1 三条命令入门
|
||||
|
||||
```bash
|
||||
# 1. 抓取全网新闻,跑通全链路
|
||||
uv run a-share pipeline --once
|
||||
|
||||
# 2. 语义检索
|
||||
uv run a-share search "宁德时代固态电池"
|
||||
|
||||
# 3. 查看数据总览
|
||||
uv run a-share status
|
||||
```
|
||||
|
||||
### 3.2 全链路 + 日报
|
||||
|
||||
```bash
|
||||
# 跑全链路末尾生成日报
|
||||
uv run a-share pipeline --once --report
|
||||
|
||||
# 或单独生成日报(已有数据时)
|
||||
uv run a-share report --date 20260822
|
||||
```
|
||||
|
||||
### 3.3 cninfo 公告管道
|
||||
|
||||
```bash
|
||||
# 全链路:公告抓取 → 提取 → 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 抓取
|
||||
|
||||
```bash
|
||||
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 提取
|
||||
|
||||
```bash
|
||||
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 去重
|
||||
|
||||
```bash
|
||||
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 抽取
|
||||
|
||||
```bash
|
||||
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 向量化
|
||||
|
||||
```bash
|
||||
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 入库
|
||||
|
||||
```bash
|
||||
uv run a-share ingest --date 20260822
|
||||
uv run a-share ingest --recreate # 重建 collection
|
||||
```
|
||||
|
||||
### 4.7 `a-share search` — 语义检索
|
||||
|
||||
```bash
|
||||
# 基础检索
|
||||
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` — 数据总览
|
||||
|
||||
```bash
|
||||
uv run a-share status
|
||||
```
|
||||
|
||||
输出各层统计(M1→M6 数据量) + 今日高重要度事件 TOP 10 + systemd 服务状态。
|
||||
|
||||
### 4.9 `a-share discover` — 站点分析
|
||||
|
||||
```bash
|
||||
# 自动分析首页,输出 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` — 追加入口
|
||||
|
||||
```bash
|
||||
uv run a-share add-entry wallstreetcn https://xxx.com/news/新频道
|
||||
```
|
||||
|
||||
### 4.11 `a-share watchlist` — 关注列表
|
||||
|
||||
```bash
|
||||
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` — 公告管道
|
||||
|
||||
```bash
|
||||
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` — 个股日报
|
||||
|
||||
```bash
|
||||
uv run a-share stock-report # 为关注列表中每家公司生成日报
|
||||
uv run a-share stock-report --no-upload # 仅生成,不上传
|
||||
```
|
||||
|
||||
### 4.14 `a-share report` — 日报生成
|
||||
|
||||
```bash
|
||||
uv run a-share report --date 20260822 # 生成指定日期日报并入库 MySQL
|
||||
```
|
||||
|
||||
日报内容:新闻联播摘要 + 高重要度新闻 + 公告调研 + AI 要点分析。
|
||||
|
||||
### 4.15 `a-share report-import` — 历史日报导入
|
||||
|
||||
```bash
|
||||
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 一次性执行
|
||||
|
||||
```bash
|
||||
# 全链路 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` 参数可强制全量重建。
|
||||
|
||||
```bash
|
||||
# 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` > 硬编码默认值
|
||||
|
||||
```bash
|
||||
# .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 渲染 |
|
||||
|
||||
**新增新闻源**:
|
||||
|
||||
```bash
|
||||
# 自动分析 → 输出 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
|
||||
```
|
||||
|
||||
**为已有源追加多频道入口**:
|
||||
|
||||
```bash
|
||||
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)
|
||||
|
||||
```bash
|
||||
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(默认) |
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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` |
|
||||
|
||||
```bash
|
||||
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/`
|
||||
|
||||
```bash
|
||||
uv run a-share ingest --date 20260822
|
||||
uv run a-share ingest --recreate # 重建 collection
|
||||
```
|
||||
|
||||
**Python 检索示例**:
|
||||
|
||||
```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 股公告/调研/互动易抓取管道,与新闻抓取分开调度。
|
||||
|
||||
```bash
|
||||
# 全链路一条命令
|
||||
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 服务
|
||||
|
||||
```bash
|
||||
sudo cp scripts/a-share-research.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable a-share-research
|
||||
```
|
||||
|
||||
### 7.2 日常操作
|
||||
|
||||
```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 # 查看状态
|
||||
|
||||
# 查看日志
|
||||
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 修改调度时间
|
||||
|
||||
```bash
|
||||
# 编辑 .env 中的 SCHEDULE_TIMES,格式: HH:MM,HH:MM,...
|
||||
nano /home/pi/news/.env
|
||||
# 例: SCHEDULE_TIMES=08:00,14:00,20:00
|
||||
|
||||
# 重启生效
|
||||
sudo systemctl restart a-share-research
|
||||
```
|
||||
|
||||
### 7.5 前台守护模式(调试)
|
||||
|
||||
```bash
|
||||
uv run a-share pipeline
|
||||
# Ctrl+C 退出
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. MCP 服务
|
||||
|
||||
### 8.1 连接 Cherry Studio
|
||||
|
||||
Cherry Studio → 设置 → MCP 服务器 → 添加:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"a-share-research": {
|
||||
"command": "uv",
|
||||
"args": ["run", "python", "-m", "scripts.run_mcp_server"],
|
||||
"cwd": "/home/pi/news"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 连接 Claude Code
|
||||
|
||||
编辑项目根目录 `.mcp.json`:
|
||||
|
||||
```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")` |
|
||||
|
||||
### 8.4 调试(SSE 模式)
|
||||
|
||||
```bash
|
||||
uv run python -m scripts.run_mcp_server --sse 8765
|
||||
# 浏览器访问 http://<host>:8765/sse
|
||||
```
|
||||
|
||||
### 8.5 工作原理
|
||||
|
||||
```
|
||||
用户输入自然语言查询
|
||||
→ DashScope 嵌入(1024 维)
|
||||
→ Qdrant 余弦相似度检索
|
||||
→ 按过滤条件筛选
|
||||
→ Markdown 格式化返回
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 日报系统
|
||||
|
||||
### 9.1 日报生成
|
||||
|
||||
```bash
|
||||
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 历史日报导入
|
||||
|
||||
```bash
|
||||
# 一次性导入 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 个股日报
|
||||
|
||||
```bash
|
||||
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: Qdrant 搜索不到结果?**
|
||||
|
||||
```bash
|
||||
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: 如何从零重建知识库?**
|
||||
|
||||
```bash
|
||||
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 新增)。中断后重跑:
|
||||
|
||||
```bash
|
||||
uv run a-share pipeline --once --resume --report
|
||||
```
|
||||
|
||||
从断点继续,已成功的步骤不会重复执行。
|
||||
|
||||
**Q: 树莓派 Qdrant Docker 启动崩溃?**
|
||||
|
||||
树莓派 ARM64 内核使用 16KB 内存页,Qdrant 官方 Docker 镜像内置的 jemalloc 仅支持 4KB 页。使用本地文件模式(默认)即可,无需 Docker。
|
||||
|
||||
**Q: 如何新增新闻源?**
|
||||
|
||||
```bash
|
||||
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` 不同,**勿互相覆盖**
|
||||
|
||||
---
|
||||
|
||||
—— 用户手册结束 ——
|
||||
Reference in New Issue
Block a user