Use Cases

Веб-скрапинг юридических исследований с обработкой CAPTCHA

reCAPTCHA v2 на PACER, капчи-изображения в судебных системах отдельных штатов и периодические проверки на профильных судебных порталах — главное, что тормозит автоматизированный сбор судебной практики. Решение простое: подключить API CaptchaAI к парсеру и обрабатывать капчу программно, а не останавливать скрипт на каждом заблокированном запросе.


Кому и зачем нужен автоматический доступ к судебной практике

Юридическим фирмам, legal tech-командам и специалистам по комплаенсу такой доступ даёт стабильный поток данных без ручного вмешательства на каждой странице:

  • Судебным командам — искать прецеденты по PACER, не останавливая скрипт на reCAPTCHA v2.
  • Legal tech и комплаенс-отделам — агрегировать нормативные акты и корпоративную отчётность SEC EDGAR.
  • IP-исследователям и due diligence — мониторить патентные базы и справочники коллегий адвокатов.

Что учесть перед стартом: доступ, оплата и персональные данные

Публичные судебные материалы, как правило, доступны для автоматизированного сбора, но у PACER постраничная оплата не зависит от способа доступа — при планировании массового сбора закладывайте эти расходы отдельно и рассматривайте RECAP как бесплатный архив уже загруженных документов. Всегда соблюдайте ограничения по частоте запросов и условия использования конкретного портала: правильнее держать паузы между страницами (как в примере ниже — 5 секунд), чем наращивать параллелизм и получать блокировки.

Если в выгрузку попадают персональные данные — ФИО сторон, адреса, судей, — собирайте только то, что вправе обрабатывать в рамках конкретной задачи. Для российской аудитории это означает должную осмотрительность в духе 152-ФЗ «О персональных данных», для трансграничных команд действует похожая логика GDPR. Это вопрос комплаенса заказчика, а не функция CaptchaAI — сервис лишь решает саму задачу CAPTCHA.


Где юридические порталы чаще всего ставят CAPTCHA

Прежде чем писать парсер, стоит понять, какая защита стоит на конкретном источнике — это определяет метод CaptchaAI: userrecaptcha для reCAPTCHA v2 или отправка изображения, если сайт использует капчу-картинку.

Источник Тип CAPTCHA Данные Кто использует
PACER reCAPTCHA v2 Материалы федеральных судов Судебные юристы
Судебные системы штатов Капча-изображение / reCAPTCHA Дела на уровне штата Адвокаты
SEC EDGAR reCAPTCHA v2 Корпоративная отчётность Комплаенс
Патентные базы данных reCAPTCHA v2 Патентные записи IP-исследователи
Регуляторные порталы Капча-изображение Нормативные акты, разъяснения Комплаенс
Базы судебных цитирований reCAPTCHA v2 Цитирования дел Legal tech
Справочники коллегий адвокатов reCAPTCHA v2 Записи об адвокатах Due diligence

Похожая картина знакома и русскоязычным командам: картотека арбитражных дел и государственные судебные системы СНГ периодически включают проверку при активном постраничном поиске, а не только при входе в систему — поэтому парсер должен уметь обрабатывать капчу на любом шаге запроса, а не только при логине.


Парсер судебной практики: рабочий пример на Python

Ниже — класс на Python, который ищет дела по ключевым словам, решает reCAPTCHA v2 через CaptchaAI (in.phpres.php) и обрабатывает капчу-изображение через base64, если портал использует именно её. Результаты выгружаются в CSV — удобно для дальнейшей сверки юристами или загрузки во внутреннюю базу.

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:[email protected]: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")

Мониторинг новых материалов и нормативных изменений

Разовый прогон парсера закрывает задачу поиска, но регуляторный мониторинг требует повторяемого процесса. Класс RegulatoryMonitor хранит уже увиденные записи (seen_entries) и на каждом цикле добавляет в выгрузку только новые дела или документы по каждому источнику — без дублей.

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

Типичные проблемы парсинга судебных порталов и как их решать

Ниже — частые сбои и то, что реально помогает в каждом случае:

  • Капча-изображение не проходит (искажённый текст) — запросите новое изображение и повторите решение через solve_image_captcha.
  • PACER блокирует доступ (превышен лимит запросов) — подождите около 30 минут и снизьте частоту запросов.
  • Детали дела неполные (раздел за платным доступом) — учитывайте постраничную оплату там, где она обязательна.
  • Поиск не возвращает результатов (вместо результатов пришла страница с CAPTCHA) — проверяйте наличие капчи до парсинга ответа.
  • Мониторинг реестра пропускает документы (интервал проверки слишком большой) — увеличьте частоту опроса в check_new_filings.
  • Токен reCAPTCHA не принимается сайтом (sitekey или pageurl не соответствуют текущей странице) — сверьте оба параметра со страницей перед отправкой в in.php.

Часто задаваемые вопросы

Законно ли собирать данные с PACER и аналогичных судебных порталов?

Публичные материалы судов, как правило, открыты для автоматизированного доступа, но это не отменяет постраничную оплату у PACER и лимиты запросов у большинства порталов. Заранее уточните условия использования конкретного источника и соблюдайте их — это общая практика, а не юридическая консультация.

Сколько запросов в час выдерживает PACER без блокировки?

Официального лимита PACER не публикует, поэтому безопасный подход — держать паузы между запросами (в примере выше 5 секунд между страницами поиска) и снижать частоту при первых признаках капчи или ошибки доступа, а не наращивать параллелизм.

Как решить reCAPTCHA v2 на PACER через API, а не вручную?

Отправьте sitekey и pageurl страницы в in.php с method=userrecaptcha, получите ID задачи в поле request и опрашивайте res.php, пока статус не сменится с CAPCHA_NOT_READY на готовый токен — как в функции solve_recaptcha выше. Токен передаётся в теле POST-запроса как g-recaptcha-response.

В каких юридических источниках CAPTCHA встречается чаще всего?

Хуже всего дело обстоит у судебных систем на уровне штатов — многие до сих пор используют капчу-изображение старого образца. Федеральные суды (PACER) используют reCAPTCHA v2, патентные базы данных также регулярно её показывают.

Что делать, если запрос к судебной базе возвращает капчу вместо результатов поиска?

Проверяйте HTML ответа на признаки капчи (data-sitekey, g-recaptcha, слово captcha) до парсинга результатов — как это делает _has_captcha в примере выше — и решайте капчу до повторной отправки запроса, а не после.


Похожие руководства


Настройте API-ключ CaptchaAI и превратите поиск судебной практики в воспроизводимый пайплайн — получите ключ CaptchaAI и запустите первый парсер уже сегодня.

Комментарии для этой статьи отключены.