返回博客

Cloudflare 抓取器指南:使用 Scrapeless 获取并验证页面内容

Michael Lee
Michael Lee

Expert Network Defense Engineer

10-Oct-2026

摘要:

  • Cloudflare 爬虫必须在获取内容后对其进行验证。 即使 HTTP 请求完成,应用程序仍可能得不到可用的页面数据。
  • Scrapeless Web Unlocker 是以响应为中心的方案。 当流程需要受控浏览器会话或页面交互时,Agent Browser 更合适。
  • 服务响应与源站响应是两种不同的观测对象。 不要把某个 API 的响应头当作目标网站的响应头来使用。
  • 依据针对特定来源约定的契约来接受记录。 检查页面身份、预期内容、必需字段,以及空结果的含义。

爬虫可能在不自知的情况下,将一个挑战页面文档存成某个产品 URL 对应的记录。请求完成了,解析器也找到了文本,结果记录看起来也被填充完整,但它描述的仍然是错误的文档。

本 Cloudflare 爬虫指南聚焦于这一“接受边界”。它使用 Scrapeless Web Unlocker 请求 HTML,并用一个小型 Python 校验器区分可接受内容、挑战页面或不完整记录。本地验证示例使用的是为演示而特意选取的页面;对需要认证的目标进行捕获时,你需要使用自己的密钥和被允许的来源。

Cloudflare 爬虫需要处理什么?

Cloudflare 爬虫需要获取被允许的目标内容,并识别何时收到了不同的响应。挑战处理与数据抽取是这项工作的两个不同部分。

Cloudflare 可能返回一个过渡性挑战页面(Challenge Page),而不是预期的资源。其 挑战页面响应信号 使用源站响应头 cf-mitigated: challenge,挑战页面的内容类型为 text/html。

当应用程序可以观测到源站响应时,该信号很有用。某个托管采集 API 可能会返回它自己的 JSON 封装和服务响应头。如果它没有暴露目标站点的响应头,那么这些响应头在 API 响应中的缺失并不能证明目标站点没有触发挑战。

在可用的挑战信号旁边保留正向内容检查。与缺少某句通用短语相比,预期的文章标题或产品标识符更能证明当前页面就是你请求的那一页。

HTTP 成功与内容成功并不相同

HTTP 成功描述的是协议层面的结果;内容成功描述的是响应是否满足你的采集任务。HTTP 响应语义并不会为你定义产品数据结构或文章接受规则。

要把服务请求、返回载荷和抽取出的记录区分开来。服务响应可以是合法的 JSON,但其中的数据仍然可能对应不合适的页面。反过来,一个合法的搜索结果页可能没有任何匹配项,但这并不代表它被拦截了。

层级 关键问题 需要保留的证据
API 请求 服务是否接受并完成了该操作? 服务状态与封装
页面身份 这是预期页面还是被允许的规范等价页? 请求的 URL 以及可用的最终身份信息
内容 页面是否包含所需的源素材? 标题、标记或辅助性段落
抽取 对于此任务,必需字段是否有效? 解析出的值与验证结果
空状态 来源本身是否表明不存在任何记录? 针对特定来源的空状态证据

在这些层级上使用不同的失败原因。“无记录”并不能充分说明问题,尤其是在捕获到的文档从未包含你请求的那一页时。

按操作选择 Web Unlocker 或 Agent Browser

当工作流从目标 URL 出发并需要返回内容时,Web Unlocker 很适合使用。其渲染配置支持通过已文档化的 jsRender 字段来请求 HTML。

当任务需要浏览器会话控制、交互或在页面状态之间导航时,Agent Browser 更适合。若应用必须直接与页面本身交互,而不是仅仅消费一次采集响应,就应选择该路径。

让每个实现都局限在它所选定的产品接口之内。下面的示例使用的是 Web Unlocker。它不会在教程中途把浏览器会话转换成 HTTP API,也不会保证对每一个受保护目标都能成功接受。

从来源所支持的访问方式开始。采集服务本身并不是权限授予;处在访问限制之后的内容,不应仅仅因为客户端能请求到其 URL,就被视作公共采集目标。

前提条件与安装

请求示例需要 Scrapeless API 密钥、对 Web Unlocker 的账户访问权限、Python 以及 requests 软件包。验证器仅使用 Python 标准库。

在你的运行环境中私下设置 SCRAPELESS_API_KEY。将 TARGET_URL 设置为你已检查其预期标题和标识字段的公开或其他已授权页面。不要打印凭据,也不要将它们放入文章的输出记录中。

在调用服务之前,在项目环境中安装 requests,并查看 Web Unlocker 快速入门。在项目锁定文件或环境清单中记录你的依赖版本。网络部分需要服务账户和已许可的目标;本示例中未声称对付费目标进行过认证抓取。

发送最小渲染 HTML 请求

当前的 Web Unlocker 渲染请求使用 v2 端点和嵌套的 jsRender 对象。将返回的 HTML 与服务封装分别保存,以便两者都可被检查。

注意:此请求需要真实的 Scrapeless API 密钥、账户访问以及已授权的 TARGET_URL。它已经根据当前请求文档进行了检查,但在本示例中并未对付费目标执行。

python Copy
import json
import os
from pathlib import Path
import requests

target = os.environ["TARGET_URL"]
response = requests.post(
    "https://api.scrapeless.com/api/v2/unlocker/request",
    headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"]},
    json={
        "actor": "unlocker.webunlocker",
        "proxy": {"country": "ANY"},
        "input": {
            "url": target,
            "jsRender": {
                "enabled": True,
                "response": {"type": "html"}
            }
        }
    },
    timeout=60
)
response.raise_for_status()
envelope = response.json()
html = envelope.get("data")
if envelope.get("code") != 200 or not isinstance(html, str):
    raise ValueError("Expected a successful HTML envelope")
Path("page.html").write_text(html, encoding="utf-8")
print(json.dumps({"requested_url": target, "html_characters": len(html)}))

API 封装检查确立了文档化的响应结构。但它尚未确立 page.html 是否包含你的应用所需的源内容。记录下来的 URL 是被请求的 URL;在未观察最终重定向目标前,不要将其重命名为 final_url。

在解析前定义内容契约

内容契约标明接受某个页面所需的最小证据。对于文章,它可能要求目标标题和来源标识符。产品任务则需要其产品及变体字段。

根据已检查的目标页面编写契约。避免只用猜测的 CSS 类名来定义成功。稳定标识符、文档化的结构化字段以及可持续的 URL 模式在来源提供时都很有用。

决定如何表示可选字段。对于某个文章来源,缺失作者也许可以接受;而缺少产品标识符却可能使整个产品记录变得不可用。记录原因,而不是用虚构文本填补缺失字段。

Start Scraping with Scrapeless

使用 Scrapeless 强化你的网络爬取和自动化工作流!
立即注册即可获得 5 美元免费额度 —— 无需信用卡。

现在就到 Scrapeless 控制台 领取你的免费额度。

运行小型内容接受性检查

本地接受性检查应当拒绝明确的挑战信号,并要求页面具备预期内容的正向证据。下面的完整脚本在示例 HTML 固件上演示了这一规则。

标题和 data-record-id 标记属于这些固件。它们并未被宣传为任意受保护网站的选择器。脚本在本地执行,用于检查验证行为;其输出并非实时的 Cloudflare 获取结果。

python Copy
import json
from html.parser import HTMLParser

class Signals(HTMLParser):
    def __init__(self):
        super().__init__()
        self.heading = []
        self.ids = []
        self.in_heading = False

    def handle_starttag(self, tag, attrs):
        if tag == "h1":
            self.in_heading = True
        marker = dict(attrs).get("data-record-id")
        if marker:
            self.ids.append(marker)

    def handle_endtag(self, tag):
        if tag == "h1":
            self.in_heading = False

    def handle_data(self, data):
        if self.in_heading:
            self.heading.append(data)

def assess(html, origin_headers, expected_heading):
    headers = {k.lower(): v for k, v in origin_headers.items()}
    if headers.get("cf-mitigated") == "challenge":
        return {"status": "quarantined", "reason": "origin_challenge"}
    signals = Signals()
    signals.feed(html)
    heading = " ".join(" ".join(signals.heading).split())
    if heading != expected_heading or not signals.ids:
        return {"status": "rejected", "reason": "content_contract"}
    return {"status": "accepted", "heading": heading, "ids": signals.ids}

# Illustrative fixtures; these are not fetched target pages.
fixtures = [
    ("article", '<h1>Public Article</h1><main data-record-id="demo-a"></main>', {}),
    ("challenge", '<h1>Challenge</h1>', {"cf-mitigated": "challenge"}),
    ("incomplete", '<h1>Public Article</h1>', {})
]
print(json.dumps({name: assess(html, headers, "Public Article")
                  for name, html, headers in fixtures}))

文章固件被接受,显式挑战固件被隔离,而缺少标识符的固件被拒绝。这证明了在给定输入上的本地分支行为。在评估服务抓取结果前,将契约调整为实际来源。

对于更复杂的字段选择,将抽取逻辑与这一接受或拒绝的决策分离。HTML 抽取教程 介绍了解析层。

区分空结果与不可用内容

一个合法的空结果需要来源空状态的正向证据。仅有空选择器结果无法提供这种证据。

对于搜索页面,检查文档化的“无结果”标记或其他来源特定条件。对于文章,缺失标题通常意味着捕获不完整或不合适,而非一篇空白文章。在记录中保留这些区分。

Observation Useful interpretation Next inspection
Explicit origin challenge signal Challenge response Acquisition and permitted access path
Expected title, missing identifier Incomplete record Source markup and extraction contract
No matching elements Unresolved Page identity, rendering, and selector
Confirmed source empty state Valid empty Store the empty-state evidence
Intended source and valid fields Accepted content Downstream storage and analysis
避免将每个被拒绝的采集都标记为 Cloudflare 拦截。选择器变更、区域性重定向或错误的起始 URL 也可能导致同样没有记录的结果。

保留输出和获取证据

被接受的记录应保留足够的源证据,以解释其为何被接受。存储所请求的 URL、可用的最终标识、捕获时间、提取规则、验证状态以及所需字段。

使用 来源出处(source provenance) 将观测与产生它的活动关联起来。对被拒绝记录,仅保留其原因,而不要将其内容作为成功的业务记录向前传递。

你的应用拥有此模式。服务的响应字段与你规范化后的记录是不同的契约,因此要记录这种转换过程,而不是将二者视为可互换。

限制与负责任的采集

Cloudflare 抓取器必须尊重来源允许的访问方式,以及所选获取路径的限制。此工作流不保证通用的成功率,也不保证能访问私有页面。

在采集前,先审查来源条款以及 机器人排除规则(robots exclusion rules)。保持有界的目标列表,并将采集限制在任务所需的数据范围内。

从一个被允许的目标开始。较小的并发上限(例如每个主机不超过三个 worker)是本示例中的应用策略,而不是 Scrapeless 服务的限制。只有在审查了来源许可、已接受内容以及运营成本之后,才扩大范围。

结论

一个有用的 Cloudflare 抓取器会返回那些来源与字段都通过任务检查的记录。请求仅是获取步骤。

对响应导向的采集使用 Web Unlocker,保留服务负载,并在接受提取字段前验证目标页面。将「挑战中」「不完整」和「有效但为空」等状态区分开来。

准备好验证你的网络数据了吗?

使用 Scrapeless 构建一个被允许内容检查机制,并依据当前定价来评估已接受的记录。在 Telegram 上讨论你的提取契约。

常见问题(FAQ)

问:抓取受 Cloudflare 保护的网站是合法的吗?

保护技术本身并不构成对页面采集的许可。在使用抓取器前,应审查来源条款、适用的合规要求以及你的授权情况。

问:这个 Web Unlocker 工作流需要单独配置代理吗?

示例请求使用的是托管的获取路径及其文档化的国家字段。只有在你的客户端自行使用独立代理产品时,才需要单独分配代理凭据。

问:HTTP 200 响应能证明抓取成功吗?

HTTP 200 响应并不能证明已经获得所请求的业务内容。在接受记录之前,应检查页面标识、负载以及所需字段。

问:工作流何时应使用 Agent Browser?

当任务需要受控的浏览器会话或与页面状态交互时,应使用 Agent Browser。对于以响应为导向的任务,单次返回的内容响应往往就足够了。

问:当页面选择器不再匹配时应该做什么?

重新检查来源标记和所需字段,然后更新提取契约。在页面标识和内容检查完成之前,缺失选择器的结果应保持为未解决状态。

问:本示例应使用多少并发?

从有界的来源集合开始,并将每个主机的 worker 数量限制在三以下,作为本示例的采集策略。后续任何扩展都应由来源许可和实际运行情况来决定。

问:此工作流能在没有 AI 代理的情况下运行吗?

HTTP 请求和 Python 验证可以在没有 AI 代理的情况下运行。在确定性检查完成后,代理可以消费已被接受的记录。

问:请求的 URL 应该作为规范 URL 存储吗?

将请求的 URL 与任何最终或规范页面标识分开存储。只有当获取过程或来源内容实际确立了某个值为规范 URL 时,才将其作为规范值使用。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录