Crawling API
通过 Crawlbase 的住宅代理网络抓取任意 URL,可选 JavaScript 渲染、机器人挑战求解和地理路由。这是支撑其他一切功能的通用端点。
工作原理
每次 Crawling API 请求都接收一个目标 URL,并返回该目标在正确地理位置、正确设备配置下、解决任何反机器人挑战之后会向真实浏览器返回的页面。每次调用都按顺序执行三件事:
- 路由。请求通过住宅或数据中心出口节点发送 — 默认自动选择,或通过传递
country=指定特定国家。可使用粘性会话,让一系列调用复用同一 IP。 - 渲染。如果您使用 JavaScript token 进行身份验证,URL 将在真实的无头浏览器中加载。Page-wait、scroll、click 和 AJAX-idle 控件让您可以等待实际内容,而不是初始的 HTML 外壳。
- 反机器人绕过。Cloudflare、PerimeterX、DataDome、hCaptcha 和其他常见挑战都在服务���端解决。您获得的是挑战通过后的 HTML,而不是挑战页面。
同一个端点涵盖以上三者。仅传递您需要的参数 — 没有单独的「JS 渲染 API」或「反机器人 API」。如果您不传递仅 JS token 支持的参数,请求走廉价、快速路径;一旦传递,请求就切换到渲染路径。无论哪种方式,每个成功响应的定价都相同。
Tokens
身份验证使用两种 token 类型之一 — 两者都属于同一账户,都对同一端点进行身份验证:
- Normal Token (TCP) — 用于不需要浏览器的静态 HTML 或 JSON 响应。更快、更便宜,适用于大多数简单的抓取目标。
- JavaScript Token — 用于 SPA、React/Vue/Angular 应用、懒加载信息流,以及任何将内容隐藏在客户端渲染之后的目标。使用
page_wait、ajax_wait、scroll和css_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 主机在长时间运行的抓取过程中保持可解析。
端点
# 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。
请求参数
所有参数都作为查询字符串值传递。只有 token 和 url 是必需的。
必需
http:// 或 https://)。路由与地理位置
选择请求的来源以及目标看到的设备。路由对店面、SERP 以及任何按 IP 本地化内容的网站都很重要 — 即使有正确的 URL,从美国出口也无法访问德国的 Amazon 商品目录,而 Google SERP 则同时按地理位置以及 URL 中的 hl/gl 参数与 IP 组合进行本地化。明确设置 country,正确的货币、语言和库存就会自动呈现。
US、GB、DE、JP、…),将抓取通过该国家的出口节点路由。默认自动选择地理位置。.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,或需要在一个流程中跨多个请求保持登录会话时非常有用。
accept-language:en-GB|accept-encoding:gzip。与 get_headers=true 配合使用还可以呈现目标的响应头。Cookie 头格式:key1=value1; key2=value2。允许的请求头。并非通过 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 作为更聪明的替代方案 — 它会在网络空闲时立即返回,而不是阻塞固定的超时时间。
scroll=true 配合使用。screenshot_url 在响应头中返回(当 format=json 时则在 JSON 主体中),并在一小时后过期。对于多次截图或整页工作流,请改用专用的 Screenshots API。截图输出选项。当 screenshot=true 时,默认捕获整个已渲染的页面。要将其缩小到仅视口范围,请追加 mode=viewport;并搭配 width 和 height(像素)以约束捕获范围。两者默认为屏幕尺寸,且仅在 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 秒,以便响应有时间返回。将 scroll 与 page_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,它位于这条相同的异步管道之前,提供重试、速率管理和结果投递功能。
async=true 标志目前仅支持 linkedin.com URL。如果您需要在其他域名上进行异步抓取,请联系支持并提供目标域名,以便我们为您的 token 启用此功能。
输出格式
默认响应是原始页面主体 — 与浏览器在渲染和反爬解析后接收到的内容完全一致。对于大多数管道来说这是合适的形态(您下游的解析器会直接处理 HTML)。当您希望将元数据(状态、最终 URL、RID、请求头)打包到单个信封中而不是分散在响应头和主体里时,请使用 format=json。当目标是我们已有解析器的网站时,请使用 scraper= 或 autoparse=true — 您可以完全跳过解析步骤,直接获得干净的结构化字段而不是原始标记。
html 返回原始页面,元数据放在响应头中。json 将页面和所有元数据包装到一个 JSON 对象中。md 将页面转换为 GitHub 风格 Markdown — 与 md_readability=true 配合可先剥离导航/侧边栏/广告。format=md 时有意义。当为 true 时,Crawlbase 会在转换为 Markdown 之前对页面运行可读性处理 — 移除外壳(导航、侧边栏、页脚、广告位)并保留主要的文章内容。最适合将博客文章和文章转换为干净的 LLM 上下文。format=json 时有意义。对 JSON 信封进行美化打印(带缩进和换行),便于人类阅读;生产环境中请关闭以保持响应较小。amazon-product-details。响应控制
这些参数会改变响应中包含的内容,或改变 Crawlbase 判定请求成功的方式。当您需要将目标站点的响应头或 Set-Cookie 值返回给您时(默认会被剥离),请使用 get_headers 和 get_cookies。当目标合法地返回非 2xx 状态、且您的管道应将其视为成功获取时,请使用 custom_success_codes — 否则 Crawlbase 会代您重试这些响应。
original_header_* 为前缀作为响应头返回,或在 format=json 时分组在 original_headers 下。Set-Cookie 值。它们作为 original_set_cookie 在响应头中返回,或在 format=json 时以相同的键名返回。custom_success_codes=403,429,503。Crawlbase 不会重试这些请求,原始状态码会保留在 original_status 中。当目标针对您的端点合法地返回这些状态码时使用(例如需要鉴权的 APIs、被地区屏蔽但您仍想获取响应体的页面)。POST 请求
当目标端点需要请求体时使用 POST — 表单提交、JSON APIs、GraphQL,以及任何无法放进查询字符串的内容。端点相同、参数相同、响应结构与 GET 相同;仅 HTTP 方法和请求体改变。
POST 请求仅适用于 Normal token。JavaScript token(以及 JS 渲染参数 page_wait、ajax_wait、scroll、css_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 的媒体类型。
Crawlbase 会主动监控 POST 和 PUT 流量。向您不拥有的第三方网站发送请求体 — 评论垃圾、欺诈性表单提交、脚本化账号创建 — 一经发现就会导致发起请求的账号被封禁。请将这些方法用于合法的 API 集成、您自己的预发布与生产端点,以及明确许可的自动化场景。
响应
成功的响应会在响应体中返回目标页面,元数据则位于响应头中。
响应头
| 响应头 | 说明 |
|---|---|
pc_status | Crawlbase 状态码。200 = 成功。 |
original_status | 目标站点返回的 HTTP 状态。 |
url | 经过重定向后的最终 URL。 |
rid | 请求 ID。在 async=true 或 store=true 时返回。 |
content-type | 响应体的 MIME 类型(text/html、application/json、image/png 等)。 |
original_header_* | 在 get_headers=true 时返回。来自目标站点的每个响应头都会带有 original_header_ 前缀(例如 original_header_x_frame_options)。在 format=json 时分组在 original_headers 下。 |
screenshot_url | 在 screenshot=true 时返回。已渲染页面的临时 JPEG URL;在抓取后一小时过期。 |
original_set_cookie | 在 get_cookies=true 时返回。来自目标站点响应的 Set-Cookie 值的拼接结果。 |
domain_complexity也包括 X-Domain-Complexity | 被抓取域名的复杂度等级 — 取值为 standard、moderate 或 complex 之一。反映了绕过该站点防护所需的资源,并直接对应到该请求计费的定价档位。详见下文的 复杂度等级。 |
storage_url | 当请求使用 store=true 发起时返回。指向 Crawlbase Cloud Storage 中已存储响应副本的指针;与 rid 配合使用以便日后检索。 |
Content-Type | 当请求使用 format=md 发起时为 text/markdown; charset=utf-8;否则为标准的 text/html 或 application/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 响应体中返回原始页面内容,元数据放在响应头中(url、original_status、pc_status、X-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-* 元数据头(Flavor、Features、Base-URL、Generator),以及常规的 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_status 为 200 且 original_status 为以下之一的请求计费:
| 状态码 | 含义 |
|---|---|
200 | OK |
201 | Created |
204 | No Content |
301 | Moved Permanently |
302 | Found — 仅当跟随了重定向并返回了内容时 |
404 | Not Found |
410 | Gone |
其他任何 original_status 都是免费的,任何非 200 的 pc_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' -Gfrom 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' -Gfrom 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 readyfrom 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 laterconst { 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 laterrequire '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 laterpackage 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 在请求入队的瞬间就释放您的并发槽位,这样长时间的抓取就不会占用预算。当您需要推送高流量时,请将其用于慢速目标(重 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_status 是 200,但 pc_status 是 520。在决定是否重试时,请始终基于 pc_status 进行分支判断。
最常见的 Crawling API 特定故障:
| 状态码 | 含义 | 处理方式 |
|---|---|---|
422 | url 缺失或未进行 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 的说明。 |
599 | Crawlbase 内部错误 | 重试。如果某个请求持续命中此错误,请联系 support 并提供 rid。 |
完整的 HTTP + pc_status 参考见 Status Codes;Error handling 介绍了推荐的退避重试循环以及在各语言中为您实现该循环的 SDK 辅助方法。
定位示例。 pc_status 与 original_status 出现差异最常见的原因是 CAPTCHA:目标网站返回 200(验证码页面渲染成功),但 Crawlbase 将该响应识别为中间页,并返回 pc_status: 503,这样您就可以绕过它,而不是把验证码 HTML 当作您的数据。
非标准 pc_status 码。 超出常规 HTTP 范围的状态码 — 601、999 等 — 是 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 错误才发现 — 响应中携带剩余的槽位数量,因此一个健康的客户端会主动休眠,而不是对错误做出反应。

