Use Cases

使用验证码解决进行学术研究网络抓取

抓取 Google Scholar、PubMed 或 IEEE Xplore 时,请求量一高就会跳出 reCAPTCHA 或 Cloudflare Turnstile——这是文献综述、文献计量分析项目最常见的卡点。本文给出一套可复用的方案:

  • 识别不同学术数据库弹出的验证码类型
  • 用 CaptchaAI 的 API 自动求解,脚本不再卡在验证码页面
  • 按各数据库的限流规则控制抓取节奏,降低被封 IP 的概率

学术数据库都用哪种验证码

多数国际学术站点把 reCAPTCHA 脚本部署在 Google 的域名下,国内网络访问这类资源本身就不太稳定,容易和验证码拦截混在一起分不清。下表是几个常用来源触发验证码的场景:

来源 验证码类型 触发条件 采集数据
Google Scholar(谷歌学术) reCAPTCHA v3 高频查询 引文、论文列表
PubMed reCAPTCHA v2 重复搜索 生物医学文献
Web of Science Cloudflare Turnstile 批量下载 引文指标
Scopus reCAPTCHA v2 导出操作 文献计量数据
IEEE Xplore reCAPTCHA v2 搜索 + 下载 工程类论文
JSTOR reCAPTCHA v2 访问页面 人文社科文献

引文抓取脚本怎么写

下面的脚本演示完整流程:请求页面 → 检测 sitekey → 调用 CaptchaAI 求解 → 提交 token 重新抓取。solve_captcha() 把提交任务和轮询结果封装成一次调用;AcademicScraper 把翻页搜索、详情抓取、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:[email protected]: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")

_has_captcha 只检测 data-sitekeyg-recaptchacf-turnstile 三个标记,命中就转 _solve_and_retry 处理,逻辑保持精简。


顺着引用关系做文献计量分析

文献计量分析常要沿着"被引用"链接做多层遍历。bibliometric_analysis() 复用同一个 scraper 实例,按 depth 递归抓取引用网络,每层最多跟进 5 篇,避免节点数量膨胀。

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

各数据库的限流建议

学术站点封 IP 很快,延迟设置偏保守更划算。下表是常见来源的建议延迟和每小时页数上限,仅供参考,请结合自己账号的历史表现调整。只采集公开可访问的元数据(标题、作者、摘要),遵循目标站点的 robots 协议和所在机构的访问授权范围。

来源 建议延迟 每小时最大页数
Google Scholar 10–15 秒 40–50
PubMed 3–5 秒 100
Web of Science 5–10 秒 60
Scopus 5–10 秒 60
IEEE Xplore 3–5 秒 100
JSTOR 5–10 秒 60

抓取学术数据库常遇到的问题

问题 原因 处理方式
每次搜索都弹验证码 学术站点已经标记了你的 IP 更换代理,把延迟提到 15 秒以上
没有返回结果 实际返回的是验证码页面 解析前先检测页面是否含验证码
摘要抓不到 内容在付费墙后面 使用机构代理,或改抓开放获取版本
Google Scholar 封了 IP 超出了它的限流阈值 等待 30 分钟以上,换一个 IP 再试
导出条数受限 站点对批量下载设了上限 拆成更小的批次分批下载

常见问题

CaptchaAI 支持 reCAPTCHA v3 吗?

支持。CaptchaAI 能识别 reCAPTCHA v2、v3 和 Cloudflare Turnstile,Google Scholar 常用的无感 v3 挑战也在覆盖范围内,接口共用同一个 userrecaptcha 方法。

国内网络访问 Google Scholar、PubMed 验证码特别多,为什么?

reCAPTCHA 脚本托管在 Google 域名下,国内网络访问本身就不太稳定,容易和验证码拦截混在一起。先确认页面能否打开、sitekey 能否取到——这是网络可达性问题。

抓取学术数据库的公开元数据算不算合规?

标题、作者、摘要通常可以采集,全文取决于授权协议。PubMed 明确开放了 E-utilities API,优先用官方接口更稳定。

机构代理和 CaptchaAI 可以一起用吗?

可以,两者互不影响。机构代理负责请求来源,CaptchaAI 只负责识别弹出的验证码。

轮询一直拿不到结果,是超时了吗?

solve_captcha() 最多轮询 60 次、每次 5 秒,等满 5 分钟才会抛 TimeoutError。多数任务 15–30 秒内就能拿到 token,长时间拿不到通常是 sitekey 取错了。


相关指南


别再对着验证码手动点了——注册 CaptchaAI 拿到 API Key,把学术数据采集接入自动识别流程。

该文章已禁用评论。