crawlbase文档
登录

工作原理

每次 Crawling API 请求都接收一个目标 URL,并返回该目标在正确地理位置、正确设备配置下、解决任何反机器人挑战之后会向真实浏览器返回的页面。每次调用都按顺序执行三件事:

  1. 路由。请求通过住宅或数据中心出口节点发送 - 默认自动选择,或在您传入 country= 时指定特定国家。提供粘性会话功能,因此一系列调用可复用同一 IP。
  2. 渲染。如果您使用 JavaScript token 进行身份验证,URL 将在真实的无头浏览器中加载。Page-wait、scroll、click 和 AJAX-idle 控件让您可以等待实际内容,而不是初始的 HTML 外壳。
  3. 反机器人绕过。Cloudflare、PerimeterX、DataDome、hCaptcha 和其他常见挑战都在服务器端解决。您获得的是挑战通过后的 HTML,而不是挑战页面。

同一个端点涵盖了所有三种场景。只需传入您需要的参数 - 没有单独的「JS 渲染 API」或「反机器人 API」。如果您不传入仅限 JS token 的参数,请求会走便宜、快速的路径;一旦传入,请求就会切换到渲染路径。两种方式按成功响应计费的价格相同。

Tokens

身份验证使用两种 token 类型之一 - 两者都在同一个账户下,都用于同一个端点的身份验证:

  • Normal Token (TCP):用于静态 HTML 或 JSON 响应,无需浏览器。更快、更便宜,适用于大多数简单的抓取目标。
  • JavaScript Token:用于 SPA、React/Vue/Angular 应用、懒加载信息流,以及任何将内容隐藏在客户端渲染之后的目标。使用 page_waitajax_waitscrollcss_click_selector 时必需。

如果 Normal token 请求返回空响应体或 525(挑战无法解决),标准的修复方法是用 JavaScript token 重试 - 大多数现代目标即使初始 HTML 看起来完整也仍需要浏览器。完整的 token 管理流程请参见 Authentication

并发与定价

每个返回 cb_status: 200 的请求都会计入您的月度配额。失败的请求(超时、被屏蔽、目标返回 5xx)是免费的 - 针对不稳定上游的重试不会让您的账单出现意外。并发限制随您的套餐扩展;响应中包含 remaining 头,您可以用它在达到上限之前主动回退。长时间运行的抓取(重度 JS 渲染、较大的 page_wait)应使用下方的异步模式,以便请求进入队列后立即释放并发槽位。

客户端超时。每次请求的平均响应时间为 4–10 秒,但尾部延迟请求(重度 SPA、scroll_interval=60、缓慢的上游网站)可能耗时更长。请将客户端超时设置为至少 90 秒,避免合法的慢响应在到达前就超时。

其他客户端建议。在每个请求上发送 Accept-Encoding: gzip - 负载并不小(完整的 HTML 页面或 markdown),gzip 通常可将其压缩到原始大小的三分之一。如果您使用 Scrapy,请禁用 DNS 缓存,以便 API 主机在长时间运行的抓取过程中始终可解析。

端点

GETPOSTPUThttps://api.crawlbase.com/?token=YOUR_TOKEN&url=ENCODED_URL
  • 方法:GET(仅查询参数)、POST(表单或 JSON body)、PUT(原始负载)。
  • url 参数必须完全 URL 编码
  • Body 返回为目标页面的内容(HTML、JSON、图片等)。
  • 元数据通过响应头返回(cb_statusoriginal_statusurlrid)。

快速开始

curl 'https://api.crawlbase.com/?token=YOUR_TOKEN&url=https%3A%2F%2Fgithub.com%2Fanthropic'
from crawlbase import CrawlingAPI

api = CrawlingAPI({'token': 'YOUR_TOKEN'})
res = api.get('https://github.com/anthropic')
print(res['body'])
const { CrawlingAPI } = require('crawlbase');
const api = new CrawlingAPI({ token: 'YOUR_TOKEN' });
const res = await api.get('https://github.com/anthropic');
console.log(res.body);
require 'crawlbase'
api = Crawlbase::API.new(token: 'YOUR_TOKEN')
res = api.get('https://github.com/anthropic')
puts res.body
<?php
use Crawlbase\CrawlingAPI;
$api = new CrawlingAPI(['token' => 'YOUR_TOKEN']);
$res = $api->get('https://github.com/anthropic');
echo $res->body;
package main

import (
    "fmt"
    "github.com/crawlbase/crawlbase-go"
)

func main() {
    api, _ := crawlbase.NewCrawlingAPI("YOUR_TOKEN")
    res, _ := api.Get("https://github.com/anthropic", nil)
    fmt.Println(res.Body)
}

请求

每个 Crawling API 请求都是对该端点的一次 HTTP 调用。大多数请求是 GET - 传入下方的查询参数来控制渲染、地理位置、输出格式和异步行为。当您需要发送表单或 JSON body 时使用 POST,原始负载上传使用 PUT

请求参数

所有参数都作为查询字符串值传递。只有 tokenurl 是必需的。

必需

token
string必需
您的 Normal 或 JavaScript token。参见 Authentication
url
string必需
完全 URL 编码的目标 URL。必须包含协议(http://https://)。

路由与地理位置

选择请求的来源以及目标看到的设备。路由对店面、SERP 以及任何按 IP 本地化内容的网站都很重要 - 即使 URL 正确,从美国出口也无法访问德国 Amazon 目录,而 Google SERP 同时按地理位置以及 URL 中的 hl/gl 参数与 IP 共同本地化。明确设置 country,正确的货币、语言和库存就会自动显示。

country
string可选
两位字母 ISO 国家代码(USGBDEJP、…),将抓取通过该国家的出口节点路由。默认自动选择地理位置。
device
desktop | tablet | mobiledesktop
模拟所选设备类别的 User-Agent 和视口。
user_agent
string可选
覆盖 User-Agent 请求头。请谨慎使用 - 默认值针对每个目标做了调优。
tor_network
booleanfalse
通过 Tor 网络路由请求,以便您抓取 .onion 站点。对任何明网目标都不要启用 - Tor 出口比住宅代理池更慢、更嘈杂。
国家可能被自动覆盖

Crawlbase 可能会覆盖 country 参数,根据 URL 自动选择代理 - 这能在大多数网站上获得最佳成功率。如果您需要禁用自动代理选择,请联系支持

指定国家可能会减少成功请求的数量,因此仅在地理位置对您要抓取的页面确实重要时才使用它。某些站点(特别是 Amazon)无论您传入哪个国家都会通过专用代理路由 - 这些域名允许任何国家,即使不在下方支持列表中。

您可以访问以下国家:

巴西 (BR)加拿大 (CA)中国 (CN)
德国 (DE)丹麦 (DK)西班牙 (ES)
芬兰 (FI)法国 (FR)英国 (GB)
荷兰 (NL)波兰 (PL)俄罗斯 (RU)
瑞典 (SE)土耳其 (TR)乌克兰 (UA)
美国 (US)

请求头与 cookie

将您自己的请求头和 cookie 转发到目标站点,或固定一个粘性会话,使一次调用返回的 Set-Cookie 值在下一次调用中重放。当目标需要 Accept-Language、CSRF cookie,或需要在一个流程中跨多个请求保持登录会话时非常有用。

request_headers
string可选
要转发的请求头列表,URL 编码并用竖线分隔:accept-language:en-GB|accept-encoding:gzip。与 get_headers=true 配合使用还可以呈现目标的响应头。
set_cookies
string可选
要转发到目标的 cookie,采用标准 Cookie 头格式:key1=value1; key2=value2
cookies_session
string可选
粘性 cookie 会话 - Crawlbase 会在每次共享相同值的后续调用中重放之前调用返回的 cookie。任意字符串,最多 32 个字符;使用新值会开启新会话。会话在最后一次调用后 300 秒过期。

允许的请求头。您通过 request_headers 传入的并非每个头都会到达目标站点 - Crawlbase 默认会剥离一小部分。要验证实际发出的内容,请向 https://postman-echo.com/headers 发送测试请求并检查 echo 服务收到的内容。如果您需要为您的 token 授权额外的请求头,请联系支持并提供请求头名称。

JavaScript 渲染

这些参数需要 JavaScript token。它们控制无头浏览器在捕获 DOM 之前如何等待内容。如果您发现自己同时需要使用多个参数,思考的顺序是:先 page_wait(针对可预测动画的固定延迟),然后 ajax_wait(如果页面在挂载后发出网络请求,则去掉固定延迟),然后 scroll(仅当您需要的内容在首屏以下时),然后 css_click_selector(仅当按钮或折叠面板挡住数据时)。

一个常见的陷阱:将 page_wait 设得过高「以防万一」。每多一毫秒都是您无法在别处使用的并发。从 0 开始,仅在看到输出被截断时才增加,并考虑将 ajax_wait 作为更聪明的替代方案 - 它在网络空闲时立即返回,而不是阻塞在固定的超时上。

page_wait
int (毫秒)0
在页面加载后等待这么多毫秒再进行捕获。对于带有动画进入效果的内容很有用。
ajax_wait
booleanfalse
等待网络空闲(约 500ms 内无请求)。最适合在挂载后获取数据的 SPA。
css_click_selector
string可选
CSS 选择器 - Crawlbase 将在捕获之前点击匹配的元素。请对特殊字符进行 URL 编码。
scroll
booleanfalse
在捕获前滚动到页面底部。触发懒加载。
scroll_interval
int (秒)10
用于滚动的最大秒数。与 scroll=true 配合使用。
screenshot
booleanfalse
捕获已渲染页面的 JPEG 图片。URL 会作为 screenshot_url 在响应头中返回(当 format=json 时则在 JSON 主体中),并在一小时后过期。对于多次截图或整页工作流,请改用专用的 Screenshots API
pdf
booleanfalse
将页面渲染为 PDF,并以 PDF 文件作为响应主体返回(Content-Type: application/pdfContent-Disposition: inline),而不是 HTML。与 screenshot 使用相同的无头渲染管线,因此上述渲染参数同样适用。不能与 screenshot 组合使用;优先级高于 format

截图输出选项。screenshot=true 时,默认捕获整个已渲染的页面。要将其缩小到仅视口范围,请追加 mode=viewport;并搭配 widthheight(像素)以约束捕获范围。两者默认为屏幕尺寸,且仅在 mode=viewport 时生效。例如:&screenshot=true&mode=viewport&width=1200&height=800。需要将页面导出为 PDF 而不是图片?请使用 pdf=true,以 PDF 文件的形式返回同样的无头渲染结果。

PDF 输出。screenshot(返回临时的 screenshot_url)不同,pdf=true 会将 PDF 直接作为响应主体返回,因此请将响应直接写入 .pdf 文件。在捕获前塑造页面的渲染参数仍然适用(countrydeviceuser_agentpage_waitscroll),标准的元数据响应头(cb_statusoriginal_statusurl)也会一并返回。它不能与 screenshot=true 组合使用:同时请求两者会返回 400,并附带类似 pdf=true cannot be combined with screenshot=true 的消息。如果您还设置了 format=json,则 pdf=true 优先,仍会返回 PDF。如果渲染或 PDF 生成失败,您会收到错误状态,而不会返回不完整或空白的 PDF。PDF 请求的计费与 JavaScript 渲染请求相同,因为它们共用同一条无头渲染管线。

scroll 的计费方式。启用滚动的请求按服务器端总处理时间计费。前 8 秒(页面加载 + 滚动合计)计为 1 个请求;之后每增加 5 秒再加 1 个计费请求。20 秒滚动 = 1(前 8 秒)+ 1(9–13 秒)+ 1(14–18 秒)+ 1(19–20 秒,部分时段按整段计算)= 4 个计费请求。如果页面在 scroll_interval 之前完成,仅按实际处理时间计费。

scroll_interval 的最大值为 60 秒 - 超过 60 秒滚动会停止并返回响应。当您设置 scroll_interval=60 时,请将客户端连接保持至少 90 秒,以便响应有时间返回。将 scrollpage_wait 结合会增加总处理时间,从而增加计费的请求数。

css_click_selector 参数仅在您使用 JavaScript token 时才生效(它在 DOM 被捕获之前在无头浏览器内运行)。它接受任何完全指定的、合法的 CSS 选择器 - 例如 ID(如 #some-button)、类(如 .some-other-button)或属性选择器(如 [data-tab-item="tab1"])。始终对该值进行 URL 编码,以便特殊字符在查询字符串中完整保留。

如果在页面上找不到该选择器,请求将以 cb_status 595 失败。要在点击目标可能不存在时仍能收到响应,可以附加一个普遍存在的选择器作为后备 - 用逗号分隔。例如 #some-button,body#some-button 不存在时会回退到点击 body

多个选择器。要在捕获前依次点击多个元素,请用竖线(|)字符分隔。整个值(包括竖线)都要进行 URL 编码。例如,先点击 #start-button 再点击 .next-page-link,原始形式为 #start-button|.next-page-link,URL 编码后为 %23start-button%7C.next-page-link。点击按给定顺序进行。如果链中任意一个选择器缺失,同样适用 cb_status 595 规则,因此 ,body 回退模式可以按选择器逐个使用。

需要在 Crawlbase 捕获 DOM 之前在页面内运行自定义 JavaScript(例如派发合成事件、修改状态、强制 fetch)吗?这是按账户根据使用场景开放的功能 - 请联系支持说明您想做什么,我们会为您配置。

异步与存储

异步模式将 API 从「阻塞直到我拿到您的页面」翻转为「把它排入队列,完成后通知我」。端点会立即返回一个 rid;实际结果会被投递到您指定的 webhook,或存储到 Cloud Storage 并稍后通过同一个 rid 获取。这是批处理任务和慢速目标的正确模式 - 异步会在请求进入队列的那一刻释放您的并发槽位,因此您可以在抓取仍在进行时继续提交。对于大批量任务(数百万个 URL),请使用 Enterprise Crawler,它位于这条相同的异步流水线之前,提供重试、速率管理和结果投递。

异步模式目前仅支持 linkedin.com

async=true 标志目前仅支持 linkedin.com URL。如果您需要在其他域名上进行异步抓取,请联系支持并提供目标域名,以便我们为您的 token 启用此功能。

async
booleanfalse
立即返回一个 rid,而不是阻塞等待。如果设置了 callback,结果会投递到 callback;否则可以通过 Cloud Storagerid 获取。
callback
URL可选
用于接收抓取结果的 webhook URL。当 async=true 且您不想轮询时是必需的。
store
booleanfalse
将抓取的页面持久化到 Cloud Storage。除返回响应主体外,还会返回一个 rid

输出格式

默认响应是原始的页面 body - 正是浏览器在渲染和反机器人解决之后会收到的内容。对大多数流水线来说这是正确的形状(您下游的解析器直接处理 HTML)。当您希望将元数据(status、最终 URL、RID、headers)打包到单个信封中,而不是分散在响应头和 body 之间时,使用 format=json。当目标是我们已有解析器的站点时,使用 scraper=autoparse=true - 您完全跳过解析步骤,直接获得干净的结构化字段,而不是原始标记。

format
html | json | mdhtml
选择响应信封。html 返回原始页面,元数据位于响应头中。json 将页面以及所有元数据封装到单个 JSON 对象中。md 将页面转换为 GitHub-Flavored Markdown - 与 md_readability=true 搭配使用可先剥离导航/侧边栏/广告。
md_readability
booleanfalse
仅在 format=md 时有意义。当为 true 时,Crawlbase 会在转换为 Markdown 之前对页面执行一次可读性处理 - 丢弃页面框架(导航、侧边栏、页脚、广告位)并保留主要的文章内容。最适合将博客文章和文章转换为干净的 LLM 上下文。
pretty
booleanfalse
仅在 format=json 时有意义。对 JSON 信封进行美化打印(带缩进和换行),便于人类阅读;生产环境中请关闭以保持响应较小。
scraper
string可选
应用内置的解析器来提取结构化数据,而不是返回 HTML。例如:amazon-product-details
autoparse
booleanfalse
自动检测页面类型并应用匹配的解析器。「能给 JSON 就给 JSON」的便捷选项。

响应控制

这些参数会改变响应包含的内容,或 Crawlbase 如何判定请求是否成功。当您需要将目标站点的响应头或 Set-Cookie 值暴露给您时(它们默认会被剥离),请使用 get_headersget_cookies。当目标合法地返回您的流水线应视为干净抓取的非 2xx 状态时,请使用 custom_success_codes - 否则 Crawlbase 会替您重试这些响应。

get_headers
booleanfalse
呈现目标站点的响应头。它们以 original_header_* 为前缀作为响应头返回,或在 format=json 时分组在 original_headers 下。
get_cookies
booleanfalse
呈现目标站点的 Set-Cookie 值。它们作为 original_set_cookie 在响应头中返回,或在 format=json 时以相同的键名返回。
custom_success_codes
string可选
以逗号分隔的 HTTP 状态码列表,将其视为成功 - 例如 custom_success_codes=403,429,503。Crawlbase 不会重试这些响应,原始状态会保留在 original_status 中。当目标对您的端点合法地返回这些状态码时使用它(需要鉴权的 API、您仍想获取响应体的区域屏蔽页面)。

POST 请求

当目标端点需要请求体时使用 POST - 表单提交、JSON API、GraphQL,任何不适合放在查询字符串中的内容。端点相同,参数相同,响应形状与 GET 相同;只有 HTTP 方法和请求体有变化。

POST 仅支持 Normal token

POST 请求仅适用于 Normal token。JavaScript token(以及 JS 渲染参数 page_waitajax_waitscrollcss_click_selector)仅支持 GET - 当您需要在 JS 渲染页面上提交表单时,请使用 JavaScript token 并通过 css_click_selector 驱动表单按钮,而不是直接向表单 URL 发送 POST。

默认的 Content-Type 是 application/x-www-form-urlencoded。将表单字段作为请求体传入 - Crawlbase 会原样转发到目标。

curl 'https://api.crawlbase.com/?token=YOUR_TOKEN' \
  --data-urlencode 'url=https://postman-echo.com/post' -G \
  -F 'parameter1=testing some post data' \
  -F 'parameter2=here goes some data'
import requests
from urllib.parse import quote_plus

url = quote_plus('https://postman-echo.com/post')
res = requests.post(
    f'https://api.crawlbase.com/?token=YOUR_TOKEN&url={url}',
    data={'parameter1': 'value', 'parameter2': 'another value'},
)
print(res.status_code, res.text)
const url = encodeURIComponent('https://postman-echo.com/post');
const body = new URLSearchParams({ parameter1: 'value', parameter2: 'another' });

const res = await fetch(`https://api.crawlbase.com/?token=YOUR_TOKEN&url=${url}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body,
});
console.log(res.status, await res.text());
require 'net/http'

uri = URI('https://api.crawlbase.com')
uri.query = URI.encode_www_form(token: 'YOUR_TOKEN', url: 'https://postman-echo.com/post')

res = Net::HTTP.post_form(uri, 'parameter1' => 'value', 'parameter2' => 'another')
puts res.code, res.body
<?php
$url  = 'https://postman-echo.com/post';
$body = http_build_query(['parameter1' => 'value', 'parameter2' => 'another']);

$ch = curl_init('https://api.crawlbase.com/?token=YOUR_TOKEN&url=' . urlencode($url));
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch);
package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
    "strings"
)

func main() {
    target := url.QueryEscape("https://postman-echo.com/post")
    body   := strings.NewReader("parameter1=value¶meter2=another")

    res, _ := http.Post(
        "https://api.crawlbase.com/?token=YOUR_TOKEN&url="+target,
        "application/x-www-form-urlencoded",
        body,
    )
    out, _ := io.ReadAll(res.Body)
    fmt.Println(string(out))
}
请勿滥用

POST 不得用于发送垃圾信息或以其他方式损害目标网站。Crawlbase 会主动监测滥用模式;对于使用 POST 进行垃圾信息、撞库或其他恶意流量的账号,将被封禁并上报。

使用 JSON 请求体的 POST

使用 post_content_type 覆盖默认的 form-urlencoded 内容类型。请对该值进行 URL 编码(例如 application/json 变为 application%2Fjson)。请求体会原样转发到目标 - 您需要自己将其编码为 JSON。

curl 'https://api.crawlbase.com/?token=YOUR_TOKEN' \
  --data-urlencode 'url=https://postman-echo.com/post' \
  --data-urlencode 'post_content_type=application/json;charset=UTF-8' -G \
  --request POST \
  --data '{"param1":"value","param2":"another"}'
import json, requests
from urllib.parse import quote_plus

url = quote_plus('https://postman-echo.com/post')
res = requests.post(
    f'https://api.crawlbase.com/?token=YOUR_TOKEN'
    f'&url={url}'
    f'&post_content_type=application/json',
    data=json.dumps({'param1': 'value', 'param2': 'another'}),
    headers={'Content-Type': 'application/json'},
)
print(res.status_code, res.text)
const url  = encodeURIComponent('https://postman-echo.com/post');
const ct   = encodeURIComponent('application/json;charset=UTF-8');
const body = JSON.stringify({ param1: 'value', param2: 'another' });

const res = await fetch(
  `https://api.crawlbase.com/?token=YOUR_TOKEN&url=${url}&post_content_type=${ct}`,
  { method: 'POST', headers: { 'Content-Type': 'application/json' }, body },
);
console.log(res.status, await res.text());
require 'net/http'
require 'json'

uri = URI('https://api.crawlbase.com')
uri.query = URI.encode_www_form(
  token: 'YOUR_TOKEN',
  url: 'https://postman-echo.com/post',
  post_content_type: 'application/json'
)

req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req.body = { param1: 'value', param2: 'another' }.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.code, res.body
<?php
$url  = 'https://postman-echo.com/post';
$ct   = urlencode('application/json;charset=UTF-8');
$body = json_encode(['param1' => 'value', 'param2' => 'another']);

$ch = curl_init(
    'https://api.crawlbase.com/?token=YOUR_TOKEN'
    . '&url=' . urlencode($url)
    . '&post_content_type=' . $ct
);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch);
package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "net/url"
)

func main() {
    target := url.QueryEscape("https://postman-echo.com/post")
    ct     := url.QueryEscape("application/json;charset=UTF-8")
    body   := bytes.NewBufferString(`{"param1":"value","param2":"another"}`)

    res, _ := http.Post(
        "https://api.crawlbase.com/?token=YOUR_TOKEN&url="+target+"&post_content_type="+ct,
        "application/json",
        body,
    )
    out, _ := io.ReadAll(res.Body)
    fmt.Println(string(out))
}

注意:目标站点决定是否接受请求体。Crawlbase 会如实转发请求 - 如果目标因为请求体结构错误而返回 4xx,那会显示在 original_status 中,而不是 cb_status 中。分支处理模式请参见 错误

PUT 请求

PUT 的工作方式与 POST 相同 - 端点相同、参数相同、请求体编码规则相同。唯一的区别是 HTTP 方法。

curl 'https://api.crawlbase.com/?token=YOUR_TOKEN' \
  --data-urlencode 'url=https://api.example.com/resource/42' -G \
  --request PUT \
  --header 'Content-Type: application/json' \
  --data '{"name":"updated","status":"active"}'
import requests
from urllib.parse import quote_plus

url = quote_plus('https://api.example.com/resource/42')
res = requests.put(
    f'https://api.crawlbase.com/?token=YOUR_TOKEN&url={url}&post_content_type=application/json',
    data='{"name":"updated","status":"active"}',
    headers={'Content-Type': 'application/json'},
)
print(res.status_code, res.text)
const url  = encodeURIComponent('https://api.example.com/resource/42');
const ct   = encodeURIComponent('application/json');
const body = JSON.stringify({ name: 'updated', status: 'active' });

const res = await fetch(
  `https://api.crawlbase.com/?token=YOUR_TOKEN&url=${url}&post_content_type=${ct}`,
  { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body },
);
console.log(res.status, await res.text());
require 'net/http'
require 'json'

uri = URI('https://api.crawlbase.com')
uri.query = URI.encode_www_form(
  token: 'YOUR_TOKEN',
  url: 'https://api.example.com/resource/42',
  post_content_type: 'application/json'
)

req = Net::HTTP::Put.new(uri, 'Content-Type' => 'application/json')
req.body = { name: 'updated', status: 'active' }.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.code, res.body
<?php
$url  = 'https://api.example.com/resource/42';
$ct   = urlencode('application/json');
$body = json_encode(['name' => 'updated', 'status' => 'active']);

$ch = curl_init(
    'https://api.crawlbase.com/?token=YOUR_TOKEN'
    . '&url=' . urlencode($url)
    . '&post_content_type=' . $ct
);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch);
package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "net/url"
)

func main() {
    target := url.QueryEscape("https://api.example.com/resource/42")
    ct     := url.QueryEscape("application/json")
    body   := bytes.NewBufferString(`{"name":"updated","status":"active"}`)

    req, _ := http.NewRequest(
        "PUT",
        "https://api.crawlbase.com/?token=YOUR_TOKEN&url="+target+"&post_content_type="+ct,
        body,
    )
    req.Header.Set("Content-Type", "application/json")

    res, _ := http.DefaultClient.Do(req)
    out, _ := io.ReadAll(res.Body)
    fmt.Println(string(out))
}

与 POST 一样,PUT 也需要 Normal token。如果请求体不是 form-urlencoded 格式,请使用 post_content_type 来控制 body 的媒体类型。

请勿使用 POST/PUT 进行垃圾发送

Crawlbase 会主动监控 POST 和 PUT 流量。向您不拥有的第三方站点发送请求体 - 评论垃圾、欺诈性表单提交、脚本化账户创建 - 一经检测,原始账户会立即被暂停。请将这些动词用于合法的 API 集成、您自己的预发和生产端点,以及明确许可的自动化。

响应

成功的响应会在响应体中返回目标页面,元数据则位于响应头中。

响应头

响应头说明
cb_statusCrawlbase 状态码。200 = 成功。 此前称为 pc_status
original_status目标站点返回的 HTTP 状态。
url经过重定向后的最终 URL。
rid请求 ID。在 async=truestore=true 时返回。
content-type响应体的 MIME 类型(text/htmlapplication/jsonimage/png 等)。
original_header_*get_headers=true 时返回。来自目标站点的每个响应头都会带有 original_header_ 前缀(例如 original_header_x_frame_options)。在 format=json 时分组在 original_headers 下。
screenshot_urlscreenshot=true 时返回。已渲染页面的临时 JPEG URL;在抓取后一小时过期。
original_set_cookieget_cookies=true 时返回。来自目标站点响应的 Set-Cookie 值的拼接结果。
domain_complexity
也包括 X-Domain-Complexity
被抓取域名的复杂度等级 - 取值为 standardmoderatecomplex 之一。反映绕过该站点保护所需的资源,并直接映射到该请求的计费等级。请参见下方的复杂度等级
storage_url当请求使用 store=true 发起时返回。指向 Crawlbase Cloud Storage 中已存储响应副本的指针;与 rid 配合使用以便日后检索。
Content-Type当请求使用 format=md 发起时为 text/markdown; charset=utf-8;否则为标准的 text/htmlapplication/json
X-Markdown-Flavor响应体的 Markdown 方言 - 目前为 GitHub Flavored Markdown (GFM)。仅在 format=md 时发出。
X-Markdown-Features响应体中使用的 GFM 特性的逗号分隔列表(例如 tables,lists)。便于您选择启用了正确扩展的解析器。仅在 format=md 时输出。
X-Markdown-Base-URL解析后 URL 的主机名(在所有重定向之后)。用于解析 markdown 响应体中的相对链接。仅在 format=md 时输出。
X-Markdown-Generator标识转换器 - 值为 ProxyCrawl-API。仅在 format=md 时发出。

HTML 响应

默认行为。format=html(或完全不指定 format)会在 HTTP 响应体中返回原始页面内容,元数据放在响应头中(urloriginal_statuscb_statusX-Domain-Complexity,以及您通过 get_headers=true 选择启用的任何 original_header_* 项)。

GET 'https://api.crawlbase.com/?token=YOUR_TOKEN&url=https%3A%2F%2Fgithub.com%2Fcrawlbase&format=html'

Response:
  Headers:
    url: https://github.com/crawlbase
    original_status: 200
    cb_status: 200
    X-Domain-Complexity: standard

  Body:
    <!doctype html><html>
      <head>...</head>
      <body>... (full page HTML) ...</body>
    </html>

JSON 响应

设置 format=json 即可将相同数据作为单个 JSON 对象返回:

GET 'https://api.crawlbase.com/?token=YOUR_TOKEN&url=https%3A%2F%2Fgithub.com%2Fcrawlbase&format=json'

Response:
  {
    "original_status": 200,
    "cb_status": 200,
    "url": "https://github.com/crawlbase",
    "domain_complexity": "standard",
    "body": "<!doctype html><html>... (full page HTML) ...</html>"
  }

Markdown 响应

format=md 在响应体中返回已转换为 GitHub Flavored Markdown 的页面,Content-Type: text/markdown; charset=utf-8,并附带一组 X-Markdown-* 元数据响应头(FlavorFeaturesBase-URLGenerator),与常见的 url / original_status / cb_status 并存。当您希望在转换运行之前先提取主内容(文章正文,不含页面框架)时,请与 md_readability=true 搭配使用 - 参见 md_readability 参数。

GET 'https://api.crawlbase.com/?token=YOUR_TOKEN&url=https%3A%2F%2Fgithub.com%2Fcrawlbase&format=md'

Response:
  Headers:
    Content-Type: text/markdown; charset=utf-8
    X-Markdown-Flavor: GitHub Flavored Markdown (GFM)
    X-Markdown-Features: tables,lists
    X-Markdown-Base-URL: github.com
    X-Markdown-Generator: ProxyCrawl-API
    url: https://github.com/crawlbase
    original_status: 200
    cb_status: 200

  Body:
    # crawlbase
    ... (markdown text of the page) ...

计费请求

Crawlbase 仅对 cb_status200 original_status 为以下之一的请求计费:

状态码含义
200OK
201Created
204No Content
301Moved Permanently
302Found - 仅当跟随重定向并返回了内容时
404Not Found
410Gone

其他任何 original_status 都是免费的,任何非 200cb_status 也是免费的。在将用量账单与您的应用日志对账时请使用此列表。

域名复杂度等级

domain_complexity 字段(也通过 X-Domain-Complexity 响应头返回)告诉您抓取目标域名的难度 - 以及该请求落入的计费等级。

  • standard:易于抓取,保护最少。最低计费等级。
  • moderate:中等强度的反机器人保护,需要专门处理。中级计费等级。
  • complex:高级保护,需要专门的资源。最高计费等级。

各档位的具体定价请查看您的订阅计划,或 联系销售

常见模式

端到端示例,将上述请求参数组合成团队最常用的工作流。

带滚动的 JS 渲染 SPA

curl 'https://api.crawlbase.com/?token=JS_TOKEN' \
  --data-urlencode 'url=https://feed.example.com' \
  --data-urlencode 'page_wait=2000' \
  --data-urlencode 'scroll=true' \
  --data-urlencode 'scroll_interval=15' -G
from crawlbase import CrawlingAPI
api = CrawlingAPI({'token': 'JS_TOKEN'})
res = api.get('https://feed.example.com', {
    'page_wait': 2000,
    'scroll': True,
    'scroll_interval': 15,
})
const { CrawlingAPI } = require('crawlbase');
const api = new CrawlingAPI({ token: 'JS_TOKEN' });

const res = await api.get('https://feed.example.com', {
  page_wait: 2000,
  scroll: true,
  scroll_interval: 15,
});
console.log(res.body);
require 'crawlbase'

api = Crawlbase::API.new(token: 'JS_TOKEN')
res = api.get('https://feed.example.com',
  page_wait: 2000,
  scroll: true,
  scroll_interval: 15
)
puts res.body
<?php
use Crawlbase\CrawlingAPI;

$api = new CrawlingAPI(['token' => 'JS_TOKEN']);
$res = $api->get('https://feed.example.com', [
    'page_wait' => 2000,
    'scroll' => true,
    'scroll_interval' => 15,
]);
echo $res->body;
package main

import (
    "fmt"
    "log"
    "github.com/crawlbase/crawlbase-go"
)

func main() {
    api, err := crawlbase.NewCrawlingAPI("JS_TOKEN")
    if err != nil {
        log.Fatal(err)
    }
    res, _ := api.Get("https://feed.example.com", map[string]string{
        "page_wait":       "2000",
        "scroll":          "true",
        "scroll_interval": "15",
    })
    fmt.Println(res.Body)
}

将页面渲染为 PDF

使用 JavaScript token 传入 pdf=true,即可将完全渲染的页面以 PDF 文件的形式返回。将响应主体直接写入磁盘;渲染参数(countrydevicepage_wait)会在捕获前塑造页面。适用于将页面导出用于报告、归档或合规。

# Basic - save the fully rendered page as a PDF
curl 'https://api.crawlbase.com/?token=JS_TOKEN' \
  --data-urlencode 'url=https://example.com' \
  --data-urlencode 'pdf=true' -G -o example.pdf

# With rendering controls (geo, device, wait)
curl 'https://api.crawlbase.com/?token=JS_TOKEN' \
  --data-urlencode 'url=https://en.wikipedia.org/wiki/Taylor_Swift' \
  --data-urlencode 'pdf=true' \
  --data-urlencode 'country=us' \
  --data-urlencode 'device=desktop' \
  --data-urlencode 'page_wait=5000' -G -o article.pdf

地理路由请求

# Route through Germany; the echoed JSON confirms the exit country
curl 'https://api.crawlbase.com/?token=YOUR_TOKEN' \
  --data-urlencode 'url=https://ipinfo.io/json' \
  --data-urlencode 'country=DE' -G
from crawlbase import CrawlingAPI

api = CrawlingAPI({'token': 'YOUR_TOKEN'})
res = api.get('https://ipinfo.io/json', {'country': 'DE'})
print(res['body'])
const { CrawlingAPI } = require('crawlbase');
const api = new CrawlingAPI({ token: 'YOUR_TOKEN' });

const res = await api.get('https://ipinfo.io/json', { country: 'DE' });
console.log(res.body);
require 'crawlbase'

api = Crawlbase::API.new(token: 'YOUR_TOKEN')
res = api.get('https://ipinfo.io/json', country: 'DE')
puts res.body
<?php
use Crawlbase\CrawlingAPI;

$api = new CrawlingAPI(['token' => 'YOUR_TOKEN']);
$res = $api->get('https://ipinfo.io/json', ['country' => 'DE']);
echo $res->body;
package main

import (
    "fmt"
    "log"
    "github.com/crawlbase/crawlbase-go"
)

func main() {
    api, err := crawlbase.NewCrawlingAPI("YOUR_TOKEN")
    if err != nil {
        log.Fatal(err)
    }
    res, _ := api.Get("https://ipinfo.io/json", map[string]string{
        "country": "DE",
    })
    fmt.Println(res.Body)
}

带 webhook 的异步抓取

何时使用 async

async 在请求入队的瞬间就释放您的并发槽位,这样长时间的抓取就不会占用预算。当您需要推送高流量时,请将其用于慢速目标(重 JS、长 page_wait)。

curl 'https://api.crawlbase.com/?token=YOUR_TOKEN' \
  --data-urlencode 'url=https://example.com' \
  --data-urlencode 'async=true' \
  --data-urlencode 'callback=https://your-app.com/webhook' -G

# → returns immediately: { "rid": "a1B2c3D4e5F6" }
# → result POSTed to your callback when ready
from crawlbase import CrawlingAPI

api = CrawlingAPI({'token': 'YOUR_TOKEN'})
res = api.get('https://example.com', {
    'async': 'true',
    'callback': 'https://your-app.com/webhook',
})
print(res['rid'])  # → returned immediately; result POSTed to callback later
const { CrawlingAPI } = require('crawlbase');
const api = new CrawlingAPI({ token: 'YOUR_TOKEN' });

const res = await api.get('https://example.com', {
  async: true,
  callback: 'https://your-app.com/webhook',
});
console.log(res.rid); // → returned immediately; result POSTed to callback later
require 'crawlbase'

api = Crawlbase::API.new(token: 'YOUR_TOKEN')
res = api.get('https://example.com',
  async: true,
  callback: 'https://your-app.com/webhook'
)
puts res.rid # → returned immediately; result POSTed to callback later
<?php
use Crawlbase\CrawlingAPI;

$api = new CrawlingAPI(['token' => 'YOUR_TOKEN']);
$res = $api->get('https://example.com', [
    'async' => 'true',
    'callback' => 'https://your-app.com/webhook',
]);
echo $res->rid; // → returned immediately; result POSTed to callback later
package main

import (
    "fmt"
    "log"
    "github.com/crawlbase/crawlbase-go"
)

func main() {
    api, err := crawlbase.NewCrawlingAPI("YOUR_TOKEN")
    if err != nil {
        log.Fatal(err)
    }
    res, _ := api.Get("https://example.com", map[string]string{
        "async":    "true",
        "callback": "https://your-app.com/webhook",
    })
    fmt.Println(res.RID) // → returned immediately; result POSTed to callback later
}

代理模式

同一个 Crawling API 也可以作为 HTTP/HTTPS 代理来调用,而不是作为 REST 端点 - 当您已有支持代理配置的抓取器、浏览器自动化脚本或 HTTP 客户端,并且更愿意把 Crawlbase 接入前端而不是重写请求层时,这很有用。

将您的客户端指向 smartproxy.crawlbase.com:8001(HTTPS,推荐)或 smartproxy.crawlbase.com:8000(HTTP),并将您的 token 作为代理用户名传入。所有 Crawling API 功能 - JS 渲染、反机器人绕过、地理路由 - 都同样适用;唯一的区别是请求的形状。

代理模式 vs. Smart AI Proxy

两个产品共享相同的主机名但使用不同的端口 - 容易混淆。两者功能基本相同(国家路由、设备模拟、会话、自定义请求头、通过 CrawlbaseAPI-* 控件实现 JS 渲染);区别在于您所计费的订阅以及该订阅提供的并发/线程等级:

  • 代理模式下的 Crawling API(本节)→ 端口 8000 / 8001。通过您的 Crawling API 套餐路由:与 REST 模式调用使用相同的月度配额、相同的并发预算、相同的按成功计费方式。当您已经为 Crawling API 付费并希望在 REST 端点之外再有一个代理形态的接口时,请选择此项。
  • Smart AI Proxy(独立产品,参见 Smart AI Proxy)→ 端口 8012 / 8013。这是独立的 SKU,有自己的订阅以及自己的线程/并发模型,适合已经运行高线程数的代理优先抓取流水线。底层网络相同、底层控制头相同 - 选择哪个取决于哪种合约和并发形状适合您的使用方式。

经验法则:选择您已经持有订阅的产品(或定价模式与您流量形态匹配的产品)。能力面是一样的;端口只是把您路由到正确的计费 + 并发通道。

快速开始

从您的 shell 发起首次调用 - Normal token,HTTPS 代理:

# HTTPS proxy (recommended)
curl -x 'https://[email protected]:8001' \
  -k 'https://httpbin.org/ip'

# HTTP alternative
curl -x 'http://[email protected]:8000' \
  -k 'https://httpbin.org/ip'
import requests

proxies = {
    'http':  'http://[email protected]:8000',
    'https': 'http://[email protected]:8000',
}
res = requests.get('https://httpbin.org/ip', proxies=proxies, verify=False)
print(res.status_code, res.text)
const { HttpsProxyAgent } = require('https-proxy-agent');

const agent = new HttpsProxyAgent('http://[email protected]:8000');
const res = await fetch('https://httpbin.org/ip', { agent });
console.log(res.status, await res.text());
require 'net/http'

uri  = URI('https://httpbin.org/ip')
http = Net::HTTP.new(uri.host, uri.port,
  'smartproxy.crawlbase.com', 8000, 'YOUR_TOKEN', '')
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

res = http.get(uri.request_uri)
puts res.code, res.body
<?php
$ch = curl_init('https://httpbin.org/ip');
curl_setopt($ch, CURLOPT_PROXY,         'smartproxy.crawlbase.com:8000');
curl_setopt($ch, CURLOPT_PROXYUSERPWD,  'YOUR_TOKEN:');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch);
package main

import (
    "crypto/tls"
    "fmt"
    "io"
    "net/http"
    "net/url"
)

func main() {
    proxyURL, _ := url.Parse("http://[email protected]:8000")
    client := &http.Client{
        Transport: &http.Transport{
            Proxy:           http.ProxyURL(proxyURL),
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        },
    }
    res, _ := client.Get("https://httpbin.org/ip")
    out, _ := io.ReadAll(res.Body)
    fmt.Println(string(out))
}

对于 JS 渲染的目标,切换为您的 JavaScript token:

curl -x 'https://[email protected]:8001' \
  -k 'https://spa.example.com'
import requests

proxies = {
    'http':  'http://[email protected]:8000',
    'https': 'http://[email protected]:8000',
}
res = requests.get('https://spa.example.com', proxies=proxies, verify=False)
print(res.status_code)
const { HttpsProxyAgent } = require('https-proxy-agent');

const agent = new HttpsProxyAgent('http://[email protected]:8000');
const res = await fetch('https://spa.example.com', { agent });
console.log(res.status);
require 'net/http'

uri  = URI('https://spa.example.com')
http = Net::HTTP.new(uri.host, uri.port,
  'smartproxy.crawlbase.com', 8000, 'YOUR_JS_TOKEN', '')
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

res = http.get(uri.request_uri)
puts res.code
<?php
$ch = curl_init('https://spa.example.com');
curl_setopt($ch, CURLOPT_PROXY,         'smartproxy.crawlbase.com:8000');
curl_setopt($ch, CURLOPT_PROXYUSERPWD,  'YOUR_JS_TOKEN:');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
package main

import (
    "crypto/tls"
    "fmt"
    "net/http"
    "net/url"
)

func main() {
    proxyURL, _ := url.Parse("http://[email protected]:8000")
    client := &http.Client{
        Transport: &http.Transport{
            Proxy:           http.ProxyURL(proxyURL),
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        },
    }
    res, _ := client.Get("https://spa.example.com")
    fmt.Println(res.Status)
}

速率限制

代理模式下的默认速率限制为每秒 20 个请求(约 170 万次请求/天)。基于并发的客户端应该以线程数而不是 RPS 来思考 - 在典型的 Crawling API 延迟下(Amazon 商品页约 4 秒),这大致相当于 80 个并发线程。更快的目标转换为更少的线程。

如果您触及上限,请联系 support 并说明您的使用场景,以协商更高的并发。

错误处理 & 重试

Crawling API 在每个响应上都会暴露两个状态码:original_status(目标站点返回的)和 cb_status(Crawlbase 在应用反机器人、重定向和内容校验规则之后的判定结果)。两者可能不一致 - 目标可能返回 200 但响应体为空,此时 original_status200,但 cb_status520。决定是否重试时始终基于 cb_status 分支。

最常见的 Crawling API 特定故障:

状态码含义处理方式
422url 缺失或未进行 URL 编码发送前对 URL 进行编码。大多数客户端(libcurl --data-urlencode、Python requests、Node fetch)会自动处理 - 但手工构造的查询字符串经常遗漏这一步。
520目标返回空响应重试一次。如果仍为空,请从 Normal token 切换到 JS token - 许多站点对非浏览器 user agent 返回空壳,并依赖 JS 来填充内容。
521目标网站宕机/不可达视为瞬时上游错误。退避 + 重试;若持续数分钟仍如此,则该网站确实宕机。
522连接目标超时带退避地重试。如果目标对地理位置敏感,尝试不同的 country
523源站从所选出口不可达不带 country 重试(让自动路由选择)或尝试另一个国家。
525无法解决反机器人挑战从 Normal token 切换到 JS token。如果已经在使用 JS,请重试;如果持续出现,请上报给支持 - 通常意味着目标推出了新的挑战变种。
595未找到选择器。页面成功加载,但您通过 css_click_selector 传入的 CSS 选择器未匹配到任何元素。为选择器追加一个回退(#start-button,body),这样点击仍能落到一个已知元素上。完整模式参见 css_click_selector 的说明。
599Crawlbase 内部错误重试。如果某个请求持续命中此错误,请联系 support 并提供 rid

完整的 HTTP + cb_status 参考见 Status CodesError handling 介绍了推荐的退避重试循环以及在各语言中为您实现该循环的 SDK 辅助方法。

定位示例。 cb_statusoriginal_status 出现差异最常见的原因是 CAPTCHA:目标网站返回 200(验证码页面渲染成功),但 Crawlbase 将该响应识别为中间页,并返回 cb_status: 503,这样您就可以绕过它,而不是把验证码 HTML 当作您的数据。

非标准 cb_status 代码。常见 HTTP 范围之外的代码 - 601999 以及类似的 - 是 Crawlbase 工程团队使用的内部标记。它们出现在响应中只是为了帮助您在联系支持时调试;您不需要在应用代码中处理它们。

重试策略

简单的版本:使用指数退避对瞬时错误(5xx)进行重试,最多重试若干次(通常 3-5 次),不要重试客户端错误(4xx - 它们不会自行修复),并在首次出现 520/525 时切换一次 token 类型再继续重试。SDK 辅助工具用合理的默认值实现了这一循环;对于自定义客户端,经验法则是:

  • 第一次重试:失败后约 1 秒
  • 第二次重试:失败后约 3 秒
  • 第三次重试:失败后约 10 秒
  • 之后:记录日志 + 告警;持续失败通常意味着目标端发生了变化,而不是瞬时网络问题

对该 API 的所有重试都是免费的 - 只有成功的响应(cb_status: 200)才会计入您的配额。这使得激进的退避成本很低;重试的唯一真实代价是您在流水线上增加的延迟。

性能 & 最佳实践

在大规模运行此 API 的客户中,有几种模式反复出现。提前采用它们可以避免最常见的几类支持工单。

  • 使用能用的最便宜的 token。不要「以防万一」就默认使用 JavaScript token - Normal token 请求更快、占用的并发更少。仅在 Normal 响应为空或被挑战阻止时才升级到 JS。
  • 优先使用 ajax_wait 而不是 page_wait固定延迟会在每个请求上消耗并发,即使是快速请求也不例外。ajax_wait 会在页面进入网络空闲的那一刻返回 - 平均通常更快,仅在真正长时间加载的页面上才会更慢。
  • 通过 async + webhook 推送高流量。同步模式是临时使用和交互式使用的合理默认。对于超过几百个 URL 的批量任务,异步模式(或 Enterprise Crawler)可以在已有抓取完成的同时,让您的并发预算空出来用于新的提交。
  • 为有状态的流程复用会话。如果目标站点需要登录会话或购物车 cookie,请保留一个 session ID 并在后续请求中传递它,这样就会复用相同的出口 IP 和 cookie jar。会话 cookie 模式请参见 Authentication
  • 关注 remaining 响应头。在触发并发上限之前主动退避,而不是通过 429 错误才发现 - 响应中会携带剩余的可用槽位数,因此健康的客户端会主动休眠,而不是在错误发生后才作出反应。