法律检索团队批量抓取判例、监控法院备案时,最先卡住的往往不是页面结构,而是几乎每个法律数据源都挂着的验证码。PACER、州法院系统、SEC EDGAR 大多用 reCAPTCHA v2 或图片验证码拦截自动请求。CaptchaAI 通过 API 自动识别这些验证码,让判例检索、备案监控脚本能持续稳定跑下去。
中国团队为什么也要处理这些验证码
这类验证码不只是欧美律所的问题:
- 合规团队抓取 SEC EDGAR 上的中概股信息披露备案
- 出海企业申请美国专利前检索专利数据库确认在先技术
- 跨境尽调项目核对律师协会名录和案例引用
只采集你有权访问的公开数据,同时满足《网络安全法》《数据安全法》和 PIPL 的要求。
常见法律数据源与验证码类型
| 来源 | 验证码类型 | 数据 | 用户 |
|---|---|---|---|
| PACER | reCAPTCHA v2 | 联邦法院文件 | 诉讼团队 |
| 州法院系统 | 图片验证码 / reCAPTCHA | 各州案件记录 | 律师 |
| SEC EDGAR | reCAPTCHA v2 | 上市公司备案 | 合规团队 |
| 专利数据库 | reCAPTCHA v2 | 专利记录 | 知识产权研究人员 |
| 监管门户网站 | 图片验证码 | 规则与指导文件 | 合规团队 |
| 法律引文数据库 | reCAPTCHA v2 | 案例引用 | 法律科技团队 |
| 律师协会名录 | reCAPTCHA v2 | 律师执业记录 | 尽职调查团队 |
抓取踩坑与处理方式
| 问题 | 原因 | 处理方式 |
|---|---|---|
| 图片验证码反复识别失败 | 文字扭曲严重 | 换一张验证码图片重试 |
| PACER 拒绝访问 | 触发速率限制 | 等待 30 分钟,降低请求频率 |
| 案件详情不完整 | 内容在付费墙后 | 按需支付逐页费用 |
| 搜索无结果返回 | 实际返回的是验证码页面 | 解析前先检测是否命中验证码 |
| 案卷监控漏掉新备案 | 检查间隔太长 | 缩短轮询间隔 |
案例检索脚本:批量识别 reCAPTCHA v2 与图片验证码
下面的 Python 示例演示判例检索脚本:请求搜索结果页,命中 reCAPTCHA v2 就走 sitekey 流程提交 token,命中图片验证码就转成 base64 上传识别。
import requests
import time
import re
import base64
from bs4 import BeautifulSoup
import csv
CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"
def solve_recaptcha(sitekey, pageurl):
resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
"key": CAPTCHAAI_KEY, "method": "userrecaptcha",
"googlekey": sitekey, "pageurl": pageurl, "json": 1,
})
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(5)
result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
"key": CAPTCHAAI_KEY, "action": "get",
"id": task_id, "json": 1,
})
data = result.json()
if data["request"] != "CAPCHA_NOT_READY":
return data["request"]
raise TimeoutError("Timeout")
def solve_image_captcha(image_bytes):
img_b64 = base64.b64encode(image_bytes).decode()
resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
"key": CAPTCHAAI_KEY, "method": "base64",
"body": img_b64, "json": 1,
})
task_id = resp.json()["request"]
for _ in range(20):
time.sleep(3)
result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
"key": CAPTCHAAI_KEY, "action": "get",
"id": task_id, "json": 1,
})
data = result.json()
if data["request"] != "CAPCHA_NOT_READY":
return data["request"]
raise TimeoutError("Timeout")
class LegalResearchScraper:
def __init__(self, proxy=None):
self.session = requests.Session()
if proxy:
self.session.proxies = {"http": proxy, "https": proxy}
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
})
def search_cases(self, search_url, query, sitekey=None, max_pages=5):
"""Search case law database."""
all_cases = []
for page in range(max_pages):
url = f"{search_url}?q={query}&page={page + 1}"
resp = self.session.get(url, timeout=30)
if self._has_captcha(resp.text):
if sitekey:
token = solve_recaptcha(sitekey, url)
resp = self.session.post(url, data={
"q": query,
"g-recaptcha-response": token,
})
else:
resp = self._solve_image_and_retry(resp.text, url, query)
cases = self._parse_cases(resp.text)
if not cases:
break
all_cases.extend(cases)
print(f"Page {page + 1}: {len(cases)} cases")
time.sleep(5)
return all_cases
def get_case_details(self, case_url):
"""Fetch full case details."""
resp = self.session.get(case_url, timeout=30)
if self._has_captcha(resp.text):
sitekey = self._extract_sitekey(resp.text)
if sitekey:
token = solve_recaptcha(sitekey, case_url)
resp = self.session.post(case_url, data={
"g-recaptcha-response": token,
})
soup = BeautifulSoup(resp.text, "html.parser")
return {
"title": self._text(soup, "h1, .case-title"),
"citation": self._text(soup, ".citation, .case-cite"),
"court": self._text(soup, ".court, .jurisdiction"),
"date": self._text(soup, ".decision-date, .date-decided"),
"judge": self._text(soup, ".judge, .authored-by"),
"summary": self._text(soup, ".summary, .headnote"),
"url": case_url,
}
def monitor_docket(self, docket_url, case_number, sitekey=None):
"""Monitor a specific case docket for new filings."""
resp = self.session.get(docket_url, timeout=30)
data = {"case_number": case_number}
if sitekey and self._has_captcha(resp.text):
token = solve_recaptcha(sitekey, docket_url)
data["g-recaptcha-response"] = token
resp = self.session.post(docket_url, data=data)
return self._parse_docket(resp.text)
def export_results(self, cases, filename):
"""Export case results to CSV."""
if not cases:
return
with open(filename, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=cases[0].keys())
writer.writeheader()
writer.writerows(cases)
def _has_captcha(self, html):
return any(tag in html.lower() for tag in [
'data-sitekey', 'g-recaptcha', 'captcha',
])
def _extract_sitekey(self, html):
match = re.search(r'data-sitekey="([^"]+)"', html)
return match.group(1) if match else None
def _solve_image_and_retry(self, html, url, query):
match = re.search(r'src="(/captcha[^"]+)"', html)
if match:
img_url = url.split("?")[0].rstrip("/") + match.group(1)
img = self.session.get(img_url)
answer = solve_image_captcha(img.content)
return self.session.post(url, data={
"q": query,
"captcha": answer,
})
return self.session.get(url)
def _parse_cases(self, html):
soup = BeautifulSoup(html, "html.parser")
cases = []
for item in soup.select(".case-result, .search-result, tr.result"):
title_el = item.select_one("a, .case-name")
if title_el:
cases.append({
"title": title_el.get_text(strip=True),
"url": title_el.get("href", ""),
"citation": self._text(item, ".citation, .cite"),
"date": self._text(item, ".date"),
"court": self._text(item, ".court"),
})
return cases
def _parse_docket(self, html):
soup = BeautifulSoup(html, "html.parser")
entries = []
for row in soup.select(".docket-entry, tr.filing"):
entries.append({
"date": self._text(row, ".date, td:first-child"),
"entry": self._text(row, ".description, td:nth-child(2)"),
"filed_by": self._text(row, ".filer, td:nth-child(3)"),
})
return entries
def _text(self, el, selector):
found = el.select_one(selector)
return found.get_text(strip=True) if found else ""
# Usage
scraper = LegalResearchScraper(
proxy="http://user:pass@residential.proxy.com:5000"
)
# Search case law
cases = scraper.search_cases(
search_url="https://caselaw.example.com/search",
query="data privacy GDPR",
max_pages=5,
)
# Get details for relevant cases
for case in cases[:10]:
if case["url"]:
details = scraper.get_case_details(case["url"])
print(f"{details['citation']}: {details['title']}")
time.sleep(3)
# Export results
scraper.export_results(cases, "gdpr_cases.csv")
脚本依次翻页检索、抓取案件详情、导出 CSV,方便后续在 Excel 或 BI 工具里做进一步筛选和交叉核对。
监管备案监控:自动比对新增文件
很多合规团队需要盯住多个监管门户的新增备案。下面的 RegulatoryMonitor 类复用同一套识别逻辑,对多个信息源做增量比对,只输出没见过的备案:
class RegulatoryMonitor:
def __init__(self, proxy=None):
self.scraper = LegalResearchScraper(proxy=proxy)
self.seen_entries = set()
def check_new_filings(self, feeds):
"""Check regulatory portals for new filings."""
new_filings = []
for feed in feeds:
try:
cases = self.scraper.search_cases(
feed["url"], feed["query"],
sitekey=feed.get("sitekey"),
max_pages=2,
)
for case in cases:
key = case.get("citation") or case.get("title")
if key and key not in self.seen_entries:
self.seen_entries.add(key)
case["source"] = feed["name"]
new_filings.append(case)
except Exception as e:
print(f"Error checking {feed['name']}: {e}")
time.sleep(5)
return new_filings
把这个类接入定时任务(cron 或 APScheduler),就能把“新增备案提醒”做成后台自动运行的流程。
并发抓取与线程数怎么选
如果要同时监控多个法院系统,CaptchaAI 按线程数计费、不按验证码次数计费,同一线程可无限次识别。
ADVANCE 套餐($90/月,50 线程)能覆盖多数中型团队的日常抓取,价格以官网定价页为准。
常见问题
抓取法院和监管网站的判例数据合规吗?
公开的法院记录和监管备案通常允许访问,但 PACER 会按页收费,跟访问方式无关。抓取前先确认服务条款和频率限制,跨境团队还要留意《数据安全法》和 PIPL 的采集范围要求。
CaptchaAI 能同时处理 reCAPTCHA v2 和图片验证码吗?
可以。同一个 API Key 既能提交 reCAPTCHA v2 任务,也能提交图片验证码,脚本按页面实际触发的类型分流调用即可。
请求太快会被法院系统封禁吗?
会。PACER 和多数州法院系统都有速率限制,请求过密会触发封禁或返回验证码页面,建议放慢翻页间隔。
案卷监控多久检查一次比较合适?
取决于案件活跃度:诉讼中的案件建议几小时检查一次,监管门户按天检查即可,间隔太长是漏备案的常见原因。
延伸阅读
- 政府门户网站验证码自动化处理
- 学术研究抓取验证码解决方案
- 自有服务器基础设施轮换方案
简化法律研究——获取你的 CaptchaAI 密钥,把判例检索和备案监控接成自动化流程。