news 2026/9/22 6:16:17

3天搞懂中国农村统计年鉴数据爬取完整示例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
3天搞懂中国农村统计年鉴数据爬取完整示例

3天搞懂中国农村统计年鉴数据爬取完整示例

看了一堆教程还是不会写项目?别慌,这行老手告诉你,缺的不是理论,是一个能跑通、能改、能用的完整示例。今天咱们不聊虚的,直接上手处理《中国农村统计年鉴》里的真实数据。很多劳务班组负责人,手里攥着全国各省的用工、收入、土地流转数据,想做个移动端看板给老板看,或者给工人算算跨地区务工的性价比,结果卡在数据提取和清洗上。

《中国农村统计年鉴》是国家统计局每年发布的权威数据汇编,涵盖全国各省、自治区、直辖市的农业经济、农村社会、农民生活等海量指标。对于做劳务管理、跨区域用工分析的团队来说,这简直是金矿。但数据格式杂、表头多、年份跨度大,手动复制粘贴?做梦。

这篇文章,我会带你从0到1,用Python写一个可运行的爬虫+清洗脚本,专门针对《中国农村统计年鉴》的HTML表格结构。代码全部开源,逻辑清晰,注释详细,你改改参数就能跑。我们聚焦三个高频痛点:跨省转介办理差异的数据对比、考试科目与题型(这里指数据字段的枚举值校验,比如“学历”、“工种”的标准化)、电子证书查询与下载(这里指数据导出为Excel/CSV的自动化流程)。

环境准备:别在第一步就翻车

很多人第一步就错了。用错了库,或者版本冲突,跑起来一堆报错。听我的,按这个清单来,稳。

核心依赖库:

  • requests:发送HTTP请求,获取年鉴HTML页面。
  • BeautifulSoup4:解析HTML,提取表格数据。这是最稳的解析器。
  • pandas:数据处理神器。提取表格后,直接转DataFrame,清洗、分析、导出一条龙。
  • openpyxl:pandas导出Excel需要它。

安装命令(终端执行):

pip install requests beautifulsoup4 pandas openpyxl

为什么不用Selenium?

有些教程让你用Selenium模拟浏览器。对于《中国农村统计年鉴》这种静态HTML表格,Selenium是杀鸡用牛刀,而且容易因为浏览器环境不同而失败。requests + BeautifulSoup 轻量、快速、稳定。除非页面是动态JS渲染的,否则别碰Selenium。

目录结构建议:

yearbook_crawler/
├── main.py          # 主脚本
├── utils.py         # 工具函数(如重试、日志)
├── output/          # 存放清洗后的数据
└── logs/            # 存放运行日志

核心语法:解析表格的三把钥匙

《中国农村统计年鉴》的网页结构相对规范,但细节坑多。核心就三步:定位表格、提取表头、提取数据行

1. 定位表格

年鉴页面通常有多个表格。你需要通过classid定位目标表格。比如,某省农民收入数据可能在<table class="stat-table">里。

from bs4 import BeautifulSoup
import requestsurl = "https://www.stats.gov.cn/sj/ndsj/2023/indexch.htm"  # 示例URL,实际需替换为具体年份页面
response = requests.get(url, timeout=10)
soup = BeautifulSoup(response.text, 'html.parser')# 假设目标表格的class是 'stat-table'
tables = soup.find_all('table', class_='stat-table')
if not tables:print("未找到目标表格,请检查class属性")exit()target_table = tables[0]  # 取第一个匹配表格

2. 提取表头

表头通常在<thead>或第一行<tr>中。注意,有些表格的表头是合并单元格(colspan/rowspan),需要特殊处理。

# 提取表头
header_rows = target_table.find('thead').find_all('tr')
headers = []
for tr in header_rows:ths = tr.find_all(['th', 'td'])row_headers = [th.get_text(strip=True) for th in ths]headers.append(row_headers)# 简化:如果只有一行表头
if len(headers) == 1:final_headers = headers[0]
else:# 多行表头需要合并,这里简单处理为下划线连接final_headers = ['_'.join(filter(None, col)) for col in zip(*headers)]

3. 提取数据行

数据在<tbody>中。注意空值、换行符、全角字符。

data_rows = []
tbody = target_table.find('tbody')
for tr in tbody.find_all('tr'):tds = tr.find_all('td')row_data = [td.get_text(strip=True) for td in tds]# 过滤空行if any(row_data):data_rows.append(row_data)# 转为DataFrame
import pandas as pd
df = pd.DataFrame(data_rows, columns=final_headers)
print(df.head())

关键细节: strip=True 很重要,能去掉空格和换行。年鉴数据里常有“--”表示缺失,后续清洗要处理。

完整代码示例:跑通一个省份数据

下面是一个完整示例,以2023年某省农民人均可支配收入数据为例。代码包含重试机制、异常处理、数据清洗和导出。

import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import logging# 配置日志
logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s',handlers=[logging.FileHandler("logs/crawler.log", encoding="utf-8"),logging.StreamHandler()]
)def fetch_page(url, retries=3):"""带重试的页面获取"""for i in range(retries):try:headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}response = requests.get(url, headers=headers, timeout=10)if response.status_code == 200:logging.info(f"成功获取: {url}")return response.textelse:logging.warning(f"状态码异常: {response.status_code}, 重试 {i+1}/{retries}")except Exception as e:logging.error(f"请求失败: {e}, 重试 {i+1}/{retries}")time.sleep(2)  # 避免请求过快return Nonedef parse_yearbook_table(html, table_class='stat-table'):"""解析年鉴表格"""soup = BeautifulSoup(html, 'html.parser')table = soup.find('table', class_=table_class)if not table:logging.error(f"未找到class为'{table_class}'的表格")return None# 提取表头thead = table.find('thead')if not thead:logging.warning("未找到thead,尝试用第一行作为表头")first_tr = table.find('tr')headers = [th.get_text(strip=True) for th in first_tr.find_all(['th', 'td'])]tbody = table.find_all('tr')[1:]else:header_rows = thead.find_all('tr')headers = []for tr in header_rows:ths = tr.find_all(['th', 'td'])headers.append([th.get_text(strip=True) for th in ths])# 合并多行表头if len(headers) == 1:final_headers = headers[0]else:final_headers = ['_'.join(filter(None, col)) for col in zip(*headers)]tbody = table.find('tbody')if not tbody:tbody = table.find_all('tr')[len(headers):]else:tbody = tbody.find_all('tr')# 提取数据data = []for tr in tbody:tds = tr.find_all('td')row = [td.get_text(strip=True) for td in tds]if len(row) == len(final_headers) and any(row):data.append(row)if not data:logging.error("未提取到数据行")return Nonedf = pd.DataFrame(data, columns=final_headers)return dfdef clean_data(df):"""数据清洗"""# 替换缺失值df.replace('--', None, inplace=True)# 删除全空行df.dropna(how='all', inplace=True)# 处理数字列:去掉逗号、空格for col in df.columns:if df[col].dtype == 'object':df[col] = df[col].str.replace(',', '', regex=False)df[col] = pd.to_numeric(df[col], errors='coerce')return dfdef main():# 示例:2023年某省农民收入数据页面url = "https://www.stats.gov.cn/sj/ndsj/2023/html/C08-01ch.htm"  # 请替换为实际URLhtml = fetch_page(url)if not html:logging.error("页面获取失败,退出")returndf = parse_yearbook_table(html, table_class='stat-table')if df is None:logging.error("表格解析失败,退出")returndf = clean_data(df)logging.info(f"成功提取 {len(df)} 行数据")# 导出Exceloutput_path = "output/farmer_income_2023.xlsx"df.to_excel(output_path, index=False, engine='openpyxl')logging.info(f"数据已导出: {output_path}")if __name__ == '__main__':main()

代码亮点:

  • 重试机制:网络不稳定时自动重试,避免单次失败。
  • 日志记录:每一步操作都有日志,出问题好排查。
  • 表头兼容:自动处理单行/多行表头。
  • 数据清洗:自动转换数字类型,处理缺失值。

常见报错:这些坑我替你踩过了

1. AttributeError: 'NoneType' object has no attribute 'find_all'

  • 原因soup.find('table', class_=...) 返回None,说明没找到表格。
  • 解决:检查class属性是否正确。用浏览器F12,查看表格的真实class。年鉴页面可能用id而非class

2. ValueError: Length of values does not match length of index

  • 原因:某行数据的列数与表头不一致。可能是合并单元格导致td数量少。
  • 解决:在parse_yearbook_table中,增加列数校验。如果len(row) != len(final_headers),记录警告并跳过该行,或尝试填充None

3. Excel 文件被占用

  • 原因:导出Excel时,文件在Excel中打开。
  • 解决:确保导出前关闭Excel文件,或在代码中加os.remove删除旧文件(谨慎使用)。

4. 数据中出现全角数字123

  • 原因:年鉴HTML中可能混用全角/半角字符。
  • 解决:在clean_data中增加全角转半角处理:
def full_to_half(s):"""全角转半角"""r = []for ch in s:code = ord(ch)if code == 0x3000:code = 0x20elif 0xFF01 <= code <= 0xFF5E:code -= 0xFEE0r.append(chr(code))return ''.join(r)

clean_data中对字符串列应用此函数。

小结:从数据到决策的最后一公里

这套完整示例,能帮你把《中国农村统计年鉴》的原始HTML数据,变成干净的Excel文件。对于劳务班组负责人来说,下一步就是做数据可视化:

  • 跨省转介办理差异:对比各省“外出务工人数”、“平均收入”、“社保覆盖率”,找出用工成本洼地。
  • 考试科目与题型(字段标准化):确保“工种”、“学历”等字段枚举值统一,便于后续分析。
  • 电子证书查询与下载(数据导出自动化):设置定时任务,每年年鉴发布后自动爬取、清洗、推送给团队成员。

技术只是工具,数据背后的洞察才是价值。别再把时间花在手动复制粘贴上了。

你更常用哪种写法?评论区交流。 是用BeautifulSoup还是Lxml?处理合并单元格有什么独门技巧?说说你的踩坑经历,帮后来人省点时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/22 6:16:06

3分钟图解编程好学吗:大厂面试官拆解核心考点与避坑指南

3分钟图解编程好学吗:大厂面试官拆解核心考点与避坑指南 官方文档动辄几百万字,新手翻开第一页就晕?别慌。很多人纠结编程好学吗,其实核心不是背代码,而是建立逻辑闭环。这篇图解原理,带你跳过那些啰嗦的理论,直击面试与实战的底层逻辑。 考点梳理:别被表象迷惑,核心只有这三层…

作者头像 李华
网站建设 2026/9/22 6:16:03

3分钟吃透冰桶算法,前端源码解析避坑指南

3分钟吃透冰桶算法,前端源码解析避坑指南 别再对着官方文档那些晦涩的数学公式发呆了,真的抓不住重点,越看越迷糊。我当年刚转行做前端时,就被这玩意儿坑得半死,直到我去翻了几个核心库的 源码解析 ,才发现逻辑其实简单得令人发指。 今天不整虚的,咱们直接上手,用最接地气的方式把 冰桶算法…

作者头像 李华
网站建设 2026/9/22 6:15:55

GD32F303CCT6 FOC引脚配置避坑指南:时序敏感型硬件设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/22 6:15:50

2026最新premiere软件报错修复实战指南

2026最新premiere软件报错修复实战指南 刚把Premiere Pro升到2026版本,打开工程文件瞬间崩了?或者运行一段之前写好的Python自动化脚本,发现 import 的API模块直接报 ModuleNotFoundError…

作者头像 李华
网站建设 2026/9/22 6:15:43

搞定lqqm报错:保姆级教程带你深挖源码避坑

搞定lqqm报错:保姆级教程带你深挖源码避坑 盯着满屏红色的StackTrace,心跳瞬间加速,脑子一片空白。这种“报错一堆看不懂”的绝望感,是每个开发者都经历过的至暗时刻。别慌,今天这篇保姆级教程,不整虚的,直接带你钻进【lqqm】的核心源码,把那些让你头疼的逻辑掰开了揉碎了讲。…

作者头像 李华
网站建设 2026/9/22 6:15:42

3年踩坑总结:剪切板在哪里?手写实现避坑指南

3年踩坑总结:剪切板在哪里?手写实现避坑指南 版本升级后 API 全变了,以前好用的 navigator.clipboard 在 Safari 里直接报错,或者在 HTTP 环境下静默失败。别急着骂浏览器,先看看你的项目是不是还在用那个过时的 document.execCommand…

作者头像 李华