Use Cases

Agregação de notícias e mídia com tratamento CAPTCHA

Se você monta um clipping diário para uma assessoria de imprensa, metade das fontes carrega normalmente e a outra trava em um desafio Cloudflare Turnstile ou reCAPTCHA antes da primeira manchete aparecer. A resposta é resolver o desafio na camada de coleta com uma API dedicada, não adivinhar manualmente como cada portal filtra os robôs. Este guia mostra onde o CAPTCHA aparece por tipo de fonte e como montar, em Python, um agregador que resolve reCAPTCHA v2, reCAPTCHA v3 e Cloudflare Turnstile com a API da CaptchaAI.


Onde o CAPTCHA aparece na cobertura de notícias

Antes de escrever código, vale mapear qual desafio cada tipo de fonte costuma usar — isso evita um agregador genérico que falha silenciosamente em metade das fontes.

Tipo de fonte CAPTCHA Gatilho Conteúdo
Grandes portais de notícia Cloudflare Turnstile Detecção de bots Matérias, manchetes
Agências de notícias (AP, Reuters) reCAPTCHA v2 Acesso em massa Notícias de última hora
Publicações com acesso pago reCAPTCHA v3 Tentativas de acesso Matérias premium
Portais regionais reCAPTCHA v2 Limite de requisições Notícias locais
Agregadores de notícias Cloudflare Turnstile em staging Detecção de coleta automatizada Feeds agregados
Sites de assessoria de imprensa CAPTCHA de imagem Download de páginas Releases e press kits

Esse padrão se repete em qualquer mesa de clipping: portais de grande porte usam Cloudflare Turnstile, agências como AP e Reuters colocam reCAPTCHA v2 no acesso em massa, e veículos regionais aplicam reCAPTCHA v2 como limite de requisições. Uma equipe que acompanha 40 ou 50 fontes por dia lida com pelo menos três famílias de desafio na mesma rotina.


Monte o agregador em Python com a API da CaptchaAI

O agregador abaixo resolve o desafio sempre que a página retorna com data-sitekey, g-recaptcha ou cf-turnstile no HTML. A classe NewsAggregator coleta manchetes por seção (collect_headlines), busca o conteúdo completo (get_article) e percorre várias fontes (aggregate_sources). Quando _has_captcha identifica um desafio, _solve_and_retry extrai a sitekey, resolve com a CaptchaAI e reenvia a requisição com o token no campo correto.

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:[email protected]: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")

A função solve_captcha() é o núcleo: envia a sitekey e a URL ao endpoint in.php, recebe um task_id e consulta res.php a cada 5 segundos até o token ficar pronto, com no máximo 60 tentativas. Para fontes que já sinalizaram o IP do coletor, prefira egress de rede autorizado e um user-agent realista — o desafio tende a reaparecer mesmo com o token correto quando o sinal de rede já está marcado.


Monitoramento de notícias por palavra-chave

Depois de coletar as manchetes, a classe NewsMonitor filtra o que interessa: mantém palavras-chave em minúsculas, roda aggregate_sources() e compara cada título com a lista. O conjunto seen_urls evita alertar duas vezes pela mesma matéria, e continuous_monitor() repete o ciclo a cada 30 minutos por padrão.

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", "coleta autorizada", "automation"],
    sources=sources,
    proxy="http://user:[email protected]:5000",
)
matches = monitor.scan()

Para uma mesa de clipping, a lista de keywords costuma misturar a marca monitorada, nomes de executivos e termos do setor — vale revisar manualmente os falsos positivos antes de automatizar o envio de alertas ao cliente final.


Orçamento de rastreamento por domínio

Nem toda fonte merece a mesma frequência de visita. Um orçamento de rastreamento por domínio evita gastar threads em fontes de baixo valor enquanto uma fonte crítica fica na fila:

  • Atribua a cada fonte um orçamento com base na frequência de atualização, na intensidade do CAPTCHA e no valor do conteúdo.
  • Reduza a velocidade nos domínios mais sensíveis primeiro, em vez de aplicar o mesmo padrão de novas tentativas a todos os editores.
  • Registre solves, erros e cadência de busca por domínio para ajustar o orçamento com sinais reais.

Problemas comuns e como resolver

Os itens abaixo cobrem a maior parte dos tickets que aparecem depois que o agregador já está em produção:

Problema Causa Correção
Cloudflare bloqueia todas as requisições Detecção agressiva de bots Use egress de rede autorizado e um user-agent realista
Retorna paywall em vez da matéria Conteúdo atrás de assinatura Detecte o acesso pago, pule ou trate separadamente
CAPTCHA aparece em toda página IP sinalizado Faça rotação de proxy autorizado e aumente os intervalos para 5 s ou mais
Conteúdo da matéria vem vazio Conteúdo renderizado em JS Use Selenium ou Puppeteer para sites com renderização no cliente
Matérias duplicadas Mesma pauta replicada por várias fontes Deduplique por similaridade de título, não só por URL

Perguntas frequentes

A coleta automatizada de manchetes e notícias é permitida?

Sim, dentro de limites claros. Coletar títulos, resumos e metadados para clipping ou pesquisa é prática comum. Reproduzir o texto integral de matérias protegidas por direitos autorais sem autorização não é. Extraia trechos, cite a fonte e, se guardar dados pessoais junto ao conteúdo por longos períodos, considere as obrigações da LGPD.

Como lido com conteúdo bloqueado por paywall?

Detecte o paywall antes de extrair o corpo da matéria — procure classes CSS típicas de acesso pago ou meça se o conteúdo é anormalmente curto. Ao identificar, registre a URL como bloqueada e acesse apenas o material que sua assinatura já cobre.

Quais tipos de CAPTCHA aparecem com mais frequência em portais de notícia?

Três famílias cobrem quase todos os casos: Cloudflare Turnstile em portais de grande porte, reCAPTCHA v2 em agências e veículos regionais, e reCAPTCHA v3 em publicações com acesso pago, que avaliam o comportamento antes de mostrar um desafio visível.

O agregador aguenta o pico de tráfego de uma notícia de última hora?

Depende do número de threads contratadas, não do volume de solves — todo plano da CaptchaAI inclui solves ilimitados por thread. Uma mesa pequena roda bem no BASIC (US$ 15/mês, 5 threads); picos de breaking news costumam pedir ADVANCE (US$ 90/mês, 50 threads) ou um plano superior.

Como evito registrar a mesma notícia duas vezes quando ela sai em fontes diferentes?

Compare títulos por similaridade de texto antes de gravar um novo registro, não só a URL — a mesma pauta costuma ser republicada com manchete diferente em cada veículo. O conjunto de URLs já vistas resolve duplicatas óbvias; um limiar de similaridade cobre o resto.


Guias relacionados

  • Rotação de proxy para resolver CAPTCHA na coleta de dados
  • Pesquisa e monitoramento em redes sociais com CAPTCHA

Notícias de qualquer fonte, sem CAPTCHA travando a coleta — obtenha sua chave da CaptchaAI e automatize o clipping do início ao fim.

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