main.log 无轮转、已涨到 34MB,且每晚任务都会继续追加。 - logrotate/xwlb 模板 + scripts/install_logrotate.sh(幂等,装到 /etc/logrotate.d/xwlb) - 每天轮转、保留 14 份、立即压缩 → main.log-YYYYMMDD.gz - 必须用 copytruncate:脚本以 >>main.log 长开句柄,改名式轮转会 让当晚日志写进归档、新文件空着 - journald 同步设 MaxRetentionSec=14day + SystemMaxUse=500M - 安装脚本自带 logrotate -d 校验(这条立刻抓到"指令行尾注释"被当成参数值的错误) - tests/test_logrotate.py 19 项,锁住保留天数、行尾注释、占位符替换 实测:34MB → 2.9MB,归档 32453 行与轮转前完全一致
370 lines
28 KiB
Markdown
370 lines
28 KiB
Markdown
# xwlb 项目缺陷清单
|
||
|
||
审查范围:仓库内全部 Python 源码 + `main.log`(2026-06-01 ~ 2026-09-23,34MB,约 46 个有效运行日)。
|
||
结论口径:每条缺陷给出**位置 → 问题 → 影响 → 证据 → 修复方向**。证据栏区分"日志实测"与"代码推断"。
|
||
|
||
严重度定义:
|
||
- **P0**:造成数据丢失或数据错误(静默发生、无人察觉)
|
||
- **P1**:功能不生效、崩溃、成本浪费、磁盘/日志失控
|
||
- **P2**:工程与可移植性问题(换环境即失败)
|
||
|
||
---
|
||
|
||
## 汇总表
|
||
|
||
| # | 严重度 | 位置 | 一句话问题 | 实际影响 | 本次状态 |
|
||
|---|---|---|---|---|---|
|
||
| B1 | P0 | `audioRead.py:186,191-193,260` | qwen 校对超时(300s)被当成 ASR 失败,整片文本丢弃 | 46 天、57 次,每天约丢 2/11 分片(~1400 字)静默缺口 | ✅ 已修复并实测 |
|
||
| B2 | P0 | `audioRead.py:275,289-292` | 校对结果恒为 `None`,回退原文 | 校对功能 0 收益,却贡献了全部 57 次超时与 token 成本 | ✅ 已修复并实测 |
|
||
| B3 | P0 | `newsProcess.py:89,119-121` | DeepSeek 返回非纯 JSON(```json 围栏 / 截断)即整批失败 | 22 天 `xwlb_daily_ext` 当天 0 条 | ✅ 已修复(回归测试覆盖) |
|
||
| B4 | P0 | `getVideo5.py:69,92,156` | `get_xwlb_video_link()` 返回值错误(只回最后一个 / 未定义 / `[]`) | 取错链接、`UnboundLocalError`、无效 URL 仍进下载 | ✅ 已修复并实测(联网) |
|
||
| B5 | P0 | `getVideo5.py:104-146`、`audioRead.py:389-399` | 下载与分片落库无幂等 | 重跑 → 重复下载 + `xwlb_daily` 重复行 → 新闻重复、token 翻倍 | ✅ 已修复并实测 |
|
||
| B6 | P1 | `audioRead.py:260`、`getVideo5.py:132-140` | 关键外部调用无 timeout/重试;ffmpeg 输出未接管 | 单点失败无补救;`main.log` 34MB 且无轮转 | 🟡 部分(校对已加超时/重试;ffmpeg 输出已接管;**日志轮转已完成**;yt-dlp 进度未处理) |
|
||
| B7 | P1 | `deepseek.py:257-268` | 降级分支 `return result`(变量错/可能未绑定) | DeepSeek 失败时抛 `NameError`,降级形同虚设 | ✅ 已修复(`return fallback_result`) |
|
||
| B8 | P1 | `audioRead.py:422-439` | `__main__` 示例用 3 参数调 4 参数签名 | `python audioRead.py` 必错 | ✅ 已修复(改为 `python audioRead.py [YYYYMMDD]`) |
|
||
| B9 | P1 | `audioRead.py:387`、`getVideo5.py:104-146` | 临时文件不清理(chunk/input.wav/mp4) | 单日约 180MB 常驻,磁盘持续增长 | 🟡 部分(分片与按日 WAV 已在 `finally` 清理;mp4/mp3 保留策略待定) |
|
||
| B10 | P2 | `mysqlHandle.py:5` | 依赖父项目 `utils.mysql_handler`,本机不存在 | 所有脚本 import 即 `ModuleNotFoundError` | ✅ 已修复(内置 `mysql_handler.py`) |
|
||
| B11 | P2 | `getVideo5.py:170,178`、`audioRead.py` | 硬编码 `/home/simon/myquant/djapi/...` | 换机即废(本机已复现) | ✅ 已修复(基于项目目录 + 环境变量可覆盖) |
|
||
| B12 | P2 | `env.py:9` | `.env` 定位按老 djapi 目录层级,且不覆盖已有变量 | 指向 `/home/pi/.env`,实际不存在 | ✅ 已修复(项目内 `.env`,支持 `XWLB_ENV_FILE`) |
|
||
| B13 | P2 | `newsProcess.py:56`、`newsRedo.py:69` | "已处理"判据为 `COUNT(*) > 5` | 少于 5 条的日期被判定未处理,无限重跑 | ✅ 已修复(存在任意记录即视为已处理,`--force` 可强制) |
|
||
| B14 | P2 | 仓库根目录 | 产物入库、非 git 仓库 | 354MB 产物 + 34MB 日志 + 失效 `.pyc` | 🟡 部分(已加 `.gitignore`、清理失效 `__pycache__`;未 `git init`、历史产物未清) |
|
||
| B15 | P2 | 全局 | 无 `.env.example`,密钥缺失/失效时报错不指向配置 | 401 当天 33 次报警,排查成本高 | 🟡 部分(已加 `.env.example` 与连接失败明确报错;启动期配置校验未做) |
|
||
|
||
---
|
||
|
||
## 本次修复的验证记录(实测,非推断)
|
||
|
||
验证环境:项目 venv(Python 3.13.5)+ 真实 DashScope/DeepSeek key + 通过 SSH 隧道(13306)连接生产 MariaDB。
|
||
端到端验证使用**测试日期 `1900-01-01`** 与裁剪后的 5 分钟音频,验证完成后已删除测试数据(`xwlb_daily` 2 行、`xwlb_daily_ext` 3 行,复查均为 0)。
|
||
|
||
| 验证项 | 方法 | 结果 |
|
||
|---|---|---|
|
||
| B12 配置加载 | 运行任意脚本 | `已加载配置文件 /home/pi/project/xwlb/.env(注入 18 个变量)` |
|
||
| B10 DB 层 | `import mysqlHandle` 后直接构造 `MySQLDB()` | 连接成功;故意用错端口时抛出带修复指引的 `RuntimeError`(不再是 `AttributeError`) |
|
||
| B8/B1/B2 真实链路 | `process_long_audio()` 处理 5 分钟真实音频 | 2 个分片全部成功;校对**有真实产出**:677→699、635→632 字;`返回 {'chunks': 2, 'ok': 2, 'failed': 0}`;落库 2 行(2011/2079、1885/1880 字),**空白行 0** |
|
||
| B5 分片幂等 | 同 `(日期, 分片)` 连续写入 3 次(其中 2 次同键) | 只保留 2 行(子号 0、1)且为最新内容;对照旧逻辑会累积成 3 行 |
|
||
| B3 解析健壮性 | `tests/test_news_parse.py`,含日志中真实坏返回 | 15 项全部通过(```json 围栏、夹解释文字、对象包装、混入字符串、缺字段等均可解析;截断/非 JSON 按预期抛错触发重试) |
|
||
| B3/B5/B13 切分入库 | 对同一测试日期连续执行 3 次 `news_to_db` | 第 1 次切出 3 条并写入;第 2 次(无 force)正确跳过;第 3 次(force)**替换**旧 3 条 → 仍为 3 条,重复 `sub_id` 组数 = 0 |
|
||
| B4 抓取 | 联网解析 20260923/20260924/20260925 三天页面 | 返回类型为 list;09-23、09-24 各解析出 1 条完整版链接;09-25(尚不存在,404)被明确记录并跳过,不再产生空 URL |
|
||
| **2026-09-04 生产级实跑** | `getVideo5.py 20260904 20260904` 完整跑一遍(下载 98MB → 抽音 → 11 片 ASR+校对 → 切分入库) | 11/11 分片成功(原空白分片 7 补回 2091 字);切分首轮遇 `Unterminated string`(**正是当初让该日 0 条精编的原因**),第 2 轮强化提示词成功切出 34 条;见下方"事实篡改"一节,修复后复查 0 漂移 |
|
||
| 数值事实守卫 | `tests/test_fidelity_guard.py`(13 用例) | 全部通过:`2027→2024`、`十一→九`、`十五五→十四五`、`5.3→5.31`、`300→30` 均拦下;`7↔七`、`2026↔二零二六`、`55%↔百分之五十五` 均放行 |
|
||
|
||
> 未验证项:本轮未对 `xwlb_daily` 中既有的 101 行空白记录与 203 天缺失 `xwlb_daily_ext` 做数据修复/回填——那属于数据治理,需你确认后再执行(会消耗 API 额度)。
|
||
|
||
---
|
||
|
||
## P0 详情
|
||
|
||
### B1 qwen 校对超时导致已成功的 ASR 结果被静默丢弃
|
||
|
||
**位置**:`audioRead.py:186`(`transcribe_audio` 内调用 `analyze_and_correct_text`)、`:260`(`Generation.call`,未设 timeout)、`:191-193`(异常兜底 `return ['', '']`)、`:369-399`(`process_long_audio` 落库,无条件插入)
|
||
|
||
**问题**:ASR 识别与"qwen 文本校对"被串在同一个 `try` 里。校对调用一旦抛异常(实测全部是 300s 读超时),异常被 `transcribe_audio` 的兜底捕获,函数返回 `['', '']`——**已经识别成功的文本不再返回**。`process_long_audio` 随后照常以空字符串插入一行。
|
||
|
||
**影响**:
|
||
- 每个受影响分片丢失约 700 字识别文本,且**没有任何失败标记**(`news_raw=''`、`news_improve=''` 与"正常但短"无法区分);
|
||
- 46 个运行日受影响,每天约 2 个分片,累计约 4 万字内容缺口;
|
||
- `news_to_db` 拿到的是缺失版全文,DeepSeek 切分出的新闻条数/内容随之缺失,且上游完全无感知。
|
||
|
||
**证据(日志实测)**:`read timeout=300` 共 57 次,**前一行 100% 是"调用通义千问模型进行文本修正"**(校验:`grep -B1 "read timeout=300" | grep -c 调用通义千问` = 57,`grep -c 开始识别音频` = 0),说明超时发生在校对而非识别。分布 2026-06-18 ~ 2026-09-23,覆盖 46 个不同日期,最后一天(09-23)仍在发生,**属存活缺陷**。
|
||
|
||
**修复方向**:
|
||
1. 解耦:ASR 成功即先落库 `news_raw`,校对作为独立后续步骤(失败保留原文,不丢数据);
|
||
2. 校对调用单独设 `timeout`(dashscope 支持 `timeout=`)+ 有限重试,超时按"放弃校对"处理而非"放弃识别";
|
||
3. 空文本不入库,或入库但显式记录 `status=failed` 供 `news_to_db` 跳过。
|
||
|
||
---
|
||
|
||
### B2 qwen 校对功能实际从未生效
|
||
|
||
**位置**:`audioRead.py:275`(`return response.output.text`)、`:277-299`(`analyze_and_correct_text`)
|
||
|
||
**问题**:`text_correction` 返回 `None`(未抛异常),`analyze_and_correct_text:290-292` 判断为 `None` 后回退原文。
|
||
|
||
**影响**:
|
||
- 校对本应修错别字/标点,实际 `news_improve == news_raw`,**功能 0 收益**;
|
||
- 代价是全部 57 次 300s 超时(B1 的根因)、每个分片 30~300s 的额外耗时,以及 qwen 的全部 token 成本;
|
||
- 它同时把"校对"变成了"随机丢分片"的赌局。
|
||
|
||
**证据(日志实测)**:每个分片固定出现 `✓ 文本修正完成` → `WARNING - 文本修正返回 None,使用原始文本` → `原始文本长度: N` / `修正后文本长度: N`(两值恒等;如 09-23 的 798/798、708/708、688/688)。
|
||
|
||
**疑似原因**:`Generation.call` 走 `messages` 时该 SDK 版本的文本取法应为 `response.output.choices[0].message.content`,`.output.text` 为空;或 `max_tokens=30000` 超出模型上限导致 output 为空但 status 仍 200。
|
||
|
||
**修复方向**:修正结果字段取法(或改用 OpenAI 兼容接口);若确认无收益则直接删掉这一步,只保留 ASR。
|
||
|
||
---
|
||
|
||
### B2 补充(2026-09-25 实测发现):校对修好后会**篡改事实**
|
||
|
||
把 B2 修好之后校对第一次真正生效,随即在 2026-09-04 的真实数据上发现它**改错了事实**:
|
||
|
||
| 内容 | ASR 原文 | qwen 校对后 | 判定 |
|
||
|---|---|---|---|
|
||
| 条例施行日期 | 自 **2027** 年1月1日起施行 | 自 **2024** 年1月1日起施行 | ❌ 改错(新签署的条例不可能追溯生效) |
|
||
| 五年规划 | **十五五** | **十四五** | ❌ 改错(2026 年应为十五五) |
|
||
| 东方经济论坛届次 | **第十一**届 | **第九**届 | ❌ 改错(同一天开场提要也作"第十一届") |
|
||
| 年份(多处) | 2025 / 2026 | 2023 / 2024 | ❌ 改错 |
|
||
| 数量 | 55 / 300亿 | 60 / 30亿 | ❌ 改错 |
|
||
|
||
11 个分片中 **8 个**存在数值/序数被改写;这些错误会经 `news_improve` 直接进入最终 `xwlb_daily_ext`(DeepSeek 忠实复制了它的输入)。
|
||
|
||
**影响**:这比"校对不生效"更危险——原文虽是 ASR 结果但事实可信,校对后反而产生**看起来通顺但事实错误**的文本。
|
||
|
||
**已实施的修复**:
|
||
1. **数值事实守卫**(`audioRead._number_drift`):中文数字与阿拉伯数字统一归一到数值后比较,若校对前后数值集合与拼接串均不一致,则该分片**整片回退 ASR 原文**;`百分之X` 与 `X%`、`7 ↔ 七`、`2026 ↔ 二零二六` 等纯书写差异不会误拦。
|
||
2. **提示词加固**:明确禁止改动数字/年份/日期/届次/数量/机构名/人名/地名/专有名词。
|
||
3. **开关**:`LLM_CORRECT_ENABLED=0` 可完全关闭校对(直接以 ASR 原文入库)。
|
||
4. 回归测试 `tests/test_fidelity_guard.py`(13 个用例,含上表中的真实篡改样例)。
|
||
|
||
**取舍**:被守卫拦下的分片会连"合理的 ASR 修复"一起放弃(例如同一分片里的 `弗拉迪沃斯。波克` → `符拉迪沃斯托克`、`丁学祥` → `丁薛祥`)。守卫的取向是**宁保留原文,不接受 LLM 改数**。
|
||
|
||
**待你决定**:`LLM_CORRECT_ENABLED` 的默认值。当前默认 `1`(开启+守卫),另一选择是默认 `0`(完全不改写原文,最保守,且每天省下 11 次 qwen 调用)。
|
||
|
||
---
|
||
|
||
### B3 DeepSeek 返回非纯 JSON 时整批切分丢失
|
||
|
||
**位置**:`newsProcess.py:86-89`(提示词与 `deepseek_text`)、`:119-121`(`json.JSONDecodeError` 仅记录后放弃)
|
||
|
||
**问题**:依赖 `response_format={"type":"json_object"}` 保证纯 JSON,但模型仍会输出 ```json 围栏或超长截断(`max_tokens=20000`),解析失败后**不重试、不降级**,直接丢弃当天全部文本。
|
||
|
||
**影响**:22 个日期 `xwlb_daily_ext` 当天 0 条记录;由于 B13 的判据,这些日期之后会被反复重跑,而每次重跑又触发 B5 的重复写入。
|
||
|
||
**证据(日志实测)**:`JSON解析失败` 22 次,覆盖 22 个不同日期(2026-06-03 ~ 2026-09-14);报错内容含 `Expecting value: line 1 column 1`(前后被 ``` 包裹,日志里可见 `DeepSeek API返回内容: ```json`)与 `Unterminated string starting at: line 80 column 21`(截断)。另有 1 次 `插入数据库失败: string indices must be integers`,说明 `news_list` 元素有时是字符串而非 dict(`news["news_id"]` 直接炸)。
|
||
|
||
**修复方向**:剥离围栏后再解析 → 失败则重试一次(提高 max_tokens / 降低 temperature)→ 仍失败则按空行/段落降级切分;对 `news_list` 元素做类型校验与字段缺失兜底。
|
||
|
||
---
|
||
|
||
### B4 `get_xwlb_video_link()` 返回值错误
|
||
|
||
**位置**:`getVideo5.py:34-69`(构建 `video_links` 但 `return video_url`)、`:86-94`(`get_all_video_links`)、`:149-171`(`process_videos`)
|
||
|
||
**问题**:三处叠加。
|
||
1. `:69` 返回的是循环里最后一次赋值的**单个 URL 字符串**,`video_links` 列表被丢弃 → 当天若有多个匹配链接只取最后一个;
|
||
2. 请求异常路径 `return []`(`:28,:31`),但 `get_all_video_links:92` 仍包装成 `{"url": [], "date": ...}`;
|
||
3. `process_videos:156` 用 `if sub_url:` 判断——非空 dict 恒为真,**错误值不会被拦截**,`[]` 会被送进 yt-dlp;
|
||
4. 若一个 a 标签都没匹配到,`video_url` 从未赋值 → `UnboundLocalError`(不是被捕获的异常类型)。
|
||
|
||
**影响**:抓取失败/页面改版时不是"跳过并告警",而是带着无效 URL 继续下载,或用错链接下载到非新闻联播内容;幂等性判断(B5)也因此失效。
|
||
|
||
**证据**:代码推断(`get_xwlb_video_link` 内 `video_links` 变量在 `:34` 定义、`:61` 追加、`:69` 未被返回,可静态确认)。
|
||
|
||
**修复方向**:`return video_links`;调用方展开列表并对空值 `continue`;`if sub_url.get('url')` 显式判空。
|
||
|
||
---
|
||
|
||
### B5 下载与分片落库无幂等
|
||
|
||
**位置**:`getVideo5.py:104-146`(`download_and_extract_audio` 不检查 mp4/mp3 是否已存在)、`audioRead.py:389-399`(无条件 `insert_data`)
|
||
|
||
**问题**:`newsRedo.py:79-108` / `main_videos.py:23-58` 判断"是否需要处理"只看 `xwlb_daily` 有没有当天的行。而重跑会**追加**一整组 `daily_sub_id = 0..N` 的新行(`nid` 自增,`(news_days, daily_sub_id)` 无唯一约束)。
|
||
|
||
**影响**:
|
||
- `get_news_improve_by_date`(`newsProcess.py:61-65`)按 `daily_sub_id` 排序拼接,会把同一天的文本**重复拼两遍**送进 DeepSeek → 切分结果重复、token 翻倍、可能超出上下文;
|
||
- 重复下载 100MB 级 mp4(B9 磁盘问题同步放大);
|
||
- 与 B13 组合会形成"重跑 → 重复 → 再重跑"的循环。
|
||
|
||
**证据**:代码推断(无唯一索引、无存在性检查)。
|
||
|
||
**修复方向**:`(news_days, daily_sub_id)` 加唯一索引 + `INSERT ... ON DUPLICATE KEY UPDATE`;下载前检查目标 mp4/mp3 是否存在且非空;`daily_sub_id` 用分片序号而非可变下标。
|
||
|
||
---
|
||
|
||
## P1 详情
|
||
|
||
### B6 外部调用无超时/重试,子进程输出未接管
|
||
|
||
**位置**:`audioRead.py:258-266`(`Generation.call` 无 timeout、无重试)、`:168-186`(`Recognition.call` 单次失败即放弃)、`getVideo5.py:132-140`(ffmpeg `check=True` 但 `stdout=None, stderr=None`)、`:123`(yt-dlp 进度 hook 直接 `print`)
|
||
|
||
**影响**:
|
||
- 单分片 ASR 失败无任何补救,直接进入下一片;
|
||
- ffmpeg 的完整 banner(版本、编译参数、流信息)与 yt-dlp 的逐帧进度全部落到 stdout,`main.log` 已达 34MB 且**无 logrotate**;一次失败排查需要在数万行进度里翻找;
|
||
- 超时阈值 300s 是 SDK 默认,与业务预期(每天 51 分钟跑完)不匹配。
|
||
|
||
**修复方向**:统一 timeout + 指数退避重试;ffmpeg 用 `stdout=subprocess.DEVNULL, stderr=subprocess.PIPE` 并只在失败时打印尾部;日志加 `RotatingFileHandler` 或把进度输出降级为 DEBUG。
|
||
|
||
---
|
||
|
||
### B7 `deepseek.py` 降级分支变量写错
|
||
|
||
**位置**:`deepseek.py:241-268`
|
||
|
||
**问题**:`try` 中 `result = api_client.process_text(...)`;`except` 里调用 `process_text_with_fallback(...)` 得到 `fallback_result`,却 `return result`。异常路径下 `result` 未绑定 → `NameError`(若部分成功则返回错误内容)。
|
||
|
||
**影响**:本应"降级返回原文"的兜底变成二次异常,被 `newsProcess.py:122` 的宽 `except` 吞成一行 `插入数据库失败`,与 B3 的静默失败叠加。
|
||
|
||
**证据**:代码推断(`deepseek.py:261-268`)。**附带**:`deepseek.py:271` 的 `deepseek_text()` 缺必填参数,`python deepseek.py` 直接 `TypeError`。
|
||
|
||
**修复方向**:`return fallback_result`;`__main__` 补参数或改成 CLI。
|
||
|
||
---
|
||
|
||
### B8 `audioRead.py` 的 `__main__` 示例已过期
|
||
|
||
**位置**:`audioRead.py:422-439`
|
||
|
||
**问题**:`process_long_audio(mp3_path, prompt, output_folder)` 传 3 个位置参数,而签名是 `(mp3_path, output_folder, date_str)` → `prompt` 被当作输出目录,`date_str` 缺失。
|
||
|
||
**影响**:任何人按文件底部示例单独调试都会失败,且会把中文提示词当目录名创建。
|
||
|
||
**修复方向**:改为 `process_long_audio(mp3_path, output_folder, date_str)`,或直接删掉示例。
|
||
|
||
---
|
||
|
||
### B9 临时文件与产物不清理
|
||
|
||
**位置**:`audioRead.py:387`(仅成功分支 `os.remove(chunk_path)`)、`:352-354`(`input.wav` 不删)、`getVideo5.py:113-114`(mp4/mp3 长期保留)
|
||
|
||
**影响**:单日约 104MB mp4 + 24MB mp3 + 55MB wav ≈ 180MB;仓库当前已含 `xwlb_video/` 299MB、`audio_processing/input.wav` 55MB。长期运行必然打满磁盘,且在失败分支 chunk 会残留。
|
||
|
||
**修复方向**:处理完即删中间产物(或按保留天数清理);mp3 作为可重建产物不必长期保存;`finally` 中清理 chunk。
|
||
|
||
---
|
||
|
||
## P2 详情
|
||
|
||
### B10 `utils.mysql_handler` 缺失(当前无法运行的首要原因)
|
||
|
||
**位置**:`mysqlHandle.py:4-5`
|
||
|
||
**问题**:`sys.path.insert(0, <xwlb 的父目录>)` 后 `from utils.mysql_handler import MySQLDB`,而 `/home/pi/project/utils/` 不存在。
|
||
|
||
**影响**:所有脚本(包括仅查库的 `newsRedo.py`)**import 阶段即失败**:已在本机复现 `ModuleNotFoundError: No module named 'utils'`。
|
||
|
||
**证据(日志实测,反向线索)**:日志中 `查询失败: local variable 'cursor' referenced before assignment` 说明该模块在连接失败时自身还有未初始化变量缺陷——重写时应一并避免。
|
||
|
||
**修复方向**:在项目内提供自包含的 `mysql_handler.py`,保持 `MySQLDB()` / `query_data(table, columns, where, params)` / `insert_data(table, data)` / `close()` 四个接口不变,配置从环境变量读取。
|
||
|
||
---
|
||
|
||
### B11 硬编码绝对路径
|
||
|
||
**位置**:`getVideo5.py:170`(`/home/simon/myquant/djapi/api/video/xwlb_video`)、`:178`(同前缀 `audio_processing`)、`audioRead.py:353` 的输出目录同样来自该前缀
|
||
|
||
**影响**:迁移到任何其他机器都不可用;本机 `pwd` 为 `/home/pi/project/xwlb`,正确位置应是仓库内的 `xwlb_video/` 与 `audio_processing/`(数据也确实已放在那里)。
|
||
|
||
**修复方向**:以 `Path(__file__).parent` 为基准,或由 `env` 提供 `XWLB_VIDEO_DIR` / `XWLB_AUDIO_DIR`。
|
||
|
||
---
|
||
|
||
### B12 `.env` 定位与优先级
|
||
|
||
**位置**:`env.py:9`(`parent.parent.parent / '.env'` → `/home/pi/.env`)、`:23`(`if key not in os.environ` 不覆盖)
|
||
|
||
**影响**:按老 `djapi/api/video/` 三层结构推导,本项目只有一层,指向错误位置且文件不存在 → 密钥全部缺失;"不覆盖已有变量"的行为未文档化,容易与容器注入的变量混淆。
|
||
|
||
**修复方向**:改为项目根/`xwlb` 目录下的 `.env`,并支持 `XWLB_ENV_FILE` 覆盖;在文档中写明优先级(环境变量 > `.env`)。
|
||
|
||
---
|
||
|
||
### B13 "已处理"判据用行数阈值
|
||
|
||
**位置**:`newsProcess.py:49-57`、`newsRedo.py:62-71`
|
||
|
||
**影响**:某天若只切出 ≤5 条新闻(短节目/切分异常),会被永久判定为"未处理"并在每次补漏时重跑;配合 B5 造成重复数据。
|
||
|
||
**修复方向**:以"是否已经跑过切分"的显式状态为准(如 `xwlb_daily_ext` 存在任意行即视为已处理,或新增处理状态表/字段)。
|
||
|
||
---
|
||
|
||
### B14 仓库卫生
|
||
|
||
**位置**:仓库根目录
|
||
|
||
**问题**:`main.log` 34MB、`xwlb_video/` 299MB、`audio_processing/input.wav` 55MB 与源码混放;`__pycache__/` 内含 `ai.cpython-310.pyc`、`parsem3u8.cpython-310.pyc` 等**已删除模块**的产物(3.10/3.11,与当前 3.13 不符);目录**不是 git 仓库**,无变更历史可回溯。
|
||
|
||
**修复方向**:加 `.gitignore`(`*.mp4 *.mp3 *.wav main.log __pycache__/`)、`git init` 纳入版本管理、清理失效 `__pycache__`。
|
||
|
||
---
|
||
|
||
### B15 配置与密钥错误不可诊断
|
||
|
||
**位置**:全局(`deepseek.py:21-23` 只 warning、`audioRead.py:166` 直接赋值空串)
|
||
|
||
**影响**:`DASHSCOPE_API_KEY` 无效/缺失时表现为 2026-06-01 那天连续 33 条 `401 Unauthorized` + 11 个分片全废,但报错信息不指向"检查 `.env`";`DeepSeekAPI` 缺 key 仅 warning,随后才在调用处抛异常。
|
||
|
||
**修复方向**:提供 `.env.example`;启动时做一次配置校验(缺 key 直接 fail-fast 并打印期望的文件路径)。
|
||
|
||
---
|
||
|
||
## 本轮新发现(2026-09-25,B16~B19)
|
||
|
||
### B16(严重 · 静默换模型)兜底分支偷偷用另一个模型
|
||
|
||
**位置**:`deepseek.py` `process_text_with_fallback`
|
||
**问题**:兜底调用 `self.process_text(prompt, text, system_prompt)` **未传 model**,于是用函数签名里的硬编码默认值 `deepseek-chat`。
|
||
**影响**:配置里的模型名无效(如 `deepseek-v4.1-flash`)时,主调用 400 报错、兜底却用另一个模型成功返回,**"按配置运行"看起来完全正常,实际配置从未生效**——排障方向会被彻底带偏。
|
||
**修复**:兜底显式传入同一模型与参数;切分环节改为 `use_fallback=False`(它自带重试与解析校验),配置错误直接暴露。现已验证:模型名写错立刻报 `The supported API model names are ...`。
|
||
|
||
### B17(严重 · 数据完整性)重试判定会把"半天"当"一天"
|
||
|
||
**位置**:`scripts/day_status.py`(本轮新增)
|
||
**问题**:只看"库里有没有分片"就判断"只缺切分"。识别中途卡死同样会留下部分分片,
|
||
于是重试走"只重跑切分",把半天内容切分入库并标记为完成。
|
||
**影响**:静默产生**不完整的一天**,且不会报错;事后极难发现(那天看起来是正常的)。
|
||
**修复**:识别全部完成后写标记 `state/.asr_complete_<日期>`(放 `state/`、**不随清理删除**,
|
||
否则无法与"识别只跑了一半但切分照样写了 ext"区分),`day_status.py` 只有**同时**满足
|
||
"有分片 + 有标记"才允许只重跑切分,否则重跑全链路;
|
||
并在 `process_videos` 中改成**识别不完整就不执行切分**,不发布"看起来完整"的半天数据。
|
||
|
||
### B18(高 · 推理模型截断/空回复)max_tokens 未按推理模型调整
|
||
|
||
**位置**:`config.yml` `llm_split.max_tokens`(原 20000 / 重试 32000)
|
||
**问题**:`deepseek-flash` 是推理模型,reasoning token 实测在 14k~24k 间波动且**计入 max_tokens**。
|
||
20000 时实测出现两种故障:输出截断在 JSON 中途(`finish_reason=length`)、
|
||
或推理耗尽全部额度导致 **content 为空**(报"LLM 响应为空")。
|
||
**影响**:切分随机失败或写入被截断的新闻;每晚概率性发生。
|
||
**修复**:上限提到 60000(重试 80000)——按实际产出计费,给足上限不额外花钱;
|
||
`timeout` 60→180 秒(2 万 token 级别输出需要更久)。
|
||
|
||
### B19(高 · 可用性)ASR 的 `Recognition.call` 无超时,长连接悬挂会卡死整晚
|
||
|
||
**位置**:`audioRead.transcribe_audio` → dashscope `Recognition.call`
|
||
**问题**:SDK 内部 `for part in responses` 无限等待 websocket 响应,**没有任何超时参数**
|
||
(签名无 timeout,内部 `while True`)。实测 2026-09-24 重跑时卡在 chunk 3 之后:
|
||
进程 0 CPU、`futex_do_wait`、无新日志,**15 分钟以上无进展**。
|
||
**影响**:手动跑会一直挂着;定时任务靠外层上限兜底,但会白白耗掉一次尝试。
|
||
**缓解**:`scripts/run_daily.sh` 单次尝试 40 分钟上限 + 21:30/22:00 重试;
|
||
systemd `TimeoutStartSec=10800`。**根治需要给 ASR 加子进程看门狗**(列入待办)。
|
||
|
||
---
|
||
|
||
## 后续待办(本轮新增)
|
||
|
||
7. **ASR 看门狗**(对应 B19):把 `Recognition.call` 放进子进程执行并加超时,
|
||
超时终止子进程、该分片记失败后**继续下一片**,而不是整晚挂死。
|
||
8. **回补校对失效期**(见 `REPORT_raw_vs_improve.md`):2026-06-15~2026-09-23 的
|
||
`news_improve` 实际等于 ASR 原文(2026-07/08 为 100% 未改动)。校对现已修复且
|
||
关掉思维链后成本降到约 1/13,回补约 100 天 ≈ 1M token,需确认是否执行。
|
||
9. **重试跳过已识别分片**:当前重试会重跑全部 ASR(识别已完成的片也重做)。
|
||
加"已入库分片跳过"可让重试只补缺失片,需注意别让 `newsRedo.py` 的强制重跑被静默跳过。
|
||
|
||
---
|
||
|
||
## 后续待办(本轮未做)
|
||
|
||
1. **数据治理**:`xwlb_daily` 现存 101 行空白记录(可识别、可重跑修复);725 天原文里 203 天没有 `xwlb_daily_ext`——回填需重新调用 DeepSeek,属于有成本的操作,需你确认范围后再执行。
|
||
2. **B6 收尾**:yt-dlp 进度 hook 仍直接 `print`;
|
||
~~`main.log` 无轮转~~ —— **已完成(2026-09-25)**:logrotate 每天轮转、保留 14 天、立即压缩
|
||
(`logrotate/xwlb` + `scripts/install_logrotate.sh`),journal 同步设 `MaxRetentionSec=14day`;
|
||
实测 34MB → 2.9MB 且归档行数与轮转前一致(32453 行)。
|
||
3. **B9 收尾**:`xwlb_video/*.mp4` 与 `*.mp3` 是否保留需定策略(mp3 可重跑,mp4 价值较低)。
|
||
4. ~~**B14 收尾**:`git init` 纳入版本管理~~ —— **已完成(2026-09-25)**:
|
||
已建仓并推送到 `git@192.168.1.10:simon/xwlb.git`(分支 `main`),
|
||
`.gitignore` 覆盖 `xwlb_video/`、`audio_processing/`、`*.log`、`.env`、`.venv/`、`state/`;
|
||
首提交 35 个文件、无密钥(已按 `.env` 逐个值核对)。
|
||
仍未做:`main.log` 已 34MB 且无轮转(见第 2 条 B6),建议加 logrotate。
|
||
5. **B15 收尾**:启动期做一次配置校验(缺 key / 连不上库时 fail-fast 并打印期望文件路径)。
|
||
6. **可选加固**:给 `xwlb_daily` 加唯一索引 `(news_days, daily_sub_id)`、`xwlb_daily_ext` 加 `(news_date, sub_id)`。当前代码已用「先删后插」实现幂等,索引属额外保险;注意建索引前必须先清理既有重复行(如 2026-06-15 全量重复 2 份、2026-06-18 ext 重复 3 份)。
|
||
|
||
## 修复优先级建议(原始评估,供回看)
|
||
|
||
1. **B2 + B1 一起做**(改/删校对步骤,保住已识别文本)—— 直接消除最大的静默数据缺口,同时省下每分片 30~300s 与全部 qwen 成本。
|
||
2. **B3**(JSON 解析健壮化 + 重试)—— 消除 22 天的整天数据缺失。
|
||
3. **B10 + B11 + B12**(可移植化)—— 让项目在任何机器可运行,是继续验证与修复的前提。
|
||
4. **B5 + B13**(幂等 + 判据)—— 防止修复过程中把重复数据写进生产库。
|
||
5. **B4**(抓取返回值)—— 抓取链路的正确性兜底。
|
||
6. B6~B9、B14、B15 —— 稳定性与工程卫生。
|
||
|
||
> 注:`main.log` 中 `'str' object has no attribute 'append'`(22 次)与 `object of type 'NoneType' has no len()`(11 次)**仅出现在 2026-06-01 与 06-16**,当前 `audioRead.py` 已分别通过"返回 list"与"`None` 回退原文"修掉,故未单列;但它们与 B1/B2 同源,重写校对步骤时不要再引入同类返回类型不一致的写法。 |