一个查美国联邦法案的小工具,上周在云端跑崩了:15秒超时。同一时间,从本机发出的请求收到空回复,换一个HTTP客户端则直接吃了个403。一天之后,同一个地址又正常了。
多数人的做法是加一层重试,然后继续。这位维护者没有。他先去查了自己到底在依赖什么,结果发现:这个API已经退休很多年了。
![]()
一个还在应答的退休接口
2016年12月,GovTrack宣布其API和批量数据将在次年夏天关停,并把开发者引向其他数据源。文档页面/developers/api现在返回404,Wayback Machine的记录显示,至少从2023年2月起它就是404。
更直接的是robots.txt:Disallow: /api,对所有user agent生效,还附带30秒的爬取延迟。
接口仍然会回话,但没有任何一部分是被支持的。一个间歇性失败的退休端点,不值得用重试去绕。于是他开始找替代方案。
两条官方路线,一条被限流卡死
第一条是Congress.gov API。官方出品,文档齐全,但需要api.data.gov的密钥。用共享的DEMO_KEY试了一下,返回的响应头是X-Ratelimit-Limit: 10——对一个跑在共享云IP上的工具来说,这个额度等于没有。
而且它的/bill列表端点没有关键词参数,列表项里也不含提案人,意味着每查一条法案都得再发一次请求。
第二条是GovInfo的BILLSTATUS批量数据。美国政府出版局为每条法案发布一个XML文件,地址形如https://www.govinfo.gov/bulkdata/BILLSTATUS/{congress}/{type}/,数据源自Congress.gov。每个文件里都有标题、提案人、共同提案人、委员会和完整的动作历史。
不需要密钥,不需要账号,文件标注为公有领域,govinfo.gov自己的robots.txt里也列出了这些批量数据的站点地图。
他选了批量数据。它同样没有搜索功能,但至少没有请求预算需要保护。
没有搜索端点,怎么找到最近的法案
每个届期和每种法案类型都有自己的站点地图,比如https://www.govinfo.gov/sitemap/bulkdata/BILLSTATUS/119hr/sitemap.xml。他查看时,这份地图列出了第119届国会的10608条众议院法案。
从他所在的位置下载这份地图用了不到3秒,而同一个文件夹的JSON目录列表要花9到13秒。
他写了一个函数来提取法案编号:抓取站点地图XML,用正则匹配出所有文件名里的数字,去重后从大到小排序。
法案编号是按提出顺序发放的。所以想拿"最近90天的法案",就按类型(H.R.、S.、H.Res.等)从最大编号往下走,一旦连续三条法案都超出时间窗口就停下。用三条而不是一条做缓冲,是防止某个编号被乱序提出。
状态字段不在文件里
XML里没有"当前状态"这个字段。它只有一串动作记录,最新的排在前面,描述是自由文本,比如"Referred to the House Committee on Oversight and Government Reform.",附带日期、类型和动作代码。
GovTrack的数据来自开源项目unitedstates/congress,像"Passed House (Senate next)"、"Failed Cloture"、"Pocket Vetoed"这些状态,都来自该项目对这些动作文本的解析。规则写在bill_info.py的parse_bill_action和new_status_after_vote里,大约400行Python和正则表达式。
他没有自己发明一套,而是把这套规则移植成了JavaScript,这样原有的状态筛选器含义不会变。
核心是一个小状态机。一次投票之后,新状态取决于是否通过、哪个议院投的、以及这是不是法案起始的议院。简化后的逻辑大致是:
- 如果是在起始议院投票且通过,若是hres或sres类型则返回PASSED:SIMPLERES,否则按议院返回PASS_OVER:HOUSE或PASS_OVER:SENATE
- 如果未通过且走的是suspension程序,返回PROV_KILL:SUSPENSIONFAILED
- 否则按议院返回FAIL:ORIGINATING:HOUSE或FAIL:ORIGINATING:SENATE
- 此外还要处理第二议院、ping-pong、cloture、否决推翻和会议委员会投票
有两个细节很关键。第一,同一次投票常常出现两次:一次来自众议院或参议院的floor系统,一次来自国会图书馆(sourceSystem代码9),文本略有差异。原代码会在国会图书馆版本的文本以它前一条动作的文本结尾时丢弃这一份。漏掉这一步,一次投票会被算两次。
第二,动作要按从旧到新回放,每一条都基于前面动作产生的状态。
拿203条法案对了一遍
趁GovTrack还能应答,他尽可能跨多个状态拉取法案,逐字段比对:状态、下一步由哪个议院行动、状态日期、提出日期、法案是否仍然存活、提案人的党派和州。
一共是203条法案,覆盖18种不同状态,包括往届国会里被否决推翻的否决案。203条全部吻合。
正是这次比对让他敢于切换。移植这么多正则表达式,很容易在某个细微处出错。
变差的部分:关键词搜索
GovTrack的搜索不只看标题。搜"artificial intelligence"、时间范围90天,它返回123条法案。在批量数据里匹配标题,只能找到57条,而这57条GovTrack也都返回了。
他抽查了缺失的8条:这个短语在它们的BILLSTATUS文件里根本没出现,而且它们都还没有CRS摘要或立法主题词,其中还包括7月提出的法案。
两个改动帮他找回了一部分。一是用OR连接同义表达,"artificial intelligence OR AI"能找到68条,其中66条在GovTrack的结果集里;另外两条标题里提到AI,却不在GovTrack的结果里。二是处理单复数。
搜索能力确实缩水了,但换来的是不再需要保护请求预算,也不再依赖一个连文档页都404的接口。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.