Use Cases

Scraping Riset Hukum dengan Penanganan CAPTCHA

Tim legaltech dan kepatuhan yang memantau putusan pengadilan atau filing regulasi kerap berhenti di satu titik: halaman pencarian meminta reCAPTCHA v2 atau image CAPTCHA sebelum menampilkan hasil. Artikel ini menunjukkan cara mengotomatiskan riset hukum di Python — meneruskan tantangan CAPTCHA ke CaptchaAI, lalu mengembalikan token ke form pencarian agar scraping berlanjut tanpa intervensi manual.

Fokusnya pada reCAPTCHA v2 dan image CAPTCHA, dua tipe yang paling sering muncul di portal hukum. Bagian berikut menjelaskan alur token CaptchaAI, kelas scraper yang bisa dipakai ulang, pemantauan filing regulasi secara berkala, dan catatan kepatuhan untuk pasar Indonesia.


Portal riset hukum yang memasang CAPTCHA

Jenis CAPTCHA menentukan metode API: reCAPTCHA v2 lewat userrecaptcha, image CAPTCHA lewat base64.

Sumber Jenis CAPTCHA Data Pengguna
PACER reCAPTCHA v2 Filing pengadilan federal Tim litigasi
Sistem pengadilan negara bagian Image CAPTCHA / reCAPTCHA Catatan kasus negara bagian Pengacara
SEC EDGAR reCAPTCHA v2 Filing perusahaan Kepatuhan
Database paten reCAPTCHA v2 Catatan paten Peneliti IP
Portal regulasi Image CAPTCHA Aturan, panduan Kepatuhan
Database sitasi hukum reCAPTCHA v2 Sitasi kasus Legaltech
Direktori asosiasi advokat reCAPTCHA v2 Catatan pengacara Due diligence

Alur token CaptchaAI dalam empat langkah

Baik reCAPTCHA v2 maupun image CAPTCHA mengikuti pola yang sama. Pahami empat langkah ini sekali, dan sisanya hanya soal menempelkan token ke form yang tepat:

  1. Kirim task ke in.php — sertakan sitekey dan URL halaman untuk reCAPTCHA v2, atau gambar ber-base64 untuk image CAPTCHA.
  2. Simpan task ID dari respons in.php.
  3. Polling res.php hingga token siap, dengan jeda beberapa detik antar-cek agar tidak membebani API.
  4. Pakai token pada request pencarian berikutnya sebagai g-recaptcha-response atau field jawaban image.

Kode di bawah membungkus keempat langkah ini agar scraper Anda tidak perlu mengulang logika polling di setiap fungsi.


Membangun scraper pencarian kasus hukum

Kelas berikut membungkus tiga pekerjaan inti — cari kasus, ambil detail, pantau docket. Setiap request mengecek apakah halaman mengembalikan CAPTCHA; jika ya, token CaptchaAI dilampirkan sebagai g-recaptcha-response atau field jawaban image sebelum request diulang.

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

Perhatikan LegalResearchScraper memakai satu requests.Session, sehingga cookie dan header tetap konsisten antar-request — penting saat portal seperti PACER mengamati perilaku sesi sebelum memutuskan menampilkan CAPTCHA.


Memantau filing regulasi baru secara otomatis

Untuk kepatuhan, kebutuhan tersering adalah pemantauan berkala. Kelas RegulatoryMonitor menyimpan sitasi dan judul yang sudah diproses di sebuah set, lalu hanya mengembalikan filing baru.

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

Jalankan pemantau lewat penjadwal seperti cron; deploy di region seperti AWS ap-southeast-3 (Jakarta) menjaga latensi rendah.


Kepatuhan dan konteks pasar Indonesia

Di Indonesia, riset hukum otomatis paling sering dijalankan oleh:

  • Startup legaltech yang membangun mesin pencari putusan dan sitasi kasus.
  • Tim kepatuhan perusahaan yang memantau perubahan regulasi sektoral.
  • Konsultan hukum yang menyusun due diligence untuk klien.

Apa pun kasusnya, satu prinsip universal berlaku: kumpulkan hanya data yang boleh Anda proses. UU Pelindungan Data Pribadi (UU 27/2022) menegaskan data pribadi tidak boleh dikumpulkan tanpa dasar yang sah — batasi scraping pada dokumen publik dan hindari data pribadi di balik login. Ini bukan nasihat hukum; saat ragu, konsultasikan tim legal Anda.


Mengatasi masalah umum saat scraping

Sebagian besar kegagalan scraping riset hukum berulang di titik yang sama. Berikut penyebab tersering beserta cara menanganinya:

Masalah Penyebab Solusi
Image CAPTCHA gagal berulang kali Teks terdistorsi Laporkan dan coba lagi dengan gambar baru
PACER memblokir akses Rate limit terlampaui Tunggu 30 menit, kurangi frekuensi request
Detail kasus tidak lengkap Di balik paywall Bayar biaya per halaman bila perlu
Pencarian tidak menghasilkan apa pun Halaman CAPTCHA dikembalikan Periksa CAPTCHA sebelum parsing
Filing baru terlewat oleh monitor Interval pengecekan terlalu panjang Perpendek interval, tingkatkan frekuensi cek

Pertanyaan umum

Apakah CaptchaAI mendukung reCAPTCHA v2 dan image CAPTCHA untuk portal hukum?

Ya, keduanya didukung — dan itu dua tipe tersering di database hukum. reCAPTCHA v2 biasanya selesai dalam waktu di bawah 60 detik, sedangkan image CAPTCHA jauh lebih cepat. hCaptcha dan FunCaptcha belum didukung; jika sebuah situs memakainya, Anda perlu solusi lain.

Berapa thread yang dibutuhkan untuk scraping database hukum?

Bergantung pada tingkat paralelisme. Untuk pemantauan satu atau dua portal, BASIC ($15/bulan, 5 thread) biasanya cukup; naikkan ke STANDARD ($30/bulan, 15 thread) saat menambah sumber. Semua paket memberi solve tanpa batas per thread, jadi biaya tetap prediktabel.

Bagaimana mengatur frekuensi request agar tidak memicu rate limit?

Beri jeda antar-request (skrip ini memakai time.sleep(5)), batasi max_pages, dan pantau respons 429. Untuk sumber ketat seperti PACER, kurangi paralelisme dan perpanjang jeda; perhatikan tarif per halaman PACER.

Bagaimana menjaga scraping riset hukum tetap patuh terhadap UU PDP?

Kumpulkan hanya dokumen publik dan hindari data pribadi di balik login. UU Pelindungan Data Pribadi (UU 27/2022) mensyaratkan dasar yang sah untuk memproses data pribadi, jadi batasi cakupan pada putusan, sitasi, dan filing yang memang terbuka untuk umum. Untuk kasus abu-abu, konsultasikan tim legal Anda; artikel ini bukan nasihat hukum.

Bisakah pemantauan filing regulasi dijalankan otomatis di server?

Bisa. Bungkus RegulatoryMonitor dalam skrip terjadwal seperti cron, lalu deploy di region terdekat seperti AWS ap-southeast-3 (Jakarta) agar latensi ke portal target tetap rendah. Simpan sitasi yang sudah diproses di dalam set agar hanya filing baru yang dilaporkan pada setiap siklus.


Panduan terkait

Komentar dinonaktifkan untuk artikel ini.