本文通过一次完整实战,演示如何使用 Python + requests 抓取
采购网公告列表数据 ,并重点分析 JSON 结构解析中常见的TypeError问题。
一、需求背景
在日常学习爬虫时,我们经常会遇到这种场景:
- 页面是 列表页
- 数据不是写在 HTML 中
- 而是通过 XHR 接口动态加载
例如湖南省政府采购网的公告列表页:
http://www.ccgp-hunan.gov.cn/page/notice/more.jsp?noticeTypeID=prcmNotices
👉 页面滚动 / 翻页时,实际请求的是一个接口。
二、定位真实接口(关键一步)
1️⃣ 打开开发者工具
- F12 → Network
- 选择 Fetch / XHR
- 翻页或刷新页面
你会发现不断请求:
getNoticeList4Web.do
2️⃣ 接口核心信息
请求地址
http://www.ccgp-hunan.gov.cn/mvc/getNoticeList4Web.do
请求方式
POST
Content-Type
application/x-www-form-urlencoded
3️⃣ 请求参数分析(表单数据)
从 Network → Payload 可以看到完整参数:
text
nType: prcmNotices
pType:
prcmPrjName:
prcmItemCode:
prcmOrgName:
startDate: 2025-01-01
endDate: 2025-12-28
prcmPlanNo:
page: 1
pageSize: 18
⚠️ 重点 :
这是 form 表单请求,不是 JSON!
三、返回数据结构解析(重点)
接口返回的是一个 JSON,大致结构如下:
json
{
"total": 4750,
"hzMap": null,
"rows": [
{
"NOTICE_TITLE": "湖南省XX项目公开招标公告",
"NEWWORK_DATE": "2025-12-25",
"PRCM_MODE_NAME": "公开招标"
},
...
]
}
📌 重点字段说明:
| 字段 | 含义 |
|---|---|
| total | 总数据量 |
| rows | 当前页的数据列表 |
| NOTICE_TITLE | 公告标题 |
| NEWWORK_DATE | 发布时间 |
四、原始代码 & 问题分析
你一开始的核心代码是:
python
for k, v in res_data.items():
for y in v:
print(y)
❌ 报错原因
接口返回的数据中:
python
res_data["total"] = 4750
于是代码等价于:
python
for y in 4750:
...
直接触发:
text
TypeError: 'int' object is not iterable
👉 int 不能被 for 遍历
五、正确的解析思路(核心)
我们真正需要的只有:
python
res_data["rows"]
因为它才是 列表(list)
六、完整可运行示例代码(推荐版本)
python
import requests
url = "http://www.ccgp-hunan.gov.cn/mvc/getNoticeList4Web.do"
page = 1
while True:
para = {
"nType": "prcmNotices",
"pType": "",
"prcmPrjName": "",
"prcmItemCode": "",
"prcmOrgName": "",
"startDate": "2025-01-01",
"endDate": "2025-12-28",
"prcmPlanNo": "",
"page": page,
"pageSize": 18
}
response = requests.post(url, data=para)
res_data = response.json()
rows = res_data.get("rows")
# 如果没有数据,说明翻页结束
if not rows:
print("数据抓取完成")
break
print(f"===== 第 {page} 页 =====")
for item in rows:
title = item.get("NOTICE_TITLE")
date = item.get("NEWWORK_DATE")
mode = item.get("PRCM_MODE_NAME")
print(date, mode, title)
page += 1
七、代码关键点解析
1️⃣ 为什么用 data=para?
因为接口是:
Content-Type: application/x-www-form-urlencoded
如果写成:
python
json=para
❌ 会直接拿不到数据。
2️⃣ 为什么只遍历 rows?
python
rows = res_data.get("rows")
rows是 list- 每一项是 dict
- 正好对应公告数据
3️⃣ 为什么要判断 rows 是否为空?
python
if not rows:
break
这是 分页爬虫的标准写法,用于自动停止。
八、常见坑位总结(必看)
❌ 坑 1:对 int / None 直接 for
python
for y in res_data["total"]:
✅ 正确:
先判断类型 or 直接定位 list 字段
❌ 坑 2:把 form 请求当 JSON 请求
python
requests.post(url, json=para) # ❌
✅ 正确:
python
requests.post(url, data=para)
❌ 坑 3:无脑遍历 res_data.items()
接口返回结构一定要 先 print 看清楚
九、可以继续扩展的方向
- 保存为 CSV / Excel
- 写入 MySQL / SQLite
- 增加异常重试
- 多进程抓取
- 解析公告详情页
十、总结
爬虫的本质不是写代码
而是 看接口 + 读数据结构 + 精准解析
这次案例里最核心的一点就是:
不要假设接口返回的数据结构,一定要先看清楚 JSON