无公开API时如何抓取DCRA政府站点动态加载的承包商数据
华盛顿特区通用承包商数据爬取操作指导
第一步:提取实际数据接口的请求信息
打开目标站点,按F12调出开发者工具,切换到「Network」标签,筛选「XHR」类型的请求。手动点击站点的翻页按钮,找到翻页时触发的对应请求,记录以下信息:
- 请求的实际URL(注意不是浏览器地址栏的页面URL,是XHR请求的接口地址)
- 请求方法(GET/POST,这类查询站点一般是POST)
- 请求头中必要字段:
User-Agent、Content-Type、Referer,如果有X-CSRFToken类的校验字段也一并记录 - 请求参数:GET请求的URL参数、POST请求的请求体参数,重点找页码参数、每页返回条数参数、查询类型参数(对应你要爬的General Contractor类型)
第二步:验证接口可独立调用
- 右键你找到的XHR请求,选择「Copy」→「Copy as cURL (bash)」,把内容转换为Python requests代码,也可以手动按照记录的参数写请求代码
- 修改请求参数中的页码值,发送测试请求,看是否能正常拿到其他页的数据。如果接口支持调整每页返回条数,尽量调到最大值(比如从默认10条调到100条),减少后续请求次数
- 如果返回403/401错误,检查是否漏了校验头或者cookie,用
requests.Session()创建会话,先访问一次站点首页获取有效cookie后再调用数据接口,可解决大部分会话校验问题
第三步:编写批量爬取脚本
以下是最简Python示例逻辑,你可以根据自己拿到的接口参数修改:
import requests import time import csv # 替换为你拿到的实际接口地址 api_url = "https://govservices.dcra.dc.gov/xxx/xxx" session = requests.Session() # 先请求首页获取会话cookie session.get("https://govservices.dcra.dc.gov/contractorratingsystem/BuildingProfessionals/BuildingProfessional?profType=General%20Contractor&profName=") # 替换为你拿到的实际请求头 headers = { "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", "Content-Type": "application/json" } all_data = [] # 先请求第一页获取总条数,计算总页数 # 替换为实际的第一页请求参数 first_page_data = { "page": 1, "size": 100, "profType": "General Contractor" } resp = session.post(api_url, json=first_page_data, headers=headers) resp_json = resp.json() # 替换为实际返回结构里的总页数字段 total_pages = resp_json["data"]["totalPages"] all_data.extend(resp_json["data"]["list"]) # 从第二页开始循环爬取 for page in range(2, total_pages + 1): time.sleep(2) # 控制请求频率,避免被封IP page_data = first_page_data.copy() page_data["page"] = page try: resp = session.post(api_url, json=page_data, headers=headers) resp.raise_for_status() current_page_data = resp.json() all_data.extend(current_page_data["data"]["list"]) print(f"已爬取第{page}/{total_pages}页") except Exception as e: print(f"第{page}页爬取失败,错误:{e}") # 可自行添加失败重试逻辑 # 保存为csv文件 with open("dc_general_contractors.csv", "w", encoding="utf-8", newline="") as f: # 替换为实际的字段名 writer = csv.DictWriter(f, fieldnames=all_data[0].keys()) writer.writeheader() writer.writerows(all_data)
注意事项
- 不要爬取过快,每次请求间隔1-2秒,避免给站点服务器造成压力同时防止IP被封禁
- 爬取过程中如果出现验证码或者访问拦截,可以适当增加间隔时间,或者更换IP后继续
- 拿到数据后建议根据承包商唯一ID字段去重,避免翻页时出现的重复数据
内容的提问来源于stack exchange,提问作者Aashish Pal
相关产品推荐
相关产品推荐

