Docs
Se connecter

Fonctionnement

Chaque requête au Crawling API prend une URL cible et retourne la page que cette cible aurait servie à un véritable navigateur dans la bonne zone géographique, avec le bon profil d'appareil, après résolution des défis anti-bot. Trois étapes s'enchaînent à chaque appel :

  1. Routage. La requête est envoyée via un nœud de sortie résidentiel ou datacenter — automatiquement par défaut, ou dans un pays spécifique si vous passez country=. Des sessions persistantes sont disponibles pour qu'une séquence d'appels réutilise la même IP.
  2. Rendu. Si vous vous authentifiez avec un JavaScript token, l'URL est chargée dans un véritable navigateur headless. Les contrôles de page-wait, scroll, click et AJAX-idle vous permettent d'attendre le contenu réel plutôt que la coquille HTML initiale.
  3. Contournement anti-bot. Cloudflare, PerimeterX, DataDome, hCaptcha et autres défis courants sont résolus côté serveur. Vous obtenez le HTML post-défi, pas la page de défi.

Le même point de terminaison couvre les trois. Ne passez que les paramètres dont vous avez besoin — il n'y a pas de « JS-rendering API » ni d'« anti-bot API » distincts. Si vous ne passez pas de paramètres réservés au JavaScript token, la requête emprunte le chemin économique et rapide ; dès que vous le faites, la requête bascule sur le chemin de rendu. La tarification est la même par réponse réussie dans les deux cas.

Tokens

L'authentification utilise l'un des deux types de token — tous deux rattachés à un même compte, tous deux authentifiant le même point de terminaison :

  • Normal Token (TCP) — pour les réponses HTML statiques ou JSON où vous n'avez pas besoin de navigateur. Plus rapide, moins coûteux, utilisé pour la majorité des cibles de scraping simples.
  • JavaScript Token — pour les SPAs, les applications React/Vue/Angular, les flux à chargement différé et toute cible qui masque son contenu derrière un rendu côté client. Requis pour utiliser page_wait, ajax_wait, scroll et css_click_selector.

Si une requête avec Normal token retourne un body vide ou un 525 (défi non résolu), la solution standard consiste à réessayer avec le JavaScript token — la plupart des cibles modernes nécessitent un navigateur même quand leur HTML initial semble complet. Voir Authentication pour le flux complet de gestion des tokens.

Concurrence & tarification

Chaque requête qui retourne pc_status: 200 est décomptée de votre quota mensuel. Les requêtes échouées (timeouts, blocages, 5xx provenant de la cible) sont gratuites — les nouvelles tentatives contre une source instable ne font pas grimper la facture. Les limites de concurrence évoluent avec votre offre ; la réponse inclut un en-tête remaining que vous pouvez utiliser pour ralentir de manière proactive avant d'atteindre le plafond. Les crawls de longue durée (rendu JS lourd, page_wait élevé) doivent utiliser le mode async ci-dessous afin de libérer le slot de concurrence dès la mise en file d'attente de la requête.

Timeouts côté client. Le temps de réponse moyen est de 4 à 10 secondes par requête, mais les requêtes en queue de latence (SPAs lourdes, scroll_interval=60, sites cibles lents) peuvent prendre plus de temps. Réglez votre timeout client à au moins 90 secondes pour que les réponses lentes mais légitimes ne dépassent pas le délai avant d'arriver.

Autres recommandations côté client. Envoyez Accept-Encoding: gzip sur chaque requête — les charges utiles sont conséquentes (pages HTML complètes ou markdown) et gzip les réduit généralement au tiers de leur taille sur le réseau. Si vous utilisez Scrapy, désactivez le cache DNS pour que l'hôte de l'API reste résoluble pendant les crawls de longue durée.

Point de terminaison

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).

Démarrage rapide

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)
}

Requête

Chaque requête au Crawling API est un seul appel HTTP vers le point de terminaison. La plupart des requêtes sont des GET — passez les paramètres de requête ci-dessous pour contrôler le rendu, la géolocalisation, le format de sortie et le comportement async. Utilisez POST lorsque vous devez envoyer un formulaire ou un body JSON, et PUT pour les uploads de payload brut.

Paramètres de requête

Tous les paramètres sont passés en valeurs de query string. Seuls token et url sont requis.

Requis

token
stringrequis
Votre Normal token ou JavaScript token. Voir Authentication.
url
stringrequis
L'URL cible entièrement URL-encodée. Doit inclure le schéma (http:// ou https://).

Routage & géolocalisation

Choisissez d'où part la requête et quel appareil voit la cible. Le routage est important pour les boutiques en ligne, les SERPs et tout site qui localise son contenu selon l'IP — le catalogue Amazon allemand n'est pas accessible depuis une sortie US même avec la bonne URL, et les SERPs Google sont localisées à la fois par la géographie et par les paramètres URL hl/gl combinés à l'IP. Définissez country explicitement et la bonne devise, langue et stocks s'affichent automatiquement.

country
stringoptionnel
Code pays ISO à deux lettres (US, GB, DE, JP, …) pour acheminer le crawl via les nœuds de sortie de ce pays. Par défaut, sélection géographique automatique.
device
desktop | tablet | mobiledesktop
Émule le User-Agent et la fenêtre d'affichage de la classe d'appareil choisie.
user_agent
stringoptionnel
Surcharge l'en-tête User-Agent. À utiliser avec parcimonie — les valeurs par défaut sont optimisées pour chaque cible.
tor_network
booleanfalse
Route la requête sur le réseau Tor afin de pouvoir crawler les sites .onion. À laisser désactivé pour toute cible clearnet — les sorties Tor sont plus lentes et plus bruyantes que le pool résidentiel.
Le pays peut être surchargé automatiquement

Crawlbase peut surcharger le paramètre country pour sélectionner automatiquement un proxy en fonction de l'URL — cela offre le meilleur taux de succès sur la plupart des sites. Contactez le support si vous avez besoin de désactiver la sélection automatique de proxy.

Spécifier un pays peut réduire le nombre de requêtes réussies, ne l'utilisez donc que lorsque la géolocalisation est réellement importante pour la page que vous crawlez. Certains sites (notamment Amazon) sont routés via des proxies dédiés indépendamment du pays passé — chaque pays est autorisé pour ces domaines même s'il ne figure pas dans la liste prise en charge ci-dessous.

Vous avez accès aux pays suivants :

Australie (AU)Brésil (BR)Canada (CA)
Suisse (CH)Chine (CN)Allemagne (DE)
Espagne (ES)Finlande (FI)France (FR)
Royaume-Uni (GB)Inde (IN)Japon (JP)
Mexique (MX)Pays-Bas (NL)Norvège (NO)
Pologne (PL)Russie (RU)Seychelles (SC)
Suède (SE)Turquie (TR)Ukraine (UA)
États-Unis (US)

En-têtes & cookies

Transférez vos propres en-têtes de requête et cookies vers le site cible, ou épinglez une session persistante pour que les valeurs Set-Cookie d'un appel soient rejouées au suivant. Utile lorsque la cible a besoin d'un Accept-Language, d'un cookie CSRF ou d'une session connectée qui doit survivre à travers les requêtes d'un flux.

request_headers
stringoptionnel
Liste d'en-têtes URL-encodée à transférer, séparée par des barres verticales : accept-language:en-GB|accept-encoding:gzip. À combiner avec get_headers=true pour faire également remonter les en-têtes de réponse de la cible.
set_cookies
stringoptionnel
Cookies à transférer vers la cible, au format standard de l'en-tête Cookie : key1=value1; key2=value2.
cookies_session
stringoptionnel
Session de cookies persistante — Crawlbase rejoue les cookies renvoyés par les appels précédents sur chaque appel suivant partageant la même valeur. N'importe quelle chaîne jusqu'à 32 caractères ; une nouvelle valeur démarre une nouvelle session. Les sessions expirent 300 secondes après le dernier appel.

En-têtes autorisés. Tous les en-têtes que vous passez via request_headers n'atteindront pas forcément le site cible — Crawlbase en supprime un petit ensemble par défaut. Pour vérifier ce qui sort réellement, envoyez une requête de test à https://postman-echo.com/headers et inspectez ce que reçoit le service d'écho. Si vous avez besoin d'un en-tête supplémentaire autorisé pour votre token, contactez le support avec le ou les noms d'en-têtes.

Rendu JavaScript

Ces paramètres requièrent un JavaScript token. Ils contrôlent comment le navigateur headless attend le contenu avant de capturer le DOM. Si vous vous retrouvez à en utiliser plusieurs à la fois, l'ordre à garder en tête est : page_wait en premier (un délai fixe pour les animations prévisibles), puis ajax_wait (laissez tomber le délai fixe si la page émet des requêtes réseau après le mount), puis scroll (uniquement si le contenu dont vous avez besoin se trouve sous la ligne de flottaison), puis css_click_selector (uniquement si un bouton ou un accordéon verrouille les données).

Un piège fréquent : régler page_wait trop haut « au cas où ». Chaque milliseconde supplémentaire est de la concurrence que vous ne pouvez pas utiliser ailleurs. Commencez à 0, augmentez seulement quand vous voyez une sortie tronquée, et envisagez ajax_wait comme une alternative plus intelligente — il revient dès que le réseau est inactif plutôt que de bloquer sur un délai fixe.

page_wait
int (ms)0
Attend ce nombre de millisecondes après le chargement de la page avant la capture. Utile pour le contenu qui apparaît avec une animation.
ajax_wait
booleanfalse
Attend que le réseau soit inactif (aucune requête pendant ~500 ms). Idéal pour les SPAs qui récupèrent des données après le mount.
css_click_selector
stringoptionnel
Sélecteur CSS — Crawlbase clique sur l'élément correspondant avant la capture. URL-encodez les caractères spéciaux.
scroll
booleanfalse
Défile jusqu'en bas de la page avant la capture. Déclenche le lazy-load.
scroll_interval
int (s)10
Nombre maximum de secondes à passer à défiler. À combiner avec scroll=true.
screenshot
booleanfalse
Capture un JPEG de la page rendue. L'URL est renvoyée dans screenshot_url dans les en-têtes de réponse (ou dans le corps JSON quand format=json) et expire après une heure. Pour les workflows multi-captures ou pleine page, utilisez plutôt l'API Screenshots dédiée.

Options de sortie de la capture d'écran. Quand screenshot=true, la capture par défaut est la page rendue complète. Pour la limiter au seul viewport, ajoutez mode=viewport ; combinez-le avec width et height (en pixels) pour contraindre la capture. Les deux ont par défaut les dimensions de l'écran et ne prennent effet qu'avec mode=viewport. Exemple : &screenshot=true&mode=viewport&width=1200&height=800.

Comment scroll est facturé. Les requêtes avec scroll activé sont facturées au temps total de traitement côté serveur. Les 8 premières secondes (chargement de page + défilement combinés) comptent pour 1 requête ; chaque tranche de 5 secondes supplémentaires ajoute 1 requête facturée. Un défilement de 20 s = 1 (les 8 premières secondes) + 1 (9–13 s) + 1 (14–18 s) + 1 (19–20 s, les blocs partiels comptent comme entiers) = 4 requêtes facturées. Si la page se termine avant scroll_interval, seul le temps de traitement réel est facturé.

Le scroll_interval maximum est de 60 secondes — au-delà de 60 s, le défilement s'arrête et la réponse est renvoyée. Lorsque vous définissez scroll_interval=60, gardez la connexion côté client ouverte au moins 90 secondes pour laisser à la réponse le temps de revenir. Combiner scroll avec page_wait augmente le temps total de traitement et donc le nombre de requêtes facturées.

Le paramètre css_click_selector ne prend effet que lorsque vous utilisez le JavaScript token (il s'exécute dans le navigateur headless avant la capture du DOM). Il accepte tout sélecteur CSS valide entièrement spécifié — par exemple un ID comme #some-button, une classe comme .some-other-button, ou un sélecteur d'attribut comme [data-tab-item="tab1"]. URL-encodez toujours la valeur pour que les caractères spéciaux survivent intacts à la query string.

Si le sélecteur n'est pas trouvé sur la page, la requête échoue avec le pc_status 595. Pour quand même recevoir une réponse lorsque la cible du clic peut être absente, ajoutez un sélecteur universellement présent comme fallback — séparé par une virgule. Par exemple, #some-button,body se rabat sur le clic de body lorsque #some-button n'existe pas.

Sélecteurs multiples. Pour cliquer sur plusieurs éléments en séquence avant la capture, séparez-les par une barre verticale (|). URL-encodez la valeur entière, y compris la barre. Par exemple, cliquer sur #start-button puis sur .next-page-link ressemble à #start-button|.next-page-link en forme brute, ou %23start-button%7C.next-page-link URL-encodé. Les clics ont lieu dans l'ordre indiqué. Si un sélecteur de la chaîne est absent, la même règle de pc_status 595 s'applique, donc le pattern de fallback ,body fonctionne par sélecteur.

Besoin d'exécuter du JavaScript personnalisé dans la page avant que Crawlbase ne capture le DOM (par exemple déclencher un événement synthétique, muter un état, forcer un fetch) ? C'est une fonctionnalité par compte conditionnée à votre cas d'usage — contactez le support en décrivant ce que vous essayez de faire et nous le mettrons en place.

Async & stockage

Le mode async fait passer l'API de « bloque jusqu'à ce que j'aie ta page » à « mets-la en file d'attente et préviens-moi quand c'est prêt ». L'endpoint répond immédiatement avec un rid ; le résultat réel est livré à un webhook que vous spécifiez, ou stocké dans Cloud Storage et récupéré plus tard via le même rid. C'est le bon mode pour les jobs en lot et les cibles lentes — async libère votre slot de concurrence dès que la requête est en file, vous pouvez donc continuer à soumettre pendant que les crawls sont encore en cours. Pour les jobs à fort volume (millions d'URLs), utilisez l'Enterprise Crawler qui s'appuie sur ce même pipeline async avec retries, gestion du débit et livraison des résultats.

Le mode async est actuellement réservé à linkedin.com

Le flag async=true n'est actuellement pris en charge que pour les URLs linkedin.com. Si vous avez besoin de crawls async sur d'autres domaines, contactez le support en indiquant le domaine cible afin que nous l'activions pour votre token.

async
booleanfalse
Renvoie immédiatement un rid au lieu de bloquer. Le résultat est livré au callback s'il est défini, ou disponible via Cloud Storage par rid.
callback
URLoptionnel
URL du webhook qui recevra le résultat du crawl. Requis quand async=true si vous ne voulez pas faire de polling.
store
booleanfalse
Persiste la page crawlée dans Cloud Storage. Renvoie un rid en plus du corps.

Format de sortie

La réponse par défaut est le corps brut de la page — exactement ce qu'un navigateur recevrait après le rendu et la résolution anti-bot. Pour la plupart des pipelines, c'est la bonne forme (votre parser en aval gère le HTML directement). Utilisez format=json quand vous voulez les métadonnées (statut, URL finale, RID, en-têtes) regroupées dans une seule enveloppe plutôt que réparties entre les en-têtes de réponse et le corps. Utilisez scraper= ou autoparse=true quand la cible est l'une de celles pour lesquelles nous avons déjà un parser — vous évitez complètement l'étape de parsing et récupérez des champs structurés propres à la place du markup brut.

format
html | json | mdhtml
Choisissez l'enveloppe de réponse. html renvoie la page brute avec les métadonnées dans les en-têtes de réponse. json emballe la page plus toutes les métadonnées dans un seul objet JSON. md convertit la page en Markdown GitHub-Flavored — à combiner avec md_readability=true pour retirer d'abord la nav, la sidebar et les pubs.
md_readability
booleanfalse
N'a de sens qu'avec format=md. Quand sa valeur est true, Crawlbase exécute une passe de readability sur la page avant la conversion en Markdown — supprime le chrome (nav, sidebar, footer, emplacements publicitaires) et conserve le contenu principal de l'article. Idéal pour convertir des articles de blog en contexte LLM propre.
pretty
booleanfalse
N'a de sens qu'avec format=json. Pretty-print l'enveloppe JSON avec indentation et retours à la ligne pour la lecture humaine ; à laisser désactivé en production pour garder des réponses compactes.
scraper
stringoptionnel
Applique un scraper intégré pour extraire des données structurées au lieu de renvoyer du HTML. Exemple : amazon-product-details.
autoparse
booleanfalse
Détecte automatiquement le type de page et applique le scraper correspondant. Pratique pour « donne-moi du JSON quand tu peux ».

Contrôle de la réponse

Ces paramètres modifient ce que contient la réponse ou la manière dont Crawlbase décide qu'une requête a réussi. Utilisez get_headers et get_cookies quand vous avez besoin que les en-têtes de réponse du site cible ou les valeurs Set-Cookie vous soient retournés (ils sont supprimés par défaut). Utilisez custom_success_codes quand la cible renvoie légitimement un statut non-2xx que votre pipeline doit traiter comme un fetch propre — sans cela, Crawlbase retentera ces réponses pour vous.

get_headers
booleanfalse
Fait remonter les en-têtes de réponse du site cible. Ils reviennent préfixés en tant qu'en-têtes de réponse original_header_*, ou regroupés sous original_headers quand format=json.
get_cookies
booleanfalse
Fait remonter les valeurs Set-Cookie du site cible. Elles reviennent en tant que original_set_cookie dans les en-têtes de réponse, ou sous la même clé quand format=json.
custom_success_codes
stringoptionnel
Liste de codes de statut HTTP séparés par des virgules à traiter comme réussis — par exemple custom_success_codes=403,429,503. Crawlbase ne retentera pas ces requêtes, et le statut original est conservé dans original_status. Utilisez-le quand la cible renvoie légitimement ces codes pour votre point de terminaison (APIs protégées par authentification, pages bloquées géographiquement dont vous voulez quand même le corps).

Requêtes POST

Utilisez POST quand le point de terminaison cible attend un corps de requête — soumissions de formulaires, APIs JSON, GraphQL, tout ce qui ne tient pas dans une chaîne de requête. Même point de terminaison, mêmes paramètres, même structure de réponse qu'avec GET ; seuls la méthode HTTP et le corps changent.

POST fonctionne uniquement avec le Normal token

Les requêtes POST ne fonctionnent qu'avec le Normal token. Le JavaScript token (et les paramètres de rendu JS page_wait, ajax_wait, scroll, css_click_selector) sont réservés à GET — quand vous avez besoin de soumettre un formulaire sur une page rendue en JS, utilisez le JavaScript token avec css_click_selector pour piloter le bouton du formulaire au lieu de faire un POST directement vers l'URL du formulaire.

Le Content-Type par défaut est application/x-www-form-urlencoded. Passez les champs du formulaire dans le corps de la requête — Crawlbase les transmet à la cible sans modification.

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))
}
N'en abusez pas

POST ne peut pas être utilisé pour spammer ni nuire de quelque manière que ce soit aux sites cibles. Crawlbase surveille activement les schémas abusifs ; les comptes pris à utiliser POST pour du spam, du credential stuffing ou tout autre trafic malveillant seront suspendus et signalés.

POST avec un corps JSON

Surchargez le content type form-urlencoded par défaut avec post_content_type. Encodez la valeur en URL (par exemple application/json devient application%2Fjson). Le corps est transmis à la cible sans modification — encodez-le vous-même en 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))
}

Note : c'est le site cible qui décide d'accepter ou non le corps. Crawlbase transmet la requête fidèlement — si la cible renvoie un 4xx parce que la structure du corps est incorrecte, cela apparaît dans original_status, pas dans pc_status. Voir Erreurs pour le schéma de branchement.

Requêtes PUT

PUT fonctionne de la même manière que POST — même point de terminaison, mêmes paramètres, mêmes règles d'encodage du corps. La seule différence est la méthode 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))
}

Comme POST, PUT nécessite le Normal token. Utilisez post_content_type pour contrôler le type de média du corps s'il n'est pas en form-urlencoded.

N'utilisez pas POST/PUT pour spammer

Crawlbase surveille activement le trafic POST et PUT. Envoyer des corps de requête ciblant des sites tiers que vous ne possédez pas — spam de commentaires, soumissions de formulaires frauduleuses, création de comptes scriptée — entraîne la suspension du compte d'origine dès la première détection. Utilisez ces verbes pour des intégrations API légitimes, vos propres points de terminaison de staging et de production, et l'automatisation explicitement autorisée.

Réponse

Les réponses réussies retournent la page cible dans le corps. Les métadonnées se trouvent dans les en-têtes de réponse.

En-têtes

En-têteDescription
pc_statusCode de statut Crawlbase. 200 = succès.
original_statusStatut HTTP du site cible.
urlURL finale après redirections.
ridID de requête. Retourné quand async=true ou store=true.
content-typeType MIME du corps (text/html, application/json, image/png, etc).
original_header_*Retourné quand get_headers=true. Chaque en-tête du site cible arrive avec un préfixe original_header_ (par exemple original_header_x_frame_options). Regroupés sous original_headers quand format=json.
screenshot_urlRetourné quand screenshot=true. URL JPEG temporaire de la page rendue ; expire une heure après le crawl.
original_set_cookieRetourné quand get_cookies=true. Valeurs Set-Cookie concaténées issues de la réponse du site cible.
domain_complexity
également X-Domain-Complexity
Le niveau de complexité du domaine crawlé — l'un de standard, moderate ou complex. Reflète les ressources nécessaires pour contourner les protections du site et correspond directement au palier tarifaire facturé pour la requête. Voir les niveaux de complexité ci-dessous.
storage_urlRetourné quand la requête a été faite avec store=true. Pointeur vers la copie stockée de la réponse dans Crawlbase Cloud Storage ; à associer avec rid pour la récupérer plus tard.
Content-Typetext/markdown; charset=utf-8 quand la requête a été faite avec format=md ; text/html ou application/json standard sinon.
X-Markdown-FlavorDialecte Markdown du corps de réponse — actuellement GitHub Flavored Markdown (GFM). Émis uniquement quand format=md.
X-Markdown-FeaturesListe séparée par des virgules des fonctionnalités GFM utilisées dans le corps (par exemple tables,lists). Vous permet de choisir un parser avec les bonnes extensions activées. Émis uniquement quand format=md.
X-Markdown-Base-URLHôte de l'URL résolue (après d'éventuelles redirections). Utile pour résoudre les liens relatifs dans le corps markdown. Émis uniquement quand format=md.
X-Markdown-GeneratorIdentifie le convertisseur — la valeur est ProxyCrawl-API. Émis uniquement quand format=md.

Réponse HTML

Le comportement par défaut. format=html (ou aucun format du tout) retourne le corps brut de la page dans le corps HTTP, avec les métadonnées dans les en-têtes de réponse (url, original_status, pc_status, X-Domain-Complexity, plus toutes les entrées original_header_* auxquelles vous avez souscrit via get_headers=true).

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>

Réponse JSON

Définissez format=json pour récupérer les mêmes données sous forme d'un seul objet 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>"
  }

Réponse Markdown

format=md retourne la page déjà convertie en GitHub Flavored Markdown dans le corps, avec Content-Type: text/markdown; charset=utf-8 et un bloc d'en-têtes de métadonnées X-Markdown-* (Flavor, Features, Base-URL, Generator) aux côtés des habituels url / original_status / pc_status. Associez-le à md_readability=true quand vous voulez une extraction du contenu principal (corps de l'article, sans habillage) avant la conversion — voir le paramètre 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) ...

Requêtes facturables

Crawlbase ne facture que les requêtes où pc_status est 200 etoriginal_status fait partie de :

CodeSignification
200OK
201Créé
204Pas de contenu
301Déplacé définitivement
302Trouvé — uniquement quand la redirection a été suivie et a renvoyé du contenu
404Introuvable
410Disparu

Tout autre original_status est gratuit, et il en va de même pour tout pc_status autre que 200. Utilisez cette liste lors du rapprochement d'une facture d'utilisation avec les logs de votre application.

Niveaux de complexité de domaine

Le champ domain_complexity (également retourné en tant qu'en-tête de réponse X-Domain-Complexity) vous indique la difficulté à crawler le domaine cible — et le palier tarifaire dans lequel la requête s'est inscrite.

  • standard — facile à crawler, protection minimale. Palier tarifaire le plus bas.
  • moderate — protection anti-bot modérée nécessitant un traitement spécialisé. Palier tarifaire intermédiaire.
  • complex — protection avancée nécessitant des ressources spécialisées. Palier tarifaire le plus élevé.

Pour la tarification spécifique à chaque palier, consultez votre plan d'abonnement ou contactez le service commercial.

Modèles courants

SPA rendue en JS avec défilement

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)
}

Requête routée géographiquement

# 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)
}

Crawl asynchrone avec 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
}
Quand utiliser le mode async

Le mode async libère votre slot de concurrence dès que la requête est mise en file d'attente, ce qui évite qu'un long crawl monopolise votre budget. Utilisez-le pour les cibles lentes (JS lourd, page_wait long) lorsque vous devez traiter un volume élevé.

Mode proxy

Le même Crawling API peut être invoqué comme proxy HTTP/HTTPS au lieu d'un point de terminaison REST — utile lorsque vous disposez déjà d'un scraper, d'un script d'automatisation de navigateur ou d'un client HTTP qui prend en charge la configuration de proxy, et que vous préférez placer Crawlbase devant lui plutôt que de réécrire la couche de requête.

Pointez votre client vers smartproxy.crawlbase.com:8001 (HTTPS, recommandé) ou smartproxy.crawlbase.com:8000 (HTTP) et passez votre token comme nom d'utilisateur du proxy. Toutes les fonctionnalités du Crawling API — rendu JS, contournement anti-bot, routage géographique — s'appliquent à l'identique ; seule la forme de la requête change.

Mode proxy vs. Smart AI Proxy

Deux produits partagent le même nom d'hôte mais utilisent des ports différents — facile à confondre. Les capacités sont essentiellement les mêmes des deux côtés (routage par pays, émulation d'appareil, sessions, en-têtes personnalisés, rendu JS via les contrôles CrawlbaseAPI-*) ; ils diffèrent par l'abonnement sur lequel vous êtes facturé et par le palier de concurrence / threads que cet abonnement procure :

  • Crawling API en mode proxy (cette section) → ports 8000 / 8001. Passe par votre forfait Crawling API : même quota mensuel, même budget de concurrence, même facturation par succès que les appels en mode REST. Choisissez cette option si vous payez déjà pour le Crawling API et souhaitez disposer d'une interface de type proxy en complément du point de terminaison REST.
  • Smart AI Proxy (produit distinct, voir Smart Proxy) → ports 8012 / 8013. Une SKU distincte avec son propre abonnement et son propre modèle de threads / concurrence, dimensionnée pour les pipelines de scraping orientés proxy qui exécutent déjà un grand nombre de threads. Même réseau et mêmes en-têtes de contrôle en dessous — le choix porte sur le contrat et la forme de concurrence qui correspondent à votre usage.

Règle générale : choisissez le produit dont vous détenez déjà l'abonnement (ou dont le modèle tarifaire correspond à la forme de votre trafic). La surface fonctionnelle est la même ; les ports vous orientent simplement vers la bonne voie de facturation et de concurrence.

Démarrage rapide

Un premier appel depuis votre shell — Normal token, proxy 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))
}

Pour les cibles rendues en JS, utilisez votre 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)
}

Limites de débit

La limite de débit par défaut en mode proxy est de 20 requêtes par seconde (~1,7M req/jour). Les clients basés sur la concurrence devraient raisonner en threads plutôt qu'en RPS — à la latence typique du Crawling API (~4 s pour une page produit Amazon), cela correspond à environ 80 threads concurrents. Des cibles plus rapides se traduisent par moins de threads.

Si vous atteignez le plafond, contactez le support en décrivant votre cas d'usage pour négocier une concurrence plus élevée.

Erreurs & tentatives

Le Crawling API expose deux codes de statut sur chaque réponse : original_status (ce que le site cible a renvoyé) et pc_status (ce que Crawlbase en a fait après application des règles anti-bot, de redirection et de validation du contenu). Ils peuvent diverger — une cible peut renvoyer 200 avec un corps vide, auquel cas original_status vaut 200 mais pc_status vaut 520. Branchez toujours sur pc_status pour décider s'il faut réessayer.

Les échecs spécifiques au Crawling API les plus fréquents :

CodeSignificationAction
422url manquant ou non URL-encodéEncodez l'URL avant l'envoi. La plupart des clients (libcurl --data-urlencode, Python requests, Node fetch) le font automatiquement — mais les chaînes de requête construites à la main l'oublient souvent.
520Réponse vide de la cibleRéessayez une fois. Si la réponse reste vide, passez du Normal token au JS token — beaucoup de sites servent une coquille vide aux user agents non-navigateurs et s'appuient sur le JS pour la remplir.
521Site cible hors service / inaccessibleTraitez-le comme une erreur amont transitoire. Backoff + nouvelle tentative ; si cela persiste sur plusieurs minutes, le site est réellement hors service.
522Délai de connexion dépassé en atteignant la cibleRéessayez avec backoff. Essayez un autre country si la cible est instable selon la zone géographique.
523Origine inaccessible depuis la sortie choisieRéessayez sans country (laissez le routage automatique choisir) ou avec un autre pays.
525Le défi anti-bot n'a pas pu être résoluPassez du Normal token au JS token. Si vous êtes déjà sur JS, réessayez ; si cela persiste, escaladez au support — cela signifie généralement que la cible a déployé une nouvelle variante de défi.
595Sélecteur introuvable. La page s'est chargée correctement mais le sélecteur CSS passé via css_click_selector n'a correspondu à aucun élément.Ajoutez un repli au sélecteur (#start-button,body) pour que le clic atteigne tout de même un élément connu. Voir les notes sur css_click_selector pour le modèle complet.
599Erreur interne CrawlbaseRéessayez. Si une requête rencontre cette erreur de manière constante, contactez le support avec le rid.

La référence complète HTTP + pc_status se trouve dans Codes de statut ; Gestion des erreurs couvre la boucle recommandée de retry-with-backoff et les helpers de SDK qui l'implémentent pour vous dans chaque langage.

Exemple d'ancrage. La raison la plus fréquente pour laquelle pc_status diverge de original_status est un CAPTCHA : le site cible renvoie un 200 (la page captcha s'est rendue correctement) mais Crawlbase reconnaît la réponse comme une page d'interstitiel et expose pc_status: 503 afin que vous puissiez la contourner plutôt que de traiter le HTML du captcha comme vos données.

Codes pc_status non standard. Les codes en dehors de la plage HTTP habituelle — 601, 999 et similaires — sont des marqueurs internes utilisés par l'équipe d'ingénierie de Crawlbase. Ils ne sont exposés dans la réponse que pour faciliter le débogage lorsque vous contactez le support ; vous n'avez pas besoin de les gérer dans le code applicatif.

Stratégie de tentatives

La version simple : réessayez les erreurs transitoires (5xx) avec un backoff exponentiel jusqu'à un plafond (typiquement 3 à 5 tentatives), ne réessayez pas les erreurs client (4xx — elles ne se corrigent pas toutes seules), et changez de type de token une fois sur le premier 520/525 avant de réessayer davantage. Les helpers des SDKs implémentent cette boucle avec des valeurs par défaut raisonnables ; pour un client personnalisé, la règle générale est :

  • Première tentative : ~1 s après l'échec
  • Deuxième tentative : ~3 s après l'échec
  • Troisième tentative : ~10 s après l'échec
  • Au-delà : journaliser + alerter ; les échecs persistants signalent généralement un changement côté cible plutôt qu'un problème réseau transitoire

Toutes les tentatives sur cette API sont gratuites — seules les réponses réussies (pc_status: 200) sont décomptées de votre quota. Cela rend le backoff agressif peu coûteux ; le seul vrai coût d'une nouvelle tentative est la latence ajoutée à votre pipeline.

Performance & bonnes pratiques

Quelques modèles reviennent chez les clients qui exploitent cette API à grande échelle. Les adopter d'emblée évite les catégories de tickets de support les plus fréquentes.

  • Utilisez le token le moins coûteux qui fonctionne. Ne basculez pas sur le JavaScript token « au cas où » — les requêtes avec le Normal token sont plus rapides et consomment moins de concurrence. Passez au JS uniquement lorsque la réponse Normal est vide ou bloquée par un défi.
  • Préférez ajax_wait à page_wait. Les délais fixes consomment de la concurrence sur chaque requête, même les rapides. ajax_wait retourne dès que la page atteint l'état network-idle — généralement plus rapide en moyenne et plus lent uniquement sur les pages réellement longues à charger.
  • Faites passer les volumes élevés par async + webhook. Le mode synchrone est le bon choix par défaut pour un usage ponctuel et interactif. Pour les traitements par lot dépassant quelques centaines d'URLs, le mode async (ou l'Enterprise Crawler) garde votre budget de concurrence libre pour de nouvelles soumissions pendant que les crawls en cours se terminent.
  • Réutilisez les sessions pour les flux avec état. Si votre cible nécessite une session connectée ou des cookies de panier, conservez un identifiant de session et transmettez-le sur les requêtes suivantes afin de réutiliser la même IP de sortie et le même cookie jar. Voir Authentication pour le modèle de cookie de session.
  • Surveillez l'en-tête remaining. Faites du backoff avant d'atteindre votre plafond de concurrence plutôt que de le découvrir via des 429 — la réponse indique le nombre de slots restants, donc un client sain temporise de manière proactive plutôt que de réagir aux erreurs.