Casos de Uso

Web Scraping de investigación académica con resolución CAPTCHA

¿Reúnes referencias para una revisión sistemática y el buscador te corta con un CAPTCHA cada pocas búsquedas? La salida práctica es automatizar la extracción y delegar el CAPTCHA a un servicio de resolución. Con la API de CaptchaAI, tu script resuelve el reCAPTCHA v2, el reCAPTCHA v3 o el Cloudflare Turnstile de portales como Google Scholar, PubMed o Scopus y sigue recopilando datos sin intervención manual. Esta guía muestra cómo montar ese flujo en Python, de los metadatos y las citas al análisis bibliométrico.

El mismo patrón sirve para repositorios muy consultados en el mundo hispanohablante —SciELO, Redalyc o Dialnet— y para buscadores de revistas universitarias. Respeta los términos de servicio del sitio y la normativa de protección de datos aplicable (RGPD y LOPDGDD en España, LFPDPPP en México).


Qué portales académicos piden CAPTCHA

Fuente Tipo de CAPTCHA Qué lo dispara Datos disponibles
Google Scholar reCAPTCHA v3 Consultas de gran volumen Citas, artículos
PubMed reCAPTCHA v2 Búsquedas repetidas Literatura biomédica
Web of Science Cloudflare Turnstile Descargas masivas Métricas de citas
Scopus reCAPTCHA v2 Operaciones de exportación Datos bibliométricos
IEEE Xplore reCAPTCHA v2 Búsqueda + descarga Artículos de ingeniería
JSTOR reCAPTCHA v2 Acceso a páginas Humanidades / ciencias sociales

Los tres tipos están entre los que CaptchaAI resuelve, así que un mismo cliente cubre casi todos estos buscadores. Conviene tener claro cómo se comporta cada uno:

  • reCAPTCHA v3 (Google Scholar) no muestra un desafío visible: puntúa cada solicitud en segundo plano, así que el bloqueo llega de golpe tras varias consultas seguidas.
  • reCAPTCHA v2 (PubMed, Scopus, IEEE Xplore, JSTOR) es el clásico «no soy un robot» que salta al repetir búsquedas o lanzar una exportación.
  • Cloudflare Turnstile (Web of Science) aparece sobre todo en descargas masivas y en accesos automatizados repetidos.

Planifica el ritmo antes de escribir código

Antes de lanzar el primer script conviene fijar un ritmo por fuente. Los portales académicos marcan y bloquean IP con rapidez, y un ritmo agresivo solo multiplica los CAPTCHA que tendrás que resolver después. Esta tabla resume un punto de partida conservador:

Fuente Retraso recomendado Páginas máx./hora
Google Scholar 10–15 segundos 40–50
PubMed 3–5 segundos 100
Web of Science 5–10 segundos 60
Scopus 5–10 segundos 60
IEEE 3–5 segundos 100
JSTOR 5–10 segundos 60

Trata estos valores como un mínimo prudente, no como un objetivo a apurar:

  • Empieza por el límite inferior de cada rango y súbelo solo si la fuente no devuelve CAPTCHA.
  • Reparte las consultas a lo largo del día en lugar de concentrarlas en ráfagas.
  • Registra qué fuente bloquea antes para ajustar su retraso de forma individual.

Recopilar metadatos y citas con Python

El siguiente recopilador detecta el CAPTCHA en cada página, lo envía a CaptchaAI y reintenta la solicitud con el token devuelto. Al terminar, exporta los resultados a CSV:

import requests
import time
import re
from bs4 import BeautifulSoup
import csv

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"


def solve_captcha(method, sitekey, pageurl, **kwargs):
    data = {
        "key": CAPTCHAAI_KEY, "method": method,
        "googlekey": sitekey, "pageurl": pageurl, "json": 1,
    }
    data.update(kwargs)
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=data)
    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,
        })
        r = result.json()
        if r["request"] != "CAPCHA_NOT_READY":
            return r["request"]
    raise TimeoutError("Timeout")


class AcademicScraper:
    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",
            "Accept-Language": "en-US,en;q=0.9",
        })

    def search_papers(self, search_url, query, max_pages=10):
        """Search academic database for papers matching query."""
        all_papers = []

        for page in range(max_pages):
            url = f"{search_url}?q={query}&start={page * 10}"
            resp = self.session.get(url, timeout=30)

            # Handle CAPTCHA
            if self._has_captcha(resp.text):
                resp = self._solve_and_retry(resp.text, url)

            papers = self._parse_results(resp.text)
            if not papers:
                break  # No more results

            all_papers.extend(papers)
            print(f"Page {page + 1}: {len(papers)} papers")
            time.sleep(5)  # Respectful delay

        return all_papers

    def get_paper_details(self, paper_url):
        """Get detailed metadata for a single paper."""
        resp = self.session.get(paper_url, timeout=30)

        if self._has_captcha(resp.text):
            resp = self._solve_and_retry(resp.text, paper_url)

        soup = BeautifulSoup(resp.text, "html.parser")
        return {
            "title": self._safe_text(soup, "h1, .article-title"),
            "authors": self._safe_text(soup, ".authors, .author-list"),
            "abstract": self._safe_text(soup, ".abstract, #abstract"),
            "doi": self._safe_text(soup, ".doi, [data-doi]"),
            "journal": self._safe_text(soup, ".journal-name, .publication"),
            "year": self._safe_text(soup, ".pub-date, .year"),
            "citations": self._safe_text(soup, ".citation-count, .cited-by"),
        }

    def export_to_csv(self, papers, filename):
        """Export collected papers to CSV."""
        if not papers:
            return
        keys = papers[0].keys()
        with open(filename, "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=keys)
            writer.writeheader()
            writer.writerows(papers)
        print(f"Exported {len(papers)} papers to {filename}")

    def _has_captcha(self, html):
        return any(tag in html.lower() for tag in [
            'data-sitekey', 'g-recaptcha', 'cf-turnstile',
        ])

    def _solve_and_retry(self, html, url):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if not match:
            return self.session.get(url)

        sitekey = match.group(1)
        if 'cf-turnstile' in html:
            token = solve_captcha("turnstile", sitekey, url)
            return self.session.post(url, data={"cf-turnstile-response": token})
        else:
            token = solve_captcha("userrecaptcha", sitekey, url)
            return self.session.post(url, data={"g-recaptcha-response": token})

    def _parse_results(self, html):
        soup = BeautifulSoup(html, "html.parser")
        papers = []
        for item in soup.select(".gs_r, .search-result, article.result"):
            title_el = item.select_one("h3 a, .result-title a")
            if title_el:
                papers.append({
                    "title": title_el.get_text(strip=True),
                    "url": title_el.get("href", ""),
                    "snippet": self._safe_text(item, ".gs_rs, .abstract-snippet"),
                    "authors": self._safe_text(item, ".gs_a, .author-info"),
                })
        return papers

    def _safe_text(self, soup, selector):
        el = soup.select_one(selector)
        return el.get_text(strip=True) if el else ""


# Usage — Literature review
scraper = AcademicScraper(
    proxy="http://user:pass@residential.proxy.com:5000"
)

papers = scraper.search_papers(
    "https://scholar.example.com/scholar",
    query="machine learning CAPTCHA solving",
    max_pages=5,
)

# Get details for top papers
detailed = []
for paper in papers[:20]:
    if paper["url"]:
        detail = scraper.get_paper_details(paper["url"])
        detailed.append(detail)
        time.sleep(3)

scraper.export_to_csv(detailed, "literature_review.csv")

El método _solve_and_retry decide por el marcado: si ve cf-turnstile usa el método turnstile; si no, userrecaptcha. En conjunto, el recopilador sigue cuatro pasos por página:

  • Detecta el CAPTCHA en cada respuesta con _has_captcha.
  • Envía el sitekey a CaptchaAI y espera el token resuelto.
  • Reintenta la solicitud con el token en el campo correcto (g-recaptcha-response o cf-turnstile-response).
  • Exporta los resultados a CSV para tu gestor de referencias.

Construir una red de citas para análisis bibliométrico

Para un estudio bibliométrico puedes seguir los enlaces de «citado por» y armar una red de citas de forma recursiva, reutilizando el mismo scraper y su lógica de CAPTCHA. Limita la profundidad y la amplitud para no disparar los bloqueos:

def bibliometric_analysis(scraper, seed_papers, depth=2):
    """Follow citations to build a citation network."""
    visited = set()
    network = []

    def _crawl(paper_url, current_depth):
        if current_depth > depth or paper_url in visited:
            return
        visited.add(paper_url)

        try:
            details = scraper.get_paper_details(paper_url)
            network.append(details)

            # Follow "cited by" links
            resp = scraper.session.get(f"{paper_url}/citations", timeout=30)
            if scraper._has_captcha(resp.text):
                resp = scraper._solve_and_retry(resp.text, f"{paper_url}/citations")

            citations = scraper._parse_results(resp.text)
            for cite in citations[:5]:  # Limit breadth
                if cite["url"]:
                    _crawl(cite["url"], current_depth + 1)
                    time.sleep(3)

        except Exception as e:
            print(f"Error crawling {paper_url}: {e}")

    for paper in seed_papers:
        _crawl(paper["url"], 0)

    return network

Los parámetros depth y el recorte citations[:5] acotan el tamaño de la red: sin esos límites, una semilla muy citada dispara miles de solicitudes y multiplica los CAPTCHA. Empieza con depth=2 y amplía solo si el volumen resultante sigue siendo manejable.


Errores comunes y cómo resolverlos

Problema Causa Solución
CAPTCHA en cada búsqueda El portal marcó la IP Cambia de proxy y sube el retraso por encima de 15 s
No se obtuvieron resultados Devolvió la página de CAPTCHA en lugar de datos Comprueba el CAPTCHA antes de analizar el HTML
Falta el resumen Contenido tras un muro de pago Usa un proxy institucional o una versión en acceso abierto
Google Scholar bloquea la IP Se superó el límite de solicitudes Espera 30 minutos y usa otra IP
Exportación limitada El sitio limita las descargas masivas Descarga en lotes más pequeños

La mayoría de estos fallos se reducen a lo mismo: pedir demasiado y demasiado rápido. Si el patrón se repite, vuelve a la tabla de ritmo antes de tocar el código.


El scraping académico es legal en muchos escenarios, pero el marco cambia según la fuente y tu jurisdicción. Antes de recopilar a escala, revisa tres cosas:

  • Términos de servicio del portal. Algunos prohíben el acceso automatizado; priorizar las API oficiales (E-utilities de PubMed, Crossref) te ahorra el conflicto.
  • Naturaleza de los datos. Los metadatos bibliográficos rara vez son personales, pero los perfiles de autor y las afiliaciones sí pueden serlo; ahí aplican el RGPD y la LOPDGDD en España o la LFPDPPP en México.
  • Uso posterior. Publicar un dataset derivado tiene requisitos distintos a un análisis interno puntual.

Preguntas frecuentes

Depende. Los metadatos públicos (títulos, autores, resúmenes) suelen ser accesibles, pero el texto completo se rige por la licencia de cada editorial. Respeta los términos de servicio y prioriza las API oficiales cuando existan, como E-utilities de PubMed.

¿Qué tipos de CAPTCHA resuelve CaptchaAI en estos portales?

Los que más aparecen: reCAPTCHA v2, reCAPTCHA v3 y Cloudflare Turnstile, todos compatibles. No resuelve hCaptcha ni FunCaptcha, pero esos rara vez se usan en sitios académicos.

¿Cuánto cuesta resolver miles de CAPTCHA en un proyecto de investigación?

CaptchaAI cobra por threads concurrentes, con resoluciones ilimitadas dentro de cada plan. El plan BASIC ($15/mes, 5 threads) sirve para revisiones puntuales; para recopilaciones sostenidas, STANDARD ($30/mes, 15 threads) o ADVANCE ($90/mes, 50 threads) dan más paralelismo. El costo mensual en USD es predecible.

¿Puedo combinar CaptchaAI con las API oficiales de las bases de datos?

Sí, y es lo recomendable. Usa las API oficiales (E-utilities de PubMed, Crossref) siempre que puedas y reserva el scraping con resolución de CAPTCHA para las fuentes que no exponen ninguna.


Guías relacionadas


Empieza hoy: crea tu clave de CaptchaAI y automatiza la recopilación de datos para tu próxima revisión de literatura.

Los comentarios están deshabilitados para este artículo.