Un equipo de tecnología legal que rastrea cientos de resoluciones al día rara vez se detiene por el scraping en sí: se detiene cuando el buscador judicial devuelve un reCAPTCHA v2 en lugar de resultados. Ese es el cuello de botella real de la investigación jurídica automatizada. CaptchaAI resuelve los dos tipos que dominan estos portales —reCAPTCHA v2 y el CAPTCHA de imagen (OCR)— para que tu pipeline siga corriendo mientras buscas jurisprudencia, vigilas expedientes y agregas datos regulatorios.
Portales jurídicos que usan CAPTCHA y de qué tipo
Antes de escribir una sola línea de scraping conviene saber contra qué CAPTCHA vas a chocar en cada fuente. La mayoría de los repositorios legales se reparten entre reCAPTCHA v2 y CAPTCHA de imagen antiguos:
| Fuente | Tipo de CAPTCHA | Datos | Quién lo usa |
|---|---|---|---|
| PACER | reCAPTCHA v2 | Presentaciones judiciales federales | Equipos de litigio |
| Sistemas judiciales estatales | CAPTCHA de imagen / reCAPTCHA | Registros de casos estatales | Abogados |
| SEC EDGAR | reCAPTCHA v2 | Presentaciones corporativas | Cumplimiento |
| Bases de datos de patentes | reCAPTCHA v2 | Registros de patentes | Investigadores de propiedad intelectual |
| Portales regulatorios | CAPTCHA de imagen | Normas y guías | Cumplimiento |
| Bases de citas jurídicas | reCAPTCHA v2 | Citas de casos | Legal tech |
| Directorios de colegios de abogados | reCAPTCHA v2 | Registros de letrados | Debida diligencia |
La conclusión práctica: si resuelves bien reCAPTCHA v2 e imagen/OCR, cubres la mayor parte del mapa, y CaptchaAI atiende ambos con la misma clave API.
Buscador de jurisprudencia con Python
El siguiente scraper cubre el flujo completo: detecta el CAPTCHA en la página, lo envía a CaptchaAI, recupera el token y reintenta la solicitud ya validada. La clase LegalResearchScraper separa la búsqueda, el detalle del caso y el seguimiento de expedientes, y exporta a 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: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")
El detalle clave: cuando la página trae un reCAPTCHA v2, extraes el sitekey, obtienes el token g-recaptcha-response de CaptchaAI y reenvías la búsqueda con ese token en el POST. Si aparece un CAPTCHA de imagen, _solve_image_and_retry descarga la imagen, la manda en base64 y adjunta la respuesta como texto. La pausa de cinco segundos entre páginas mantiene tu ritmo por debajo de lo que la mayoría de los portales considera abuso.
Monitoreo regulatorio continuo
La investigación jurídica pocas veces es un rastreo único. Cumplimiento y estudios de abogados necesitan enterarse de una nueva presentación el mismo día en que aparece. RegulatoryMonitor reutiliza el scraper anterior, guarda lo ya visto en memoria y solo devuelve las novedades, de modo que puedes programarlo cada hora sin duplicar alertas.
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
Para producción, sustituye el set en memoria por Redis o una tabla en base de datos, de modo que el estado sobreviva a un reinicio.
Escenario: seguimiento de jurisprudencia en el mundo hispanohablante
El mismo patrón se traslada tal cual a los portales oficiales de habla hispana. El CENDOJ (buscador de jurisprudencia del Consejo General del Poder Judicial en España), el BOE, el Diario Oficial de la Federación en México o el SAIJ en Argentina publican resoluciones y normativa de acceso público, y muchos de sus buscadores se protegen con reCAPTCHA o CAPTCHA de imagen. Un despacho que sigue cambios regulatorios en varios países puede apuntar RegulatoryMonitor a cada portal y consolidar las novedades en un CSV o panel.
Dos advertencias válidas en toda la región: respeta los términos de servicio del portal y la normativa de protección de datos aplicable —GDPR y LOPDGDD en España, LFPDPPP en México o su equivalente— y limita tu ritmo a lo que el sitio tolera. Automatizar el acceso a información pública es legítimo; saturar el servicio no lo es.
Cómo evitar bloqueos y errores comunes
La mayoría de los fallos en producción no vienen del solver, sino de detalles operativos: confundir una página CAPTCHA con resultados vacíos, o disparar demasiadas solicitudes seguidas. Esta tabla resume lo más habitual:
| Problema | Causa | Solución |
|---|---|---|
| El CAPTCHA de imagen falla una y otra vez | Texto muy distorsionado | Reporta y reintenta con una imagen nueva |
| PACER bloquea el acceso | Límite de solicitudes superado | Espera 30 minutos y baja la frecuencia de solicitudes |
| Detalles del caso incompletos | Contenido tras un muro de pago | Paga la tarifa por página cuando sea imprescindible |
| La búsqueda no devuelve nada | Llegó la página CAPTCHA en lugar de resultados | Verifica si hay CAPTCHA antes de parsear |
| El seguimiento omite presentaciones | Intervalo de control demasiado largo | Reduce el intervalo y aumenta la frecuencia de control |
La regla de oro es introducir esperas entre solicitudes y reintentos con retroceso exponencial cuando un portal empieza a responder con lentitud: es más barato ir despacio que quedar en una lista de bloqueo.
Qué plan de CaptchaAI se ajusta a la investigación jurídica
CaptchaAI cobra por thread concurrente, no por CAPTCHA resuelto, y cada plan incluye resoluciones ilimitadas dentro del mes: un thread es un CAPTCHA en curso y, al terminar, queda libre para el siguiente. Para agencias y freelancers de la región que facturan en monedas volátiles, este costo mensual predecible en USD es más fácil de presupuestar que un esquema de pago por resolución.
Preguntas frecuentes
¿Qué tipos de CAPTCHA aparecen en los portales jurídicos y cuáles resuelve CaptchaAI?
Predominan reCAPTCHA v2 en los tribunales federales y de citas, y CAPTCHA de imagen (OCR) en los sistemas estatales antiguos. CaptchaAI resuelve ambos, además de reCAPTCHA v3, grid, Cloudflare Turnstile y GeeTest v3. No resuelve hCaptcha ni FunCaptcha: verifica el tipo antes de integrar.
¿Automatizar la consulta de registros judiciales cumple con la normativa de protección de datos?
Depende del portal y del dato. Los registros judiciales públicos suelen ser accesibles, pero debes respetar los términos de servicio del sitio y las leyes aplicables —GDPR y LOPDGDD en España, LFPDPPP en México y equivalentes—. No es asesoría legal: ante datos personales sensibles, consúltalo con tu equipo jurídico.
¿Cómo controlo el ritmo para no saturar el portal ni que me bloqueen?
Introduce pausas fijas entre páginas (el ejemplo usa cinco segundos), aplica reintentos con retroceso exponencial y respeta cualquier límite de solicitudes publicado. Si un portal empieza a devolver errores o CAPTCHA en cadena, baja la frecuencia antes de que te incluya en una lista de bloqueo.
¿Qué plan de CaptchaAI conviene para investigación jurídica a gran escala?
Para un solo scraper secuencial, BASIC ($15/mes, 5 threads) suele bastar. Si monitoreas varios portales en paralelo o corres varios workers, sube a STANDARD ($30/mes, 15 threads) o ADVANCE ($90/mes, 50 threads): al ser ilimitado por thread, pagas por concurrencia, no por volumen de resoluciones.
Guías relacionadas
- Automatización de portales gubernamentales con CAPTCHA
- Scraping de investigación académica con manejo de CAPTCHA
- QA y pruebas autorizadas de resolución de CAPTCHA
Agiliza tu investigación jurídica: obtén tu clave de CaptchaAI y automatiza las búsquedas de jurisprudencia y el seguimiento de expedientes.