Docs
登录

工作原理

每次 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

并发与定价

每个返回 pc_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、图片等)。
  • 元数据通过响应头返回(pc_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)无论您传入哪个国家都会通过专用代理路由 - 这些域名允许任何国家,即使不在下方支持列表中。

您可以访问以下国家:

澳大利亚 (AU)巴西 (BR)加拿大 (CA)
瑞士 (CH)中国 (CN)德国 (DE)
西班牙 (ES)芬兰 (FI)法国 (FR)
英国 (GB)印度 (IN)日本 (JP)
墨西哥 (MX)荷兰 (NL)挪威 (NO)
波兰 (PL)俄罗斯 (RU)塞舌尔 (SC)
瑞典 (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

截图输出选项。screenshot=true 时,默认捕获整个已渲染的页面。要将其缩小到仅视口范围,请追加 mode=viewport;并搭配 widthheight(像素)以约束捕获范围。两者默认为屏幕尺寸,且仅在 mode=viewport 时生效。例如:&screenshot=true&mode=viewport&width=1200&height=800

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 编码,以便特殊字符在查询字符串中完整保留。

如果在页面上找不到该选择器,请求将以 pc_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。点击按给定顺序进行。如果链中任意一个选择器缺失,同样适用 pc_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 中,而不是 pc_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 集成、您自己的预发和生产端点,以及明确许可的自动化。

响应

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

响应头

响应头说明
pc_statusCrawlbase 状态码。200 = 成功。
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_statuspc_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
    pc_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,
    "pc_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 / pc_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
    pc_status: 200

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

计费请求

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

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

其他任何 original_status 都是免费的,任何非 200pc_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)
}

地理路由请求

# 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(目标站点返回的)和 pc_status(Crawlbase 在应用反机器人、重定向和内容校验规则之后的判定结果)。两者可能不一致 - 目标可能返回 200 但响应体为空,此时 original_status200,但 pc_status520。决定是否重试时始终基于 pc_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 + pc_status 参考见 Status CodesError handling 介绍了推荐的退避重试循环以及在各语言中为您实现该循环的 SDK 辅助方法。

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

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

重试策略

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

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

对该 API 的所有重试都是免费的 - 只有成功的响应(pc_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 错误才发现 - 响应中会携带剩余的可用槽位数,因此健康的客户端会主动休眠,而不是在错误发生后才作出反应。