Files
news/continuation.md
T
simon 366e60e8a9 feat: 日报结构化入库(M10 前后端分离数据层)
- 新增 report_db 包: MySQL 连接/建表/幂等写入 (news_report/news_event, myquant 库)
- 新增 report_import 包: 历史 178 份日报 HTML 解析入库, 表头驱动列映射
- reporter.py 完全切换: generate_report 结构化入库, 不再生成/上传 HTML
- CLI: 新增 report-import 子命令
- 依赖: uv add pymysql; 配置: NEWS_DB_* / REPORT_HISTORY_DIR
- 文档: docs/report_db_design.md(实现逻辑), docs/db_schema.md(表结构供 API/前端)
- 测试: 24 个单测通过 (parser/builder/models/importer)
2026-08-03 21:32:07 +08:00

261 lines
9.1 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.
# continuation.md
> `checkpoint` @ 2026-08-03 21:30
---
## 当前状态
| 项目 | 值 |
| --- | --- |
| 新闻源 | 14 个(13 Web + 1 API: xwlb 新闻联播) |
| Qdrant | 本地文件模式 `data/qdrant_storage/` |
| 日报 | **M10 完成: 结构化入库 MySQLmyquant 库 news_report/news_event 表),不再生成 HTML** |
| 调度器 | APSchedulersystemd `a-share-research.service` |
| LLM | `deepseek-v4-flash`(绝不允许擅自修改) |
| 服务器 | `pi@192.168.1.160`,项目 `/home/pi/news/` |
| 抓取方式 | js_render=false → httpx 直连;js_render=true → Playwright |
---
## 本次完成 (2026-08-03) — M10 日报结构化入库
**目标:** 日报前后端分离的数据层——日报内容结构化存入 MySQL(`news_` 前缀表),历史 178 份日报 HTML 解析入库;本项目不做 API/前端(用户另行实现)。
**表结构(myquant 库,见 docs/db_schema.md):**
- `news_report`:主表,唯一键 (report_date, report_type, file_name)`file_name=''` 表示新生成日报(每天每类型一行,重复生成覆盖)
- `news_event`:事件明细,section ∈ xwlb/news/cninfo/intl
**新增/修改文件:**
- `report_db/`models/schema/db):连接、建表、幂等 save_report
- `report_import/`parser/importer):历史 HTML 解析(表头驱动列映射)+ 批量导入
- `scheduler/reporter.py``generate_report()` 完全切换为结构化入库;`_render_html/_upload` 标记废弃保留
- `a_share_cli/main.py`:新增 `report-import` 子命令;`report` 命令改为入库提示
- `pyproject.toml``uv add pymysql`(唯一新增依赖)
- `.env`:新增 `NEWS_DB_*`(连接 127.0.0.1:13306,需 ssh 隧道)、`REPORT_HISTORY_DIR`
- `docs/report_db_design.md`(实现逻辑)、`docs/db_schema.md`(表结构,供 API/前端)
**验证结果:**
- 历史 178 份全部入库:scanned=178 imported=7(首轮)+170 skipped=171 failed=0DB 177 行(finance 49 + intl 1281 个跨目录同名文件被幂等合并)+ 事件 4222 条
- 端到端:`a-share report --date 20260616` → report_id=180 入库成功,无 HTML 产出
- 单测 24 个通过(parser 19 + builder 4 + models/import 补充)
**命令:**
```bash
ssh -L 13306:127.0.0.1:13306 pi # Mac 开发连库隧道
uv run a-share report --date YYYYMMDD # 生成日报入库
uv run a-share report-import # 历史导入(幂等)
```
**注意:** 本机 .env 无 LLM API keyAI 摘要会降级 WARNING(不影响入库);生产 pi5 需配置 NEWS_DB_PASSWORD 且解决 13306 隧道可达性(见待确认项)。
---
## 历史 (2026-07-17) — 禁用个股日报
**操作:** `STOCK_REPORT_TIME=` 设为空,`run_scheduler.py` 加空值守卫。
**文件:**
- `scripts/run_scheduler.py` L127-141`STOCK_REPORT_TIME` 为空时跳过注册并输出 `已禁用个股日报`
- Pi `.env``STOCK_REPORT_TIME=08:00``STOCK_REPORT_TIME=`
**恢复方法:** 改回 `STOCK_REPORT_TIME=08:00`,重启 `a-share-research`
---
## 本次完成 (2026-07-13) — eeo 反爬修复 + 静态直连 + 超时保护
### 问题
eeo(经济观察网)在 Pi 上用 Playwright 连续抓取后触发反爬,服务器故意不响应导致 `page.goto()` 超时(30s → 60s 均超时)。而在本机 Mac 上一切正常——反爬是 **IP 级别**的,Pi 的 IP 已被限流。
### 根因
eeo 是纯静态页面(`js_render: false`),但 Crawl4AI 始终走 Playwright。eeo 检测到 headless Chrome 的连续请求模式后,对后续请求挂起 TCP 连接直至超时。
### 修复
**`crawler/engine.py` — 三个改动:**
1. **新增 `_fetch_static()`**`js_render=False` 且无 `wait_for` 的源用 `httpx` 直连,绕过 Playwright 反爬。HTML → Markdown 用 `markdownify` + BeautifulSoup 清洗。
2. **新增 `_TimeoutGuard`**:同源连续超时 2 次后自动跳过剩余请求,防止整个 pipeline 被单一故障源拖死。
3. **`_crawl_once` 分流逻辑**
```
js_render=False 且无 wait_for → _fetch_static() (httpx)
其他 → Playwright
```
**`configs/sources.yaml` — eeo 源两处调整:**
- 删除 `wait_for: "css:body"`(静态页面无需等待 CSS 选择器)
- `page_timeout_ms: 30000 → 60000`(恢复 Crawl4AI 默认值)
### 测试结果
| 环境 | Playwright (修复前) | httpx (修复后) |
|------|---------------------|-----------------|
| Mac 本地 | 全部通过 | 36 篇 / 7 秒 |
| Pi 服务器 | **前 3 个请求全部 15s 超时** | 36 篇 / 9 秒,0 失败 |
### 验证方法
Pi 上本地复现了反爬(Playwright 前 3 个 URL 全部超时),同批 URL 用 httpx 全部瞬间返回——证明方案有效。
---
## 历史 (2026-06-24)
### 1. 环境变量全面审计与修复
**触发:** 服务器 `dedup` 步骤超时 120s。
**修复 6 个文件:**
| # | 文件 | 修改 |
|---|------|------|
| 1 | `scheduler/pipeline.py` | 超时解析重写:`TIMEOUT_{NAME}` > `PIPELINE_STEP_TIMEOUT` > 硬编码 > 1800s |
| 2 | `a_share_cli/main.py` | 新增 `_resolve_timeout()`,CLI 命令超时改为读环境变量 |
| 3 | `crawler/cninfo.py` | `CNINFO_PDF_BASE` 从硬编码改为 `os.environ.get()` |
| 4 | `scheduler/reporter.py` | `STOCK_REPORT_DAYS` 默认值 7→15`max_tokens` 600→1500 |
| 5 | `.env` | 清除 10 个死配置;`EMBEDDING_MODEL`→`LOCAL_EMBEDDING_MODEL`;补全遗漏参数 |
| 6 | `.env.example` | 重写为仅包含实际生效配置 |
**审计结果:**
| 类别 | 数量 | 处理 |
|------|------|------|
| ✅ 正常工作 | 23 | — |
| ❌ 死配置 | 10 | 已清除 |
| ⚠️ 默认值不一致 | 1 | 已统一 |
| 🔧 硬编码 | 1 | 已修复 |
### 2. AI 摘要截断修复
**触发:** 日报 AI 摘要内容极少(137 字),末尾被截断。
**根因:** `_llm_call()` `max_tokens=600` 太低,deepseek-v4-flash 输出被硬截断。
**修复:** `max_tokens` 三处提升 + 截断检测日志。
| 位置 | 修复前 | 修复后 |
|------|--------|--------|
| `_llm_call()` 默认 | 600 | 1500 |
| 分块摘要 | 400 | 800 |
| 合并摘要 | 600 | 1500 |
**验证:** 手动生成日报,摘要从 137 字 → 486 字,结构完整无截断。
---
## 超时优先级(最终设计)
```
TIMEOUT_{NAME} 环境变量 ← 单步精确控制
↓ 未设
PIPELINE_STEP_TIMEOUT 环境变量 ← 全局兜底 (覆盖所有硬编码)
↓ 未设
STEP_TIMEOUTS 硬编码字典 ← 代码内默认值
↓ 步骤不在字典中
1800s ← 最终兜底
```
---
## 服务器 .env 当前配置
```env
# ---- 调度 ----
SCHEDULE_TIMES=07:00,12:00,18:00,22:00
CNINFO_SCHEDULE_TIME=06:00
STOCK_REPORT_TIME=08:00
STOCK_REPORT_DAYS=15
# ---- Pipeline 超时 ----
PIPELINE_STEP_TIMEOUT=3600
TIMEOUT_CRAWLER=900
TIMEOUT_XWLB=60
TIMEOUT_EXTRACTOR=300
TIMEOUT_DEDUP=900
TIMEOUT_LLM=900
TIMEOUT_EMBEDDING=900
TIMEOUT_QDRANT=1800
TIMEOUT_CNINFO_CRAWL=900
TIMEOUT_CNINFO_EXTRACT=900
TIMEOUT_CNINFO_PDF=900
```
---
## 调度器
| 时间 | 步骤 |
|------|------|
| 06:00 | cninfo 公告管道 |
| 07:00 | crawler→xwlb→extractor→dedup→llm→embedding→qdrant→**日报** |
| 08:00 | 个股日报 |
| 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 |
---
## 常用命令
```bash
# 全链路 + 日报
uv run a-share pipeline --once --report
# 仅日报 (指定日期)
/home/pi/.local/bin/uv run python -m scripts.run_scheduler --once --date 20260623 --steps report
# 检索
uv run a-share search "关键词" --top 5
# 同步到服务器
scp <file> pi@192.168.1.160:/home/pi/news/<path>/
# 重启服务
ssh pi@192.168.1.160 "sudo systemctl restart a-share-research"
```
---
## 已知技术债务
| 问题 | 文件 | 状态 |
|------|------|------|
| QdrantClient HTTP 超时硬编码 10s | `vectorstore/client.py:93` | 无可配环境变量 |
| DashScope HTTP 超时硬编码 60s | `embedding/remote.py:81` | 无可配环境变量 |
| 超时解析逻辑在 pipeline.py 和 main.py 各一份 | 两处 | 可抽取公共函数 |
| `deepseek-v4-flash` 概率性返回空 | — | 分块处理 + 失败跳过 |
| wallstreetcn JS 渲染拦截 | — | 正文提取率 0% |
| eeo 列表页仍走 Playwright(仅文章页走 httpx | `crawler/engine.py` | 列表页目前正常,暂不改动 |
---
## 架构备忘:静态/动态抓取分流
```
SourceConfig.js_render == False 且 wait_for == None
→ _fetch_static() → httpx 直连 (无浏览器, 快, 不被反爬)
→ markdownify + BeautifulSoup (清洗 script/style 后转 Markdown)
SourceConfig.js_render == True 或 wait_for 有值
→ _crawl_once() → Playwright (现有逻辑)
```
`_TimeoutGuard`:
- 同源连续超时 2 次 → `skip=True`
- 后续请求在 `semaphore` 内检查 `guard.skip`,立即返回 `Skipped`
- 一次成功即重置计数器
---
## 记忆
- `never-change-llm-model.md` — 绝不允许修改大模型配置
- `deploy-html.md` — user_guide.html 同步到 Pi 和 Web 服务器
- `sync-sources-yaml.md` — 修改 sources.yaml 前从 Pi 拉取,改完推回
- `update-user-guide.md` — 更新 md+html+部署