¿Estás siguiendo la cobertura de un tema en decenas de medios y, a mitad de la recolección, la fuente empieza a devolver un CAPTCHA en lugar del titular? Es el punto donde se rompe casi cualquier pipeline de seguimiento de prensa. La respuesta corta: no hace falta abandonar la fuente ni resolver el desafío a mano. CaptchaAI resuelve por API los CAPTCHA que protegen los sitios de noticias —Cloudflare Turnstile, reCAPTCHA v2 y v3, Cloudflare Challenge y los CAPTCHA de imagen— para que un gabinete de prensa, una agencia de comunicación o un equipo de análisis de medios pueda recopilar titulares y metadatos de forma continua. En esta guía verás qué CAPTCHA aparece según el tipo de fuente y cómo integrarlo en un recolector en Python.
Qué CAPTCHA aparece según la fuente
No todas las fuentes protegen su contenido igual. Antes de escribir una línea de código conviene saber qué vas a encontrar, porque el tipo de CAPTCHA determina el método que envías a la API.
| Tipo de fuente | CAPTCHA | Motivo | Contenido |
|---|---|---|---|
| Principales medios | Cloudflare Turnstile | Detección de bots | Artículos y titulares |
| Agencias de cable (AP, Reuters) | reCAPTCHA v2 | Acceso masivo | Noticias de última hora |
| Publicaciones de pago | reCAPTCHA v3 | Intentos de acceso | Artículos premium |
| Diarios locales | reCAPTCHA v2 | Límite de solicitudes | Noticias regionales |
| Agregadores de noticias | Cloudflare Challenge | Detección de scraping | Feeds agregados |
| Sitios de notas de prensa | CAPTCHA de imagen | Páginas de descarga | Contenido de RR. PP. |
Todos estos tipos entran en la cobertura de CaptchaAI: resuelve reCAPTCHA v2 y v3, Cloudflare Turnstile y Challenge, GeeTest v3 y los CAPTCHA de imagen y grid. El límite honesto es que no resuelve hCaptcha ni FunCaptcha, así que si una fuente migra a esos tipos tendrás que replantear el acceso a ese medio en concreto.
Recolector con resolución de CAPTCHA integrada
El siguiente recolector detecta el CAPTCHA en la respuesta, extrae el sitekey del HTML y envía el desafío a la API. Cuando llega el token, reintenta la solicitud con el campo correcto —cf-turnstile-response para Turnstile, g-recaptcha-response para reCAPTCHA— y sigue con el parseo de titulares. La salida de red se pasa por parámetro para que puedas usar una red residencial autorizada en las fuentes más estrictas.
import requests
import time
import re
from bs4 import BeautifulSoup
from datetime import datetime
import json
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 NewsAggregator:
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 collect_headlines(self, source_url, section=None):
"""Collect headlines from a news source."""
url = f"{source_url}/{section}" if section else source_url
resp = self.session.get(url, timeout=30)
if self._has_captcha(resp.text):
resp = self._solve_and_retry(resp.text, url)
soup = BeautifulSoup(resp.text, "html.parser")
articles = []
for item in soup.select("article, .story, .headline-item, h2 a, h3 a"):
link = item if item.name == "a" else item.select_one("a")
if link:
articles.append({
"title": link.get_text(strip=True),
"url": self._abs_url(source_url, link.get("href", "")),
"source": source_url,
"collected_at": datetime.now().isoformat(),
})
return articles
def get_article(self, article_url):
"""Fetch full article content."""
resp = self.session.get(article_url, timeout=30)
if self._has_captcha(resp.text):
resp = self._solve_and_retry(resp.text, article_url)
soup = BeautifulSoup(resp.text, "html.parser")
# Remove unwanted elements
for tag in soup.select("script, style, nav, footer, .ad, .sidebar"):
tag.decompose()
content_el = soup.select_one(
"article, .article-body, .story-body, .entry-content"
)
return {
"url": article_url,
"title": self._text(soup, "h1, .article-title"),
"author": self._text(soup, ".author, .byline, [rel='author']"),
"date": self._text(soup, "time, .publish-date, .article-date"),
"content": content_el.get_text(separator="\n", strip=True) if content_el else "",
"word_count": len(content_el.get_text().split()) if content_el else 0,
}
def aggregate_sources(self, sources, max_articles_per=20):
"""Aggregate headlines across multiple sources."""
all_articles = []
for source in sources:
try:
articles = self.collect_headlines(source["url"], source.get("section"))
all_articles.extend(articles[:max_articles_per])
print(f"{source['name']}: {len(articles)} headlines")
except Exception as e:
print(f"{source['name']}: Error - {e}")
time.sleep(3)
return all_articles
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})
token = solve_captcha("userrecaptcha", sitekey, url)
return self.session.post(url, data={"g-recaptcha-response": token})
def _text(self, soup, selector):
el = soup.select_one(selector)
return el.get_text(strip=True) if el else ""
def _abs_url(self, base, href):
if href.startswith("http"):
return href
return base.rstrip("/") + "/" + href.lstrip("/")
# Usage
aggregator = NewsAggregator(
proxy="http://user:pass@residential.proxy.com:5000"
)
sources = [
{"name": "Tech News A", "url": "https://technews-a.example.com", "section": "latest"},
{"name": "Business B", "url": "https://business-b.example.com", "section": "tech"},
{"name": "Industry C", "url": "https://industry-c.example.com"},
]
headlines = aggregator.aggregate_sources(sources)
print(f"Total: {len(headlines)} headlines collected")
Cada cuánto consultar una fuente sin quemarla
El error más caro no es el CAPTCHA en sí, sino la frecuencia. Consultar la portada cada pocos segundos es la vía más rápida para que una fuente marque tu IP y empiece a exigir un desafío en cada carga. Un presupuesto por dominio mantiene el volumen bajo el radar.
| Tipo de página | Cadencia sugerida | Señal para frenar |
|---|---|---|
| Portada | 1 consulta cada 3–5 minutos | Aparición de CAPTCHA en la portada |
| Sección temática | 1 consulta cada 5–10 minutos | Respuestas 403 o cambios bruscos del DOM |
| Artículo individual | Solo bajo demanda | CAPTCHA al cargar contenido ya conocido |
Alertas por palabra clave para seguimiento de medios
El seguimiento de prensa rara vez busca todo: busca menciones. Esta clase envuelve el recolector y filtra los titulares por una lista de palabras clave —el nombre de una marca, un competidor, un tema regulatorio— y evita duplicados guardando las URL ya vistas. Puedes ejecutarla una vez o dejarla en monitoreo continuo con alertas.
class NewsMonitor:
def __init__(self, keywords, sources, proxy=None):
self.keywords = [kw.lower() for kw in keywords]
self.aggregator = NewsAggregator(proxy=proxy)
self.sources = sources
self.seen_urls = set()
def scan(self):
"""Scan for articles matching keywords."""
headlines = self.aggregator.aggregate_sources(self.sources)
matches = []
for article in headlines:
if article["url"] in self.seen_urls:
continue
title_lower = article["title"].lower()
matched_kws = [kw for kw in self.keywords if kw in title_lower]
if matched_kws:
article["matched_keywords"] = matched_kws
matches.append(article)
self.seen_urls.add(article["url"])
return matches
def continuous_monitor(self, interval_min=30):
"""Run continuous monitoring with alerts."""
while True:
matches = self.scan()
if matches:
print(f"\n=== {len(matches)} new matches found ===")
for m in matches:
print(f" [{', '.join(m['matched_keywords'])}] {m['title']}")
print(f" {m['url']}")
else:
print(f"No new matches at {datetime.now().strftime('%H:%M')}")
time.sleep(interval_min * 60)
# Monitor for specific topics
monitor = NewsMonitor(
keywords=["captcha", "bot detection", "web scraping", "automation"],
sources=sources,
proxy="http://user:pass@residential.proxy.com:5000",
)
matches = monitor.scan()
En un caso real —una agencia que sigue la reputación de un cliente en medios de España y América Latina— basta con apuntar sources a los portales relevantes y keywords al nombre de la marca y sus voceros para recibir solo lo que importa.
Errores frecuentes y cómo resolverlos
Cuando la recolección se degrada, casi siempre es uno de estos cinco problemas.
| Problema | Causa | Solución |
|---|---|---|
| Cloudflare bloquea todas las solicitudes | Detección agresiva de bots | Usa una red residencial autorizada y un User-Agent realista |
| Muro de pago en lugar del artículo | Contenido tras suscripción | Detecta el muro de pago y omítelo o etiquétalo |
| CAPTCHA en cada página | IP marcada | Rota las salidas de red autorizadas y añade retrasos de más de 5 segundos |
| Contenido del artículo vacío | Contenido renderizado en JS | Usa Selenium o Puppeteer para sitios SPA |
| Artículos duplicados | La misma noticia en varias fuentes | Deduplica por similitud de título |
Recolección responsable
La agregación de titulares y metadatos para investigación o seguimiento es una práctica habitual; reproducir artículos completos con derechos de autor sin permiso no lo es. Trabaja con fragmentos, enlaza siempre a la fuente original y respeta los términos de servicio de cada medio y la normativa de protección de datos aplicable —GDPR y LOPDGDD en España, LFPDPPP en México, LGPD en Brasil—. El objetivo es medir cobertura, no republicar contenido ajeno.
Preguntas frecuentes
¿Qué CAPTCHA de sitios de noticias resuelve CaptchaAI?
Los que aparecen en la práctica: Cloudflare Turnstile y Cloudflare Challenge, reCAPTCHA v2 y v3, GeeTest v3 y los CAPTCHA de imagen. No resuelve hCaptcha ni FunCaptcha, así que si una fuente migra a esos tipos tendrás que tratar ese medio aparte.
¿Cuánto cuesta monitorear varias fuentes a la vez?
Depende de cuántas solicitudes tengas en vuelo al mismo tiempo, no de cuántos CAPTCHA resuelvas. CaptchaAI cobra por thread concurrente con resoluciones ilimitadas dentro del plan. Para seguir unas pocas fuentes, el plan BASIC ($15/mes, 5 threads) suele bastar; si paralelizas decenas de dominios, STANDARD ($30/mes, 15 threads) o ADVANCE ($90/mes, 50 threads) dan más margen.
¿Con qué frecuencia puedo consultar una fuente antes de que aparezca el CAPTCHA?
No hay un número universal, pero la tabla de cadencia es un buen punto de partida: minutos, no segundos, entre consultas a la misma sección. Si el CAPTCHA empieza a aparecer en cada carga, baja la frecuencia y rota la salida de red antes de volver a subir el volumen.
¿Necesito un navegador headless para las fuentes que cargan con JavaScript?
Solo cuando el contenido se renderiza en el cliente. Para HTML estático, requests y BeautifulSoup bastan; en sitios SPA que construyen el artículo con JavaScript, un navegador headless controlado por Selenium o Puppeteer recupera el DOM ya renderizado antes de resolver el CAPTCHA.
Guías relacionadas
- QA de flujos autorizados con resolución de CAPTCHA
- Investigación en redes sociales con manejo de CAPTCHA
Agrega noticias de cualquier fuente: obtén tu clave de CaptchaAI y automatiza la recolección de contenido.