Trường Hợp Sử Dụng

Quét web nghiên cứu pháp lý bằng cách xử lý CAPTCHA

PACER, SEC EDGAR, hệ thống tòa án tiểu bang và cơ sở dữ liệu sáng chế đều dùng reCAPTCHA v2 hoặc CAPTCHA hình ảnh để chặn truy cập tự động — script tra cứu án lệ chạy mượt trên dữ liệu mẫu vẫn có thể bị treo ngay khi đụng dữ liệu thật. Với công ty luật, đội legal-tech và bộ phận tuân thủ cần tra cứu án lệ hàng loạt hay theo dõi hồ sơ vụ án, CAPTCHA thường là nút thắt lớn nhất trong pipeline. Bài này đi thẳng vào cách CaptchaAI giải các thử thách đó qua API, kèm code Python chạy được cho công cụ tìm kiếm án lệ và giám sát hồ sơ theo quy định.


CAPTCHA chặn dữ liệu pháp lý ở đâu

Nguồn Loại CAPTCHA Dữ liệu Người dùng
PACER reCAPTCHA v2 Hồ sơ tòa án liên bang Đội tranh tụng
Hệ thống tòa án tiểu bang CAPTCHA hình ảnh / reCAPTCHA Hồ sơ vụ án tiểu bang Luật sư
SEC EDGAR reCAPTCHA v2 Hồ sơ doanh nghiệp Đội tuân thủ
Cơ sở dữ liệu sáng chế reCAPTCHA v2 Hồ sơ bằng sáng chế Chuyên gia sở hữu trí tuệ
Cổng thông tin quy định CAPTCHA hình ảnh Quy định, hướng dẫn Đội tuân thủ
Cơ sở dữ liệu trích dẫn án lệ reCAPTCHA v2 Trích dẫn vụ án Legal tech
Danh bạ đoàn luật sư reCAPTCHA v2 Hồ sơ luật sư Thẩm định hồ sơ

Vì sao đội pháp lý và outsourcing tại Việt Nam quan tâm bài toán này

Nhiều công ty outsourcing và legal-tech tại TP.HCM, Hà Nội xây dựng pipeline tra cứu án lệ, thẩm định hồ sơ (due diligence) cho khách hàng là công ty luật hoặc quỹ đầu tư ở Mỹ. Khối lượng này gần như luôn đụng CAPTCHA, vì PACER hay hệ thống tòa án tiểu bang được thiết kế để chặn bot ngay từ đầu. Khi đội lưu trữ dữ liệu vụ án đã thu thập — nhất là dữ liệu cá nhân trong hồ sơ tố tụng — Nghị định 13/2023/NĐ-CP về bảo vệ dữ liệu cá nhân yêu cầu ghi log truy cập và tối thiểu hóa dữ liệu lưu trữ.


Xây dựng công cụ tìm kiếm án lệ tự động

Class dưới đây gộp cả luồng xử lý: gửi request tìm kiếm, phát hiện CAPTCHA, giải bằng CaptchaAI rồi parse kết quả — dùng được cho cả reCAPTCHA v2 lẫn CAPTCHA hình ảnh trên các cổng án lệ.

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")

solve_recaptcha() polling res.php mỗi 5 giây trong tối đa 5 phút; solve_image_captcha() xử lý các cổng tòa án tiểu bang vẫn còn dùng CAPTCHA hình ảnh kiểu cũ, không cần sitekey.


Giám sát hồ sơ theo quy định

Để theo dõi hồ sơ mới trên nhiều cổng quy định cùng lúc, bọc LegalResearchScraper trong một lớp giám sát có nhớ trạng thái đã thấy:

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

RegulatoryMonitor chỉ trả về các mục mới chưa thấy trước đó — chạy theo cron mỗi giờ là đủ cho phần lớn trường hợp giám sát hồ sơ.


Chọn gói CaptchaAI theo khối lượng tra cứu án lệ

CaptchaAI tính phí theo thread, không theo mỗi lần giải — mỗi thread giải không giới hạn số CAPTCHA trong tháng. Đội thẩm định hồ sơ nhỏ chạy vài nghìn lượt tra cứu PACER mỗi tháng thường đủ với gói BASIC ($15/tháng, 5 thread). Khi giám sát nhiều cổng quy định song song hoặc crawl cả SEC EDGAR lẫn cơ sở dữ liệu sáng chế, ADVANCE ($90/tháng, 50 thread) cho throughput cao hơn mà chi phí không tăng theo số CAPTCHA đã giải.


Xử lý lỗi thường gặp

  • Tìm kiếm không trả về kết quả — CAPTCHA được trả về thay vì trang kết quả. Kiểm tra CAPTCHA trước khi parse HTML.
  • CAPTCHA hình ảnh liên tục sai — Ảnh chữ bị bóp méo mạnh. Báo lỗi task, lấy ảnh mới rồi giải lại.
  • PACER chặn truy cập — Vượt giới hạn tần suất request. Đợi 30 phút rồi giảm tần suất request.
  • Giám sát hồ sơ bỏ sót mục mới — Chu kỳ kiểm tra quá dài. Tăng tần suất kiểm tra (poll thường xuyên hơn).
  • Chi tiết vụ việc không đầy đủ — Nội dung nằm sau bức tường phí. Trả phí mỗi trang nếu hệ thống yêu cầu.

Hướng dẫn liên quan


Câu hỏi thường gặp

reCAPTCHA v2 và CAPTCHA hình ảnh trên cổng tòa án khác nhau thế nào khi giải bằng API?

CaptchaAI xử lý cả hai qua cùng cặp endpoint in.php/res.php. Với reCAPTCHA v2 (PACER, SEC EDGAR, phần lớn cơ sở dữ liệu sáng chế), gửi sitekeypageurl qua method=userrecaptcha, polling res.php lấy token rồi post vào form dưới field g-recaptcha-response. Với CAPTCHA hình ảnh (nhiều hệ thống tòa án tiểu bang cũ), gửi ảnh base64 qua method=base64 và nhận lại text — không cần sitekey.

Thu thập tự động hồ sơ tòa án công khai có hợp pháp không?

Có, về nguyên tắc. Hồ sơ tòa án công khai như PACER hay hồ sơ tiểu bang nói chung được phép truy cập tự động; vấn đề thường nằm ở tốc độ request và điều khoản sử dụng chứ không phải bản thân việc scraping. Luôn tôn trọng rate limit và đọc kỹ terms of use trước khi chạy pipeline quy mô lớn.

Giải CAPTCHA cho vài nghìn lượt tra cứu PACER mỗi tháng tốn bao nhiêu?

Với gói BASIC ($15/tháng, 5 thread), bạn giải không giới hạn CAPTCHA trong tháng — chi phí không tăng theo số lượt tra cứu, chỉ giới hạn bởi số thread chạy song song. Đội xử lý vài nghìn hồ sơ mỗi tháng thường không cần nâng cấp; chỉ khi crawl song song nhiều cổng cùng lúc mới nên lên ADVANCE ($90/tháng, 50 thread).

Cần lưu ý gì về bảo vệ dữ liệu khi lưu hồ sơ vụ án đã thu thập?

Nếu hồ sơ chứa dữ liệu cá nhân (tên đương sự, luật sư, địa chỉ), Nghị định 13/2023/NĐ-CP yêu cầu tối thiểu hóa dữ liệu lưu trữ và ghi log truy cập. Thiết kế pipeline có audit log và thời hạn lưu trữ rõ ràng ngay từ đầu, thay vì bổ sung sau khi phát sinh khiếu nại.


Lấy API key CaptchaAI và đưa reCAPTCHA v2 hay CAPTCHA hình ảnh trên PACER, SEC EDGAR vào pipeline tra cứu án lệ tự động của bạn — lấy API key CaptchaAI ngay hôm nay.

Os comentários estão desativados para este artigo.