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 请求返回空 body 或 525(无法解决挑战),标准修复方法是改用 JavaScript token 重试 — 即使初始 HTML 看起来完整,大多数现代目标仍需要浏览器。完整的 token 管理流程请参见 Authentication

并发与定价

每个返回 pc_status: 200 的请求都计入您的月度配额。失败的请求(超时、屏蔽、来自目标的 5xx)免费 — 对不稳定上游的重试不会让您的账单意外增长。并发限制随您的套餐扩展;响应包含一个 remaining 头,您可以用它在达到上限前主动退避。长时间运行的抓取(重度 JS 渲染、较大的 page_wait)应使用下面的 async 模式,以便在请求入队的瞬间释放并发槽位。

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

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

端点

GEThttps://api.crawlbase.com/?token=YOUR_TOKEN&url=ENCODED_URL
# All requests are GET. The url parameter must be fully URL-encoded.
# Body is returned as the target page's content (HTML, JSON, image, etc).
# Metadata is returned as response headers (pc_status, original_status, url, rid).

快速开始

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

输出格式

默认响应是原始页面主体 — 与浏览器在渲染和反爬解析后接收到的内容完全一致。对于大多数管道来说这是合适的形态(您下游的解析器会直接处理 HTML)。当您希望将元数据(状态、最终 URL、RID、请求头)打包到单个信封中而不是分散在响应头和主体里时,请使用 format=json。当目标是我们已有解析器的网站时,请使用 scraper=autoparse=true — 您可以完全跳过解析步骤,直接获得干净的结构化字段而不是原始标记。

format
html | json | mdhtml
选择响应信封。html 返回原始页面,元数据放在响应头中。json 将页面和所有元数据包装到一个 JSON 对象中。md 将页面转换为 GitHub 风格 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 中。当目标针对您的端点合法地返回这些状态码时使用(例如需要鉴权的 APIs、被地区屏蔽但您仍想获取响应体的页面)。

POST 请求

当目标端点需要请求体时使用 POST — 表单提交、JSON APIs、GraphQL,以及任何无法放进查询字符串的内容。端点相同、参数相同、响应结构与 GET 相同;仅 HTTP 方法和请求体改变。

POST 仅支持 Normal token

POST 请求仅适用于 Normal token。JavaScript token(以及 JS 渲染参数 page_waitajax_waitscrollcss_click_selector)仅支持 GET — 当您需要在 JS 渲染的页面上提交表单时,请使用 JavaScript token 配合 css_click_selector 来触发表单按钮,而不是直接 POST 到表单 URL。

默认的 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 content type。请对该值进行 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)
}

地理路由请求

# Get the German version of a localized site
curl 'https://api.crawlbase.com/?token=YOUR_TOKEN' \
  --data-urlencode 'url=https://www.amazon.com/dp/B08N5WRWNW' \
  --data-urlencode 'country=DE' -G
from crawlbase import CrawlingAPI

api = CrawlingAPI({'token': 'YOUR_TOKEN'})
res = api.get('https://www.amazon.com/dp/B08N5WRWNW', {'country': 'DE'})
print(res['body'])
const { CrawlingAPI } = require('crawlbase');
const api = new CrawlingAPI({ token: 'YOUR_TOKEN' });

const res = await api.get('https://www.amazon.com/dp/B08N5WRWNW', { country: 'DE' });
console.log(res.body);
require 'crawlbase'

api = Crawlbase::API.new(token: 'YOUR_TOKEN')
res = api.get('https://www.amazon.com/dp/B08N5WRWNW', country: 'DE')
puts res.body
<?php
use Crawlbase\CrawlingAPI;

$api = new CrawlingAPI(['token' => 'YOUR_TOKEN']);
$res = $api->get('https://www.amazon.com/dp/B08N5WRWNW', ['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://www.amazon.com/dp/B08N5WRWNW", map[string]string{
        "country": "DE",
    })
    fmt.Println(res.Body)
}

带 webhook 的异步抓取

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
}
何时使用 async

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

代理模式

同一个 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

两个产品共用相同的主机名但使用不同端口 — 容易混淆。两者的能力本质上相同(国家路由、设备模拟、会话、自定义 headers、通过 CrawlbaseAPI-* 控制项进行 JS 渲染);区别在于您所计费的订阅以及该订阅提供的并发/线程层级:

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

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

快速开始

在您的 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 延迟(亚马逊商品页约 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 切换到 JS token — 许多网站对非浏览器 user agent 返回空壳,依赖 JS 来填充内容。
521目标网站宕机/不可达视为瞬时上游错误。退避 + 重试;若持续数分钟仍如此,则该网站确实宕机。
522连接目标超时带退避地重试。如果目标对地理位置敏感,尝试不同的 country
523源站从所选出口不可达不带 country 重试(让自动路由选择)或尝试另一个国家。
525无法解决反机器人挑战从 Normal 切换到 JS token。如果已经在 JS 上,重试;如果持续失败,联系 support 升级处理 — 通常意味着目标推出了新的挑战变体。
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 工程团队使用的内部标记。它们仅在响应中出现以帮助您在联系 support 时进行调试;您无需在应用代码中处理它们。

重试策略

简单版本:对瞬时错误(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 错误才发现 — 响应中携带剩余的槽位数量,因此一个健康的客户端会主动休眠,而不是对错误做出反应。