中国统计年鉴 → Excel(抓取 + 视觉识别)
用途
把《中国统计年鉴》在线版里任意年份、任意章节的表格批量下载并识别成结构化 Excel。
支持单年快取(如"2025年鉴教育前5表")和跨年对比(如"2020–2025 各年教育前5表")。
何时使用
- 用户提到"国家统计局""统计年鉴""年鉴网站",要求下载某年、某领域的表格。
- 用户要求"图片表格转 Excel""原始数据(图片)+ Excel"。
- 用户明确说"不要用浏览器技能"——本技能本来就是纯 HTTP + 读图,天然满足。
三条铁律(先读这里,能省掉 90% 的返工)
① 目录即真相 —— 绝不拼文件名、绝不硬编码章节号
历年文件命名格式反复变更,任何拼接规则都会在某个年份翻车:
| 年份 | 目录路径 | 表格文件名 | 形态 |
|---|---|---|---|
| 2021–2025 | sj/ndsj/{y}/ |
html/C21-01.jpg |
JPG 图片 |
| 2019–2020 | sj/ndsj/{y}/ |
html/C2101.jpg |
JPG 图片 |
| 2018 | sj/ndsj/{y}/ |
html/CH2101.jpg |
JPG 图片 |
| 2016 | sj/ndsj/{y}/ |
html/2101CH.jpg |
JPG 图片 |
| 2015 / 2017 | sj/ndsj/{y}/ |
html/CH2101.jpg |
JPG 图片 |
| 2013–2014 | sj/ndsj/{y}/ |
html/Z2002C.htm |
HTML 表格 |
所以:一律从 left.htm 解析真实 href,再相对基准 URL 解析。
章节号也逐年变(教育 2013 年是第 20 章,2014 年起是第 21 章),必须靠章节标题关键词定位。
② 图片分辨率极低,必须切分放大后才能识别
原始图片只有约 480×1150 px,整张直接读必然读错数字。
必须切成条带、放大 3 倍,再逐片读;易错数字再单独做 5–10 倍超清复核。
③ 识别完必须做表内加总校验
分项之和应当等于合计行。这是唯一能自动发现识别错误的手段。
加总全通过,才敢交付。 详见 references/ocr-tips.md、references/check-cookbook.md。
需求口径与验收清单
用户的固定要求(本技能的既定口径)
- 先下载原始数据(图片),再做识别 —— 原始图必须一并交付,不能只给 Excel。
- 用大模型自身的视觉识别能力还原表格,不接第三方 OCR 服务。
- 不使用"浏览器自动化技能" —— 全程 HTTP 抓取 + 读图。
- 交付 Excel,并保留原表体例(用户对排版美观度要求高,格式细节不能省)。
交付前逐条核对
- [ ] 原始图片已下载,放在
原始数据图片/,按表号命名 - [ ] Excel 一个 sheet 一张表,sheet 名 = 表号(
21-1、21-2…) - [ ] 保留了:表标题行、单位行(如有)、层级缩进、加粗汇总行
- [ ] 注释照抄原表;原表没有注释就写「注:原表无附注。」,绝不自己编造
- [ ] 空白单元格 = 空(
null),不是 0——空与零是两种含义 - [ ]
verify全部通过,含"不计校数"按列拆分的两条(见check-cookbook.md模式 C) - [ ] 关键数字已做
zoom超清复核(末位 0/1/7、易混字形 6-8 / 3-5 / 1-7) - [ ] 已向用户说明数据时点:年鉴年份 = 数据年份 + 1
标准作业流程(SOP)
| 步 | 动作 | 命令 | 完成标志 |
|---|---|---|---|
| 0 | 确认年份口径 | — | 明确是"哪年年鉴",并向用户复述数据时点 |
| 1 | 探目录、找章节、列清单 | toc --year Y --find 关键词 |
输出表号+表名+真实 URL 清单 |
| 2 | 下载原始图片 | fetch --year Y --chapter C --tables 1-N --out 原始数据图片 |
每张图有字节数、无 403 |
| 3 | 切分放大 | prep --src 原始数据图片 --out crops |
输出"共 N 片" |
| 4 | 逐片视觉识别 | 逐张 Read crops/*.png |
可并行读取;保留缩进层级 |
| 5 | 落盘 data.json |
参照 templates/data.example.json |
字段齐全,空白填 null |
| 6 | 加总校验 | verify --data data.json |
「全部通过 ✓」;差分则按决策树排查 |
| 7 | 超清复核存疑数字 | zoom 或用 templates/zoom_helper.py 批量 |
易错位逐一确认 |
| 8 | 生成 Excel 并交付 | build --data data.json --out xxx.xlsx |
按上方验收清单核对 |
> 第 6 步是硬门槛:不通过不许交付。
> 但要注意verify 报错时先分辨是"识别错"还是"我的 check 写错"——
> 见 references/check-cookbook.md 的排错决策树。
收尾动作:把本次的 data.json 复制进 examples/ 作为新的回归基线(顺手提升下次的覆盖度)。
变体:跨年 / 跨章节批量
单年流程照搬即可,多年时按"先各自跑通,再合并"来做,不要一上来就写大文件:
# 各年分别下载、分别 prep、分别识别成各自的 data.json(每年一个,互不干扰)
for y in 2020 2021 2022 2023 2024 2025; do
"$PY" "$YB" fetch --year $y --chapter 教育 --tables 1-3 --out "原始数据图片/$y"
done
- 每年单独
verify通过后,再把各年的sheets拼进一个"跨年对比" data.json。 - 合并时注意
id不能重名(21-1每年都有)——改成2023-21-1之类,或改为按年分文件。 - 逐年跑的好处:某一年版式异常时能立刻定位,不会被后面的大文件掩盖。
快速上手
PY=C:/Users/14811/.workbuddy/binaries/python/envs/default/Scripts/python.exe
YB=~/.workbuddy/skills/stats-yearbook-table-to-excel/scripts/yb.py
# 1) 看目录:找章节、列出表格(这一步同时验证年份是否可访问)
"$PY" "$YB" toc --year 2025 --find 教育
# 2) 下载原始数据(图片)
"$PY" "$YB" fetch --year 2025 --chapter 教育 --tables 1-5 --out 原始数据图片
# 3) 切分放大,生成待识别切片
"$PY" "$YB" prep --src 原始数据图片 --out crops
# 4) 【模型视觉】逐片读取 crops/*.png,按 data.json 格式落盘
# 骨架见 templates/data.example.json;完整真实样例见 examples/
# 5) 加总校验(务必通过;差分时看 references/check-cookbook.md 的排错树)
"$PY" "$YB" verify --data data.json
# 5b) 存疑数字超清复核(CLI 单块,或改 templates/zoom_helper.py 批量出图)
"$PY" "$YB" zoom --src 原始数据图片/21-2.jpg --box 0,920,576,1010 --scale 6
# 6) 生成 Excel
"$PY" "$YB" build --data data.json --out 中国统计年鉴2025_教育_前五表.xlsx
一次跑完 6 步即可交付。toc / fetch / prep / build / verify 全部可重复执行且有缓存,
重跑不会重复下载(--force 可强制刷新)。
子命令参考
| 命令 | 作用 | 关键参数 |
|---|---|---|
toc |
抓取解析目录 | --year(必填)、--find 关键词、--list、--json out.json |
fetch |
下载表格原始文件 | --year、--chapter 教育、--tables 1-5\|1,3,5\|all、--out、--force |
prep |
图片切分放大 | --src、--out、--scale 3、--band 380、--overlap 50 |
zoom |
局部超清放大 | --src、--box x1,y1,x2,y2、--scale 8 |
build |
生成 Excel | --data data.json、--out x.xlsx |
verify |
加总校验 | --data data.json(不一致时返回码 1) |
公共参数 --cache(默认 .yb_cache)存放目录缓存。
data.json 格式
识别结果按此结构落盘,build 与 verify 都读它。
完整可运行样例见 examples/年鉴2025_教育_前5表.json(2025 年鉴教育前 5 表真实数据)
与 examples/年鉴2023_教育_前3表.json(2023 年鉴教育前 3 表真实数据)。
两份都是verify 全通过的回归基线,改脚本后拿它们各跑一遍即可确认没有回归。
{
"yearbook": 2025,
"chapter": "二十一、教育",
"source": "国家统计局《中国统计年鉴2025》",
"sheets": [
{
"id": "21-1", // 作 sheet 名
"title": "各级各类学校和教职工情况(2024年)",
"unit": "单位:人", // 无单位写 null
"widths": [26, 16, 16], // 列宽,可选
"columns": ["项 目", "学校数(所)", "教职工数(人)"],
"rows": [
{"label": "高等教育学校", "bold": true, "values": [3119, 3032454]},
{"label": "#独立学院", "indent": 1, "values": [154, 79648]}
],
"notes": ["注:……", "资料来源:……"],
"checks": [
{"total": "高等教育学校",
"parts": ["普通本科学校", "本科层次职业学校", "高职(专科)学校", "成人高等学校"]}
]
}
]
}
字段说明:
bold:一级行加粗(原表中加粗的汇总行)。indent:缩进层级,1 表示缩进一级。用indent不要用空格硬凑。values:与columns[1:]一一对应;原表空白处填null,不要填 0。- 带
#前缀的行(#独立学院、#特殊教育学校)是子集,不参与加总。 - 带
()的值("(224)"、"(52247)")表示不计校数,以字符串保存。
checks 的语义
total/parts:按行标签定位。- 解析策略:从合计行往下找第一个同名行。这天然对应表格层级,
能自动消解"完全中学/本科/专科/十二年一贯制学校"等同名行在不同板块重复出现的歧义。
若其后再无同名行,退回全局唯一匹配;仍不唯一则报错并列出所有候选行号。 columns(可选):只校验指定列。"不计校数"行几乎都必须显式限定列。- ⚠️ 核心陷阱:
不计校数只作用于"学校数"这一列,不代表该行整体不参与合计。
这类行(其他普通高教机构、其他中职机构)的教职工数是要计入上级合计的。
表 21-1 有两处,必须各写两条 check 按列拆开:
jsonc
// 学校数:不含"不计校数"行
{"total": "高等教育学校",
"parts": ["普通本科学校", "本科层次职业学校", "高职(专科)学校", "成人高等学校"],
"columns": ["学校数(所)"]},
// 教职工数:必须加上"其他普通高教机构"
{"total": "高等教育学校",
"parts": ["普通本科学校", "本科层次职业学校", "高职(专科)学校", "成人高等学校", "其他普通高教机构"],
"columns": ["教职工数(人)"]}
同样适用于 中等职业教育(学校数 只含 中等职业学校;教职工数 要加 其他中职机构)。
漏掉第二种情形时,verify 会报一个等于该行教职工数的固定差额——
看到差额恰好等于某"不计校数"行的数值,就说明是口径写错、不是识别错,
别回去重读图片。
- 合计行该列为
null、或分项为null时自动跳过。
章节关键词与年份差异
常用章节:综合、人口、国民经济核算、就业和工资、价格、人民生活、财政、资源和环境、能源、
科学技术、教育、卫生和社会服务、文化和体育、公共管理/社会保障和社会组织、城市农村和区域发展。
- 章节标题含全角空格,如"二十一、教 育"。关键词匹配前必须归一化去除所有空白——
脚本已内置norm_text(),直接传--find 教育即可。 - 章节号逐年可能变动,先
toc --find确认,不要凭记忆。 - 更细的版本变迁见
references/version-changes.md。
年份与数据的对应关系
年鉴年份 = 数据年份 + 1。《中国统计年鉴2025》装的是 2024 年数据。
向用户交付时必须说清数据时点,避免误解。
输出目录规范
{主题}_前N表/ (或用户指定)
├── 中国统计年鉴{年}_{主题}_前N表.xlsx
└── 原始数据图片/ {表号}.jpg ...
Excel 需保留:单位行、层级缩进、加粗汇总行、原表注释与资料来源。
原表没有注释就写"原表无附注",绝不自己编造注释。
环境依赖
- Python:
C:/Users/14811/.workbuddy/binaries/python/envs/default/Scripts/python.exe - 包:
openpyxl(build 用)、Pillow(prep/zoom 用) - 装包:
.../Scripts/python.exe -m pip install <pkg>
⚠️ 该 venv 内直接调用pip.exe会报 Permission denied,必须走python -m pip。
常见问题速查
| 现象 | 原因 | 对策 |
|---|---|---|
| 找不到章节 | 章节号逐年变 / 标题含全角空格 | 用 --find 关键词,别用章节号 |
| 下载 403/失败 | 缺 Referer/UA | 脚本已带,勿去掉 |
| 目录解析乱码 | 编码是 gb2312 | 脚本自动探测 utf-8→gb18030 |
| 数字识错 | 图太小 | prep 放大,再 zoom 复核末位 |
| 加总对不上 | 漏了某个分项行 | 看差额,回去补 |
| 加总差一个固定值(如 209、11877) | 不计校数 行口径写错,不是识别错 |
该行"教职工数"要计入合计,按 columns 拆成两条 check(check-cookbook.md 模式 C) |
| 加总怎么都平不了,差额表内无从解释 | 原表本身不平(口径含未列出成分) | 读原表注释确认,删掉这条 check,不要改数据(模式 D) |
| 加总取错行 | 同名行歧义 | 用 parts 从合计行向下的排序;仍冲突时看报错列出的候选行号 |
| 原表明明有"资料来源",却被要求不写 | 该行并非每表都有,逐年逐表不同 | 一律看图确认,有则抄、无则写「原表无附注」 |
命令报 sandbox-center ... missing actual resource subject |
本机沙箱偶发拒绝 | 换成完整字面路径直接重试;仍失败就用 templates/zoom_helper.py 这类独立 .py 跑,别卡在这 |
技能包文件导航
| 文件 | 作用 | 什么时候读 |
|---|---|---|
scripts/yb.py |
单文件 CLI,6 个子命令 | 每次必用 |
SKILL.md |
本文件:铁律、SOP、验收清单 | 开工前 |
references/check-cookbook.md |
checks 写法模板库 + 排错决策树 | 写 checks 时、verify 报错时 |
references/ocr-tips.md |
图片识别方法论与教训 | 识别质量存疑时 |
references/version-changes.md |
历年 URL / 命名 / 编码 / 章节号实测 | 换年份或解析失败时 |
templates/data.example.json |
data.json 骨架(带字段注释) | 第 5 步落盘时 |
templates/zoom_helper.py |
批量局部复核脚本模板 | 第 7 步复核时 |
examples/*.json |
真实数据回归基线(2023 / 2025) | 改脚本后跑回归 |