Hi, 我是PaperBrew,目前专注于本硕博论文写作方法、文献阅读方法、学术规范科普\/:PaperBrew_Tang
研途千嶂,PaperBrew伴君求索
做涉农、乡村振兴或公共政策研究,最磨人的往往不是读文件,而是把中国政府网上的公文一条条找齐、对齐字段、落到可分析的表格里。
本文只做一件事:用 Python 调用中国政府网政策文件库的搜索接口,按关键词抓取近十年相关政策,补全详情与正文,导出一份结构化 Excel。示例主题是“农业 / 农村 / 乡村振兴 / 三农 / 粮食”,关键词换成你的题目即可复用。
目标站点:https://www.gov.cn/
搜索接口:https://sousuo.www.gov.cn/search-gov/data
打开中国政府网政策检索,按 F12 → Network,输入关键词搜索,会看到请求打到:
https://sousuo.www.gov.cn/search-gov/data
返回的是 JSON,不是一整页排好版的列表。这意味着技术路线是:
带参数 GET 搜索接口,翻页拿标题、发文机关、发文字号、日期、链接;
用链接去重,再按时间窗过滤;
逐条打开详情页,解析元数据表与正文;
按时间线排序,写入 Excel。
比静态列表页多一步“读接口”,但字段更干净,也更适合正式研究样本框。
import re import time from datetime import datetime from pathlib import Path import pandas as pd import requests from bs4 import BeautifulSoup SEARCH_API = "https://sousuo.www.gov.cn/search-gov/data"KEYWORDS = ["农业", "农村", "乡村振兴", "三农", "粮食"] END_DATE = datetime(2026, 7, 29) START_DATE = datetime(2016, 7, 29) DELAY = 1.0 # 详情页间隔,降低对服务器压力 OUT_FILE = Path.home() / "Desktop" / "中国政府网_农业政策_近十年.xlsx"SESSION = requests.Session() SESSION.headers.update( { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/120.0.0.0 Safari/537.36"), "Referer": "https://www.gov.cn/", } )依赖安装:
pip install requests beautifulsoup4 pandas openpyxl
注意三点:带上合理的 User-Agent 与 Referer;详情请求之间留 DELAY;关键词列表按课题改,不要一次塞几十个无关词。
核心参数含义可以这样理解:
t=zhengcelibrary | |
q | |
mintimemaxtime | |
pn | |
type=gwyzcwjk | |
sort=pubtime |
def search_page(keyword: str, page: int, page_size: int = 50) -> list[dict]: params = { "t": "zhengcelibrary", "q": keyword, "timetype": "5", "mintime": START_DATE.strftime("%Y-%m-%d"), "maxtime": END_DATE.strftime("%Y-%m-%d"), "sort": "pubtime", "sortType": "1", "searchfield": "title", "p": str(page), "n": str(page_size), "type": "gwyzcwjk", } data = SESSION.get(SEARCH_API, params=params, timeout=30).json() items = ( data.get("searchVO", {}) .get("catMap", {}) .get("gongwen", {}) .get("listVO", []) or [] ) rows = [] for it in items: title = re.sub(r"<[^>]+>", "", it.get("title") or "").strip() url = (it.get("url") or "").strip() if not title or not url: continuepub = (it.get("pubtimeStr") or "").replace(".", "-") rows.append( { "政策名称": title, "出文单位": (it.get("puborg") or "").strip(), "发文字号": (it.get("pcode") or "").strip(), "主题分类": (it.get("childtype") or "").strip(), "索引号": (it.get("index") or "").strip(), "发布日期": pub, "链接": url, } ) return rows多关键词检索后,同一公文常被不同词命中,必须用链接去重。接口一般按时间倒序;当本页最旧一条已经早于起始年,可以提前停翻,少打无效请求。
def collect() -> list[dict]: """多关键词翻页 → 去重 → 十年窗口过滤 → 按时间线从早到晚。"""merged = {} for kw in KEYWORDS: print(f"搜索:{kw}") for page in range(1, 20): # 每词最多翻 20 页,可按题调整 rows = search_page(kw, page) if not rows: breakfor row in rows: merged[row["链接"]] = row oldest = parse_date(rows[-1]["发布日期"]) if oldest and oldest < START_DATE: breaktime.sleep(0.4) rows = [row for row in merged.values() if in_range(row["发布日期"])] rows.sort(key=lambda x: parse_date(x["发布日期"]) or datetime.min) return rows日期解析写成小函数即可:兼容 2020-01-01、2020.01.01、2020年1月1日 等常见写法,再判断是否落在 [START_DATE, END_DATE]。
搜索结果里已有标题、链接、大致日期;正式分析还需要详情页里的索引号、发文机关、成文日期,以及正文全文。
详情页元数据多在表格单元格中,标签形如“索引号”“主题分类”“发文机关”“成文日期”“发文字号”“发布日期”。正文常见容器:
• #UCAP-CONTENT
• .pages_content
• .TRS_Editor
建议优先用 html.parser。部分页面用 lxml 解析时可能丢正文,这是实操里踩过的坑。
def parse_table(soup: BeautifulSoup) -> dict: label_map = { "索引号": "索引号", "主题分类": "主题分类", "发文机关": "出文单位", "成文日期": "成文日期", "标题": "政策名称", "发文字号": "发文字号", "发布日期": "发布日期", } out = {} for td in soup.select("table td"): b = td.find("b") or td.find("strong") if not b: continuelabel = re.sub(r"[su3000::]", "", b.get_text()) field = next((v for k, v in label_map.items() if label.startswith(k)), None) nxt = td.find_next_sibling("td") if field and nxt: val = nxt.get_text(" ", strip=True) if val: out[field] = val return out def parse_body(soup: BeautifulSoup) -> str: box = ( soup.find("div", id="UCAP-CONTENT") or soup.select_one(".pages_content") or soup.select_one(".TRS_Editor") ) if not box: return""for tag in box.select("script, style"): tag.decompose() return re.sub(r"n{3,}", "nn", box.get_text("n", strip=True)).strip() def fetch_detail(url: str) -> dict: r = SESSION.get(url, timeout=40) r.raise_for_status() r.encoding = "utf-8"soup = BeautifulSoup(r.text, "html.parser") info = parse_table(soup) info["正文内容"] = parse_body(soup) if not info.get("政策名称"): h1 = soup.select_one("h1#ti, h1") if h1: info["政策名称"] = h1.get_text(strip=True) return info主循环建议打印进度:[当前条/总条] 日期 标题前若干字。单条失败不要整批中断,记日志后继续;正文过长时可截断并注明“详见链接”,避免 Excel 单元格写爆。
建议固定列顺序,冻结首行,方便筛选:
cols = [ "发行日期", "成文日期", "政策名称", "出文单位", "发文字号", "主题分类", "索引号", "正文内容", "链接", ] df = pd.DataFrame(records, columns=cols) with pd.ExcelWriter(OUT_FILE, engine="openpyxl") as writer: df.to_excel(writer, index=False, sheet_name="农业政策_近十年") ws = writer.sheets["农业政策_近十年"] for col, w in { "A": 12, "B": 12, "C": 42, "D": 14, "E": 20, "F": 32, "G": 20, "H": 55, "I": 40, }.items(): ws.column_dimensions[col].width = w ws.freeze_panes = "A2"print(f"完成:{len(df)} 条,正文成功 {sum(1 for r in records if r['正文内容'])} 条") print(f"文件:{OUT_FILE}")落盘后先做三件事:看时间线首尾是否合理;抽 5–10 条点开“链接”核对标题与日期;看“正文成功”条数是否接近总数。这三步比再写一百行代码更重要。
礼貌抓取。 搜索翻页与详情请求都加间隔,控制总量,避免短时间高频访问。
保留出处。 表格必须留下原始链接、发布日期、发文字号(若有),便于复核与规范引用。
抽样核验。 随机对照官网,确认没有串行、截断或日期解析错误。
合规使用。 公开政策文本可用于学术整理与分析;批量下载后仍应遵守网站使用条款,不作未经授权的再分发或商用包装。
爬虫只解决“材料在桌面上”。论文真正被评价的,是政策总体如何界定、如何编码、如何接到研究问题上。