# 待办 / 问题登记（TODO_ISSUES）

> **用途**：集中登记**已知但尚未解决**的问题。cronjob 发现的问题与平时排查发现的问题**一律写入本文件并标注日期**，慢慢消灭。
> **约定**：
> 1. 人工登记写进「一、人工登记」区，**按日期倒序**（最新在最上面），每条标 `日期 / 问题 / 位置 / 严重度 / 状态`。
> 2. 「二、日跑自动记录」区由 `~/.hermes/scripts/daily_run_log.py`（cron `06da696372dc`，每日 10:00）**自动追加**，勿手改该区块。
> 3. 状态用：`待办` / `进行中` / `已修复(日期)` / `不修(原因)`。修复后**不删行**，改状态并补日期——保留决策痕迹。
> 4. 三处同步（改完务必核 md5）：
>    - 服务器 `/root/gov_crawler/TODO_ISSUES.md`
>    - 本地 `~/Crawler/gov_crawler/TODO_ISSUES.md`
>    - 坚果云 `~/Nutstore Files/Crawler/gov_crawler/TODO_ISSUES.md`

**当前未解决条目数**：见下方各表 `状态=待办/进行中` 的行。

---

## 一、人工登记（按日期倒序）

### 2026-09-28

| # | 问题 | 位置 / 对象 | 严重度 | 状态 | 备注 |
|---|---|---|---|---|---|
| 1 | **日报「最新数据」指标语义错误 + 「今日发布」近 4 日骤降**（做 2026-09-28 日报核验时发现）。① `最新数据` 取的是 `SELECT publish_date FROM gov_raw ORDER BY rowid DESC LIMIT 1` —— **最后插入那一行的发布日期**，与「最新数据」无关；实测该字段天天乱跳：09-23 报 `2019-07-29`、09-24 报 `2024-05-17`、09-27 报 `2026-08-06`、09-28 报 `2026-09-24`，**纯噪声**。② `publish_date` 按日分布：09-16~09-24 稳定 **1,381~1,636 条/日**，09-25 起骤降到 **62 / 42 / 54 / 5**（09-25/26/27/28）—— 同时段 `inserted_at=2026-09-28` 已 **13,848 行**、`crawl_scheduler_v2` 07:18~07:32 仍在跑且持续 `+NEW`（+20/+50/+100 条/站），说明**抓取侧在产出**，差异可能来自「报告固定 07:30 取数而调度器正跑到一半」+ 新公告 publish_date 滞后；**未定为故障，继续观察 2 日**（若 09-29 仍 <200 条则需按站点排查新公告日期解析是否退化） | `~/.hermes/scripts/notion_daily_report.py:144`（`get_crawler_stats`）；日报页父页 `3b5399b8...` | 低 | 待办 | 建议 ① 改 `SELECT MAX(publish_date)` 或直接删该字段；② 改指标后历史日报页口径会变，**须先请用户裁定**（本轮只报告未改代码）。另发现历史日报**缺 2026-08-28 一页**（08-27 → 08-29 跳空），不影响数据 |

### 2026-09-18

| # | 问题 | 位置 / 对象 | 严重度 | 状态 | 备注 |
|---|---|---|---|---|---|
| 1 | **第三份本地副本 `~/Crawler/gov_crawler/` 已滞后，且不在每日同步链路上**：`sync_gov_crawler.sh` 双向同步实际只覆盖 **服务器 ↔ `~/Nutstore Files/Crawler/gov_crawler`**（2026-09-18 02:46 实测：两侧各 4147 个 .py/.sh/.json，独立 checksum 比对（含 Archive）**0 个内容不一致**），**未覆盖 `~/Crawler/gov_crawler`**。同名文件 6 个体积不符，其中 5 个关键基础设施文件比镜像/服务器旧：`search_app.py`（09-11 406210 → 应为 09-14 412083）、`crawler_lib.py`（09-11 33278 → 09-16 34884）、`ccpc_zc_sync.py`（09-10 62132 → 09-14 67982）、`import_jsonl.py`（08-21 → 09-04）、`crawl_yingde.py`（09-16 08:13 → 09-16 09:50）；仅 `rebuild_fts.py` 本地较新（1275 vs 497）。另 46 个文件仅本地有（多为 `check_*`/`bulk_fix` 调试件），268 个仅镜像有（`backups/`、`_analyze_results.json`、`LOCAL_DEGRADED_CRAWLERS.md` 等） | 备份源：`~/.hermes/scripts/backup_gov_crawler.sh`（3:00 tar，`SRC=~/Crawler/gov_crawler`）；`~/.hermes/scripts/daily_run_log.py` 的 TODO 写入路径 | 中 | 待办 | 影响：服务器若全损，从 tar 备份恢复会退回 09-11 版 `search_app.py` / `crawler_lib.py`。候选处置（待裁定）：① 3:00 备份 `SRC` 改指坚果云镜像 ② 或先 `rsync -av --update 镜像 → ~/Crawler` 归并再备份（本地较新的 `rebuild_fts.py` 与 46 个独有文件会被保留）——**未自动执行**，因涉 268/46 个文件双向取舍。**2026-09-26 02:46 本 cron 复核**：服务器 ↔ 镜像 **4312** 文件、双向独立 checksum **0 差异**（链路本身健康）；工作副本 ↔ 镜像 非 Archive 内容不一致 **9** 个 —— 其中 **8 个是镜像侧更新**（`crawl_anlu / crawl_jxdy_hjypj / crawl_jxln / crawl_shaowu / crawl_xunwu / crawl_xunwu_tzgg / crawl_yantai / crawl_zhcqhj.py`，＝今日刚从服务器拉回的新站修复版），**仅 `rebuild_fts.py` 仍是工作副本较新**（`6cc1526c` 1275B / 07-20 vs 镜像+服务器 `60304ce3` 497B / 06-21，与前次记录**完全一致**）。前次记录的 `import_jsonl.py` 漂移**已消失**（两侧 md5 一致）。文件集：仅工作副本有 **51** 个非 Archive（本次新增 `dis_jxst.py` 09-25 07:39 3250B —— 服务器与镜像都没有，疑似一次性 jxst 诊断件）、仅镜像有 **274** 个非 Archive（含 Archive 则 1644）。处置仍待裁定。**2026-09-28 02:45 本 cron 复核**：服务器 ↔ 镜像 **4361** 文件、双向独立 md5 全量 **0 差异**（链路健康；本日实跑仅拉回 `run_status.json`、推送 **0** 文件，未再出现 09-26 那种「坚果云刷 mtime 导致数百次无效回推」）。工作副本 ↔ 镜像（顶层核心文件）：镜像 **2722** / 工作 **2720**，仅镜像有 **42** / 仅工作副本有 **40** / 同名不同内容 **2** —— ① `crawl_zhcqhj_gsl.py` 工作副本仍是 **09-26 10:09 旧版**，镜像+服务器已是 **14:20 修版本**（regex 兼容 `https?://(?:www\.)?`，07-26 那类 http→https 改版修复）；实测**无任何本地调度引用它**（`local_degraded_scheduler.sh` 不含），仅在手动在此目录执行时才会踩坑。② `rebuild_fts.py` 与 09-18/09-12 记录**完全一致**（工作 1275B 07-20 / 镜像+服务器 497B 06-21），两侧均无调度引用，属遗留工具。⚠️ **本次新发现**：本地降级专用脚本 **4 个只存在于 `~/Crawler/gov_crawler`** —— `crawl_smx_yima_local.py` / `crawl_fengkai_local.py` / `crawl_njna_xxgk_local.py` / `crawl_tuoxian_bmwj_local.py`，镜像与服务器**均无**；其中前 3 个正是 `~/Crawler/local_degraded_scheduler.sh`（cron 每日 18:40）的实际执行体（该脚本自身 `cd ~/Crawler/gov_crawler` 后本地跑、再 scp JSONL 到服务器导入，故服务器端不需要这些文件）。**已核实无丢失风险**：3:00 的 `backup_gov_crawler.sh` tar 实测含这 4 个文件（`gov_crawler_20260927_030022.tar.gz` 用 `tar tzf` 逐名确认）。|
| 2 | **坚果云镜像内有垃圾文件 `=`**：0 字节、2026-07-24 建立（疑似早年 shell 命令重定向误建的名字），会随坚果云同步进云端，服务器上不存在 | `~/Nutstore Files/Crawler/gov_crawler/=` | 低 | 待办 | 建议删除；未自动删（处置类操作待确认） |
| 3 | **`crawl_hbfgw_jnscyj.py` 的 `urljoin` 覆盖循环变量（潜伏 bug）被「栏目上提一级」引爆**：`main()` 先 `url = LIST_URL`，随后在 `for href, … in items` 里写 `url = urljoin(url, href)` —— **把列表页 URL 覆盖成了上一个详情页 URL**，于是**从第 2 条起基址已污染**，再 join 相对 href 就跑飞 → 入库 URL 形如 `/fbjd/xxgkml/xkfw/xkfw/xkfw/…/xkfw/xzxkjg/xmspqk/…`（`xkfw` 重复 8~10 次且每条次数不同）→ 详情页取不到 → **19/20 条正文降级成「只有标题」**。**旧栏目没暴露此 bug**：`/sp/jnscyj` 的 href 是**绝对路径**（`/fbjd/xxgkml/xkfw/…`），`urljoin` 直接返回绝对路径、不碰基址；新的 `/sp/` 用**相对路径**（`../../../xkfw/…`）才触发 | 用户 2026-09-18 指令：`…/zdjsxm/pzjgxx/sp/jnscyj/index.shtml` **上提一级**到 `…/zdjsxm/pzjgxx/sp/`（「审批」总栏目，含全部子栏目）。改脚本 `LIST_DIR` | 中 | ✅ **已修复(2026-09-18)** | ① 分裂为 `list_page_url` / `detail_url` 两变量（不再覆盖列表页 URL）② 加兜底 `re.sub(r"(?:/xkfw)+/", "/xkfw/", detail_url)` 折叠重复路径段 ③ 已入库的 **20 条错误 URL 记录**先备份到 `bak_hbfgw_sp_badurl_20260918`（20 行）再清除（残留 0）。**验证**：重跑 `--pages=1` → `xkfw 重复 0 条`、正文 408~39355 字（`<200` 的 **0 条**，修复前 19/20）、`new:16 skip:4`；`--pages=5` → 5 页各 20 条、`new:63 skip:37`（**skip 证明 URL 构造与旧 212 条完全一致**，即 `/sp/` 确为 `/sp/jnscyj` 超集）。md5 `221ffee77d`→`a781b54163`→`1a14aaf619` 三处一致；备份 `.bak_20260918_sp_url` / `.bak2_20260918_urlfix`；新栏目共 **125 页**（`createPageHTML(125,…)`，原 39 页）| ⚠️ 通用教训：**「换一个列表 URL」会暴露 urljoin/相对路径类潜伏 bug** —— 换栏目后必须校验入库 URL 的路径段是否重复，不能只看条数 |
| 4 | **上提一级后 `site_name` 分裂 + `category` 列恒空**：① `SITE_NAME` 由「湖北省发改委-节能审查」改为「湖北省发改委-审批」（不改的话非节能审查的批复会被错误标注），但**已入库的 212 条仍是旧 site_name** → 同一脚本下两个 site_name 并存（`湖北省发改委-节能审查` 212 条 / `湖北省发改委-审批` 79 条）。② `category` 列**全库为空**：`crawler_lib.push_to_searchdb()` 的形参 `batch_label` **在函数体内从未被引用**，脚本传的 `CATEGORY` 是 **no-op**；`category` 实际取 `item.get("category","")`，而该脚本的 items 里没有 `category` 键 | `search.db` → `gov_raw.site_name` / `gov_raw.category`；脚本 L35 `SITE_NAME`、L36 `CATEGORY` | 低 | ✅ **已处理(2026-09-18)** | 候选：① 旧 212 条 `site_name` 一并 UPDATE 成新名（一致性优先，代价是丢失「曾是节能审查专抓」的痕迹）② 保留分裂 ③ 反向把 `SITE_NAME` 改回带子栏目名。⚠️ UPDATE 会触发 `trg_gov_raw_fts_upd` 重写 FTS 行（212 行，量小）。**顺带发现**：任何脚本传进 `push_to_searchdb` 的 `batch_label`/`CATEGORY` 都是白传 —— 需要 category 语义时得改 `crawler_lib` 或让 items 自带 `category` 键。**【2026-09-18 用户裁定「旧的统一改名」→ 已执行】** 备份 `bak_hbfgw_scname_20260918`（212 行：id/site_name/script_name/title/publish_date）→ `UPDATE gov_raw SET site_name='湖北省发改委-审批' WHERE script_name='crawl_hbfgw_jnscyj.py' AND site_name='湖北省发改委-节能审查'`（**212 行，0.1s**，触发器同步 FTS）→ 读回：**残留旧名 0**、`gov_search` 旧名 **0** / 新名 **291**，该脚本现统一 **291 条（2023-08-21 ~ 2026-09-16）**。`category` 恒空一项**未动**（属设计问题，需改 `crawler_lib` 才能修） |
| 5 | **FTS 索引存在 312,139 条孤儿行**：`gov_search` **880,156** 行 vs `gov_raw` **568,017** 行，差值 **312,139**，这些 rowid 在 `gov_raw` 里查不到（样本逐条直查命中 **0**），但**都有真实标题**（样本为「桐柏县人民政府…」公告系列）→ 即**历史删除记录时 FTS 未同步清理**的残留。三个触发器 `trg_gov_raw_fts_ins/del/upd` 现均健在。**反向检查正常**：`gov_raw` 缺 FTS 的 = **0 条 / 568,080** → 新增链路健康，问题纯属历史残留 | `search.db` → `gov_search`（FTS5）| 中 | ✅ **已清理(2026-09-18)** | 2026-09-18 做湖北栏目改动、顺手查触发器是否健在时的副产物。影响：搜索可能返回无法回溯到原文的幽灵命中（若 `search_app` 按 rowid 回查则静默少几条，不回查则显示幽灵）。候选：① `DELETE FROM gov_search WHERE rowid NOT IN (SELECT id FROM gov_raw)` ② FTS 整表 `INSERT INTO gov_search(gov_search) VALUES('rebuild')`。**【2026-09-18 用户裁定「孤儿行需清理」→ 已执行】** 先存证清单 `Archive/fts_orphans_manifest_20260918.txt`（含清理前后计数、rowid 范围 `2065247163013495948~2100404691824428456`、site_name 分布 TOP20、40 行样本、恢复方法），再**分批 20000 删除**（16 批共 **96s**，单批最长 18s）。**验证**：`gov_raw` **568,080 不变**；`gov_search` **880,219 → 568,080**（与 gov_raw **完全相等**）；剩余孤儿 **0**；`gov_raw` 缺 FTS 仍 **0**；湖北 291 条全在 FTS、`MATCH '免税确认'/'产教融合'` 查询正常；删除期间 search_app 实测仍正常服务（搜索页 HTTP 200 / 1.18s）。⚠️ **文件体积未缩**（FTS 删行留空洞，`/mnt/data` 37G/99G 40%），如需回收需 `VACUUM`（1.75GB 内存机需另评估） |

### 2026-09-15

| # | 问题 | 位置 / 对象 | 严重度 | 状态 | 备注 |
|---|---|---|---|---|---|
| 1 | **中策 PDF 的「版本更新」被 project_id 去重静默丢弃**：入库脚本只按 `project_id` 判重（`SELECT 1 FROM zc_projects WHERE project_id=?` → `continue`），**不比较 `version_type` / `publish_date`** → 同一项目的新版本 PDF 重复下载（典型形态：文件名尾带 ` (1)`）入库等于 no-op，而同步报告只显示「上传 N 条 / 导入 M 条」，看不出哪几条被丢。2026-09-15 01:00 增量同步实测 **2 例**：`ZC0000780768`（合肥融捷…退役锂电池回收项目）库内 `第2版本 / 2026-07-11`、建设内容 33 字，新 PDF `第2-2版本 / 2026-09-11`、建设内容 247 字；`ZC0001339369`（宁夏中柔…年产35万吨再生铝项目）库内 `第1版本 / 2026-05-17`、151 字，新 PDF `第2版本 / 2026-09-14`、364 字 | `/root/import_zc_batch.py`（本地 cron `712067a435ec` 每日 01:00 路径 → 上报「导入 9 条」即此脚本）；`ccpc_zc_sync.py` 的 ZC 引擎同病（同样「跳过已存在项目」）；源目录 = 坚果云 `Downloads_nuc` | 中 | 待办 | 2026-09-15 01:00 增量同步 cron 巡检发现（当日 26 个新 PDF：9 条新增、17 条 project_id 已存在 → 其中这 2 条版本比库内新）。**范围未定**：本地 `Downloads_nuc` 有 **188 组同名重复**（189 个 ` (1).pdf`）→ 存量同类「新版本被丢」确切条数需按文件对逐一解析 `version_type/publish_date` 与库内比对（本轮只验了当日 26 个）。候选处置：① 入库改为「版本较新则 UPDATE」（比较 `publish_date` / `version_type`）② 或先出差异清单交用户裁定再回填 |
| 2 | **日报脚本 ZC 段静默降级成「?」**：（同源问题已在上方 09-14 #5 记录：09-14 ZC 切本地镜像后日志行从 `新增: 0, 跳过: 743, …` 变成 `源=local 新增: 0, 跳过: 2639, 下载: 0, 失败: 0, ZC.db 总计: 2420 条`）而 `get_zc_status()` 仍用 `l.strip().startswith("新增:")` 取数 → 匹配不到、detail 回退成 `(2026-09-15) ?`；**同时**展示端 `zc['detail'][:60]` 截断，即使匹配到也会把尾部「ZC.db 总计: N 条」切掉。因为状态判定看的是 `ZC OK` + 日志日期新鲜，页面仍显示「✅ 正常」→ **数据缺失但无告警** | `~/.hermes/scripts/notion_daily_report.py` 的 `get_zc_status()`（第 248 行）与 ZC 区块（第 425 行截断） | 低 | ✅ **已修复(2026-09-15)** | 两处改动：① 取数改**包含匹配** `"新增:" in l and "ZC.db" in l`（新旧两种日志格式都命中）② 截断 60 → **130**（与 CCPC 段对齐，不再切掉 DB 总计）。**验证**：直接调 `get_zc_status()` 返回 `{'status': '✅ 正常', 'detail': '(2026-09-15) 源=local 新增: 0, 跳过: 2639, 下载: 0, 失败: 0, ZC.db 总计: 2420 条'}`；重跑日报走幂等分支（`⚠️ 今日日报已存在 … 已更新页面内容`）→ 父页子页数仍 **50**、`2026-09-15` 当日页 **1 个**（无重复），页面 ZC 行已含完整字段。首次出现 = 09-15（09-14 切镜像后首个 02:30 排程跑） |

### 2026-09-14

| # | 问题 | 位置 / 对象 | 严重度 | 状态 | 备注 |
|---|---|---|---|---|---|
| 1 | **双向同步的推送方向未排除「服务器托管的状态文件」**：`notion_pushed_urls.json`(9.3MB) 与 `run_status.json`(566KB) 属日跑运行时状态（服务器写），却落在同步的 `*.json` 白名单内，而推送方向同样带 `--update`（＝本地较新即覆盖服务器）。若本地出现更新的同名副本（手动改、或本地跑一次相关脚本），就会把服务器生产状态**推回覆盖**——与 2026-08-24 ZC.db 被本地旧备份覆盖属同一类事故 | `~/.hermes/scripts/sync_gov_crawler.sh`（LOCAL_DIR=坚果云镜像，rsync include `*.json`） | 中 | 待办 | 2026-09-14 02:46 本 cron 巡检发现；本次运行**推送 0 文件**（未发生覆盖），当前两侧 mtime 相同故稳态无风险，属潜伏项。候选处置：仅对推送方向加 `--exclude=notion_pushed_urls.json --exclude=run_status.json`（拉取仍保留），或把状态文件移出 `gov_crawler` 目录 |
| 2 | 坚果云客户端 `all_sandbox_list` 中名为 **Crawler** 的同步根 `doNotSync=false`（同步已启用），但云端记账 `usedSpace=0`，而本地 `~/Nutstore Files/Crawler` 实体有 gov_crawler 镜像 87MB → 「该镜像是否真在云上」待核实 | 坚果云 `Crawler` sandbox（id 29897256，本地建根时间≈2026-08-26） | 低 | 待办（待核实） | 2026-09-14 巡检记录，未定性。核实方法：坚果云网页端查该同步文件夹内容与占用；另 `~/Nutstore Files/Mac-爬虫`（sandbox 29844236，usedSpace 2.3MB）本地无对应目录 → 本地↔根映射需一并确认。注意历史坑：仅「路径在 ~/Nutstore Files 下」不等于已上云 |
| 3 | **明文 root 密码散落**：已启用的 cron「每日爬虫配置校验」prompt 里明文写着服务器 root 密码（`sshpass -p '...'`），已删除的 `pull-gov-crawler-snapshot-to-onedrive` 同样带密码 → 消费方已全量清零，但旧密码仍存 **384 个文件 / 3,075 处**，**只有轮换能真正收尾** | `~/.hermes/cron/jobs.json` + `~/.hermes/sessions/*`(288 文件/2,904 处) + `cron/output`(69) + `skills`(17) + `logs`(3) + `.curator_backups`/`state-snapshots` + 备份树 | 中 | 待办（**消费方已清零，轮换已解锁**） | 2026-09-14 处置：①删除 `pull-gov-crawler-snapshot-to-onedrive`（目标目录 4.5GB 已由用户清理，且已有密钥通道）②`每日爬虫配置校验`(`8211569f7054`) 改 `ssh -o StrictHostKeyChecking=no hw-backup` 免密，实跑 `EXIT:0`（2101 条配置/2065 脚本/无孤儿）；`jobs.json` 内 `sshpass` 与密码值均 **0 次** ③实测密钥免密在 cron 环境可用（`env -i HOME PATH=… ssh -o BatchMode=yes` → `CRON_KEY_OK`；两私钥均无 passphrase）④`~/.hermes/scripts/backup_server_dbs.sh` 早已免密（2026-09-10 改，仅注释含 "sshpass" 一词，非调用）⑤服务器 crontab / 活目录无 sshpass，命中仅在 `Archive/legacy_sync_20260910` 与 `Archive/gov_crawler_DUP_20260910`。**剩余：用户自行在服务器 `passwd` 轮换 → 助理再验证旧值被服务端拒绝**（按技能规矩勿经聊天传新密码）。顺手修复技能脚本 bug：`scan_plaintext_secrets.py` 的 `scan_blob()` 对 str 调 `data.count(tok.encode())` → `TypeError: must be str, not bytes`，已改为先统一转 bytes |
| 4 | **IC13 导入被 1 条 id_code 冲突拖死整批**：`import_ic13_dedup.py` 的 `existing` 只查 `source='pharma'`，而 `idx_tsk_data_id_code` 是**全库**唯一索引 → 某 id_code 已作为 cpi/plant 存在时被误判为「新增」→ `executemany` 裸 INSERT 撞 UNIQUE → **整批回滚，同批其余新增记录一并丢失**（实测 20 文件中 18 条因此未入库；09-10 起每天可能静默发生） | `/root/scripts/import_ic13_dedup.py` ——**不在三处同步范围**（`sync_gov_crawler.sh` 的 `REMOTE_DIR` 只覆盖 `/root/gov_crawler`），且此前**无任何服务器外副本** | 高 | ✅ **已修复(2026-09-14)** | 三处改动，md5 `0aa4a83b` → `c17c39c2` → `cb9ec7fe`：①`existing` 改按 id_code **全库**取（与唯一索引语义一致：一个 id_code 只应有一行）②UPDATE 去掉 `AND source='pharma'`（否则命中 cpi/plant 行时该 id_code 的新内容永远进不来）③`conn.text_factory` 加容错解码——改成全库取之后会扫到历史坏行（content 非 UTF-8），默认 factory 直接抛 `sqlite3.OperationalError: Could not decode to UTF-8 column 'content'` 中断整批（这是修完①才暴露的第二层 bug）。**验证**：重跑 `Updates: 2 / New inserts: 36 / Skipped: 2 / Errors: 0`，`IMPORT_RC=0`；总数 **70054 → 70093**，此前被拖死的 301225594/301226004/301226696/301227454/301227828 等全部落入；冲突样本 `301001336` 保持 `source='cpi'` 且 content **815 → 2825**（新内容正确追加）；`HAVING COUNT(*)>1` 重复 id_code = **0**；完整编排器再跑 **rc=0 全静默**（两引擎无变化）。备份 `.bak_20260914_pre_unique` / `.bak_20260914_textfactory`；修复版已另存服务器外副本 `~/.hermes/backups/server_scripts/import_ic13_dedup.py`。**遗留建议**：`/root/scripts/` 整目录不在任何备份链路，宜纳入 |
| 5 | **坚果云 WebDAV PROPFIND【截断】→ ZC 数据 71% 对引擎不可见**：`zc_webdav_list()` 实测源目录有 **2634 个条目**，却只返回 **751 个 `<response>`（743 个 PDF）** —— 其余 1885 个文件等于不存在。表现为 `新增: 0, 跳过: 743`、**12 秒跑完、rc=0、无任何告警**（最危险处：完全静默）。用户报「/zc/ 没有 11 号的数据」即此因：那些文件一直躺在坚果云目录里，只是从未被看见 | `ccpc_zc_sync.py` 的 `zc_run()` / `zc_webdav_list()`；源目录 = 坚果云 `Downloads_nuc` | 高 | ✅ **已修复(2026-09-14)** | **工作流按用户指令改为「先走坚果云本地同步目录，走不通再回落 WebDAV」**：① 新增 Mac 端 `~/.hermes/scripts/zc_push_fresh.sh`（rsync 坚果云 `~/Nutstore Files/Downloads_nuc` → 服务器 `/mnt/data/zc_pdfs`，`--ignore-existing` 只补缺，成功时静默）+ cron **`043de840b37a` 每日 02:05**（`deliver=local` 成功静默 / `failure_deliver=telegram` 失败告警），排在服务器 ZC 段 02:30 之前 ② `zc_run()` 改为**优先迭代本地镜像目录**（日志 `源=local`），目录为空才回落 WebDAV 并在日志写明截断风险 ③ 顺带硬化：下载成功也记日志（原来成功不打日志，「到底下没下」无从判断）、下载失败由静默 `continue` 改为**计数 + rc=1**。**回填实测**：服务器镜像 1308 → **2628**（= 本地坚果云 PDF 数，rsync rc=0）；入库 `源=local 新增: 747, 跳过: 1881, 下载: 0, 失败: 0`，**ZC.db 1664 → 2411 条**（zc_contacts 8450）。**验收**：publish_date **09-11 由 0 → 3 条**、09-13 由 2 → 7、09-14 由 0 → 2 —— 用户报的问题消失。备份 `Archive/ccpc_zc_sync.py.bak_20260914_zclocal`、`/root/ZC.db.bak_20260914_presync`。⚠️ 附带坑：bash 中 `$VAR）`（中文全角括号紧跟变量名）会被当作变量名的一部分 → 未定义报错，必须写 `${VAR}`
| 6 | **CCPC 引擎同样被坚果云 WebDAV 截断**：本地坚果云三目录实测 **5688** 个 office 文件（4415 / 1048 / 225），WebDAV PROPFIND 只列到 **1587** 个（748 / 623 / 216）—— 仅 **27%**，约 4301 个文件对引擎不可见 | `ccpc_zc_sync.py` 的 `ccpc_run()` / `ccpc_webdav_list()`；源目录 = 坚果云 `Downloads` / `下载1` / `Downloads2` | 高 | ✅ **已改造(2026-09-14)** | 与 ZC 同一策略（用户指令「先走坚果云，走不通再 WebDAV」）：① 新增 Mac 端 `~/.hermes/scripts/ccpc_push_fresh.sh`（rsync 三个坚果云目录 → 服务器 `/mnt/data/ccpc_src/{Downloads,下载1,Downloads2}`，`--ignore-existing` 只补缺、**成功静默**）② `ccpc_run()` 列文件改为**本地镜像优先**（日志打 `[本地镜像] <子目录>: N files`），目录缺失/为空才回落 WebDAV（打 `[WebDAV]`）；取文件同样镜像优先 `shutil.copy2` 而非下载 ③ 与 ZC 推送器**合并为单 cron 入口** `push_nutstore_mirrors.sh`（符合用户既有偏好「同类每日同步合并为薄编排器：单 cron 入口 + 各引擎保留自身处理」），cron `043de840b37a` 每日 02:05（成功静默 / 失败 telegram 告警）。**回填实测**：镜像 5688 文件（rsync rc=0，25 秒）；入库 `Total: 5688 files → Done: +42 U5645 err1`（err1 = 一个源 docx 本身损坏：`Bad offset for central directory`），**ccpc.db 6122 → 6164 projects**、`cceup_contacts` **15053 → 16520**，rc=0，`search_app` active。备份 `Archive/ccpc_zc_sync.py.bak_20260914_ccpclocal`
| 7 | **正文双编码乱码（用户报样本）**：山东佳兆科技…第一次信息公示（id `2097613798365842865`，68883 字）正文为「UTF-8 字节被当 Latin-1 解码后又存成 UTF-8」（`å¾®è½¯é\x9b\x85é»\x91` = 「微软雅黑」），整篇不可读 | `search.db` → `gov_raw.content`（巨野县政府链路：`crawl_juye.py` 3119 / `crawl_juye_env.py` 88 / script_name 空 119） | 中 | ✅ **已修复(2026-09-14)** | 巨野站共 **259 条**双编码。用 Level-4 CP1252 逐字节映射修复。⚠️ **必须同时修 `gov_raw.summary`** —— 触发器 `trg_gov_raw_fts_upd` 是用 `new.summary` 同步 `gov_search` 的，只修 content 修不到搜索层（实测该站 summary 本身没乱，故 summary 实变 0 条）。**分两轮**：严格签名（拉丁字母**紧邻** C1 控制符）命中 **168** 条；第二轮发现**签名漏检** —— `å®\x8bä½\x93`（= 宋体）是**字母 + 字母 + C1**，严格签名匹配不到，改用「标记字符 `åæäãèéïð` 存在 + `is_good_fix` 把关」补修 **91** 条（0 条被拒）。**验收**：`仍含 å/æ/ä = 0`、严格签名 `= 0`，抽样已显示 `font-family: 宋体` / `微软雅黑`，`search_app` active；可回滚备份表 `gov_raw_moji_bak_20260914`（3326 行）。⚠️ **爬虫侧无需改动**（`crawl_juye.py:83` 与 `crawl_juye_env.py:34` 都已设 `r.encoding='utf-8'`）→ 属历史遗留数据。**方法论补充给技能**：检测双编码别只用「字母紧邻 C1」的严格签名，CSS 字体名里是「字母+字母+C1」，会整批漏掉
| 8 | **ZC「项目主要采购设备」被并进「建设内容」**：`extract_basic()` 收集「建设内容描述」时只在 `该项目可能` / `温馨提示` 处 break，**没把 PDF 里紧跟其后的独立小标题「项目主要采购设备」当边界** → 整段设备清单被拼进建设内容（实测 **2397 / 2411** 条中招，尾部形如 `…生产能力。项目主要采购设备炉类：裂解炉、气化炉…`），而 `equipment_list` 全库只有 **3** 条有值 | `ccpc_zc_sync.py` 的 `extract_basic()`（ZC 引擎）+ 展示端 `search_app.py` | 中 | ✅ **已修复(2026-09-14)** | 三处改动：① 解析器加 `DESC_STOPS`（`项目主要采购设备`/`精准采购设备`/`工艺流程`/`项目工期及阶段进展`/`该项目可能`/`主要设备`/`温馨提示`/`项目基本信息`），建设内容遇任一小标题即收工；② 新增 `PE_STOPS` 单独抽取「项目主要采购设备」块入 `equipment_list` ——⚠️ **PE 的终止词绝不能含 `该项目可能` / `主要设备`**，这两个词出现在该块**正文内部**（块首常是「该项目可能用到的设备如下，供参考…」或「主要设备：原料贮槽…」），当终止词会把块从中间截断；旧回退路径改为仅在未取到时才生效；③ 展示端 `设备清单` 模块改名为「**项目主要采购设备**」（`render_project_detail_body` 的 ZC 分支 + `/zc/` 抽屉片段两处；CCPC 的 `render_cceup_detail_body` 有意不动）。**回填**：因被吞内容**已完整存在库中**，采用**纯字符串切分**（不重读 PDF）→ **2390 条切分 + 4 条仅清尾巴**；验收 `construction_content 仍含该小标题 = 0`、`equipment_list 非空 = 2393`；示例 ZC0001391328 建设内容 **618 → 415** 字、设备清单 195 字。备份表 `zc_projects_pe_bak_20260914`（2411 行）+ `/root/ZC.db.bak_20260914_pe`；解析器改动已用 `--zc-preview` 在示例 PDF 上实测正确拆开。📎 另注：设备块中位长度仅 **206 字符** —— 与源站第 2 页「温馨提示：此处仅显示部分所需设备，更多信息请登录官网查看」吻合，**源本身只给预览片段**，库内照实保留
| 9 | **列表页全部空白（我自己造成的回归）**：为「复制原文」写 JS 时把 `\n` 写进了**非 raw** 三引号 Python 字符串 `LIST_UI_JS` → Python 先把它解释成**真实换行** → 切断了 JS 字符串/正则 → 整个 `<script>` 解析失败 → **`/crawler` `/zc` `/ccpc` `/contact` 四条路由列表全部停在「已加载 0 条」** | `search_app.py` 的 `LIST_UI_JS`（第 2370 / 2373 行）| 高 | ✅ **已修复(2026-09-14)** | 修法：源码里写成 **`\\n`（双反斜杠）**，Python 才会输出单个 `\n`。md5 `adf2a421` → **`c2732a45`**；备份 `Archive/search_app.py.bak_20260914_jsnl`。**验证三层**：① 从**服务端实际吐出的** HTML 抽 `<script>` 跑 `node --check` → 两个块均 **`SYNTAX_OK`**（⚠️ **关键教训**：对**源文件**抽出来检查会**假通过** —— 源里仍是 `\n` 两字符，本次就这样先被骗过一次）② 浏览器真机（CDP）：`lp-count` / `lp-item` = **30**、`__lpDebug.items=30`、首条即 `ZC0001391328` ③ 抽屉功能：`d-copy` x=**1100** < `d-close` x=**1189**（复制确在关闭左侧）、**Alt+I → `✓ 已复制`**、「项目主要采购设备」与「建设内容」**两模块各自独立**。**诊断捷径**：`typeof window.__lpDebug === 'undefined'` ⇒ 脚本**没跑完**（不是数据问题）；`__lpDebug` 有值而 `items:0` 才是数据问题。⚠️ 排障时为绕过登录墙在 `session.db` 建过临时会话 token，**已全部删除**（`LIKE 'VERIFY%'` 残留 = 0）
| 10 | **「复制原文」三处体验问题（用户反馈）**：① 复制/关闭两按钮 `float:right` **挤占标题**（标题绕排被压窄换行）② **ZC 路由复制内容不完整**（只拿到「建设内容」一块）③ Crawler 路由的**来源/发布时间/原文链接被拆成多行** | `search_app.py` 的 `LIST_UI_JS`（`__lpCopy` + `.d-acts` CSS）| 中 | ✅ **已修复(2026-09-14)** | ① `.d-acts` 去掉 `float:right`，改为 `justify-content:flex-end;height:0;pointer-events:none`（按钮 `pointer-events:auto`）→ **不占布局高度**、悬浮右上角；实测几何 `d-copy@1100-1183 / d-close@1189-1247 y=74`，**标题 y=98 位于其下方**，不再被挤。② 根因：zc/ccpc/contact 的抽屉片段是**模块化**的（每个模块一个 `.ds` 卡片 = `h3` + 正文），而原实现 `pick(['.ct-html','.ct'])` **只取第一个 `.ct`** → 只复制到「建设内容」；改为有 `.ds` 就**全部卡片按原顺序**收集（并补 `.dh .dg` 基本信息网格），无 `.ds`（crawler）才退回单块。实测 ZC：**1071 字**，含标题/项目编号/发布时间/建设内容/**项目主要采购设备**/项目联系人，模块顺序完整。③ `.d-meta` 元信息改用 `replace(/\s+/g,' ')` **折叠成一行**；实测 `来源： 江苏环评信息公示平台 发布时间： 2026-09-14 原文链接 5 次访问`。另抽出 `window.__lpCopyText()` 便于直接验证复制内容。md5 `c2732a45` → **`c3d66386`**；**验证**：服务端实际吐出的 JS 两个 `<script>` 块 `node --check` 均 **`SYNTAX_OK`** + 浏览器真机几何/内容/单行三项实测。备份 `Archive/search_app.py.bak_20260914_copy3`。🔸 遗留小瑕疵：折叠后 `来源：` 与值之间留一个空格（换行折叠所致），用户未提，暂不动
| 11 | **联系人复制粘贴后分行很多**（用户反馈, ZC/CCPC）：联系人卡片 `div.cc` 的字段区 `div.fd` 是 **CSS grid**（`span.cl` 标签 + 紧邻值 span 交替），`innerText` 会把**每个 span 各占一行** → **一个人 12 行**（`姓名：\n王冬冬\n部门：\n项目部…`）| `search_app.py` 的 `LIST_UI_JS`（`__lpCopyText`）| 中 | ✅ **已修复(2026-09-14)** | 新增 `pairFields(fd)`：按 `.cl` 标签 + 紧邻值**配对**成「标签：值」，每个联系人 = **公司名 1 行 + 字段 1 行**（字段间两空格）。实测 ZC `姓名：王冬冬  部门：项目部  职务：项目负责人  手机：15653637907  地址：…  备注：分管现场`（**12 行 → 2 行**）；CCPC `联系人林工  手机13132579352  座机13132579352  地址…`。⚠️ 过程中**又踩了 `\n` 转义坑**（`rec.join('\n')` 单反斜杠被 Python 转成真换行）—— **被「服务端实际吐出的 JS 跑 node --check」闸门拦下**（`sv5_1.js:177 SyntaxError`），改双反斜杠后 `SYNTAX_OK`。md5 `c3d66386` → `f6317956` → `7c727ed8`；备份 `.bak_20260914_contact` / `.bak_20260914_contact2` |
| 12 | **`/ccpc/`（带尾斜杠）返回 404 空白页**：路由写成 `elif parsed.path == '/ccpc':`，而 `/crawler` `/zc` `/contact` `/znlh` 均为 `in ('/x','/x/')` 两种写法都收 → 手输或收藏 `/ccpc/` 会看到**空白页**（实测 `404 3B`）| `search_app.py` 第 4307 行 | 中 | ✅ **已修复(2026-09-14)** | 改为 `in ('/ccpc', '/ccpc/')`；md5 `7c727ed8` → **`b1e62a10`**。实测 `/crawler/ /zc/ /ccpc /ccpc/ /contact/` 全部 **200 + 含 lp-drawer**；服务端 JS 闸门 `SYNTAX_OK`。备份 `.bak_20260914_ccpcslash` |
| 13 | **联系人「单位名称」被正则吃掉**（用户报样本：澳宝化妆品惠州有限公司香港独资澳宝兆婷化妆品制造项目）：`extract_contacts()` 用 `re.sub(r'\s*[（(].*?[）)]$', '', val)` —— **非贪婪 + `$` 锚**会从【第一个】左括号一路吞到【最后一个】右括号：`[业主]澳宝化妆品（惠州）有限公司(外资)` → **`澳宝化妆品`**（中间整段丢失）；随后还剥掉 `[业主]` 前缀。**ZC 联系人 8450 条里 8425 条（99.7%）中招** | `ccpc_zc_sync.py` 的 `extract_contacts()`；`ZC.db` 的 `zc_contacts.company` | 高 | ✅ **已修复(2026-09-14)** | 用户裁定「**字段都保留，不清洗**」→ 移除两条清洗（尾部括号剥离 + `[业主]` 前缀剥离），`company = val` 原样保留；解析器 md5 `e6e0a462` → **`a93852e3`**。**存量回填**：按 `zc_parse_one` 同源方式重读 2629 个 PDF 的「第 2..N 页」文本 → 按 `(project_id, contact_name)` 比对 → **更新 8425 行**（0 异常, 28.5s；备份表 `zc_contacts_company_bak_20260914` + `/root/ZC.db.bak_20260914_company`）。验收：样本 4 条全部恢复完整（`[业主]澳宝化妆品（惠州）有限公司(外资)` / `[施工图设计]广东省惠阳建筑设计院(国有/集体所有)` / `[主体承建商]广东华移建设有限公司(私营)`），浏览器抽屉与复制文本实测通过。⚠️ **括号语义有两类**：ZC 是**所有制**（外资/私营/国有/集体所有——真信息，必须留）；**CCPC 是角色**（`甘肃昌明矿产有限责任公司(业主)`，`role` 列已另存）。同一条清洗在 CCPC 侧也已按「不清洗」移除（md5 → `78509cf5`）；**CCPC 无需手工回填** —— 引擎每晚 02:30 全量重跑会 `DELETE + 重建` contacts，次日自动生效（ZC 引擎跳过已存在项目，故 ZC 必须手工回填）。若要维持 CCPC「剥掉角色括号」的旧行为，回退备份 `.bak_20260914_ccpccompany` 即可 |

### 2026-09-13

| # | 问题 | 位置 / 对象 | 严重度 | 状态 | 备注 |
|---|---|---|---|---|---|
| 1 | 日报脚本「今日页已存在」判重只取首页 50 个子块（无翻页）→ 父页子页数达 50 后，最新页落到第 2 页，判重失效 → 同日重跑/次日会**创建重复日报页** | `~/.hermes/scripts/notion_daily_report.py` L415 | 中 | **已修复(2026-09-13)** | 实测父页 `3b5399b8` 当日已有 **49 个子页**（2026-07-27 起每天 1 个），距 50 仅差 1 天 → 「即将触发」的潜伏 bug。修法：判重改为翻页遍历全部子块（`page_size=100` + `start_cursor` 循环）。验证：修复后同日重跑输出 `⚠️ 今日日报已存在 … 跳过创建` + `✅ 已更新页面内容`；Notion API 独立复核当日页仅 **1 个**（id `3d9399b8-3c5b-8137-975a-de7c5c1460b4`，40 blocks） |
| 2 | `gov_raw` 总量读数数分钟内波动 ±150（560,466 → 560,320 → 560,469），站点数 2206↔2205 抖动 | `/root/search.db` 的 `gov_raw` | 低 | 不修（非数据丢失） | 排查：`gov_raw` 是**真表非视图**；脚本 total 即 `SELECT COUNT(*)`（无估算/缓存）。定位为**站点批量刷新窗口**（`DELETE FROM gov_raw WHERE site_name=…` 后重新 INSERT，见 `batch_refresh_tables*.py` / `clear_*.py`）——读到的是「删完未插完」的中间态；静止期连读 3 次均 560,469 稳定。影响：日报「今日净增」基线若恰好采到刷新间隙会偏小数百条。候选改进：基线取当日多次读数最大值，或错峰推送（现 07:30） |
| 3 | 日报的 CCPC / ZC 状态**读的是 09-10 已合并的旧单引擎日志**（`/var/log/ccpc_sync.log`、`/var/log/sync_zc.log`，mtime 停在 09-10）→ 永远显示同一条 09-10 的「Done」且判为 `✅ 正常`，**合并引擎 02:30 每天的真实结果完全看不到**（假绿） | `~/.hermes/scripts/notion_daily_report.py` `get_ccpc_status`/`get_zc_status` | 中 | **已修复(2026-09-13)** | 改为读合并引擎日志 `/var/log/ccpc_zc_sync.log`（09-13 02:31 最新）：按 `===== CCPC [` / `===== ZC [` 切块取最后一次运行；**新增新鲜度判据**（日志 mtime 日期 ≠ 服务器当日日期 → `⚠️ 日志未更新`；缺 `* OK` 标记 → `❌ 异常`），不再无条件报绿。站点区文案同步修正为「CCPC/ZC/联系人 合并同步 (cron 02:30)」。实测页面显示：`2026-09-13 02:31:07 \| Done: +0 U1587 err0 \| ccpc.db: 6122 projects, 15053 contacts` / `(2026-09-13) 新增: 0, 跳过: 743, ZC.db 总计: 1661 条` |

### 2026-09-12

| # | 问题 | 位置 / 对象 | 严重度 | 状态 | 备注 |
|---|---|---|---|---|---|
| 1 | 本地 cron「坚果云增量同步」01:00 秒退 rc=1：`ModuleNotFoundError: No module named 'fitz'`。根因 = `hermes update` 于 9-11 19:20 重建 venv，清掉了 venv 里的 `fitz`/`python-docx`/`openpyxl`（它们只在系统 python3.9 用户 site-packages 里），而 cron 用的是 venv 解释器（3.11） | `~/.hermes/scripts/incremental_sync.py` + venv | 高 | **已修复(2026-09-12)** | 修法：入口加解释器自愈 bootstrap（探测必需模块 → 缺则 `os.execve` 到 `/usr/bin/python3`，env 标记防递归）。⚠️ 验证必须用 **venv 解释器**跑（手工用 `python3`=3.9 跑成功是假阳性，9-11 就踩过）。已全量扫其余启用中本地 cron 脚本依赖：无其它缺失。坑已入 `data-sync-pipeline-audit` 技能 |
| 2 | 同类隐患：任何依赖非 venv 自带第三方包的本地 cron 脚本，都会在 `hermes update` 重建 venv 后静默失败（每天只报 Script Error，不重试） | 全部本地 `~/.hermes/scripts/*.py` cron 脚本 | 低 | 待办 | 候选动作：① 给其余脚本也加 bootstrap；② 建专用 `~/.hermes/scripts/.venv`（bootstrap 已把该路径列为候选解释器）；③ `hermes update` 后加一步依赖自检 |
| 3 | 双向同步的范围盲区：`sync_gov_crawler.sh` 只覆盖 **服务器 ↔ 坚果云镜像**，第三处副本 `~/Crawler/gov_crawler` 不在同步范围，长期漂移——同名不同内容 2 个：`import_jsonl.py`（镜像已含 09-11 批量补的 `timeout=120`，`~/Crawler` 缺）、`rebuild_fts.py`（`~/Crawler` 1275B 是内容表重建版；镜像 497B 是已知 no-op 的 `INSERT ...('rebuild')` 版）；仅 `~/Crawler` 有 46 个非 Archive 文件，仅镜像有 267 个（`data/`、`utils/`、`*.sh` 启动器） | `~/Crawler/gov_crawler`（＝ `backup_gov_crawler.sh` 03:00 备份源 + `originals_sync.py` 运行处） | 低 | 待办 | 2026-09-12 02:46 同步后扫描发现，非本次同步引入。两个脚本均无调度器引用（`rebuild_fts` 仅 `crawl_ahjx` 内部自定义同名函数）。候选处置：① 把该目录纳入同步，或明确标注「工作副本、无需与镜像一致」；② 两个差异脚本各以镜像为准拉一次。**2026-09-14 02:46 本 cron 复核**：内容不一致 **6** 个——非 Archive **2** 个＝`import_jsonl.py`（镜像 `2c788c53` / 工作 `40389d24`）、`rebuild_fts.py`（镜像 `60304ce3` / 工作 `6cc1526c`），与 09-12 记录**完全一致（仍未处置）**；另 4 个在 `Archive/`：`crawl_anyi_xsthjj.py`、`crawl_wwhjbh.py`、`crawl_zjg_jsxmv.py`、`search_app.py`。仅工作副本有 **46** 个非 Archive / 仅镜像有 **267** 个非 Archive（与 09-12 计数一致，本日无新增漂移）。同次复核确认 **服务器 ↔ 坚果云镜像 4146 文件 md5 全量 0 差异**（双向同步链路本身健康） |
| 4 | **FTS 全量重建语句拖垮日跑（系统性根因）**：28 个活跃爬虫 + 模板 `base_crawler.py` 每天执行冗余的 `INSERT INTO gov_search(gov_search) VALUES('rebuild')`（`gov_search` 是普通 FTS5 表、且有 3 条触发器 `trg_gov_raw_fts_ins/del/upd` 维护，重建纯属多余）→ 单次 **400~600s** 且**整段独占 SQLite 写锁** → 日跑并联 3 个时写锁被连续霸占数小时，其它脚本撞 `database is locked` 被 `classify_error` 判成「脚本异常」 | 28 个 `crawl_*.py` + `base_crawler.py` | **高** | **已修复(2026-09-12)** | 替换为 no-op `SELECT 1`（保留 try/except 与日志结构）；备份 `backups/gov_crawler_pre_ftsrebuild_20260912.tar.gz` + 每文件 `.bak_20260912_ftsrebuild`。实测提速：`heshan_gkmlpt` 600→8.4s、`bynesrmtzx` 600→8.6s、`sthjsh` 406→1.6s、`sxepb_spgs` 595→5.0s（**70~254×**，全 rc=0）。另修 `qhd_gggs`/`lixin_hp` 的 connect 锁等待 15/30→60。三处同步 md5 已验 |
| 5 | ✅ **已完成（2026-09-14）** `state.db` 瘦身：FTS 旧布局 → v23 external-content 迁移 + VACUUM | `~/.hermes/state.db` | — | **已解决：1527.8 MB → 465 MB（↓ 1,062.8 MB，−70%）**；`freelist_count=0`、`quick_check=ok`、messages 100,799 / sessions 3,944 完好。**执行三步**：① `sqlite3 state.db ".backup ~/state_db_snapshot_20260914.db"` 做一致性快照（1.5G，普通打开验 `quick_check ok`；⚠️ **`sqlite3 -readonly` 打开该快照会 rc=14 假失败**，非文件损坏）② `hermes sessions optimize-storage --yes --no-vacuum`（**限流跑，CPU 仅 0.4%**，旧表先改名 `fts_v22_trash_*` 保留 → **可回滚**，重建完自动清理）③ 单独 `sqlite3 state.db "PRAGMA busy_timeout=300000; VACUUM;"` 回收 **293,786 空闲页 = 1.20 GB**。**实测效果**：`messages_fts_trigram_data` **616.4→10.2 MB**、`messages_fts_data` 111.4→79.1 MB、`messages_fts_content` 与 `messages_fts_trigram_content`（各 207 MB 重复副本）**直接消失**。✍️ **纠正**：此前「VACUUM 只能回收约 16MB」**仅对「不迁移布局的单跑 VACUUM」成立**；迁移后 VACUUM 实收 **1.04 GB** —— 真问题是**布局**不是碎片，两步缺一不可。⚠️ 坑：macOS **无 `setsid`**（Linux 命令）；`rclone size` 列举 6 万文件 >300s 会撞 execute_code 上限，须走受管后台 |
| 6 | **OneDrive File Provider 长期挂死 —— 真因 = 单个 ~398MB `.db.gz` 卡在「已上传但报错」循环**（不是死锁、不是网络、不是代理，且**重启 Mac 无效**） | 本机 `OneDrive-个人` 同步目录；影响 `IC13 Project TXT` / `originals note` 两个源（日跑 `originals-route-daily-sync` 因此停摆，已暂停）| 中 | 待办 | 实测：重启后 33 分钟仍 **100–112% CPU**、GC/CS 两路径 listdir 与单文件 open **全挂死**。统一日志 `log show` 命中 `item.uploadingError` / `FP -2005` / `OneDriveItemError 58` / `doc sz:398340063` / `nsattr: ul:uploaded|error` → 一个 ~398MB `.db.gz` 反复重试。**已排除**：网络（graph.microsoft.com 301 / onedrive.live.com 403，均 ~1s）、代理（utun4 有 198.18.192.51 = 隧道已连）。处置（需 GUI，命令行无能为力）：① 菜单栏 OneDrive 图标看状态/待处理项 → ② **暂停同步→恢复同步** → ③ 无效则 **重置 OneDrive**（重建本地缓存，不动云端）→ ④ 最后退出重登。⚠️ **不阻塞 rclone 方案**（走 Graph API，不经 File Provider）。⚠️ 排查禁令：`find ~`（即使用 -not -path 排除）仍会下钻卡死目录并挂满 300s，一律改用 `log show`/`ps`/有界探针 |

### 2026-09-11

| # | 问题 | 位置 / 对象 | 严重度 | 状态 | 备注 |
|---|---|---|---|---|---|
| 1 | 慢脚本（不崩但可能超时 600s）：`wudang` / `lufeng` / `jseia` / `hnccci` | `crawl_wudang.py` 等 4 个 | 中 | 待办 | `wudang` 修好 SQL 残片后仍 150s+；`jseia` 22 条详情逐条抓；`lufeng` 79 行输出；`hnccci` 12 条详情 > 260s。需逐条量耗时分布 |
| 2 | `hnccci` 有 12 条 URL 与库中记录不匹配（`source_url` UNIQUE 冲突但 `page_url` 不同）→ 每天重抓、正文回填不上 | `crawl_hnccci.py` `save_to_db` | 中 | 待办 | 已有回填 UPDATE 分支，但 12 条既不 INSERT 也不 UPDATE 命中，需打印实际 SQL 比对 |
| 3 | config 有**重复条目**且 `args=[]` → 脚本默认 50 页 | `daily_crawl_config.json` idx 1633/1634 `crawl_changshu_gsgg.py` | 中 | 待办 | 两条同名同名；`args=[]` 时 `max_pages=50`（`--incremental` 未传） |
| 4 | `crawl_huangchuan.py` 与 `jxlc_tzgg` 覆盖同一 `site_name`（临川区人民政府-社会公益事业）→ 疑似重复爬虫 | 两脚本 + `gov_raw.site_name` | 低 | 待办 | 前者 2280 行 / 后者新入库 6 行；需确认是否合并或分工 |
| 5 | 死站点 / WAF 的 config 处置未做 | `baixiang`(DNS 失效) / `wudang`(域名变更) / `sdharmony.cn`(双协议超时) / WAF 403 的 `anji` `shangyu` `cs_gz` | 中 | 待办 | 需决定：换域名 / `enabled=false` + note / 回填 Dis-db 脚本状态列 |
| 6 | 存量：`gov_raw.attachments` 列含 HTML 标签 | 全库 **64 行 / 5 站**（铜仁22·高唐14·贵州化工院12·扬州10·灵璧6） | 低 | 待办 | 来自「表格 HTML 误入 attachments」bug（脚本已修）。清洗：把 HTML 移入 content 或直接清空 |
| 7 | 搜索路由 Ctrl+K 目标路由错误（进主路由而非本路由）+ 主路由 ⌘K 冗余 | `search_app.py` L2463 / L2655 / `render_main_page` | 中 | **已修复(2026-09-11)** | 改 `__BASE__`/`meta['base']` 拼接 + 加 Ctrl+Enter + 移除主路由 ⌘K；四条路由实测通过 |
| 8 | ~~是否还有其它脚本在做无效 FTS rebuild 未全库排查~~ → **已全库排查并清理** | 全库 `*.py` | 低 | **已修复(2026-09-12)** | ⚠️ **原结论「实测是 no-op」是错的**（在小库上测的）。生产库 `gov_search` 有 **826,853 行**，`VALUES('rebuild')` 是**真实 FTS5 全量重建**，单次 400~600s 且全程独占写锁。活跃树命中 36 处 → 已清理 28 个爬虫+模板；余 8 个 `rebuild_fts*.py`/`fresh_db.py` 等是**重建工具本身**，属正常用途，不动。详见 2026-09-12 第 4 条 |
| 9 | `crawl_linli.py` 类：脚本引用不存在的 `gov_fts` 表（已修 1 处），未全库排查其它引用 | 全库 `*.py` | 低 | 待办 | `grep -rl 'gov_fts' --include='*.py' .` |

### 2026-09-11（第二轮 · 大丰新站上线 + 检索索引治理）

| # | 问题 | 位置 / 对象 | 严重度 | 状态 | 备注 |
|---|---|---|---|---|---|
| 19 | **检索索引是离线预计算 → 新入库内容有约 20 小时"搜不到"窗口**。`gov_bigram`(1.42 亿行) 与 `gov_entity` **不是**触发器维护，只靠 cron `4:20 / 4:25` 重建；而日跑 `00:00 → 08:23` 才收工，于是「04:25 之后写入」的当天数据要等**次日**凌晨才进索引。症状：新站入库成功、FTS 也命中，但 `/crawler` 列表页搜不到 | `crawl_scheduler_v2.py` 日跑收尾 / `rebuild_bigram_inc2.py` / `rebuild_entity_inc.py` | **高** | **已修复(2026-09-11)** | ① 索引刷新挂到日跑收尾新增 `[IDX]` 块（收工即索引，不再猜 cron 时刻）；② bigram 脚本改写为水位线版；③ 已补录 2,869 行 bigram + 2,870 篇 entity；④ `4:20/4:25` cron **保留**作调度器异常时兜底 |
| 20 | `rebuild_bigram_inc2.py` 旧实现**内存 + 性能双缺陷**：`SELECT DISTINCT rowid FROM gov_bigram` 全扫 1.42 亿条索引项（实测**仅"查找缺失"就要 82s**，且 0 条缺失照跑）+ `fetchall()` 把缺失行全部读进内存（生产机仅 1.75GB） | `rebuild_bigram_inc2.py` | **高** | **已修复(2026-09-11)** | 新实现：`index_watermark(name,value)` meta 表 O(1) 读水位线 + keyset **200 行/批**流式。无新行 **0.03s**（原 82s）。⚠️ **禁用 `MAX(rowid)`**：`gov_bigram.rowid` 是**显式 INTEGER 列**、无以其打头的索引 → MAX 退化为全扫 **38s**；`MAX(_rowid_)` 虽 0.003s 但那是插入序号、**不是 doc id**，不可用 |
| 21 | `/crawler/?q=` **稀疏中文词 70s 全表扫**：`_list_term_preds` 的 bigram 分支一律用相关 `EXISTS`，查询计划 = `SCAN gov_raw USING idx_gov_raw_date` + 每行 2 次索引探测——只有匹配**足够稠密能快速凑满 LIMIT** 时才快；稀疏词凑不满 31 行就须扫完 55 万行。（Ctrl+K 修复后「查看全部结果」正好落在这条路径上，所以更刺眼） | `search_app.py` `_list_term_preds` / `_LIST_GRAM_IN_MAX` | **高** | **已修复(2026-09-11)** | 探测各 gram 基数取 min（**交集 ≤ min 是硬上界**）：min ≤ 2 万 → `r.id IN (SELECT rowid … INTERSECT …)` 物化小集合；否则沿用 EXISTS。实测 `大丰区` **74s → 0.041s**、`双随机` 27s → 0.33s；稠密词 0.03s → 0.16s（探测开销，可接受） |
| 22 | **`mailto:` / `tel:` 链接被当附件** → 写进 `attachments` 列 + 正文生成假附件段。大丰脚本首轮 **122/249 行**中招（正文「邮箱：xxx@163.com」） | `crawl_dafeng_sthj.py`（已修）；**其它 hanweb 系 / 同模板脚本未全库排查** | 中 | **待办**（大丰已修） | 判据：`attachments` 含 `mailto:` / `tel:` / 裸 `#`。排查：`sqlite3 search.db "SELECT count(*) FROM gov_raw WHERE attachments LIKE '%mailto:%'"`（走全表扫，慢）或 `grep -l '_link_repl' crawl_*.py` 逐个人工确认 |
| 24 | `[IDX]` 钩子**首次运行要初始化水位线**（`MAX(rowid)` 全扫 38s，一次性） | `rebuild_bigram_inc2.py` `get_watermark()` | 低 | 已修复(2026-09-11) | meta 表已写入。⚠️ 若做 DB 灾难恢复/换库，meta 表丢失 → 下次运行会再付一次 38s，属正常 |

### 2026-09-11 之前（历史遗留，来自会话/技能记录）

| # | 问题 | 位置 / 对象 | 严重度 | 状态 | 备注 |
|---|---|---|---|---|---|
| 10 | 存量正文含 HTML 注释 | `gov_raw.content` **66,354 行** | 低 | 待办 | 一直未动；清洗前需确认注释里是否有附件/图片信息 |
| 11 | 白名单覆盖面：62 个「绕过脚本」存量未重抓 | 爬虫侧 | 低 | 待办 | 与新站爬虫 10 步闭环一同处理 |
| 12 | `/api/*` 是否加 `Cache-Control: no-store` | `search_app.py` | 低 | 待办 | 未决策 |
| 13 | 独立详情页标题 28px vs 抽屉 20px 是否统一 | `search_app.py` `DETAIL_CSS` | 低 | 待办 | 目前**刻意不一致**（抽屉窄用 20px）；要统一改 `.dh h1` 一行 |
| 14 | `render_gov_page` 与 `render_main_page` 合并 | `search_app.py` | 低 | 待办 | 延后（属重构，收益有限） |
| 15 | ccpc 抽屉 19 字段 vs 独立页 25 字段（样式已统一、**内容集未统一**） | `render_ccpc_frag` vs `render_cceup_detail_body` | 低 | 待办 | 让抽屉复用 `render_cceup_detail_body` 即可 |
| 16 | `crawler-infrastructure-audit` 的 `## Audit Workflow`（4.3 万字符）是否下沉到 references | 技能库 | 低 | 待办 | 技能瘦身最后一块 |
| 17 | 决策点：四个爬虫技能是否合并成 umbrella | 技能库 | 低 | 待办 | 未定 |
| 18 | 坚果云镜像内 `gov_crawler/gov_crawler/` 嵌套重复树 1502 文件待清理 | 坚果云 | 低 | 待办 | 只占空间，不影响功能 |
| 19 | 服务器 root 密码待改（同步脚本已不依赖） | 服务器 | 中 | 待办 | 安全项 |
| 20 | `wal_checkpoint(TRUNCATE)` 回收 WAL 占用空间（曾 1.32GB，现 0） | `/mnt/data/search.db-wal` | 低 | 待办 | 属写操作，需人工确认；空间不急 |

---

<!-- AUTO:DAILY_LOG:BEGIN (由 daily_run_log.py 每日自动追加, 勿手改本区块) -->
## 二、日跑自动记录

| 日期 | 成功 | 失败 | 失败率 | 明细 |
|---|---|---|---|---|
| 2026-09-11 | 1912 | 71 | 3.6% | `daily_run_logs/2026-09-11.md` |
| 2026-09-12 | 1956 | 42 | 2.1% | `daily_run_logs/2026-09-12.md` |
| 2026-09-13 | 1968 | 30 | 1.5% | `daily_run_logs/2026-09-13.md` |
| 2026-09-14 | 1973 | 25 | 1.3% | `daily_run_logs/2026-09-14.md` |
| 2026-09-15 | 1973 | 25 | 1.3% | `daily_run_logs/2026-09-15.md` |
| 2026-09-16 | 1962 | 36 | 1.8% | `daily_run_logs/2026-09-16.md` |
| 2026-09-17 | 1958 | 40 | 2.0% | `daily_run_logs/2026-09-17.md` |
| 2026-09-18 | 1958 | 40 | 2.0% | `daily_run_logs/2026-09-18.md` |
| 2026-09-19 | 1979 | 22 | 1.1% | `daily_run_logs/2026-09-19.md` |
| 2026-09-20 | 1974 | 27 | 1.3% | `daily_run_logs/2026-09-20.md` |
| 2026-09-21 | 1968 | 33 | 1.6% | `daily_run_logs/2026-09-21.md` |
| 2026-09-22 | 1970 | 32 | 1.6% | `daily_run_logs/2026-09-22.md` |
| 2026-09-23 | 1976 | 26 | 1.3% | `daily_run_logs/2026-09-23.md` |
| 2026-09-24 | 1981 | 21 | 1.0% | `daily_run_logs/2026-09-24.md` |
| 2026-09-25 | 1981 | 28 | 1.4% | `daily_run_logs/2026-09-25.md` |
| 2026-09-26 | 1982 | 28 | 1.4% | `daily_run_logs/2026-09-26.md` |
| 2026-09-27 | 1992 | 21 | 1.0% | `daily_run_logs/2026-09-27.md` |
| 2026-09-28 | 1993 | 20 | 1.0% | `daily_run_logs/2026-09-28.md` |
| 2026-09-29 | 1988 | 25 | 1.2% | `daily_run_logs/2026-09-29.md` |
| 2026-09-30 | 1984 | 29 | 1.4% | `daily_run_logs/2026-09-30.md` |
| 2026-10-01 | 1986 | 27 | 1.3% | `daily_run_logs/2026-10-01.md` |
| 2026-10-02 | 1986 | 27 | 1.3% | `daily_run_logs/2026-10-02.md` |
| 2026-10-03 | 1992 | 21 | 1.0% | `daily_run_logs/2026-10-03.md` |
| 2026-10-04 | 1994 | 19 | 0.9% | `daily_run_logs/2026-10-04.md` |
| 2026-10-05 | 1996 | 17 | 0.8% | `daily_run_logs/2026-10-05.md` |
| 2026-10-06 | 1994 | 19 | 0.9% | `daily_run_logs/2026-10-06.md` |
| 2026-10-07 | 1997 | 16 | 0.8% | `daily_run_logs/2026-10-07.md` |
| 2026-10-08 | 1982 | 31 | 1.5% | `daily_run_logs/2026-10-08.md` |
<!-- AUTO:DAILY_LOG:END -->

---

## 三、已闭环（留着备查，勿重复处理）

| 日期 | 问题 | 处置 |
|---|---|---|
| 2026-09-11 | **上高县爬虫静默停更 3 周**：老 `crawl_shanggao.py` 用 `http://` + `curl -s`（**不跟 301**）→ 站点 http→https 迁移后 JSON 解析失败 → 每天 0 条、DB 停在 2026-08-19；且 332/346 行 `script_name` 为空、栏目与用户 URL 不同（镜像 `jsxmhjyjpj2` vs `jsxmhjyxpj`） | 按现代模板重写（https+请求库跟跳转、正文内联免详情、CUTOFF、--pages、push_to_searchdb 写 script_name）；按栏目分别抓 + 标题去重；备份旧 346 行到 `Archive/shanggao_rows_before_rebuild_20260911.sql` 后重建 → **316 条**（300 条用户栏目 + 16 条镜像独有）；config #265 args 改 `--pages=5`；Dis-db 两条记录回填 `crawl_shanggao.py` #265（旧记录编号 267 修正为 265） |
| 2026-09-11 | **TRS 附件 `oldsrc="/protect/..."` 不可达（404）**：模板 `real_href = a.get("oldsrc") or href` 在 TRS 站取到 CMS 内部路径 → 本批 longli/xiaogan/qdn_sphgs/qdn_spqgs **180 行附件全 404** | 改为优先 `href`（`javascript:`/`mailto:`/`#` 才回落 oldsrc），14 站全部 DELETE 重跑；实测每站附件 `curl` 全 200（含完整 Chrome UA + Referer，避免 UA 门禁 403 假阳性） |
| 2026-09-11 | **`createPageHTML` 第4参数(扩展名)猜错 → 第2页起静默 404**：孝感高新区分页是 `index_1.shtml`，脚本写成 `.html` → 只入 1 页（15 条 vs 应有 75 条） | 后缀改 `.shtml`，重跑 75 条；判据固化为「新增数 == 页数 × 每页条数」 |
| 2026-09-11 | 大丰新站 **Dis-db 回填**（`page_id=3d8399b8-3c5b-8141-9212-c7cb46add585`） | PATCH `Script_name=crawl_dafeng_sthj.py` + `配置编号=2087`；Url 精确匹配 `/col/col25839/index.html`（全库仅 1 条命中）→ 重新 query 复核通过。⚠️ 坑：Notion PATCH body 必须包一层 `properties`（写成 `{"Script_name":…}` 直接 400 validation_error） |
| 2026-09-11 | **Ctrl+K 目标路由错误**：v2 列表页外壳被四条路由共用，但「查看全部结果」硬编码 `/?q=`（L2463 click 处理器 + L2655 foot 链接）→ 在 `/crawler/` 里按 Ctrl+K 搜索后跳到了主路由；`/zc` `/ccpc` `/contact` 同病 | 改用 `'__BASE__' + '?q='`（JS）/ `meta['base'] + '?q='`（模板）→ 各进本路由；**新增 Ctrl+Enter 热键**直接进本路由搜索结果列表（并加 foot 提示）；**移除主路由 ⌘K**（主搜索页本身就是搜索页，无使用场景）。四条路由 HTTP 实测 + node JS 语法校验 + 结构校验 65 类方法全过；md5 `3cdd6794` → `6761c9b9` |
| 2026-09-11 | 6 个脚本被 09-02 批量编辑删掉 `CREATE VIRTUAL TABLE` 首行 → 0.3s 崩（`guiyang`/`gz_hpspgg`/`wudang`/`suixi_sthj`/`gemxizang`/`zjcx`）+ `linli` 引用不存在的 `gov_fts` | 删残片（完成半截重构），实跑 rc=0 |
| 2026-09-11 | 7 个脚本把表格 HTML 塞进 `attachments` 而非正文 `parts`（`linxixian` 崩、其余 6 个静默丢表格） | 改入 `parts` + 贯穿型补 `continue` |
| 2026-09-11 | `gxq_yl_tzgg` `UnboundLocalError: new_count`（空结果日崩） | 循环前初始化 |
| 2026-09-11 | `soochowchem` 每条记录 fork `sqlite3` CLI 且 `timeout=10` 未捕获 | `-cmd ".timeout 60000"` + try 住 |
| 2026-09-11 | `jxlc_tzgg` **抓完不入库**（`if args.db and push_to_searchdb` 恒假）→ 每天白跑 400s | 接通入库 + 预去重（383→6） |
| 2026-09-11 | 1391 个脚本 `sqlite3.connect` 缺 timeout（1096 处）+ 60 个 CLI 脚本缺 `.timeout`（121 处）→ 锁竞争即失败 | 批量补 `timeout=60` / `.timeout 60000` |
| 2026-09-11 | T2 类：89 文件未定义名（`urllib`/`json` 等缺 import、模板孤儿变量名）→ 表格静默丢失 | 全部修复（89→6，余 6 个非 config 引用） |
| 2026-09-11 | T1 类：14 个持续失败脚本（NameError/无 timeout/参数未生效/全量重抓/嵌套 `<a>` decompose） | 逐个修复并实跑验证 |
| 2026-09-16 | **正文段落被拍平（全库级）**：样本「年产12000吨2,6-二氯对硝基苯胺技改项目生态环境影响评价第一次公示」（田东县 `crawl_tiandong.py`，id `2099118466438884056`）小标题与正文粘连成一段。根因：清 Word 粘贴中文字间空格的 `re.sub(r'(?<=[\u4e00-\u9fff])\s+(?=[\u4e00-\u9fff])', '', body)` —— **`\s` 含 `\n`，把段间 `\n\n` 一并吃掉**；因前后必须是汉字，所以「…联系方式」+`\n\n`+「建设单位…」被吞，而「…正常生产。」+`\n\n`+「本项目…」（前一字符是句号 U+3002，不在 `\u4e00-\u9fff`）得以保留，造成「有的段分开、有的粘连」的迷惑现象 | ① 量化：全库 **43 个脚本**中招（4 类变体：汉字↔汉字 38 处 / 汉字↔数字 17 处 / 数字↔汉字 16 处 / 后向引用 5 处），名下 **9772 条**记录；采样 1791 条中 **1400 条(78%)** 含超长无换行行（最惨 `crawl_dayawan_hjyxpj.py` 最长行中位 6201 字符 = 整篇一行）② 修复：`\s+` → `[ \t\u3000]+`（只吃行内空白），43 个脚本全部改完 + `py_compile` 全过 + 全部留 `Archive/*.bak_20260916_nl` ③ 验证：田东样本重跑 **19 段 → 30 段**，与源 HTML `<p>` 结构逐段一致 ④ ⚠️ **已入库的 9772 条换行已永久丢失、库内无法还原，只能重爬** ⑤ 修复用 `\s+` → `[ \t\u3000]+` 已生效，田东重爬实测 **33% → 4%** |
| 2026-09-16 | **重爬绝不能用 `DELETE` + 重跑（会丢历史数据）**：宿迁 `crawl_suqian_jsxm.py` 试点时先 `DELETE FROM gov_raw WHERE script_name=...`（2820 条）再重跑，爬虫**只抓当前列表页可见的记录** → 写回仅 **640 条，丢 2180 条**；田东/英德条数基本不变是巧合（它们列表页覆盖全量）。**正确做法：按 `page_url` 重抓详情页 → `UPDATE gov_raw SET content=...`，不碰任何记录的存在性**。已从备份表 `bak_crawl_suqian_jsxm_20260916` 用 `INSERT OR IGNORE ... SELECT` 恢复 2181 条（宿迁回到 2821 条） | 三站备份表保留：`bak_crawl_tiandong_20260916`(694) / `bak_crawl_yingde_20260916`(743) / `bak_crawl_suqian_jsxm_20260916`(2820) |
| 2026-09-16 | **906 个脚本的 `INSERT` 缺 `script_name` 字段** → 全库 **90,460 条** `script_name` 为空（memory 里「福清 6263 / 冠县 4466 / 长兴 4409」即此因）。实测：直接 INSERT 的脚本中 **906 个缺 / 300 个有**（走 `crawler_lib.push_to_searchdb` 的自动带 `sys.argv[0]`，自己 INSERT 的多数漏写）。影响：按脚本名定位记录/归档/重爬全部失效 | 已修 `crawl_yingde.py`（补 `script_name`，备份 `Archive/crawl_yingde.py.bak_20260916_sn`）+ 补齐其 721 条记录；其余 905 个脚本待批量处理 |
| 2026-09-16 | **"拍平"判据要排除 HTML 记录，否则大面积误报**：`content` 保留 `<td>`/`<span>` 标签的站（如英德），以及含全角空格 `\u3000` 的行，会让「汉字紧跟『一、』」判据误命中 —— 英德实测判 48%、但抽样确认换行其实**已保住**（新记录 `'…影响分析\n（一）…'`）。判据须跳过含 `<` 的行 + 排除 `\u3000` | 田东/宿迁是纯文本站，判据准确（33%→4% / 7%→1%）；英德这类含 HTML 的站需换判据 |
| 2026-09-16 | **刷存量方案：给 `crawler_lib` 加刷新开关而非改 43 个脚本**：实测 43 个中招脚本里 **39 个走 `crawler_lib.push_to_searchdb` 统一入口**，故在 `crawler_lib.py` 加环境变量 `SEARCHDB_REFRESH=1`（默认关闭，561 个调用方行为不变）—— 开启时按 `page_url` 先做 `UPDATE` 命中已有记录（不改变记录存在性），未命中才 INSERT。试点 `crawl_mingguang_hj.py`：19→100 条、全库零减少、吞并率归零。备份 `Archive/crawler_lib.py.bak_20260916_refresh` | ⚠️ 5 个**直写脚本**不走 crawler_lib，开关无效，需单独处理：`crawl_dongxiang.py` / `crawl_mz_gsgg.py` / `crawl_sxepb_spgs.py` / `crawl_xam_hp.py` / `crawl_yingde.py` |
| 2026-09-16 | **部分站的列表页已废弃 → 存量无法通过重跑刷新**：`crawl_chizhou_hpcy.py`（池州市生态环境局-环评公众参与）实测列表页 `showList/5577/page_1.html` 返回空列表「空列表, 停止 / 列表总计: 0 条」；`crawl_jingtang_yqdt.py` 同样 0 秒空跑。这类站的存量正文**只能靠 page_url 直接重抓详情**（若详情页仍在）或接受现状 | 待逐站核实：跑完批量刷新后统计 0 秒退出/列表为空的站 |
| 2026-09-16 | **补 `script_name` 存量：批量改 906 个脚本风险高，改用「存量回填」**。实测 906 个直写脚本的 INSERT 形态差异极大（列数 5/6/7/8/9/10/11/12 八种、VALUES 里混字面量与占位符、含 `INSERT_SQL` 常量写法），批量改代码极易错位。改走回填：用 `daily_crawl_config.json` 的 `site_name → script` 映射 UPDATE 空记录 —— dry-run 结果 **725 个 site_name / 70,711 条可回填（占 89,793 条的 78.8%）**，其中 31 个是「site_name 本身即脚本名」；**18 个 site_name 一名多脚本（2,330 条）需人工定**（如济南市生态环境局 → 4 个候选）；202 个无映射（16,752 条，TOP：环评云-报告公示 1649 / 环评云-验收报告 1322 / 茂县人民政府-通知公告 1019 / wanzai.gov.cn-株潭镇便民服务 738） | 执行器 `/tmp/nb_backfill_scname.py`（dry-run 默认，`--apply` 才写库）；⚠️ 回填会触发 `trg_gov_raw_fts_upd` 重写 FTS 行，**建议临时 DROP 该触发器→UPDATE→重建**；须等批量刷新结束后再跑避免撞锁 |
| 2026-09-16 | **`script_name` 回填：按 site_name 一刀切会错，必须按 URL 栏目号归**：济南市生态环境局一个 site_name 下混了 **5 个栏目号**（`col115248`×480 / `col10489`×212 / `col32967`×175 / `col32954`×150 / `col115241`×25），按「该站已有一个脚本名」的全站推断把 988 条全归 `crawl_jinan_jnepb.py` 是**错的** —— 用「脚本源码里出现的栏目号 ↔ 库内 URL 的栏目号」反查才对得上：`col115241→crawl_jinan_jnepb.py` / `col115248→crawl_jinan_epb_yppz.py` / `col32954+col32967→crawl_jinan_epb_new.py` / `col10489→crawl_jinan_hbj.py` | 已按栏目号重归（4 个脚本各归其位，988 条 → 480/325/212/25）；判据脚本 `/tmp/nb_fix_jinan.py`；**全库扫描：主栏目占比≥95% 的 815 个 site_name / 63,154 条可一刀切，真多栏目 123 个 / 24,506 条必须按栏目号归** |
| 2026-09-16 | **`script_name` 自动匹配一轮结果（可复用方法）**：候选脚本集 = `daily_crawl_config.json` 的 site_name→script 映射 ∪ 该站已有非空 script_name ∪ **按库内 URL 域名反查全目录脚本**；特征 = 栏目号（`col\d+`/`c\d+`/`site_*`/`channel_*`/目录名，排除通用词与年月）。第一轮（仅 config+已有分布）**17%**；第二轮（加域名反查 + 域名兜底）→ **599 个 site_name / 54,160 条（61.8%）**；再筛「候选脚本唯一」+50 个 / 4,938 条 → **合计 649 个 / 59,098 条 ≈ 67%**。依据分布：栏目特征唯一 207（高置信）/ 命中特征最多 16 / **仅域名唯一 376（中置信，抽查 12 个未见错）** / 候选唯一 50 | ⚠️ 仍需人工 **287 个 / 28,461 条**（同域名多脚本：长兴县 4438、铜仁市审批后公告 1965+拟审批公示 1944、环评云系列 4178、eiafans 349、薛城区 401、河津市 401、平罗县 400、天全县 486、衡阳市 672）；对照表 `~/Crawler/_scname_multiscript_20260916.tsv`（337 行，含每站库内 URL 栏目分布 + 各候选脚本栏目号 + 交集建议）；结果 JSON `/tmp/auto_match_round2.json`；匹配脚本 `/tmp/nb_auto_match2.py` |
| 2026-09-16 | **用户亲自裁定的 7 条 script_name 归属（Dis-db 列表页核对，权威）**：济南 `col115241`→`crawl_jinan_jnepb.py` / 吉林省受理公示→`crawl_jilin.py` / 细河区便民通知→`crawl_fxxh.py` / 石大胜华信息公开→`crawl_sinodmc_xxgk.py` / 新星经开区公示公告→`crawl_btnsss2.py` / 青岛生态环境局→`crawl_qingdao_mbee.py` / 东乡区环境保护→`crawl_jxfzdx_hjbh.py`。**其中 4 条推翻了「按已有记录分布」的自动推荐**（原推荐 jinan_hbj/fxxh_bmtz/sinodmc/btnsss_kfq_gsgg 全错）→ 结论：**自动推断不能替代人工看列表页** | 已回填 2,133 条并读回验证；裁定表 `~/Crawler/_scname_verdicts.json`（可续加）；空 script_name 89,793 → 87,660 |
| 2026-09-16 | **批量刷存量 40 站完成（`SEARCHDB_REFRESH=1`）**：全库 563,877 → **564,695（净增 2,448 条，零删除）**。吞并率下降 7 站：`shouguang` 50%→3% / `hypharm` 20%→0% / `jnnmip` 17%→0% / `gyjkq` 24%→10% / `zongyang2` 7%→0% / `zongyang` 3%→0% / `kel` 18%→14%；持平 27 站（多本来就 0%）。⚠️ **4 站吞并率上升是因抓到大量新条目**（`mee_nscxmgs` 80→334、`geermu_gcjs` 105→475、`sinodmc` 0→30）→ 新抓内容是否也带粘连待查 | 脚本 `/tmp/nb_refresh40.py`（3 并发、断点续传 `/tmp/refresh40_done.txt`）；结果 `/tmp/refresh40_result.json`；⚠️ 2 站列表页废弃（`chizhou_hpcy`/`jingtang_yqdt`）仍无法刷新 |
| 2026-09-17 | **正文重复 + 其中一份拍平**（用户报「粗制硫酸钴改造项目环境影响评价第一次公示」）：`crawl_fjhb.py:161` `for p in content_div.find_all(['p','div'])` —— **同时收 `<p>` 和 `<div>`，容器 `<div class="c">` 与它内部的 38 个 `<p>` 全部命中**（实测 49 命中 = 11 div + 38 p），正文输出两份；容器那份经 `get_text(strip=True)` **拍平**，`<p>` 那份用 `\n\n` 连接有分段 → 用户看到「重复 + 一份缺分段」。同类隐患：任何 `find_all(['p','div'])` / `find_all(['div','p'])` 的写法都可能有此 bug | 已修：改为**只取叶子节点**（排除「其后代也被命中」的节点）+ `<br>` 还原换行 + 清图标字体私用区字符（`\ue000-\uf8ff`）+ 清模板元数据行与评论尾巴（`共 0 条评论` / `赶紧来抢沙发`）。实测 2282 → 1097 字符，重复消除。存量重抓刷新 **333 条**（171s，备份表 `bak_fjhb_20260917`，全站「首段重复」残留 0）。备份 `Archive/crawl_fjhb.py.bak_20260917_dupfix`，md5 `f13b7fcab7` 三处一致 |
| 2026-09-17 | **正文分段被拍平 · 数据源侧根因**（用户报「中国石油独山子石化公司LDPE/EVA中试装置项目…拟报批公示」）：`crawl_dsz.py` 走 **API**（`/search/{channelId}?_isJson=true`）取 `content` 字段，而该字段是**平铺无分段的纯文本**（实测 477 字 / 0 换行 / 0 个 `<p>`）；**网页详情页 `#zoomcon` 里才有 `<p>` 分段**（同一内容 10 个 `<p>` → 5 段）。即爬虫选了没有分段的那条路，**不是 `\s` 类正则吃的**（与 #14 拍平案根因不同） | 已修：新增 `fetch_detail()` 抓详情页 `#zoomcon` 的 `<p>`（失败自动回落 API content），并解码 `&ensp;` 等 HTML 实体、归一全角/不换行空格。实测第一条 477→479 字/5 段、第二条 1000→906 字/26 段。存量重抓 **45 条**（31s，备份表 `bak_dsz_20260917`）；**7 条未更新属正常**——「双随机、一公开抽查结果表」页面 `#zoomcon` 内仅 281 字模板元数据+附件名、无 `<p>` 无 `<table>`，原本 API 也为空，保持空是正确的。备份 `Archive/crawl_dsz.py.bak_20260917_flat`，md5 `6999f02371` 三处一致 |
| 2026-09-17 | **排查「正文质量」问题的判据顺序**（本轮两个案例的通用教训）：①先看库内 `content` 的**换行数 / `<p>` / `<br>`** 定量指标 → ②再抓**源页面**看真实 HTML 结构 → ③对比「爬虫取的那份」与「网页有的那份」。案例 2 是**代码 bug**（取重了）；案例 1 是**取源不对**（API 平铺 vs 网页分段）。两种情况症状都是「拍平」，但根因完全不同 —— 不可套用同一修法 | 案例1/2 均已修并刷存量；`crawl_fjhb.py` / `crawl_dsz.py` 已三处同步 |
| 2026-09-17 | ⚠️ **站点被 WAF/DDoS 防护拦死，日跑白烧 30 分钟**：`crawl_hsq_tztg.py`（黄山市黄山区-公示公告，`www.hsq.gov.cn`）—— DNS 已解析到 `www.hsq.gov.cn.iname.damddos.com` + `www-hsq-gov-cn.sdns.gateway.jxdx.nc.shield.15ai.cn` 两层防护；实测服务器直连：**详情页 HTTP 412**（Precondition Failed）、**列表页 HTTP 405**（Method Not Allowed），均 216 字节空响应 → 被拦。该站日跑配置 `timeout=1800s`，最近三天（06:46 / 06:56 / 06:57）都在跑，即**每天浪费 30 分钟**在必然失败的站上。全库该站 205 条，最后入库数据停留在防护上线之前 | **已处置(2026-09-17)**：② 已试 —— cloudscraper + curl_cffi(6 种指纹) + 7 组手工头共 9 种方案**全部被拦**（HTTP 412/405，216B 挑战页），确认该 WAF 必须执行 JS，纯 HTTP 客户端无解；改走 ① —— 日跑 timeout **1800s → 600s**（配置 `daily_crawl_config.json`，备份 `.bak_20260917_hsq_waf`，2101 条目逐条 diff 仅此 1 处变更），站点保留观察。**本轮刷存量该站 200 条全失败，属正常现象非代码 bug**，已在批量刷新中跳过（失败计数已记录）<br><br>✅ **2026-09-22 复查：结论反转 —— 站点早已恢复，WAF 未再拦截**。实跑 `--pages=1` → 第1页拿到 **2026-09-18 ~ 2026-09-22 共 19 条**（含当天内容）并**成功入库 19 条**，EXIT=0 / 150s。此前「被长亭 CT2-WAAP 拦死、9 种方案全被拦」的判断**已过期**（脚本本来就用 playwright+stealth 走浏览器，能执行 JS）。<br>⚠️ **但由此暴露出两个真问题，已修**：① **stealth 从未生效** —— 脚本调的是 `Stealth().apply_stealth_async(ctx)`，在同步上下文里**从未被 await**（`RuntimeWarning: coroutine ... was never awaited` 实证）→ 改为 `apply_stealth_sync(ctx)`；② **无时限保护 → 必然超时白跑** —— 每条详情 `sleep(3~6)+goto+sleep(2)≈6.5s`，5 页×19 条=95 条 ≈ 620s，加列表页等待共 ~650s，**数学上必然冲破 600s 被杀**（这就是 avg 651s / 4-15 次超时的根因）。修法：新增 `--deadline=480` 兜底（列表阶段用掉一半预算即停止翻页；详情阶段超时限即提前收尾，**已抓的照常落库**）+ 配置页数 **5→3**（3页≈57条≈445s，一轮跑完；**注：脚本不跳过已入库条目，页数给大反而会让后段条目永远轮不到**）。**实跑验证**：`--pages=5` → `[时限] 已达 480s，提前收尾：本轮处理 62/95 条` → `[入库] 新增 11` → **EXIT=0 / 484s**（稳稳落在 600s 预算内）✅ 备份 `Archive/crawl_hsq_tztg.py.bak_20260922_deadline` + `daily_crawl_config.json.bak_20260922_hsq`，三处同步 ✅ |
| 2026-09-17 | ⚠️ **批量刷存量暴露的两个站点侧故障**（非代码 bug）：① `crawl_jseia.py`（江苏省环保产业协会，`www.js-eia.cn`）—— 站点为 **SPA**，首页与详情页均返回同一份 9181 字节壳页面（内容靠 JS 渲染），`requests` 取不到正文；该脚本自带 Cookie/登录逻辑，推测为**登录态失效**。刷存量 560 条全失败、**耗时 6658s（1.8 小时）**，严重拖慢批处理。② `crawl_leiyang_tzgg.py`（耒阳市-通知公告，`www.leiyang.gov.cn`）—— 详情页与列表页均 **HTTP 412**（246 字节挑战页），该站已启用 WAF（与 `crawl_hsq_tztg.py` 同类问题）。刷存量 67 条全失败 | **待办**：① jseia 检查登录态/改用浏览器渲染抓取；② leiyang 补 WAF 过盾或调小 timeout 止损；③ **批处理脚本应加「连续 N 条失败即短路跳过该站」**，避免又一次 1.8 小时空跑 |
| 2026-09-17 | ❗❗ **【已修复+已恢复】批量刷存量把正常内容写成乱码（mojibake），损坏 249 条** —— 根因：`refresh_dup2.py` / `refresh_dup_fixed.py` 的 `fetch_html()` 直接 `return requests.get(...).text`，**未修正 encoding**；而 `www.shuicheng.gov.cn` 响应头 `Content-Type: text/html` **不声明 charset**，requests 便按 HTTP/1.1 默认 **ISO-8859-1** 解码 → 中文变 mojibake（`根据` → `æ ¹æ®`）→ 写回库。**损害**：`crawl_shuicheng.py` 249 条 content + 249 条 summary（第一批 248 + 第二批 1）；其余 19 个脚本**均未受损**（它们的站点有 `charset=utf-8` 声明）。**恢复**：已从干净备份 `bak_dup_20260917_crawl_shuicheng`（乱码 0）恢复 249 content + 249 summary，复查乱码 0、抽样逐字符一致。**修法**：`fetch_html()` 加 `if not r.encoding or r.encoding.lower() in ('iso-8859-1','latin-1','ascii','cp1252'): r.encoding = r.apparent_encoding or 'utf-8'`（原版备份 `/tmp/refresh_dup2.py.bak_enc_20260917`）| **遗留**：全库另发现 **11 个站 6385 条**旧乱码（jinjiang 1539 / tuoxian_bmwj 69 / my_gov 19 / bp 70 / shuicheng 已修 / zmqz 81 / tengzhou 1 / ne 16 / scps 2 / hhjs_jianshui 21 / lhk 19），其中 9 站脚本已设 encoding（属旧数据，可重刷修复）、**`crawl_tuoxian_bmwj.py` 未设 encoding 仍在产生乱码** |
| 2026-09-21 | **大英县「生态环境」栏目断供 3 个月**（用户报 `https://www.daying.gov.cn/gongkai/kuozhan/10002.html` 没有最近数据）：① `crawl_daying_gk.py` **原先抓的就是 10002 生态环境**，后被改去 10053「重大项目实施进展」（SITE_NAME 改为 `大英县-信息公开`）—— 旧 41 条残留（`script_name` 仍写 `crawl_daying_gk.py`）停在 **2026-06-09**；② 全目录**无任何 .py 再写** `大英县人民政府-生态环境`；③ config #197 `crawl_daying.py` 的 `name` 误标为「大英县人民政府-生态环境」，实际它抓 **10043 通知公告**、写 `daying_tzgg` —— 备注与实况不符，属**配置漂移**。三栏目实况：10002 生态环境 / 10043 通知公告（`daying_tzgg` 1026 条、最新 09-20，正常）/ 10053 重大项目实施进展 | 新建 `crawl_daying_sthj.py`（10002；列表 `ul.content-list > li`、详情 `/gongkai/show/{md5}.html` 正文 `div.xqing-web-box`、附件 `div.xqing-web-file`）**沿用旧 site_name** 使历史连续。`--pages=1` 沙箱审计 **0 污染**（0 空正文 / 0 拍平 / 0 实体残留 / 0 乱码 / 0 导航污染；正文 125~1495 字、段落 6~43）→ `--pages=5` 入库 **109 条**（2025-03-25~2026-09-20，**6 月空洞已补**）；config 2104→**2105**（#2105 args=`--pages=5`）+ #197 正名「大英县-通知公告」；三处 md5 一致（脚本 `8debccc8` / config `cfeb6244`）；search_app 重启 PID 795470；FTS rowid **109/109 缺 0**；entity+bigram 增量补跑（+16,748 gram、剩余缺口 0）。三站 page_url **零跨站占用**（daying_tzgg 1026 / 大英县-信息公开 79 / 大英县人民政府-生态环境 109） |
| 2026-09-21 | **本地 `~/Crawler/gov_crawler/crawler_lib.py` 副本过期 5 天**：本地 `9a58e92a`(33278B / 09-11) vs 服务器+坚果云 `a3b3f147`(34884B / 09-16) —— **不是三方漂移，是本地没拉回来**（易误判成漂移） | 从服务器 scp 覆盖，三处 md5 `a3b3f147` 一致 |
| 2026-09-21 | **Notion `/databases/{id}/query` 三个静默失效坑**（做 flomo 21,029 条存量时踩到；共同点是 **HTTP 200 + 不报错 + 数据错**）：① **单次查询 10000 行硬顶且谎报 `has_more=false`** → 只看 `has_more` 判「拉完了」会无声丢数据；正确判据 = **返回条数 ≥10000 就当触顶**并按日期二分（21,029 条 = 20+ 次查询）② `{"date":{"on_or_after":D,"on_or_before":D}}` **同一个 date 对象塞上下界会被静默忽略**，返回完全不筛的数据 → 必须用 `{"and":[…]}` 拆两条件或 `equals` ③ 分页**不带显式 `sorts`** 时顺序不稳定会静默跳行（实测漏掉最早一页 100 条里的 88 条） | 判据与修法已写入 `notion` 技能；flomo 存量管道改为按 id 游标 + 进度文件分批（**不按日期**，因为导入日期不可信：13,287 条挤在 2022-07-01~03） |
| 2026-09-21 | **「当日是否成功」判据重构（四层）** —— 旧判据 = 脚本 status + new_count，实测基本失效：当日 2001 次运行中 **new_count=0 的有 1546 次（77%）**，其中只有 33 次（2%）标为失败 → **98% 的「0 新增」场景不产生任何信号**。三个根因：① `status` 是**解析中文输出猜的**（`extract_new_count` 找「新增: N」），脚本不打印就退化成 0 且记为成功 —— 实证 `crawl_ahsthj.py` **零输出 + exit 0 + 记为成功**；② `new_count` 把「源站无新数据/已入库去重/脚本哑火」混成一个数 —— 实证 `crawl_baotou.py`（看到 10 条全已知，健康）与 `crawl_ahsthj.py`（哑火）在 run_logs 里**完全一样**；③ 耗时不可作判据（0.4s 可以是健康的）。**铁证**：`crawl_hbxhhj_hpgs.py` 日志称近 14 天新增 804 条，库里 `ins_14d=0`（**一条都没插进去**）→ new_count 是误抓的数字 | **已落地**：① `crawl_scheduler_v2.py` 新增 `extract_items_seen` / `is_silent` / `extract_run_contract`，`run_logs` 加 `seen/flag/output_len` 三列；**flag 四态**：`SILENT`(零输出+exit0，哑火) / `NOITEMS`(列表页 0 条，抓取失败) / `IDLE`(看到但都已入库，正常空轮) / `NEW`；单元测试 **9/9 通过**（含上述两个真实案例）。② 新增 **`site_coverage.py`**（cron `40 9 * * *`）：**自校准**判定 —— 阈值 = `max(7天, 3 × 该站中位发布间隔)`，不问「今天抓到东西了吗」而问「比这个站自己的正常节奏落后了吗」；分级 正常1283 / 停滞313 / 观察184 / 新增未推进47 / 已废弃36 / 缺日期5。快照表 `site_coverage` 按日累积，供后续「连续 K 天超阈值才报警」。**脚本契约（渐进）**：新脚本收尾打印 `##RUN seen=N new=M skip=K##`，调度器自动解析 | ⚠️ 两个待办：① 47 个「新增未推进」需人工看（在写但库内最新日期不前进）② 5 个「缺日期」是真 bug（`crawl_fqlook.py` 近 14 天入库 7 条**全部无日期**、`crawl_yantai.py` 32 条里 18 条无日期）。⚠️ `publish_date` 格式不统一（`2026-9-9` 与 `2026-09-09` 并存，**字典序比较会错**），分析前必须归一化 |
| 2026-09-21 | ❗ **判定口径必须按「站点(域名)」，不能按脚本** —— 逐个过「47 新增未推进 + 5 缺日期」时发现**大部分是假阳性**，三种真因全部指向同一根问题（`script_name` 归属）：① **写入空 script_name** → 实证 `crawl_jingmen_jtysj.py` 的内容就在空名记录里（今日荆门交通局公告），不是没入库；② **page_url 全局 UNIQUE + INSERT OR IGNORE → 谁先插归谁** → 实证 `crawl_szepia.py` 喊「新增55条」，其条目在库里但挂在 `crawl_eiacloud.py` 名下（另两个标题 0 命中 = 静默忽略）；③ **站点本身停更** → 实证 `crawl_hlgls_hpxx.py` / `crawl_hbxhhj_hpgsb.py` 手动跑 `Page 1: found 20 items → [DB] new:0 skip:10`，列表页第 1 页本身只有 2022-2023 年内容 | 新建 **`site_coverage_domain.py`**（cron `40 9 * * *`，**主力判定**）：按域名聚合、阈值自校准 `max(7天, 3×中位发布间隔)` → **停滞 186 / 观察 113 / 正常 1076 / 无日期 5**（≥20 条记录的 1380 个域名），比脚本口径的 360 个告警干净得多。原 `site_coverage.py` 保留作脚本级诊断（cron `45 9`）。**结论：`script_name` 归属彻底修好前，任何按脚本的停滞/成功判定都不可靠** |
| 2026-09-21 | ⚠️ **`inserted_at` 66.71% 为空（382,878 / 573,953）→ 不能用于「入库新近度」判定** | 批量裁定 27 个「真未入库」脚本时踩到：`ins_14d` 靠 `inserted_at` 统计，而 2/3 记录根本没这个字段。**可靠替代 = `id`（雪花式单调递增）**：实测今日入库记录 id ≈ `2.10196e18`，而 `crawl_szepia.py` 的 `MAX(id)` = `2.0748e18`（远低于水位）→ 判「确实无新入库」准确。另：尝试给 `inserted_at` 加 AFTER INSERT 触发器自动填充（`trg_gov_raw_ins_ts`，已建于库），但**自测插入持续报 `constraint failed`**（复制真实行、剔除 id 后仍失败，怀疑与既有 FTS 触发器交互），**触发器是否真正生效尚未证明**，勿依赖 |
| 2026-09-21 | ✅ **8 个日期 bug 已全部修复 + 存量回填 974 条**（前一格「待修」已完成）。**各脚本病因都不同，逐个定位**：① `crawl_dongyingnews_bzgg.py` —— `re.search(r"(20\d{2}-\d{2}-\d{2})", html)` **盲取页面首个日期** → 抓到侧栏/版权日期，把 `/system/2026/09/18/` 的记录写成 `2024-07-02`；② `crawl_fqlook.py` —— 站点改版，日期文本变成相对时间「4 天前」，真值在 `<span title="2026-9-18 15:32:22">` 的 **title 属性**里（旧版是 `<span>发表于</span><span>date</span>`）；③ `crawl_zx_tzgg.py` —— `<meta name="PubDate">` 已废弃，改读 `<!-- AddDateStart --> 2026-09-16`；④ `crawl_cjgjgxq_tzgg.py` —— **两个 bug 叠加**：meta 里塞的是 HTML（`content="<span style='color:red'>"`）导致 `pub_date` 被赋成 `'<span styl'`（非空 → 后续兜底全被跳过），且页面是 GBK 却解码错 → 含「发布日期」字样的正则永远匹配不上；⑤ `crawl_juxian_gggs.py` —— 列表日期只有 `span.time` 一个选择器，且 `fetch_detail` **只返回正文不返回日期**；⑥ `crawl_zibo_epb.py` —— 详情页日期无兜底；⑦ `crawl_yantai.py` —— `li.find("span")` 只取**第一个** span（可能是图标）；⑧ `crawl_yichun_jjxw.py` —— 列表解析取不到日期且无 URL 兜底 | **修法**：统一「按可靠性排序取日期，绝不盲取页面首个日期」+ 各级兜底（精确 meta/元素 → 页面标记 → **URL 内嵌日期**）。**回归验证**：8 个脚本逐个调其解析函数喂现场页，全部取到正确日期（东营网 `2024-07-02`→`2026-09-18`、cjgjgxq `'<span styl'`→`2026-09-10`）✅。**存量回填** `backfill_dates_20260921.py`（683 条）+ `backfill_fqlook_dates.py`（31 条，用列表页建 tid→日期 映射）+ 二轮补跑（251+9 条）→ **共 974 条**，备份表 `bak_datefix_20260921`（974 行）可完整回滚。**验收：8 个脚本 0 无日期、100% 规范格式；全库无日期 8,344 → 7,379**。另顺手修 `crawl_juxian_gggs.py` 的 `INSERT OR REPLACE` **漏写 script_name**（page_url 冲突即删行重建 → 清空归属 = 空值的第 4 种成因） | ⚠️ 回填过程中自己踩的 3 个坑（已记教训）：① `"url" in names` 精确匹配失败（真实参数名是 `page_url`）→ 改子串匹配；② URL 兜底**写在 `try` 内部** → 模块调用抛异常（详情页 404）时直接被跳过；③ `dongyingnews` 详情页现已 **HTTP 404**（站点改版删旧 URL），只能靠 URL 内嵌日期修 |
| 2026-09-21 | **`new_count` 误抓根因定位（已可复现）** —— `extract_new_count` 逐行倒序找「新增: N」，但**脚本常同时打印两个数**：`crawl_hbxhhj_hpgs.py` 输出 `[DB] new:0 skip:8` + `新增: 8` → 正则抓到后者（8），实际入库是前者（0）。实测虚报规模极大：`crawl_fxxh_bmtz` 称+3420 实入1、`crawl_jingmen_jtysj` 称+2250 实入0、`crawl_tinci` 称+120 实入0 | **修法**：调度器已加 `##RUN seen=N new=M skip=K##` 契约解析（优先于旧正则）；**更可靠的是直接读脚本自身的 `[DB] new:N`**（很多脚本本来就在打，且是库侧真值）。**不要再用 `extract_new_count` 的单一结果做判据** |
| 2026-09-21 | ✅ **第 3 步「治本」：186 个「销毁者」脚本已修（198 个文件）** —— 靶向定位优于盲改 812 个文件。**关键洞察**：`INSERT OR REPLACE` 在 `page_url` 冲突时**删旧行再插入**，SQL 里没有 `script_name` 就会**每次重跑都清空归属**（= 空值持续再生的主因）。实证：济南市生态环境局 949 条天天被清空，写它的 `crawl_jinan_epb_new.py` / `crawl_jinan_epb_yppz.py` 正在 A 类名单里。**量化**：A 类「销毁者」**186 个**（REPLACE 且不含 script_name）/ B 类「生产者」812 个（INSERT 且不含，交给 janitor 兜）。**修法（外科手术级）**：只改 SQL 文本 —— 列清单尾加 `, script_name`，VALUES 尾加字面量 `'crawl_xxx.py'`，**不动参数元组**（避免错位）。**安全网**：改前逐文件备份到 `Archive/*.bak_20260921_replfix`、改后用 sqlite3 在临时库 `EXPLAIN` 校验列/值数量、ast 语法检查 → **195 个文件全部通过；195/195 语法 OK；三处同步完成**。另 3 个（`crawl_cyx.py`/`crawl_fxxh.py`/`crawl_zhanhua.py`）用 f-string 内联值而非 `?` 占位符 → 走 `fix_destroyers_fstring.py` 单独修好 | **运行时验证**：跑 `crawl_jinan_epb_yppz.py` → 济南市生态环境局归属从 **0 → 482 条**（`crawl_jinan_epb_yppz.py`）✅ **修复确实生效**。⚠️ **重要**：这 195 个是在**服务器上直接改的**，已 scp 回本地 + 坚果云（195/195 成功） |
| 2026-09-21 | ✅ **「无上限翻全站」全库扫描完毕 —— yeda 是孤例，不是普遍模式**。**方法**：静态（代码里的 `range(..total_pages..+1)`）+ 动态（`run_logs.elapsed_seconds`）双证据交叉。**静态命中 161 个**但属常见写法（很多小站跑得很快）→ **以动态为准**。**动态结果（近 14 天 30,000 次运行）**：成功 29,290 / 脚本异常 378 / 站点不可达 143 / **超时 108（0.36%）** / 失败 4。**交叉验证推翻了「没限页导致超时」的假设**：224 个慢脚本里 **201 个本来就有页数上限**，而无上限的 23 个 max 只有 194~357s（全都安全）。**结论**：超时是**偶发网络停顿**（超时脚本平均耗时只有 23~73s），不是系统性慢。⚠️ 唯一系统性超标的是 `crawl_hsq_tztg.py`（avg **651s**，4/15 次超时）—— 正对上已知问题「被 CT2-WAAP 拦死」 | **已修**：扫出 **10 个 requests 调用缺 `timeout=`** 的脚本（`lankao_tzgg` 7 处 / `yc_hb` 5 / `list_no_data` 5 / `fx` 4 / `spider` 3 / `spider_unified` 3 / `jxyy_tzgg` 1 / `liyang_tzgg` 1 / `liyang_xxgk` 1 / `zdhjkj_notice` 1）→ `fix_req_timeout.py` 统一补 `timeout=30`（逐文件备份 `Archive/*.bak_20260921_reqtimeout` + ast 语法校验 + 冒烟实跑通过）→ **10/10 三处同步**。（全库 1733 个脚本的 requests 调用本来就都带了 timeout） | ⚠️ `crawl_hsq_tztg.py`（WAF）仍待解；超时 108 次属偶发，暂不需特殊处理 |
| 2026-09-22 | ❗❗❗ **系统性静默丢数据 bug：FTS 触发器链 + 脚本手动写 `gov_search` 相互撞车**（起因：用户报 `www.xiantao.gov.cn/bmxxgk/shbj/.../gsgg/` 无近期数据）。**症状**：`crawl_xiantao.py` 每天跑、状态 `成功`、耗时仅 **2.1 秒**、`new=0`，连续两个月（库内 296 条卡在 2026-07-27），而站点列表页明明有 2026-09-02 的内容。**排查链**：① 站点 HTTP 200/55KB/0.5s 完全正常（先排除站点侧）；② 脚本实跑打印「找到14条 … 重复: 14」但**逐条核实发现 8 条根本不在库里** → 判「重复」是假的；③ 摸到 `except sqlite3.IntegrityError: total_dup += 1` —— **异常被当成「重复」吞掉**；④ 复现插入 → `IntegrityError: constraint failed`；⑤ 副本库上逐个拆触发器 → **元凶 `trg_gov_raw_fts_ins`**。<br>**完整链条**：`INSERT INTO gov_raw` → `trg_gov_raw_ins`(AFTER INSERT) 执行 `UPDATE gov_raw SET inserted_at=...` → 触发 `trg_gov_raw_fts_upd`(AFTER UPDATE) 写一次 `gov_search(rowid)` → 随后 `trg_gov_raw_fts_ins`(AFTER INSERT) **又写同一 rowid** → `constraint failed` → **整个 INSERT 失败**。<br>**第二段病因（触发器修好后仍失败）**：脚本自己在 try 里又手动 `INSERT INTO gov_search(rowid,...)` 一次，而触发器已经写过 → 再次 `IntegrityError`；因 `conn.commit()` 排在两条之后，**gov_raw 那条被一并回滚** → 数据彻底消失 | **已修（两层）**：<br>**① 核心库 schema（已停 search_app → 改 → 重启，active/监听 8000）**：`fix_fts_trigger_chain.py` —— `trg_gov_raw_fts_ins`/`fts_upd` 改 `INSERT OR REPLACE`（幂等）+ `fts_upd` 加 `WHEN` 守卫（title/site_name/summary 未变则不动 FTS，避免每次写 inserted_at 都重写索引）+ 删掉我 2026-09-21 加的冗余 `trg_gov_raw_ins_ts`（`trg_gov_raw_ins` 已做同样的事）。DDL 备份 `Archive/fts_triggers_20260922.sql`。自测：普通 INSERT ✅ 成功、FTS 1 行、inserted_at 自动填。<br>**② 脚本侧**：`crawl_xiantao.py` 移除手动 `gov_search` 插入（备份 `Archive/crawl_xiantao.py.bak_20260922_ftsdup`）；另用 `fix_fts_precommit.py` 给 **23 个 crawl_*.py** 在手动 FTS 插入前补一行 `conn.commit()`（一行插入、不动语句结构、避免注释掉后出现空 try 块；备份 `Archive/*.bak_20260922_precommit`）→ 语法 0 失败、三处同步 23/23。<br>**验收**：`crawl_xiantao.py` 实跑 **`新增: 8  重复: 6`** → 库内 **296 → 304 条，最新 2026-07-27 → 2026-09-02** ✅ | ⚠️ **待办**：① 首轮扫描共命中 **97 个** crawl_*.py 有该模式，本轮只安全修了 23 个，**剩余约 74 个需逐个看**（多为 gov_search 插入是 try 块内唯一语句，注释会破语法）；② **强烈建议复核「停滞 186 站」里有多少是这个 bug 造成的**（xiantao 卡了两个月却天天报「成功」）；③ 全库 FTS 有 **30,347 行孤立 rowid**（gov_raw 已删、FTS 残留），可考虑重建 FTS |
| 2026-09-22 | ✅ **A 方案完成：静默丢数据 bug 的影响面已量化。** **方法**：静态（脚本含 `INSERT INTO gov_search` 且无修复标记）+ 动态（近14天 ≥5 次运行、≥90% `new_count=0`、中位耗时<15s）+ 数据（`MAX(publish_date)` 落后）三重交叉 → 得 **94 个高危候选**，再**逐个实跑分类**（每脚本最多 90s，同时记录「脚本自报新增」与「库内实际变化」）。<br>**分类结果**：`C 72 个`（自报 0 且库未变 = **不是 bug**，站点确实没新内容）、`OK 7 个`（本次真的补入了 = 触发器修复已生效）、**`A 5 个`（自报 N 但库 +0 = 真静默丢数据）**、`B 2 个`（列表解析 0 条 = 站点改版另一类问题）、`D 8 个`（90s 超时）。<br>**A 类明细**：`crawl_changji`(自报566) / `crawl_kecheng`(435) / `crawl_hld_tzgg`(10) / `crawl_hky`(5) / `crawl_ningguo_sthj`(1)，加上我手动证实的 `crawl_lanshantunhe`(2) 共 **6 个**。<br>**关键认知**：那 72 个 C 类**不是没问题，而是「这次恰好没有新内容可丢」** —— 修法仍必须做，否则站点一发新公告就会静默丢。静态带该模式的共 **420 个** crawl_*.py。 | **已修（6 个 A 类，`fix_fts_precommit2.py --list=`）**：在每个手动 `INSERT INTO gov_search` 前插入一行 `<connvar>.commit()`（连接变量自动探测；逐文件 ast 校验 + 备份 `Archive/*.bak_20260922_precommit`）。<br>**复验（决定性）**：`crawl_hld_tzgg` 自报 **+10 → 站点近 7 天入库正好 10 条** ✅、`crawl_hky` 自报 **+5 → 近7天 7 条** ✅、`crawl_ningguo_sthj` 输出 `FTS sync verified: 76`（75→76）✅、`crawl_kecheng` 站点最新已到 **2026-09-22** ✅ —— **自报数与库内增量精确吻合**。6 个文件三处同步 ✅ | ⚠️ **剩余**：420 个带该模式的脚本中只修了 30 个（23 + 6 + xiantao），**其余约 390 个仍是潜伏状态**；B 类 2 个（`crawl_nmxhq` / `crawl_qianwei_hjbh`）列表解析失效待查；D 类 8 个跑超 90s 需单独看 |
| 2026-09-22 | ✅ **54 个「真危险形态」脚本已批量修复，全部同步完成。** 一开始增强版修复器报了 420 个（= 任何含手动 `gov_search` 插入的文件），**风险过大** → **加了安全闸**：只有「gov_search 插入之前确有 `INSERT INTO gov_raw`、且两者之间没有 `commit`」的形态才动手（那才是真会把 gov_raw 一起回滚的形态）。**结果**：命中 **54 个**、安全闸拦下 150 个（形态不危险，未碰）、24 个探不到连接变量。改法仍是**一行插入**（`<connvar>.commit()`，连接变量自动探测），每个文件只多 2 行注释 + 1 行 commit。 | **验证**：① 改动抽检 —— `diff` 确认只多那 3 行、缩进与变量名正确（`db.commit()` / `conn.commit()`）；② **83 个（23+6+54）语法全检 0 失败**；③ **冒烟实跑 5 个**：全部 `rc=0`、无 `IntegrityError`/`Traceback`，其中 **`crawl_wuwei.py` 一次补回 75 条**（此前按脚本名 0 条，全在被丢）；④ **83/83 三处 md5 全一致**。备份：`Archive/*.bak_20260922_precommit`（83 个，回滚 = 覆盖回去即可）。修复器 `fix_fts_precommit3.py`（带安全闸 + 幂等标记） | **剩余**：24 个探不到连接变量的（`crawl_ahlx_hjsp` / `crawl_anhua` / `crawl_guangde_hjpzjg` / `crawl_gzxr` / `crawl_jinxi` …）需人工看一眼连接变量名；B 类 2 个列表解析失效；D 类 8 个超时 |
| 2026-09-22 | ✅ **本轮最终战果**：`crawl_xiantao.py`（296→304，最新 07-27→09-02）+ 6 个 A 类（`changji`/`kecheng`/`hld_tzgg`/`hky`/`ningguo_sthj`/`lanshantunhe`，自报数与库内增量精确吻合）+ 54 个潜伏危险形态 —— **共 61 个爬虫脚本**修掉静默丢数据；另修 `crawl_scheduler_v2.py` 的孤儿进程 bug（killpg）+ 清理 2 个跑了 3.5/5.4 天的卡死进程。核心库触发器链也已修（`INSERT OR REPLACE` + `WHEN` 守卫 + 删冗余触发器）。 | 全部三处同步 | 见上一行的剩余项 |
| 2026-09-22 | ✅ **调度器孤儿进程 bug（顺带挖出并修复）** —— 排查中发现两个卡死进程：`crawl_pujiang_hjbh.py --pages=5` 跑了 **465,357 秒（5.4 天）**、`crawl_xinjin_gsgg.py` 跑了 **299,586 秒（3.5 天）**，一直占着 sqlite 连接。**根因**：`crawl_scheduler_v2.py` 用 `subprocess.run(cmd, shell=True, timeout=...)`，超时只杀掉 `sh`，`python3` 子进程变孤儿继续跑。 | **已修**：改为 `subprocess.Popen(..., start_new_session=True)`（自成进程组）+ 超时时 `os.killpg(os.getpgid(pid), SIGKILL)` 整组杀（另补上漏掉的 `import signal`——该文件是 `import json, os, sys, time, subprocess, ...` 合并写法，第一次替换没命中）。**实测**：`sleep 45` 的假脚本限时 3s → 返回 `[TIMEOUT][KILLED_PROCESS_GROUP]`、**孤儿进程 ✅ 无**。备份 `Archive/crawl_scheduler_v2.py.bak_20260922_killpg`，三处同步 `661fcff9`。两个卡死进程已 TERM 清理 |
| 2026-09-24 | ✅ **`search_app.py` 的 `/originals/` 三项功能增强（用户需求）** —— ①**字段可点即搜**：`id_code` 变成链接跳 `/originals/?q=<值>`；另加「选中页面任意文字 → 浮层给出🔍搜索选项」（用于标题/正文里的公司名、产品名，如「自贡中天胜新材料科技有限公司」）②**ID 外链图标**：纯数字 7 位 → `ed.industrialinfo.com/NED/ui/#/ED/plant/<码>`；9 位 → `.../confirmedProject/<码>`；其余位数不加（**实测 7 位 plant / 9 位 confirmedProject 均正确**）③**列表显示正文访问次数**：新建独立库 `/root/originals_views.db`（**与 originals.db 隔离，不会被 tms 同步覆盖**），详情页打开即 +1，列表按 id_code 批量取，显示 `👁 N`。 | **实现**：`patch_search_app_originals.py` —— 9 段 anchor 替换（**命中数必须为 1，否则整脚本中止不落盘**）+ ast 校验 + 备份 `Archive/search_app.py.bak_20260924_origfeat`。8644 → 8777 行。<br>**踩的两个坑（都被安全网拦住，未落盘）**：① 锚点 `'</div></body></html>'` 出现 4 次（非唯一）→ 换带上下文的锚点；② **辅助函数插在了类方法中间且 0 缩进** → 会把 `class SearchHandler` 提前截断 → 改插到**类定义之前**（模块级）；③ 注入的 JS 里 `\u{1F50D}` 被 Python 当成非法 unicode 转义 → JS 串改 `r"""` raw 字符串。<br>**实测验收**：列表页含 `class="kw"`（可搜）+ `class="extlink"`（外链）+ `class=vc`（计数）+ `selbar`（选中脚本）；详情页连开 3 次 → 计数 **1 → 2 → 3** ✅；`/root/originals_views.db` 落盘 `._3085230 = 3 次` ✅；三处同步 `1523b2d3` ✅ | 📌 **发现**：`id_code` 有脏前缀值（`._300369030`）—— **按纯数字位数判定**，所以它们已被正确挂上 confirmedProject 外链 ✅（显示保留原值，遵守"字段不清洗"）。⚠️ 6/10/12/13/14/15 位共 16 条不加图标（非 7/9 位）。**重启已做**：stop → start 两次调用，active / 监听 8000 ✅ |
| 2026-09-24 | ✅ **`/originals/` 三项微调（用户反馈）** —— ①拼接 URL 的 ID 文字**去掉下划线、不改小手**（`cursor:text`）②外链图标 **方框箭头 → 链条(link)图标**。**关键认知**：光改 cursor 不够 —— `<a>` 按住拖动会变成「拖链接」而不是选中文字，**必须加 `draggable="false"`** 才能正常拖选；另外**浏览器给 `<a href>` 的默认 `text-decoration:underline` 必须显式 `text-decoration:none` 关掉**（去掉自定义点线后它还在，第一次只改了 cursor/border 不够）。**实测（真浏览器读 computed style）**：`cursor=text` ✅ `decoration=none` ✅ `border=0px` ✅ `draggable=false` ✅；程序化选中 ID → `selected='3392417', ok=True` ✅；SVG 已是链条路径（`M10 13a5 5 0 0 0 7.54.54l3-3…`）✅。补丁 `patch_search_app_originals2.py`（3 段）+ `..._3.py`（1 段），备份 `Archive/search_app.py.bak_20260924_origfeat2/3`，三处同步 `ab5f24dc` ✅ | ⚠️ `capture_screenshot()` 连续两次 IPC 超时（浏览器 daemon 截图通道问题，与改动无关），改用 DOM computed style 取证 |
| 2026-09-24 | ✅ **访问次数挪到 crawler 列表页 + 选中即搜加到 crawler（用户需求）** —— ①访问次数**从 `/originals/` 移除**（列表的 `👁 N` span、`_vc` 查询、详情的自增与显示全删）②**改为显示在 crawler 列表页**（`/crawler/` v2 版）：**复用库内既有的 `project_visits`**（session.db，`project_type='crawler'`）而不是另起计数器 —— 因为 crawler 详情页本来就在用它记访问，语义完全一致。新增 `_crawler_views_map()`（按 id 批量 GROUP BY）+ `_crawler_attach_views()`（给 items 附 `v`）；接入点两处：`/api/list` 响应 + `render_list_shell` 首屏 boot 数据；前端 `rowEl` 在 meta 行渲染灰底小标签。③**选中即搜**（跳 `/originals/?q=<选中文字>`）注入 crawler 列表页。 | ❗ **顺带修掉一个虚增计数的真 bug**：抽屉有**预取**（`prefetchItem` 会请求 `/api/item` → 走 `handle_db_detail` → 触发 `_track_project_visit`），所以**预取也会被算成一次访问**，把计数刷虚。修法：抽屉**真实打开**时带 `view:1`（预取不带），`handle_db_detail` 里加闸门 —— `(not self._detail_frag) or view==1` 才计数（直接访问详情页无 `_detail_frag` → 照常计）。<br>**验证（不碰认证）**：① 直接调 `render_list_shell(route='crawler')` → 产出 54,728 字节，含 `selbar`/`encodeURIComponent`/浮层跳 `/originals/?q=`/`rowEl` 计数渲染 ✅，首屏 30 条**每条都带 `v`** ✅；② `_crawler_views_map` 读路径往返（插假访问 → 读到 1 → 清理 → 还原 0）✅；③ `/originals/` 页面已无 `class=vc` ✅。备份 `Archive/search_app.py.bak_20260924_views2crawler`，三处同步 `bdbbb427` ✅ | ⚠️ **crawler 路由需登录**（匿名请求返回登录页），所以没有伪造会话去端到端点验 —— 改用「直接调渲染函数 + 计数读路径往返」等效取证。**请你在已登录浏览器里确认一眼**列表页的 `👁 N` |
| 2026-09-24 | ✅ **清理 `originals_views.db` 与孤儿函数（用户：清理吧）** —— 按用户既定规矩「**孤儿代码移入 Archive 而不是删除**，数据文件也归档不删」，做的是**搬家不是删除**：① `search_app.py` 里 `_ORIG_VIEWS_DB` / `_orig_vconn` / `_orig_views_get` / `_orig_views_inc` 四个函数（改建 crawler 后已无调用方）→ **原文切片**抽存 `Archive/search_app_orig_views_orphan_20260924.py`（1,924 字节，含移除原因与恢复方法），主文件里该区块删除并在原处留注释说明去向；② `_ORIG_EXTRA_CSS` 里已无人用的 `.vc{...}` 规则一并移除；③ `/root/originals_views.db`（3 条记录）→ `Archive/originals_views.db.20260924`（12,288 字节）。<br>**保留** `_orig_digits` / `_orig_ext_url` / `_orig_search_link`（外链与选中即搜仍在用）。 | ⚠️ **踩两个坑**：① 第一次用**手打的原文**做锚点 → 命中 0 次（空白/字符与文件里不完全一致），连续失败两次 → 改为**按函数名边界 `src.index()` 切片移走原文**才成功。**教训：要「原文原样搬运」时别手打待匹配文本，直接切片**（顺带保证归档的是精确原文）。② 同步时把 TODO **拉反了方向**（`scp 服务器→本地` 覆盖了我刚写的行）→ 重新写入并按「本地→服务器+坚果云」推送。**教训：同步方向必须逐文件确认，改动过的文件只能推送**。<br>**验证**：crawler 壳 54,598 字节含 selbar/rowEl 计数 ✅；孤儿函数已不在 ✅ 保留函数还在 ✅；originals 页 kw 链接 ✅ 外链图标 ✅ 残留计数 False ✅；`ED/plant/3392417` 正确 ✅；服务 active/8000 ✅。备份 `Archive/search_app.py.bak_20260924_cleanup`，行数 8820 → 8786，三处同步 `fb28711f` ✅ |
| 2026-09-24 | ✅ **新增站点：丹寨县人民政府 环境执法监管（11 步闭环完成）** —— `https://www.qdndz.gov.cn/zwgk/zdlygk/sthj/hjzfjg/`。**⚠️ 站点身份纠错**：`qdndz` 不是青岛，是**黔东南·丹寨**（页面 title 实测「丹寨县人民政府门户网-环境执法监管」）—— 按域名猜城市会猜错，必须看 title。**CMS**：TRS `createPageHTML(19, 0, "index", "html", 275)` → 19 页/275 条/15 条每页；第 1 页=目录本身，第 N 页=`index_{N-1}.html`（`index_18` 末页 5 条、`index_19` 404、`.shtml` 全 404）。**列表**：`ul.NewsList>li` 内 `<a TARGET="_blank" title="…" href="…">…</a><span>2026-09-22 16:21</span>` —— ⚠️ **title 在 href 之前**（与同域 qdn 模板的 `</a><b>日期</b>` 不同），故 parse_list 改为按 `<li>` 逐条 + 属性顺序无关提取。**详情**：meta ArticleTitle/PubDate；正文 `div.Article_Con > font#Zoom > div.trs_editor_view`（`Article_Con` 内 `#Zoom` 之前还有个空的 `div#sp` 视频占位 → 取 `#Zoom` 内容天然避开）；`div.ArchiveGdPart` 是 JS 生成的「已归档」红章水印、`div.ArticleProperties` 是元数据块 → 都不在 `#Zoom` 内，无需清洗。 | **脚本** `crawl_danzhai_hjzf.py`（复用同域 `crawl_qdn_sphgs.py` 模板，改站点/栏目/列表标记/正文容器）。**列表对账 15/15**（标记数=解析数，无静默漏抓）。`--pages=1` JSONL 测试：audit 全绿 `n=15 empty=0 badtitle=0 atturl=0` + nopara/notin/markdown_link 全 0；0 污染词、0 实体残留、标题无省略号截断（24~47 字）。`--pages=5`：**新增 32 / 跳过 1 / CUTOFF 截断 41**（5 页 74 条；第 3 页起即早于 3 年线 → **3 年窗口已被前 3 页完整覆盖，无遗漏**）。库内：32 条 / 2023-09-25~2026-09-22 / 空正文 0 / 重复 URL 0 / 平均 1411 字 / 含 `<p>` 32 / 含表格 2 / **FTS 已同步 32/32**。config 注册序号 **#2106**（同组贵州，args=`["--pages=5"]`；调度器 `isinstance(args,list)→join` 两种格式通吃，已核实）。search_app 重启：MainPID 1640692 → **1643885**，监听 8000。 | 📌 **源站数据特性（不是 bug，已实测取证）**：① 首条正文里 URL 被切成 `…665329.ht` + `<a>ml</a>` —— 查原始 HTML 确认**源站 CMS 只把 URL 末尾两个字符自动做成了链接**，原文如此，按「源站原文如此即保留不改」保留 ✅；② 两篇公示的 `attachments` 是正文里的 mee.gov.cn 链接（模板既有的「残余 http `<a>` 转附件」约定，非文件型附件），`atturl` 审计仍为 0 ✅；③ 全站**无文件型附件**（环评资料走百度网盘外链，正文内保留为可点 `<a>`）✅。<br>⚠️ **v2 路由可搜延迟**：`gov_bigram` 本站行数 0 → `/crawler/?q=` 中文词暂时搜不到本站新数据（走离线预计算索引，次日 04:25 增量重建），**DB 层 FTS 已即时可搜** ✅。属既有设计，未改（要「入库即可搜」需设计变更，须先问用户）。<br>📎 **Dis-db 已回填 ✅**（2026-09-25：`crawl_danzhai_hjzf.py` / 编号 2106，详见证 09-25 那条总记录）
| 2026-09-24 | ✅ **新增站点：阜阳市颍泉区人民政府 公告公示（11 步闭环完成）** —— `https://www.yingquan.gov.cn/Content/showList/508/page_1.html`。**CMS**：showList 系（安徽站群）；`<pagination currentpage="1" pagesize="20" pagecount="12" total="224">` → 12 页/224 条/20 条每页；第 N 页=`/Content/showList/508/page_N.html`（第 1 页也是 `page_1`）。**列表**：`div.m-cglist.m-liststyle1 > ul > li` → `<li><span>2026-09-21</span><a href="/Content/show/1343340.html" title="完整标题">…</a></li>`（⚠️ **日期 span 在 a 之前**，与丹寨相反）。**详情**：meta ArticleTitle/PubDate；正文 `div#zoom`。 | ⚠️ **两个本站特有坑**：① **`page_100+` 返回 HTTP 200 的「空壳页」而不是 404** → 停止判据必须用「解析出 0 条」，靠状态码会以为还有内容；② **附件 URL 是 `/download?siteId=4&id=739198` 形态（无文件扩展名）**，且附件区 `div.m-dtdownload` 是 `#zoom` 的**兄弟节点**（在正文容器外）→ 按扩展名找必**全漏**；专门写了 `collect_extra_attachments()` 解析该容器并合并内嵌成 `<p><a>段落</a></p>`。另：侧栏其他栏目(494/563/511/512/549)的条目也是同样的 `/Content/show/ID.html` 形态 → **不能按 URL 路径过滤**，必须**限定在 `m-cglist` 容器内**解析（实测全页带日期的 span 恰好 20 = 容器内 20，无混入）。<br>**脚本** `crawl_yingquan_gggs.py`。**列表对账 20/20** ✅。`--pages=1` JSONL + audit 全绿 `n=20 empty=0 badtitle=0 atturl=0`；污染词扫描**全清**（扫一扫/顶部/打印/页脚导航/党委群体 一个都没进正文 ← `#zoom` 精确取容器）；附件 URL 全部绝对化 + 已内嵌成段（问题数 0）。`--pages=5`：**新增 95 / 跳过 1 / CUTOFF截断 0**。库内：95 条 / 2025-07-10~2026-09-21 / 空正文 0 / 重复URL 0 / 平均 1093 字 / 含`<p>` 95 / 表格 9 / 附件 64 / **FTS 同步 95/95**。config 序号 **#2107**（安徽组）。重启 MainPID 1643885 → **1646726**。<br>**FTS 验证**：DB MATCH ✅（大盘香 2 / 氨基葡萄糖盐酸盐 1 / 颍泉区公开引进 3）；详情页渲染 ✅ 无噪声；**附件真实下载实测 HTTP 200 / 15,419 字节 / octet-stream，与附件名里的【15.06KB】吻合** ✅。 | 📌 **剩余项**：按新规只抓前 5 页 → 本次覆盖 **2025-07-10 ~ 2026-09-21**，该栏目共 12 页 224 条，**还有 7 页（约 124 条，2025-07 以前）未抓** —— 若要补历史需一次性 `--pages=12`（CUTOFF 3 年会自动截断到 2023-09-25）。已问用户。<br>📎 **Dis-db 已回填 ✅**（2026-09-25：`crawl_yingquan_gggs.py` / 编号 2107，详见 09-25 那条总记录）
| 2026-09-24 | ✅ **新增站点：江城县人民政府 通知公告（11 步闭环完成）** —— `https://www.jcx.gov.cn/xwzx/tzgg.htm`。**⚠️ 查重子串陷阱**：`zjcx.gov.cn`（浙江**长兴**县）**包含** `jcx.gov.cn` 子串 → 裸 grep / `LIKE '%jcx.gov.cn%'` 全部假命中（6016 条其实是长兴）；改用带 `://` 锚点的精确匹配后确认**未覆盖**（脚本 0 / 库内 0 / config 0）。**站点身份**：`jcx` = **云南普洱·江城哈尼族彝族自治县**（不是泾川；SiteIDCode 5308260020）。**CMS**：TRS vsb 静态列表（`li#line_u9_N`，同族望谟 `line_u6_N`）。 | ⚠️ **三个本站特有坑**：① **分页是倒序静态链接** —— 页脚 `<a href="tzgg/32.htm" class="Next">下页</a>`；第 1 页=`/xwzx/tzgg.htm`，第 N 页=`tzgg/{34-N}.htm`（编号递减）；**页脚「尾页」也是 `class="Next"`** → 必须**按链接文字「下页」取**，不能按 class 取第一个；本脚本改为**跟随下页链接**（不按数字猜，符合该 CMS 既有教训）。② **附件是正文内 `../../virtual_attach_file.vsb?afc=...`**（vsb 虚拟附件，**无文件扩展名**）→ 按扩展名找会漏，须在附件判据里显式加 `virtual_attach_file`。③ **源站正文存在 `<p><ul><li>` 非法嵌套** → BeautifulSoup 解析时会把 `<ul>` 移出 `<p>`，残留空 `<p></p>` 紧贴内容 → 渲染成 `<p><p><a>` 且附件被 `<ul><li>` 包着（违反「多附件独立成段」规范）→ 先做 `<li>`→段落边界，**并在最终输出加一道兜底去嵌套/空标签**（实测库内 `非法嵌套 <p><p>` = 0、`残留 <li>` = 0）。另：链接文字可能被站点截断（带 ...）→ **优先取 `title` 属性**（实测 52 字的巡察通报取到完整版）。<br>**脚本** `crawl_jcx_tzgg.py`。**列表对账 15/15** ✅；下页链 4 跳全对 ✅。`--pages=1` JSONL + audit 全绿 `n=15 empty=0 badtitle=0 atturl=0`，污染词 **0 命中**（「已下载 次」计数器已清）。`--pages=5`：**新增 75 / 跳过 0 / CUTOFF截断 0**（5×15=75 精确吻合）。库内：75 条 / 2026-07-13~2026-09-24 / 空正文 0 / 重复URL 0 / 含`<p>` 75 / 表格 37 / 附件 9 / **FTS 同步 75/75**。config 序号 **#2108**（云南组）。重启 MainPID 1646726 → **1651886**。<br>**附件下载实测**：HTTP 200 / **104,241 字节** / `Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` / **xlsx 魔数 PK=True**（真 Excel，非错误页）。 | 📌 **剩余项**：栏目共 **33 页 492 条**，按新规只抓前 5 页 → 覆盖 2026-07-13~2026-09-24，**还有 28 页约 417 条未抓**（最早可到 2024-02，**仍在 3 年 CUTOFF 窗口内**）→ 若要补需一次性 `--pages=33`。<br>📎 **Dis-db 已回填 ✅**（2026-09-25：`crawl_jcx_tzgg.py` / 编号 2108，详见 09-25 那条总记录）
| 2026-09-24 | ✅ **修复「正文分段被拍平」+ 三个新站补跑完成** —— **用户报告**：颍泉《安徽福方高科…环境影响评价第一次公示》正文分段有问题。**根因**：该站（及很多站）**源站每个段落是一个 `<div style="text-align:justify;text-indent:2em;">`**（实测 35 个 div、0 个 `<p>`），而 `html_to_text` 把 `<div>` **全部 unwrap** → 段落边界丢失 → content 只剩裸文本行，search_app 按 HTML 渲染时 `\n` 被浏览器折叠 → **33 段糊成一坨**（实测该行 `<p>` 数 = 1）。**⚠️ 该逻辑继承自既有模板 `crawl_qdn_sphgs.py`，属模板级缺陷。**<br>**修法（3 处，写成 `fix_paragraph_div.py` 一次性施加到 3 个脚本）**：① 叶子 `<div>`（不含 div/table 子块）**改名成 `<p>`** 保住段落语义；② parts 循环里凡未被 `<p>` 包裹且非 table/ul/ol/h* 的块**一律补 `<p>`**；③ 最终兜底去嵌套/空 `<p>`。 | **验证**：修复后颍泉 32/32、丹寨 33/33、江城 29/29（段落数 == `<p>` 数）✅；库内 2 行受损（颍泉 219 行中的 2 行，`<p>` 1→33 / 1→24）已用 **UPDATE 回写**修复（备份表 `bak_para_20260924`，**未 DELETE 任何行**）✅；app 详情页实测渲染 **33 个独立段落** ✅。<br>**顺带发现一个「伪受损」**：`<p><p>` 与「长正文无 `<p>`」的 19 行全部是**纯表格正文**（`content` 直接以 `<table>` 开头）→ 无 `<p>` 属正常 ✅；另有 1 行 `<h2>` 逐行排版（源站标记习惯，行是分开的，只是渲染成大号粗体）→ 未动，待用户裁定。<br>**三站补跑**（`--pages=12` / `--pages=33`）：颍泉 **219 行**（= 栏目数 224 − 进行中重复 5）日期 2024-06-26~2026-09-21；江城 **492 行**（= 页面自称「共492条」**精确吻合**）日期 2024-01-03~2026-09-24；丹寨 32 行无需补。三站 **FTS 同步 100%** ✅、空正文 0 ✅。 | ⚠️ **全库体检（只读扫描，未改数据）**：`content` 长 >400 且**完全不含 `<p>`** 的共 **64,361 行 / 890 个站点**（占全库 578,239 行的 11.1%；含 `<p>` 的 433,871 行）。⚠️ 这是**疑似集不是确定受损集** —— 抽样可见有的行是**带 `\n\n` 的纯文本**（Markdown 渲染下能分段，未必糊），有的（如看福清）只有单 `\n`（**真会糊**）。**下一步建议**：细化判据（区分 `\n\n` 型与单 `\n` 型、排除纯表格行）后再定治理范围 —— 待用户定夺。
| 2026-09-25 | ✅ **修复「正文容器外的附件区被漏掉」（linkbox）—— 影响 85/100 行** —— **用户报告**：宾阳《南宁浮法玻璃…危险废物暂存间…受理情况公示》末尾那个含附件的小表格缺失。<br>**根因**（与颍泉 `div.m-dtdownload` 同类）：源站正文表格里那句「见附**件**」其实是**纯文本**（中间夹的是 Word 书签 `<a name="_GoBack"></a>`，不是链接）；**真附件在正文容器 `div.trs_editor_view` 之外**的下载面板：`<div class='linkbox'><a href="./P020260720396340403399.docx">…报告表（公示本）.docx</a></div>` → `extract_body` 只取正文容器 → **附件链接丢失、attachments 列全空**。<br>**修法**（`patch_linkbox.py`，照搬颍泉的 `collect_extra_attachments`，4 处锚点全中）：① 新增 `collect_extra_attachments()` 扫 `div.linkbox / div.atile / div.xxgk_fj / div.attach_list / div.fjlist`；② `parse_detail` 返回第 4 个值 `extra_atts`（在 return 前单独 parse 一次，不依赖 try 块内状态，更稳）；③ main 解包 4 值；④ **合并要在「空正文/占位」判据之前**（否则纯附件型公告会被误 skip），每条附件按规范**独立成段** `<p><a href=绝对URL>附件名</a></p>`。补丁打到 **3 个脚本**：`crawl_binyang_hjsp.py` / `crawl_dazu_glz_qtgw.py` / `crawl_danzhai_hjzf.py`（均留 `.bak_20260925_linkbox`）。<br>**存量回写**（`refresh_linkbox.py`，守三铁律：备份表 `bak_linkbox_20260925` 145 行 / 不 DELETE / UPDATE WHERE page_url）：**宾阳 85 行需更新且附件数全部变多** → 回读 **100/100 行有附件**（原仅 15 行）；大足 0 行变化；丹寨 2 行内容归一（附件行 8）。<br>**目标文章验证**：正文末尾已带 `<p><a href="…P020260720396340403399.docx">南宁浮法玻璃…报告表（公示本）.docx</a></p>` ✅；详情页渲染含附件名与链接 ✅；**附件真实下载 HTTP 200 / 1,967,312 字节 / 魔数 PK\x03\x04 / Content-Type …wordprocessingml.document**（真 Word，与文件名标注一致）✅。 | ⚠️ **教训**：**「正文里出现『见附件』字样 ≠ 附件已在正文里」** —— 必须扫正文容器**之外**的附件面板（`linkbox`/`m-dtdownload`/`atile` 等）；判据不能只看扩展名（本站附件是 `./P020*.docx`，但颍泉/江城是无扩展名的`/download?`、`virtual_attach_file.vsb`）。**新增/派生脚本后，务必抽查几条详情页的 attachments 列是否为空** —— 本站首轮 100 行里 85 行空附件，单看 `--pages=1` 的 audit 全绿**发现不了**（audit 的 `atturl` 只查「有附件但 URL 非绝对」，不查「本该有附件却没有」）。
| 2026-09-25 | ✅ **江西厅 col42221「办理状态」已新增并闭环（我前一天的判断是错的，已更正）** —— **⚠️ 教训**：前一天我只看了静态 HTML（可见文本仅 28 字、`index_1.html` 404）就下结论「不是文档列表页」→ **判错**。实际它与已有脚本覆盖的 col42169 **页面结构完全一致**（都加载 `handlebars.min.js`+`dayjs.min.js`）→ **列表由 JS 调接口渲染**，静态 HTML 里当然看不到。<br>**正确姿势**：先看同站**已有脚本**怎么调的 —— `crawl_jxsthjt_nslx.py` 里写着 `POST /queryList (form: current/unitid=380055/webSiteCode=jxssthjt/channelCode=<col>/perPage/pageSize)`；换成 `channelCode=col42221` 即通 ✅ **total=1323 条 / 15 条每页 / 89 页**，且 `results[].source.content.content` **直接带完整正文 HTML（含表格）→ 无需二次请求** ✅。<br>**脚本** `crawl_jxsthjt_blzt.py`（自 `crawl_jxsthjt_nslx.py` 派生，替换 9 处：SITE_NAME/SCRIPT_NAME/CHANNEL_CODE/Referer/docstring）。`--pages=5` → **新增 60**（含冒烟 15，共 75 条入库）/ 2026-06-18~2026-09-24 / 空正文 0 / 表格 75 / **FTS 75/75** ✅；MATCH「数字减影血管造影」「核技术利用」「拟审查」各 5 ✅。config 序号 **#2114**。重启 MainPID 1662895 → **1794724**。<br>**Dis-db**：该行（`办理状态 - 江西省生态环境厅`）**一直是空的**（3 个兄弟栏目 2049/2050/1492 都有值）→ 已回填 `crawl_jxsthjt_blzt.py` / **2114**，回读确认 ✅。<br>**顺带核查**：`/root/search.db` 与 `/mnt/data/search.db` 是**同一 inode（3145735）**，脚本 DB_PATH 写 `/mnt/data` 无害 ✅。 | ⚠️ **通用教训**：**判「不是列表页」之前，必须先看同站已有脚本的接口写法 + 找 `handlebars/dayjs/jQuery` 这类模板渲染库**；只看静态 HTML 会把 JS 渲染列表误判成「非列表页」。另：该站 **https 握手被拒（TLS alert）→ 必须 http://**（既有 3 个脚本也都用 http）。
| 2026-09-24 | ✅ **批量新增 6 站（4 脚本 / 5 栏目入库；江西厅 1 个待定性）** —— 用户给了 6 个 URL。**查重**：缙云/宾阳/大足=真未覆盖；**江西厅 col42221** 已有 3 脚本(crawl_jxsthjt_nslx/pzxmgg/npzgs 覆盖 col42169/42170/42171)+库内 657 条；**青阳**已有 `crawl_ahqy.py` 但库内 0 条。<br>**① 宾阳** `crawl_binyang_hjsp.py`（TRS createPageHTML(19,0,index,html) 19页/20条；正文 `div.trs_editor_view`；⚠️ **该站无 meta ArticleTitle** → 回退 `<title>` 会带后缀「_栏目_广西南宁宾阳县人民政府门户网站」→ 已加剥尾正则）→ **100 条** / 2025-02-13~2026-09-18 / 表格 78 / FTS 100 ✅。<br>**② 大足区古龙镇** `crawl_dazu_glz_qtgw.py`（createPage(4,0,index,html) 4页57条/15条每页；正文 `div.zwxl-content > div.trs_editor_view`，**外层含标题副本+索引号元数据须取内层**）→ **12 条**(+45 CUTOFF) / 2023-12-14~2026-09-16 / FTS 100 ✅。⚠️ 4 条短正文（「详情见附件。」/标题+附件图）**属正常源站数据**（技能已记：纯附件/短正文型勿判空）✅。<br>**③ 青阳** `crawl_ahqy_hjsp.py`（安徽 Jczwgk，pagecount=30/20条每页，第N页 `/Jczwgk/opennessList/650/102002008/page_N.html`；正文 `div.m-dttexts` —— **与颍泉同款 CMS**，直接复用颍泉模板）→ **100 条** / 2025-12-18~2026-09-21 / 表格 26 / 附件 25 / FTS 100 ✅。<br>**④ 缙云（一脚本两栏目）** `crawl_jinyun_hjgs.py --col=1229425888|1229856959`（浙江 **JPAAS API 站**：`GET /api-gateway/jpaas-publish-server/front/page/build/unit?webId=3658&tplSetId=PnEqYxUh1MkK3CjYn4xc5&tagId=列表&pageId=<col>&paramJson={pageNo,pageSize}` → JSON `data.html`；参数从页面 `<script queryData="...">` 读出）→ 受理公示 **100 条**(表格51/附件100) + 环评信息公示 **85 条**(表格34/附件49) / FTS 100 ✅。 | ⚠️ **缙云踩两个坑**：① 正文容器**不是**浦江参考里的 `div.content>div.wenz`，实测是 **`div.content`**（外层 `barrierfree_container` 含左侧导航+标题+索引号等元数据）→ 首次 20 条全被判空；已按「元数据逐行必清」规范加过滤（索引号/发布机构/发布时间/原文链接/政策解读链接/点击查看源文件）。② **双 URL 格式**（洞头同款）：新版 `/col/colNNN/art/YYYY/art_HEX.html` + 旧版 `/art/YYYY/M/D/art_<colId>_N.html` —— 单格式过滤会静默丢掉旧版条目（col-B 20 条只出 10 条），已按「COL_PATH 或 `art_<col>_`」双判据放行。<br>**方法**：两站用**生成器从已验证模板派生**（`gen_batch6.py`/`gen_jinyun.py`：按 `\n\ndef ` 切顶层函数，替换常量块 + list_page_url/parse_list/extract_body），生成后必做 **py_compile + 真实列表冒烟**（宾阳 20/20、大足 15/15、青阳 20/20 全对 ✅）。<br>**配置** 5 条已注册 **#2109–#2113**（宾阳/大足/青阳/缙云A/缙云B，缙云一脚本两栏目各占一条）；重启 MainPID 1651886 → **1662895**；三处同步 `76266434`/`607e5849`/`ab504ea5`/`ae54e138` ✅。 | ⚠️ **江西厅 col42221 待你确认**：该 URL 的页面 `<title>` 是「**办理状态**」，`index_1.html` 返回 **404**，且几乎无静态列表 → **不是文档公告列表页**（疑似办理状态查询工具）。另：**https 握手被拒**（`TLS alert, handshake failure`），而 **http:80 正常 200 / 51KB** → 该站必须用 `http://`（现有 3 个江西厅脚本也正是用 http ✅）。**请确认这个 URL 想抓什么**，或给该栏目的真实列表页。<br>📎 **Dis-db 已于 2026-09-25 07:3x 批量回填完毕 ✅**（全部 URL 精确命中、各 1 条、无歧义）：丹寨=`crawl_danzhai_hjzf.py`/#2106；颍泉=`crawl_yingquan_gggs.py`/#2107；江城=`crawl_jcx_tzgg.py`/#2108；宾阳=`crawl_binyang_hjsp.py`/#2109；大足=`crawl_dazu_glz_qtgw.py`/#2110；青阳=`crawl_ahqy_hjsp.py`/#2111；缙云A=`crawl_jinyun_hjgs.py`/#2112；缙云B=`crawl_jinyun_hjgs.py`/#2113。脚本 `backfill_dis_batch.py`（dump 全库 2193 条→URL 精确匹配→3 并发 PATCH→回读抽查）。⚠️ 两个坑：**青阳在 Dis-db 有重名双记录**（另一条已有 `crawl_ahqy.py`/#489）；**江城按域名查会混入浙江长兴 2 条**（`crawl_zjcx_hb.py`/748、`crawl_zjcx_hp.py`/1762）→ 一律按 URL 精确匹配，不可按域名。
| 2026-09-22 | ⚠️ **排查过程中的两个假信号（已识破）**：① `pgrep -f "[s]earch_app"` 报「仍在运行」→ 实为**命中了我自己那条 bash 命令的字符串**（`-f` 匹配整条命令行），`systemctl is-active` + `MainPID=0` 才作准；② `crawl_xiantao.py` 打印的「重复: 14」**不可信** —— 与库内实际状态（8 条不在库）矛盾，`total_dup` 把 IntegrityError 也算进去了 | 已识破；**教训：脚本自报的计数当证据前，必须用独立查询核对** |
| 2026-09-21 | ✅ **第 2 步「3 个超时脚本」已复核完毕**：`crawl_wtq.py` **EXIT=0 / 16s**（我的分类器 `"TIMEOUT" in output` 撞上日志字样**误报**）；`crawl_yanshi_hjbh.py` **EXIT=0 / 154s**（只是慢，在 600s 预算内）；`crawl_yeda.py` **EXIT=124 / 满 600s → 真超时**。**yeda 病因**：`range(2, total_pages+1)` 翻**全站 199 页**、每页 sleep 0.3s 再逐条抓详情 → 必然被调度器杀掉，**而「写 gov_raw」那步在爬完之后 → 永远执行不到 → 库里长期停在 2026-06-25**（它先写暂存表 `site_yeda`，之后才导入 gov_raw） | **yeda 已修**：默认页数上限 10（`fix_yeda_pagecap.py`，备份 `Archive/crawl_yeda.py.bak_20260921_pagecap`）→ 实跑 **EXIT=0 / 80 秒** ✅。⚠️ `crawl_ywtq*.py` 等同类「无上限翻全站」模式可能还有，值得扫一遍 |
| 2026-09-21 | ❗❗ **`gov_raw.script_name` 大面积空值 → 所有按脚本的排查全部失效**（起因：查大英/三水等站「缺数据」时按 script_name 统计，得出「1109 个脚本停滞」的**错误结论**，实测多为假象）。**根因两条**：① **812 个脚本手写 `INSERT INTO gov_raw (...)` 时漏了 `script_name` 字段**（另有 703 个走 `crawler_lib.push_to_searchdb` 自动带名）→ 每跑一次就产生一批空值；② `crawler_lib.py:572` 的 `caller_script = basename(sys.argv[0]); if not endswith('.py'): ''` —— runpy/exec 场景下 argv[0] 不是脚本名就落空串。**规模**：93,075 条（占全库 16.3%）。**危害实例**：宁城县通知公告最新数据（2026-09-17）全在空名下，按脚本查只见 2026-07-20，遂误判为「停滞 60 天」 | **已治理（93,075 → 1,727，98.1%）**：新建 `backfill_script_name.py`（**已挂 cron `30 9 * * *`**，日跑约 08:30 结束后收尾）。三级口径，**只取唯一映射，歧义一律弃权**（宁可空着不填错）：① 库内同 `site_name` 的非空记录（唯一 / 或占比≥90% 且样本≥10 的压倒性多数）② 脚本源码的 `SITE_NAME` 常量 + 多站点字典里的 `"site"` 值（**必须 `ast.literal_eval` 解转义**，很多脚本写 `\uXXXX`）③ 源码常量行里的域名。**备份表 `bak_scriptname_20260921`**（93,075 行 id，回滚 = 把这些 id 的 script_name 置回空串）。`crawler_lib.py` 已补 `__main__.__file__` 兜底 |
| 2026-09-21 | **治理过程中自己踩的三个坑（已修，留作教训）**：① janitor 里在**同一游标的 SELECT 迭代中执行 UPDATE+commit** → 游标重置，只填 500 条就静默停（改为先 `list(...)` 物化）；② 用 `SITE_NAME\s*=\s*['"]([^'"]+)['"]` 直接抓原始文本 → **脚本里写 `\uXXXX` 转义的中文永远比不中**（如 `crawl_xw_shx.py` 声明 `SITE_NAME="\u5ba3\u5a01..."` 实为「宣威市-公示公告」），改用 `ast.literal_eval`；③ janitor **扫 `*.py` 时把自己也扫了**，而自己源码注释里举例写了「长兴县-公告公示」→ 自己被当成第二个写该站点的脚本 → 撞成歧义 → 4,460 条永远弃权（加 `if name == _SELF: continue`）。④「库内同域名非空记录」这个中间口径实测**收益 0 条**却要全表 `GROUP BY page_url`，白跑 7 分钟 → 已删，janitor 从 425s 降到 ~10s | 全部已修并验证；剩余 1,727 条（约 25 个站点）为**真歧义**（同域多脚本、或多个脚本声明同一 SITE_NAME），需逐个裁定，最大两处：济南市生态环境局 949（4 个脚本同名）、广元市昭化区 150+42（脚本已不在库中） |
| 2026-09-23 | ⚠️ **Dis-db 统计快照与 `backfill_script_name.py` 时序倒挂**（本日 Notion Dis-db 更新任务交叉核对时发现）：`crawl_scheduler_v2.py` 在日跑收尾 **07:24:57** 生成 `/tmp/disdb_stats.json`，而 `backfill_script_name.py` 的 cron 是 **09:30** —— 当日回填出来的 `script_name` 归属**赶不上当天快照**。**实测差额**：快照自报 `total=564523`（与文件内 `by_script` 汇总一致 → **文件本身没问题**），同一时刻 DB 实况 `counted=567921`，差 **+3,398（+0.6%）**，其中近 7 日 **+836**。**排除法定位**：① 非新入库 —— 全表最大 `id` 的 `inserted_at` 仍是 `07:23:56`（07:24 后无插入）；② 非快照缺陷（自报数与文件一致）。**根因（回填日志实证）**：`[09:30:01] 发现 4192 条空 script_name → 回填 ①site 1401 ①'major 27 ②const 1978 ③src 15 → 仍无法归属 771`，即回填 **3,421** 条，其中 3,398 条 `publish_date` 有效 → 正好等于差额；`/mnt/data/search.db` 的 mtime `09:47` 亦为这次 UPDATE 所留（`/root/search.db` 是它的软链） | 影响面小且**自愈**（这些记录次日快照即纳入，仅滞后 1 天）。若要消除：把 `backfill_script_name.py` 提到日跑内部、**快照之前**执行，或把快照挪到 09:45 之后。**本次 5 列仍按既定口径（日跑后快照）写入，未擅自改流程**。附：残留 **771 条**仍为空 `script_name`（无唯一映射可归，最大一处「广元市昭化区人民政府-环境保护」150 条）→ 这些记录**永远不会**进入 Dis-db 统计 |
| 2026-09-24 | ⚠️ **Dis-db 5 列更新：1800 行已填，但 34 行「有旧值却永不更新」**（本日更新任务做全量核对时发现，与 09-23 那条同源）：全库 2189 行，`disdb_update_stats.py` matched=**1800** / no_match=**389**；389 里 **34 行虽 no_match 却仍带数值**（是历史轮次填的，本轮未碰）→ 分两类：① **10 行的 script_name 在当日 stats 里根本不存在** —— `crawl_linzi.py`×2 / `crawl_gzhgyjy.py` / `crawl_smx_yima_local.py`×2 / `crawl_njna_xxgk_local.py` / `crawl_fengkai_local.py` / `crawl_yanshou100_local.py` / `crawl_longtan_zcfg.py` / `crawl_ccdri.py`。其中 **5 个是本地降级站 `*_local.py`**（服务器 stats 的 by_script 用非 `_local` 键名，两边键名对不上 → 永久 no_match；同站的服务器版脚本如 `crawl_njna_xxgk.py` 其实在 stats 里，其 3 个栏目页也都在 by_site 里）。② **24 行是 script 命中但对应多个栏目、site_name 又与 by_site 对不上**（如 Dis-db「竣工环保验收公示-环保公示-黄冈市华清生态环境咨询有限公司」vs stats「黄冈华清-竣工环保验收公示」；`crawl_smx_district.py` 7 行全名「区县政务-三门峡市生态环境局」、`crawl_ningguo_tzgg.py` 3 行）→ 属「宁缺毋滥」策略的**预期结果，不是 bug**。 | 本轮**未改策略、未动这 34 行**（避免不可逆误填）。若要补齐，两个低风险方向：① 归一化去掉 `_local` 再查 by_script；② script→多栏目时，用 site_name 与 by_site 条目的中文关键词（「竣工环保验收」「环境影响评价」「环境保护」等）细分。**建议先由用户裁定站点命名口径**再动手。<br>另（性能）：本轮 1800 条实跑 **~72 分钟**（约 2.4~2.5s/条），远高于脚本内 `sleep 0.35` 暗示的 25~40 分钟 —— 瓶颈是每条 PATCH 的往返延迟，非重试/报错（errors=0）。后续若纳入常规定时任务，需按 **~75 分钟**预留窗口 |
