China Statistical Yearbook Crawler
[!WARNING]
⚠️ 使用前提:必须使用支持「多模态」的大模型
本技能的核心步骤是对国家统计局年鉴的 JPG 图片进行视觉 OCR 识别。在开始任何下载或识别之前,请务必确认当前正在使用支持「多模态」的大模型,例如:
- Hy3
- MinMax
- Kimi
- GLM-5V
若当前为纯文本大模型,Read 工具将无法读取图片内容,本技能将无法有效工作——继续运行只会下载一堆图,却识别不出任何数据。
如果你(AI)不确定当前模型是否支持多模态,请执行 Step 0 自检(10 秒即可)。 不要盲目进入 Step 1,否则可能下载完一整章(数张至二十余张)图片才发现完全无法 OCR。
Step 0: 多模态自检(开工前必做,约 10 秒)
目的:在下载任何年鉴数据之前,自动判定当前模型是否支持多模态图像识别。非多模态环境下继续只会浪费网络和时间。
做法:
- 用
Read工具读取本技能自带的自检图assets/multimodal_selfcheck.png(120x40 像素,红/绿/蓝三色块,188 字节)。 - 根据返回值判定:
| 返回特征 | 判定 | 下一步 |
|----------|------|--------|
| 看到红/绿/蓝三色块的视觉内容(任意形式描述,如"三色块""RGB""色带"等) | ✅ 多模态 OK | 输出 [OK] 多模态自检通过,进入 Step 1 |
| 返回 [Image from ...] 标记 + 图像描述 | ✅ 多模态 OK | 同上 |
| 出现 synthetic_tool_result: true / synthetic placeholder | ⚠️ 重试一次 | 重试成功 → OK;仍失败 → 不可用 |
| 返回 Content filtered / model does not support images / 系统提示当前模型不支持图像 | ❌ 非多模态 | 立即停止,向用户明示:「⚠️ 当前模型不支持图像识别,本技能无法继续。请切换到支持「多模态」的大模型(如 Hy3、MinMax、Kimi、GLM-5V 等)后重试。」不要进入 Step 1 |
| Read 工具调用直接报错/异常 | ❌ 不可用 | 同上处理 |
- 自检失败时必须立即停止,不要继续 Step 1 的下载——否则会下载一堆无法识别的图,浪费时间和磁盘空间。
Parameters
| Parameter | Example | Description |
|-----------|---------|-------------|
| year | 2023 | 版本年份(非数据年份)。年鉴印制年份 = 数据年份 + 1。用户说"2022 年数据"→ 填 2023;若误填 2022 会拿到 2021 年数据。开工前必须先完成此换算。 |
| chapter | 21 | Chapter number; 21=教育, 11=工业, etc. Available chapters vary by edition — probe zk{chapter:02d}.htm to confirm |
| out_dir | ./yearbook_data | Output directory for images + Excel |
Step 1: Batch Download
前提:Step 0 多模态自检必须通过。
python scripts/download_yearbook_chapter.py --year {year} --chapter {chapter} --out {out_dir}
The script probes C{chapter:02d}-01.jpg through C{chapter:02d}-NN.jpg until first 404, downloads each image plus the indicator definition zb{chapter:02d}.pdf, and writes manifest.json.
URL pattern (note: NO zk/ prefix; the directory is /html/):
https://www.stats.gov.cn/sj/ndsj/{year}/html/C{chapter:02d}-{num:02d}.jpg
https://www.stats.gov.cn/sj/ndsj/{year}/html/zb{chapter:02d}.pdf
https://www.stats.gov.cn/sj/ndsj/{year}/html/zk{chapter:02d}.htm (TOC only — `zk` is a filename, not a directory)
Background on the path: see references/url_verification_log.md.
Step 2: Recognize via Multimodal LLM (Read Tool)
前提:Step 0 多模态自检已通过。
Do NOT use traditional OCR (Tesseract / PaddleOCR / EasyOCR) — all fail in sandbox:
- Tesseract: binary not on PATH
- PaddleOCR: writes to
~/.paddlex/temp→ SANDBOX PERMISSION DENIED - EasyOCR: model download times out (
WinError 10060)
下载的
zb{chapter:02d}.pdf是该章的指标定义文件(可选)。若某个表格的列名含义不明确,可用Read工具读取该 PDF 获得指标解释。一般情况下直接看图即可,PDF 仅在需要时才读。
For each downloaded JPG, call Read(file_path="{out_dir}/C{chapter:02d}-{NN:02d}.jpg"). The Read tool's multimodal capability returns the table as a natural-language description. From each description, independently extract four elements:
- title — 表格顶部标题行(如 "21-1 各级各类学校和教职工情况(2022年)")
- unit — 单位行(如 "单位:所、人")——注意有些表的单位是混合的("万人、‰、%")
- header — 列标题。单行表头用
["列1","列2",...];多级表头用[["组1","组1",...], ["子1","子2",...]] - data — 数据行。每行一个 list;空单元格用
None,数字保持 Pythonint/float
每张图的描述格式略有不同(甚至同一章内),必须逐张独立解析,不能假设任何固定结构。
关键纪律(区分两类失败):
- 硬失败:
Read返回Content filtered/model does not support images→ 当前模型已不是多模态,立即停止识别,重新提示用户切换模型。 - 软失败:
Read返回synthetic placeholder→ 单次异常,重试一次,第二次通常正常;仍失败则 fallback 到tencentcloud-ocrskill。 - 无论哪种失败,严禁凭记忆或常识"编造"表格内容——这会产生错误数据并写入 Excel。
识别规则(空值/零值、二级表头扁平化、抽样表标记、跨省排序等)→ 见 references/reference.md § Recognition Tips。
Step 3: Generate Excel
⚠️ 表头来自 OCR,不存在「标准模板」。不同章的表格数量、列名、单位、表头层级完全不同——第21章教育23表、第2章人口15表、第11章工业可能有不同数量且完全不同结构的表。title、unit、header、data 全部来自 Step 2 逐张图的 Read 结果,不存在跨章复用的固定表头库。以下只展示通用的
write_table调用模式。
代码架构
import sys
from pathlib import Path
SCRIPTS = Path.home() / ".workbuddy" / "skills" / "china-statistical-yearbook-crawler" / "scripts"
sys.path.insert(0, str(SCRIPTS))
from excel_writer_template import write_table, generate_workbook
from openpyxl import Workbook
两种生成模式
模式 A — generate_workbook:所有表共享 total_label
当整章没有跨省表或所有表全国行写法一致时,用 spec 列表批量生成。每个 spec 可单独覆写 total_label、source、freeze_panes:
specs = [
{"sheet": "表1", "title": "...(来自OCR)", "unit": "...(来自OCR)",
"header": [...(来自OCR的列名列表)], "data": [...(来自OCR的数据)],
"total_label": None},
{"sheet": "表2", "title": "...(来自OCR)", "unit": "...(来自OCR)",
"header": [...(来自OCR)], "data": [...(来自OCR)],
"total_label": "全 国"}, # 仅当该表的 OCR 结果显示第一列为"全 国"(带空格)
]
generate_workbook("all_tables.xlsx", specs)
模式 B — 独立脚本:需要 per-table 定制
当各表差异大(典型的混合章场景,如 snapshot + time series + cross-province 混用),写独立 build_*.py:
wb = Workbook()
# 表1:无全国行(snapshot / time series 表)
ws1 = wb.active
write_table(ws1, title="...(来自OCR)", unit="...(来自OCR)",
header=[...(来自OCR)], data=[...(来自OCR)], total_label=None)
# 表N:有全国行(cross-province 表)
# total_label 必须与 OCR 结果中第一列的实际文字完全一致(包括空格)
ws2 = wb.create_sheet("...(来自OCR的标题)")
write_table(ws2, title="...(来自OCR)", unit="...(来自OCR)",
header=[...(来自OCR)], data=[...(来自OCR)],
total_label="全 国")
wb.save("all_tables.xlsx")
关键约定
total_label空格陷阱:write_table默认total_label="全国"(无空格),但年鉴跨省表第一列常印作全 国(中间空格)。两者不匹配则全国行不会浅黄高亮。先Read一张跨省表确认第一列的实际文字,再决定传"全 国"还是"全国"。 非跨省表通常没有全国行,直接传total_label=None。- 数字保持 Python
int/float:禁止"10,145,421"格式——单位放表头/注释。 - 多级表头:
header=[["组1","组1",...], ["子1","子2",...]],同行相同值自动合并。仅当 OCR 结果显示多级表头时才用此格式。完整布局规范 →references/reference.md§ Excel Layout。 - 完整参考实现:
references/worked_example_c21_2022.py展示第21章(教育)23 表的完整写法——但 title/header/unit/data 必须替换为你目标章的 OCR 结果,仅借用脚本结构。
验证(不可跳过)
# 1. 内容抽查 — 注意表头是否错乱、关键数字是否合理
python -m markitdown all_tables.xlsx
# 2. 行列完整性 — 以下 Python snippet 确认每个 sheet 非空
python -c "
from openpyxl import load_workbook
wb = load_workbook('all_tables.xlsx')
for ws in wb.worksheets:
print(f'{ws.title:40s} {ws.max_row} rows x {ws.max_column} cols')
"
Technical Reference
references/reference.md— URL encoding (GBK), network quirks, edition-year vs data-year mapping, table-number gaps, Excel layout spec, and worked table indexes for chapter 21 (Education) and chapter 2 (Population).references/worked_example_c21_2022.py— 第21章(教育)专用参考示例,展示write_table调用模式。跨章使用时需将 title/header/unit/data 全部替换为目标章的 OCR 结果,仅复用脚本结构。references/url_verification_log.md— URL path correction log (the directory is/html/, not/zk/html/).assets/multimodal_selfcheck.png— Self-check test image (120x40 RGB color blocks) used by Step 0.
Scan to join WeChat group