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 请求返回空响应体或 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 主机在长时间运行的抓取过程中始终可解析。
端点
- 方法:
GET(仅查询参数)、POST(表单或 JSON body)、PUT(原始负载)。 url参数必须完全 URL 编码。- Body 返回为目标页面的内容(HTML、JSON、图片等)。
- 元数据通过响应头返回(
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 启用此功能。
输出格式
默认响应是原始的页面 body - 正是浏览器在渲染和反机器人解决之后会收到的内容。对大多数流水线来说这是正确的形状(您下游的解析器直接处理 HTML)。当您希望将元数据(status、最终 URL、RID、headers)打包到单个信封中,而不是分散在响应头和 body 之间时,使用 format=json。当目标是我们已有解析器的站点时,使用 scraper= 或 autoparse=true - 您完全跳过解析步骤,直接获得干净的结构化字段,而不是原始标记。
html 返回原始页面,元数据位于响应头中。json 将页面以及所有元数据封装到单个 JSON 对象中。md 将页面转换为 GitHub-Flavored 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 中。当目标对您的端点合法地返回这些状态码时使用它(需要鉴权的 API、您仍想获取响应体的区域屏蔽页面)。POST 请求
当目标端点需要请求体时使用 POST - 表单提交、JSON API、GraphQL,任何不适合放在查询字符串中的内容。端点相同,参数相同,响应形状与 GET 相同;只有 HTTP 方法和请求体有变化。
POST 请求仅适用于 Normal token。JavaScript token(以及 JS 渲染参数 page_wait、ajax_wait、scroll、css_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 的媒体类型。
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)
}地理路由请求
# 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' -Gfrom 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 在请求入队的瞬间就释放您的并发槽位,这样长时间的抓取就不会占用预算。当您需要推送高流量时,请将其用于慢速目标(重 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 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
}代理模式
同一个 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_status 为 200,但 pc_status 为 520。决定是否重试时始终基于 pc_status 分支。
最常见的 Crawling API 特定故障:
| 状态码 | 含义 | 处理方式 |
|---|---|---|
422 | url 缺失或未进行 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 的说明。 |
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 工程团队使用的内部标记。它们出现在响应中只是为了帮助您在联系支持时调试;您不需要在应用代码中处理它们。
重试策略
简单的版本:使用指数退避对瞬时错误(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 错误才发现 - 响应中会携带剩余的可用槽位数,因此健康的客户端会主动休眠,而不是在错误发生后才作出反应。

