Use Cases

使用验证码处理进行新闻和媒体聚合

做新闻聚合和媒体监测,验证码几乎每一步都会挡在前面:通讯社常挂 reCAPTCHA v2,主流新闻站爱用 Cloudflare Turnstile,付费媒体靠 reCAPTCHA v3 打分。人工点验证码撑不起每天几十上百个信源的抓取节奏,CaptchaAI 用同一套 API 把它们接进流水线,识别到验证码就自动提交、拿 token、继续跑。

海外通讯社走 reCAPTCHA v2,国内新闻站更常见 Cloudflare 检测或 GeeTest(支持到 v3);reCAPTCHA 靠 Google 托管脚本,国内网络访问海外信源常见加载慢,超时判断要留够余量。


新闻站点验证码速览

  • 主流媒体:Cloudflare Turnstile,机器人检测。
  • 通讯社(美联社、路透社):reCAPTCHA v2,批量访问。
  • 付费媒体:reCAPTCHA v3,频率评分。
  • 地方新闻站:reCAPTCHA v2,限流触发。
  • 新闻聚合站:Cloudflare 验证流程,抓取检测。
  • 通稿网站:图片验证码,下载页面。

抓取实战:预算规划与聚合器代码

先定抓取预算

按信源更新频率和验证码触发强度分别定预算,不要所有域名共用一套节奏;触发频繁的域名先降速,同时记录识别成败数据,反过来调整预算。

新闻聚合器代码

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

关键词监控

在聚合器基础上加一层关键词匹配,命中就报警,适合品牌监测、竞品追踪。

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:[email protected]:5000",
)
matches = monitor.scan()

命中关键词的文章会按 URL 去重,避免同一条新闻反复报警。


常见故障排查

问题 原因 处理方式
Cloudflare 拦截所有请求 检测策略过严 自有服务器基础设施 + 真实 UA
打开付费墙而非文章 内容在订阅后 检测付费墙特征,跳过或按权限处理
每个页面都触发验证码 IP 被标记 切换 IP,间隔加到 5 秒以上
文章正文抓空 JS 渲染内容 SPA 页面改用 Selenium/Puppeteer
同一条新闻反复出现 多信源报道同一事件 按标题相似度去重

常见问题

高频问题:

新闻聚合、媒体监测这类抓取合法吗?

抓取标题、摘要研究是常见做法,整篇复制版权正文则不行——保留摘要片段并链接回原文更稳妥,境内数据处理留意《网络安全法》《数据安全法》。

CaptchaAI 能处理新闻网站上的哪些验证码?

覆盖 reCAPTCHA v2/v3、Cloudflare Turnstile 和验证流程,国内常见的 GeeTest 支持到 v3;hCaptcha、FunCaptcha 暂不支持。

识别一次验证码要等多久,会拖慢抓取吗?

Turnstile 通常 10 秒内,reCAPTCHA v3 约 4 秒,reCAPTCHA v2 上限约 60 秒,并发抓取不会拖慢节奏。

遇到付费墙内容该怎么处理?

识别付费墙特征(CSS 类名或偏短正文),命中打标记,只处理有权限内容,付费正文不抓取完整版。


相关阅读

自有服务器基础设施看轮换指南,社交媒体信源看验证码处理。


不管新闻来源是通讯社还是聚合站,拿到你的 CaptchaAI API Key,接入你的采集流程。

该文章已禁用评论。