# 政务网站采集系统管理员操作手册

本文面向系统管理员，说明如何配置、管理和排查采集站点、采集对象、调度周期、请求间隔、附件下载和运行任务。

## 1. 基本概念

系统里的采集配置分为三层：

| 名称 | 表 / 文件 | 说明 |
|---|---|---|
| 站点 | `crawl_site` / `config/sites_v2/*.yaml` 的 `site` | 一个政府网站或 CMS 入口，例如 `gd_wjk`、`gd_amr` |
| 部门 | `site_department` / YAML 的 `depts` | 适用于有部门路径的 CMS，例如政务公开目录中的具体部门 |
| 采集对象 | `crawl_target` / YAML 的 `columns` | 实际被调度和执行的栏目，例如“政策文件”“通知公告” |

实际采集任务以 `crawl_target.target_code` 为最小执行单元。后台点击运行、定时调度、任务队列、抓取日志和附件统计都围绕采集对象展开。

## 2. 配置来源和生效规则

系统支持两类配置来源：

| 来源 | 使用场景 | 是否会被 YAML 同步覆盖 |
|---|---|---|
| YAML 托管 | 批量、长期、可审计的站点配置 | 是，YAML 是源配置 |
| 后台 UI 托管 | 临时新增、测试、少量手工维护 | 不受 `sync-yaml --dir` 管辖 |

长期稳定配置应优先写入 `config/sites_v2/*.yaml`，提交 git 后部署。后台 UI 修改适合临时调整，但如果该对象来自 YAML，同步配置时可能被 YAML 覆盖。

生产发布约束：

1. 不直接修改生产容器内文件。
2. 先在 git 中提交并推送。
3. 生产服务器或 Jenkins 拉取代码。
4. 如涉及代码变更，重新构建并重启容器；仅 YAML 或文档变更通常不需要重新构建。
5. 如涉及运行中数据库配置，确认是否需要执行 `sync-yaml` 或直接做受控 SQL 调整。

## 3. 站点配置

YAML 站点配置位于：

```text
config/sites_v2/<site_code>.yaml
```

常用字段：

| 字段 | 说明 | 注意事项 |
|---|---|---|
| `site.code` | 站点编码，对应 `crawl_site.site_code` | 必须唯一，建议使用稳定英文缩写 |
| `site.name` | 站点中文名 | 后台展示使用 |
| `site.base_url` | 站点基础地址 | 影响列表、详情和 host 队列归属 |
| `site.role` | 站点角色 | 常见值：`qingyuan_local`、`county_local`、`province_ref`、`nation_ref` |
| `site.cms_adapter` | CMS 适配器 | 如 `gkmlpt`、`gov_cn_policy`；与 `yaml_path` 二选一 |
| `site.default_fetch_strategy` | 默认抓取方式 | 通常为 `httpx`；特殊站点才用浏览器策略 |
| `site.respect_robots` | 是否遵守 robots.txt | 默认 `true`，除非明确确认可关闭 |
| `site.enabled` | 是否启用站点 | 站点禁用后，其下目标不应继续执行 |
| `site.schedule_cron` | 站点默认定时 | 当目标和 YAML column 未配置 cron 时作为兜底 |

注意事项：

- `cms_adapter` 和 `yaml_path` 必须二选一，不能同时有，也不能都没有。
- 同一个政府主站下多个采集对象通常共享 host 队列，调速时要考虑整体请求压力。
- 禁用站点比禁用单个采集对象影响更大，操作前应确认没有其他目标仍需运行。

## 4. 部门配置

有部门路径的站点使用 YAML 的 `depts`：

```yaml
depts:
  - dept_path: gdhrss
    dept_binding: mapped
    local_dept_id: 123
    dept_display_name: 人力资源和社会保障厅
    enabled: true
    columns:
      - column_id: zcwj
        name: 政策文件
```

常用字段：

| 字段 | 说明 |
|---|---|
| `dept_path` | CMS 或 URL 中的部门路径 |
| `dept_binding` | 与本地 OA 部门的绑定关系 |
| `local_dept_id` | 本地部门 ID，仅 `dept_binding: mapped` 时必填 |
| `dept_display_name` | 后台展示名 |
| `enabled` | 是否启用该部门 |

`dept_binding` 可用值：

| 值 | 含义 |
|---|---|
| `pending` | 待确认绑定 |
| `mapped` | 已绑定到本地 OA 部门，必须填写 `local_dept_id` |
| `city_level` | 市级整体部门，不绑定单个 OA 部门 |
| `cross_dept` | 跨部门栏目 |
| `external_ref` | 外部引用或非本单位部门 |

注意事项：

- `mapped` 必须有 `local_dept_id`。
- 非 `mapped` 不能填写 `local_dept_id`。
- 同步 YAML 前，如果 `local_dept_id` 不存在于 `local_department`，同步会失败；需要先同步 OA 部门数据。

## 5. 采集对象配置

采集对象是实际运行单位，核心字段在 `crawl_target` 和 YAML 的 `columns` 中。

常用字段：

| 字段 | 说明 | 建议 |
|---|---|---|
| `target_code` | 采集对象编码 | 通常为 `<site_code>__<column_id>` 或 `<site_code>__<dept_path>__<column_id>` |
| `target_name` / `name` | 中文名 | 后台展示和人工识别使用 |
| `entry_url` | 列表入口地址 | 必须能打开并稳定返回列表 |
| `sample_article_url` | 样例文章 | 用于测试详情解析 |
| `channel_name` | 栏目名 | 写入文章元数据 |
| `channel_path` | 栏目路径 | 用于分类和检索 |
| `content_category` | 内容大类 | 例如政策文件、通知公告 |
| `content_subcategory` | 内容子类 | 按业务需要填写 |
| `schedule_cron` | 定时规则 | 目标级最高优先级 |
| `expected_cadence_days` | 预期更新周期 | 用于监控长时间未更新 |
| `interval_sec` | 基础请求间隔 | WAF 敏感站点建议调大 |
| `interval_jitter_sec` | 正向随机抖动 | 实际等待为 `interval_sec + 0..interval_jitter_sec` |
| `track_checkpoint` | 是否记录页码 checkpoint | 大批量、易中断目标建议开启 |
| `enabled` | 是否启用 | 禁用后手工和定时任务都应跳过 |

注意事项：

- 新增目标后，先用少量 `max_items` 试跑，再放开全量。
- `entry_url` 不要填写详情页，应填写列表或栏目入口。
- `sample_article_url` 应选择结构典型、包含正文和附件的文章。
- `target_code` 一旦被文章、附件、日志引用，不建议随意改名。

## 6. 调度时间配置

调度使用 5 段 cron 表达式：

```text
分 时 日 月 周
```

示例：

| 表达式 | 含义 |
|---|---|
| `0 2 * * *` | 每天 02:00 执行 |
| `30 1 * * *` | 每天 01:30 执行 |
| `*/30 9-17 * * 1-5` | 工作日 09:00-17:59 每 30 分钟执行 |

调度优先级：

1. `crawl_target.schedule_cron`
2. YAML column 的 `schedule_cron`
3. `crawl_site.schedule_cron`
4. 系统默认调度规则

注意事项：

- 同一 host 下大量目标不要配置到同一分钟启动。
- 大型目标建议避开白天业务高峰。
- 修改 `enabled`、`schedule_cron` 后，scheduler 会周期性 reconcile；通常无需重启容器。
- 如果需要立即确认调度结果，可查看后台“任务队列”和“抓取日志”。

## 7. 请求间隔配置

请求间隔用于控制访问频率，避免被目标站点限流或封禁。

优先级：

1. `crawl_target.interval_sec`、`crawl_target.interval_jitter_sec`
2. YAML column 的 `interval_sec`、`interval_jitter_sec` 同步到数据库后的值
3. 适配器 `DEFAULT_INTERVAL_SEC`
4. `HostThrottle` 默认值

实际运行时，系统先读取数据库中的 `crawl_target.interval_sec`。如果为空，并且站点配置了 `cms_adapter`，才读取适配器默认值。最后才使用 `HostThrottle` 兜底默认值。

广东省级站点动态调速：

- 对 `gd*` 开头的采集目标，系统会在工作日工作时间临时放慢运行时请求间隔。
- 默认工作时间为周一至周五 `08:00-18:00`，默认倍率为 `1.2`。
- 例如数据库配置 `interval_sec=70`、`interval_jitter_sec=20`，工作时间实际运行约为 `84 + 0..24` 秒。
- 周末和非工作时间恢复数据库/YAML 中配置的正常间隔。
- 该策略只影响当前运行时节流，不直接改写数据库或 YAML。
- 可通过环境变量调整：`GD_WORKTIME_INTERVAL_MULTIPLIER`、`GD_WORKTIME_START_HOUR`、`GD_WORKTIME_END_HOUR`。

`interval_jitter_sec` 是正向随机抖动。例如：

```text
interval_sec = 40
interval_jitter_sec = 20
实际等待 = 40 到 60 秒之间
```

调整建议：

| 场景 | 建议 |
|---|---|
| 普通站点 | `interval_sec` 10-30 秒 |
| WAF 敏感站点 | 40-120 秒 |
| 同 host 目标很多 | 分散 cron，并适当增加 interval |
| 临时频繁失败 | 先提高 DB 中 `interval_sec`，观察日志 |
| 长期调整 | 同步修改 YAML 并提交 git |

批量调整时，建议只更新旧值匹配的记录。例如只把 `80` 改成 `40`，不要无条件覆盖所有目标，避免破坏已经单独调过速的对象。

## 8. 附件下载配置和命名

附件下载依赖详情页解析出的附件链接。

文件名来源优先级：

1. 正文附件链接附近的中文文本。
2. 详情页解析到的附件标题。
3. 单附件文章可回退到文章标题。
4. 以上都没有时，使用 URL 或响应头中的文件名。

附件下载节流：

- 附件下载会参考当前采集对象的 `interval_sec` 和 `interval_jitter_sec`。
- 实际等待会被系统参数 `attachment_throttle_cap_s` 限制，避免附件阶段等待过久。

注意事项：

- 如果页面中附件链接文本是“附件1”“下载”等泛化名称，系统可能需要从附近文本推断文件名。
- 已下载附件如果文件名不正确，需要同时处理文件存储记录和数据库附件字段。
- 修复历史附件前，应先按站点和采集对象统计可纠正数量，优先处理数量大的类型。

## 9. 启停和运行任务

管理员常用操作：

| 操作 | 影响 |
|---|---|
| 禁用站点 | 该站点下所有目标跳过执行 |
| 禁用采集对象 | 仅该目标跳过执行 |
| 手工运行目标 | 立即提交一个采集任务 |
| 批量运行目标 | 多个目标进入队列，按 host 串行执行 |
| 取消任务 | 标记取消；同步执行中的抓取会在当前阶段结束后收敛 |

注意事项：

- 同一 host 队列内任务会串行执行，避免同站点并发过高。
- 手工运行也会遵守站点和目标的 `enabled` 状态。
- 对大目标执行全量前，建议先限制条数试跑。
- 如果任务显示反复失败，先看抓取日志，不要连续重复运行。

命令行试跑示例：

```bash
python -m govcrawler crawl <target_code> --max-items 3
```

同步 YAML 到数据库：

```bash
# 预检查，不提交数据库变更
python -m govcrawler sync-yaml --dir config/sites_v2 --dry-run

# 正式同步
python -m govcrawler sync-yaml --dir config/sites_v2

# 只同步单个站点配置
python -m govcrawler sync-yaml --file config/sites_v2/<site_code>.yaml
```

## 10. 日志和排查

优先查看：

| 位置 | 用途 |
|---|---|
| 后台“抓取日志” | 单篇文章或请求级错误 |
| 后台“任务队列” | 任务状态、重试、取消、当前页码 |
| 后台“告警中心” | 长时间失败、积压等问题 |
| 容器日志 | API、scheduler 进程异常 |
| 诊断压缩包 | 隔离网络下交付给开发分析 |

常见问题：

| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| `ConnectionResetError`、超时增多 | WAF 或访问频率过高 | 调高 `interval_sec`，分散 cron |
| `host_cooldown: ... WAF protection` | 同一 host 连续网络错误，触发 WAF 保护 | 系统会中断本次尝试、保存 checkpoint、暂停同一 rate queue 至少 10 分钟，并自动排队续跑；`gd*` 目标还会自动把 DB 间隔上调 10% |
| 任务卡在某页 | 列表页翻页慢或目标站异常 | 开启 `track_checkpoint`，下次从 checkpoint 后继续；cooldown 中断会自动续跑 |
| 附件名是 `附件1`、URL 文件名 | 页面链接文本不完整 | 检查附件链接附近中文文本解析 |
| 目标不执行 | 站点或目标被禁用、cron 未生效 | 检查 `enabled` 和 `schedule_cron` |
| YAML 同步失败 | 字段不合法或部门绑定错误 | 先 `--dry-run`，按错误修正 |
| 后台看到旧配置 | 数据库未同步或生产未 pull 最新代码 | 确认 git commit、生产 pull、必要时执行 sync |

## 11. 变更流程建议

小范围临时调整：

1. 在后台或数据库中修改目标字段。
2. 观察任务队列和抓取日志。
3. 确认有效后，把长期配置补到 YAML。
4. 提交 git 并走生产更新流程。

新增或批量修改采集对象：

1. 新建或修改 `config/sites_v2/*.yaml`。
2. 本地或测试环境执行 `sync-yaml --dry-run`。
3. 少量目标试跑，确认列表、详情、附件解析正常。
4. 提交 git。
5. 生产拉取代码。
6. 生产执行 `sync-yaml`。
7. 在后台确认目标数量、调度时间、启用状态。

紧急止血：

1. 禁用异常目标或站点。
2. 调高 `interval_sec`。
3. 如果日志出现 `host_cooldown` 或 WAF protection，不要连续手工运行同一 host 的目标；系统会自动暂停并续跑。
4. 收集抓取日志和诊断包。
5. 修复后再恢复调度。

自动退避：

- `gd*` 目标触发 `host_cooldown` 后，系统会将该目标的 `crawl_target.interval_sec` 自动增加 10%。
- 如果再次触发，会在上次基础上继续增加 10%，直到 `COOLDOWN_INTERVAL_MAX_SEC` 上限。
- 抓取任务遇到 `host_cooldown` 会返回 `status=aborted`、`reason=cooldown`，并把当前任务标记为一次失败尝试；队列随后按 checkpoint 自动创建 `source=retry` 的续跑任务。
- 自动续跑默认最多连续 12 次，可通过 `TASK_QUEUE_COOLDOWN_AUTO_RETRY_MAX` 调整。达到上限后需要管理员介入检查目标站状态或临时禁用目标。
- 该调整是数据库运行态调整，YAML 仍是人工维护的基线；长期确认后应回写 YAML。

## 12. 管理员注意事项

- 不要直接修改生产容器内文件；容器重建后会丢失且无法审计。
- 长期配置必须进入 git，避免被部署或同步覆盖。
- 修改 YAML 后不等于数据库立即变化，必须确认是否执行了 `sync-yaml`。
- 修改数据库后不等于 YAML 已更新，后续同步可能覆盖数据库。
- 大批量 SQL 更新必须带明确条件，优先按 `site_code`、`target_code`、旧值过滤。
- 对同一 host 下大量目标，调度时间和请求间隔要一起考虑。
- 对 WAF 敏感站点，宁可慢一点稳定采，也不要频繁重试。
- 新目标上线前至少验证列表解析、详情正文、发布时间、附件链接和附件文件名。
- 已有文章和附件的历史数据修复，应先统计影响范围，再分批执行。
