Files
xwlb/docs/BUGS.md
T
simon 1d3ba02703 feat: 日志轮转(保留 14 天)
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 行与轮转前完全一致
2026-09-25 12:50:11 +08:00

28 KiB
Raw Blame History

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 元素做类型校验与字段缺失兜底。


位置: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 加子进程看门狗(列入待办)。


后续待办(本轮新增)

  1. ASR 看门狗(对应 B19):把 Recognition.call 放进子进程执行并加超时, 超时终止子进程、该分片记失败后继续下一片,而不是整晚挂死。
  2. 回补校对失效期(见 REPORT_raw_vs_improve.md):2026-06-15~2026-09-23 的 news_improve 实际等于 ASR 原文(2026-07/08 为 100% 未改动)。校对现已修复且 关掉思维链后成本降到约 1/13,回补约 100 天 ≈ 1M token,需确认是否执行。
  3. 重试跳过已识别分片:当前重试会重跑全部 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 同源,重写校对步骤时不要再引入同类返回类型不一致的写法。