Логотип «2Captcha»Перейти на главную страницу

Scraper API — руководство для пользователей API

Это руководство описывает работу с Scraper API напрямую по HTTP: создание задач, синхронное выполнение, получение результатов, просмотр истории, загрузку веб-страниц и поиск Google.


Quick Start — получить HTML страницы

Самый простой сценарий — синхронно загрузить страницу и получить её HTML в JSON.

Понадобятся:

  • адрес API;
  • API-ключ;
  • URL страницы.

Задайте адрес API и ключ:

bash Copy
BASE_URL="https://scraper.2captcha.com"
API_KEY="<ВАШ_API_КЛЮЧ>"

Отправьте запрос:

bash Copy
curl -i -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "scrape",
    "url": "https://example.com",
    "data_format": "raw",
    "format": "json"
  }'

Параметры задачи:

Поле Значение Назначение
task_type scrape загрузить веб-страницу
url https://example.com адрес целевой страницы
data_format raw вернуть исходный HTML
format json поместить результат в JSON

Успешный ответ имеет HTTP-статус 200 и примерно такое тело:

json Copy
{
  "status": "success",
  "http_code": 200,
  "headers": {
    "content-type": "text/html; charset=utf-8"
  },
  "body": "<!doctype html><html>...</html>"
}

Здесь:

  • status — итог метода scrape: success, warn или error;
  • http_code — HTTP-статус целевой страницы;
  • headers — заголовки ответа целевой страницы;
  • body — полученный HTML.

Метаданные задачи находятся не в теле, а в ответном заголовке x-debug:

http Copy
x-debug: {"response_id":"...","add_datetime":1747983214000,"finish_datetime":1747983220000,"price":0.0005,"status_code":200,"status_message":"OK"}

Проверьте:

  • внешний HTTP-статус Scraper API равен 200;
  • status в JSON равен success; при warn прочитайте warning, при error — error;
  • http_code равен ожидаемому HTTP-статусу целевой страницы;
  • body не пуст и содержит нужный HTML;
  • x-debug.response_id заполнен;
  • x-debug.status_code совпадает с внешним HTTP-статусом.

Поле x-debug.price служебное и не используется для расчётов: стоимость задачи определяет биллинг.

Если нужен только HTML без JSON:

bash Copy
curl -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "scrape",
    "url": "https://example.com",
    "data_format": "raw",
    "format": "raw"
  }' \
  --output page.html

Результат будет сохранён в page.html. При недоступности целевого сайта даже с format: "raw" приходит JSON с описанием ошибки; перед использованием файла проверьте Content-Type и результат. Метаданные страницы доступны в заголовке x-debug_response (см. раздел 4.1).


1. Адрес API

Production:

text Copy
https://scraper.2captcha.com
bash Copy
BASE_URL="https://scraper.2captcha.com"

2. Авторизация

API поддерживает два способа авторизации. Для одного запроса достаточно одного способа.

2.1. API-ключ

Рекомендуемый способ:

http Copy
Authorization: Bearer <API_KEY>

Пример:

bash Copy
curl -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "scrape",
    "url": "https://example.com"
  }'

Не передавайте API-ключ в URL, не сохраняйте его в репозитории и не прикладывайте к логам или баг-репортам.

2.2. Email и пароль

Для POST-запросов email и password передаются в JSON-теле:

json Copy
{
  "email": "user@example.com",
  "password": "secret"
}

Для GET-запросов они передаются в query string:

text Copy
GET /task_history?email=user@example.com&password=secret

Этот способ менее безопасен для GET-запросов: URL может попасть в историю клиента, журналы прокси и серверные логи. По возможности используйте API-ключ.

2.3. Требования по эндпоинтам

Эндпоинт Авторизация
POST /tasks/request обязательна
POST /tasks/sync обязательна
GET /task_history обязательна
GET /tasks/result/:response_id не требуется

При неверных или отсутствующих учётных данных либо заблокированном аккаунте API возвращает:

http Copy
401 Unauthorized
json Copy
{
  "error": "Unauthorized",
  "status": "error"
}

Если учётные данные верны, но баланс исчерпан (≤ 0), ответ также имеет HTTP-статус 401, с другим телом:

json Copy
{
  "error": "Balance depleted — top up to continue",
  "status": "error"
}

Это отличается от 402 Payment Required: при 402 баланс положительный, но его не хватает на новую задачу с учётом уже зарезервированных средств. Неподтверждённый email сам по себе не закрывает доступ к API.


3. Общие правила запросов

  • Для POST-запросов используйте Content-Type: application/json.
  • Параметры метода передаются плоско в JSON-теле, без вложенного объекта params.
  • task_type можно передать в теле или в query string. Если он указан в обоих местах, приоритет имеет тело.
  • Размер JSON-тела не должен превышать 10 000 байт.
  • Поля email и password используются только для авторизации и не сохраняются в задании.
  • Стоимость задачи определяет биллинг. Поле price в заголовке x-debug служебное; не используйте его для расчётов.

Правильно:

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com",
  "data_format": "raw"
}

Не используйте вложенный params, если отдельная версия API явно не требует обратного:

json Copy
{
  "task_type": "scrape",
  "params": {
    "url": "https://example.com"
  }
}

4. Заголовок x-debug

Все ответы API содержат заголовок x-debug. Его значение всегда JSON, независимо от format. В нём находятся метаданные задачи и самого ответа API:

http Copy
x-debug: {"response_id":"0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a","add_datetime":1747983214000,"finish_datetime":1747983220000,"price":0.0005,"status_code":200,"status_message":"OK"}
Поле Тип Описание
response_id string или null идентификатор задачи; null, если задача не создана
add_datetime number или null время создания задачи, Unix milliseconds
finish_datetime number или null время завершения; null, пока задача выполняется
price number служебное поле; не использовать для расчётов, стоимость задачи определяет биллинг
status_code number HTTP-статус Scraper API
status_message string текст HTTP-статуса

HTTP-статус API, итог метода и HTTP-статус целевой страницы — разные значения:

text Copy
HTTP 200 от Scraper API — задача завершена
├── status: "success" — метод scrape вернул страницу без замечаний
└── http_code: 404 — целевой сайт ответил 404

4.1. Заголовок x-debug_response

Заголовок содержит служебные поля результата конкретного метода. Он доступен при format: "json" и format: "raw":

http Copy
x-debug_response: {"http_code":404,"status":"warn","warning":"waitFor not met within 30s (element=#comments) — page returned as is"}

Для scrape в него входят status, http_code и, при наличии, warning; при недоступности сайта — status, http_code и error. Заголовки целевой страницы (headers) и адрес ошибки (url) в него не переносятся. Заголовок содержит только часть полей результата: полный набор доступен в JSON-теле.

x-debug_response может присутствовать на ответах 200 и 422, если воркер передал служебные поля. Если он их не передал, заголовка не будет — это само по себе не ошибка. На 202 и 410 заголовка нет. Его наличие или отсутствие не определяет успешность запроса.

Значение всегда JSON в ASCII: нелатинские символы экранируются как \uXXXX. Обычный разбор JSON восстанавливает исходный текст.

4.2. Сервисный status и status метода

В JSON-ответах, которые формирует сам сервис для создания, ожидания или ошибки задачи, поле status описывает её состояние:

Значение Когда
pending задача создана, выполняется или не уложилась в синхронное ожидание, но продолжает работать
error запрос отклонён или задача завершилась с ошибкой
expired срок хранения результата истёк

На HTTP 200 эндпоинты выполнения и получения результата возвращают результат метода как есть, без сервисной обёртки. Собственное поле status метода описывает полученный результат. У scrape это success, warn или error (см. разделы 7.2 и 7.11); у google_search — success, no_results или error (см. раздел 8.3).

Например, HTTP 408 с status: "pending" означает, что синхронное ожидание завершилось, но задача продолжает выполняться. HTTP 200 с status: "error" в результате scrape означает, что задача завершена, но целевой сайт недоступен. Это отличается от сервисной ошибки HTTP 422.

Для служебных ответов POST-эндпоинтов при format: "raw" обычно возвращается строка без поля status: ID задачи или текст ошибки. У GET /tasks/result/:response_id нет параметра format, а ответы 202, 404, 410 и 422 всегда JSON. Недоступность сайта в scrape также всегда описывается JSON, включая format: "raw".

В истории задач status имеет отдельные значения: done и error (см. раздел 9).


5. Синхронное выполнение: POST /tasks/sync

Эндпоинт создаёт задачу и ожидает её завершения в рамках одного HTTP-запроса.

5.1. Общие параметры

Поле Тип Обязательно По умолчанию Описание
task_type string да — метод: scrape или google_search
format json или raw нет json формат результата метода
timeout number нет 60 время ожидания в секундах, допустимо 1–120
email string при авторизации паролем — email пользователя
password string при авторизации паролем — пароль пользователя
uule string нет — Canonical Name или lat,lon[,radius]; радиус в метрах, по умолчанию 200; для google_search также готовое значение Google (см. раздел 8.7). scrape также принимает этот параметр; его влияние на геопозицию браузера не описано
cdpurl string нет — WebSocket URL собственного Chrome/CDP для метода scrape; если подключиться не удаётся, задача завершается ошибкой 422
остальные поля зависит от метода зависит от метода — параметры scrape или google_search

5.2. Успешный ответ

При успешном выполнении API возвращает:

  • HTTP 200;
  • результат задачи в теле;
  • метаданные задачи в x-debug;
  • служебные поля результата в x-debug_response, если воркер их передал.

Для scrape с format: "json" фактически используется такая структура:

json Copy
{
  "status": "success",
  "http_code": 200,
  "headers": {
    "content-type": "text/html; charset=utf-8"
  },
  "body": "<!DOCTYPE html>..."
}

Для scrape с format: "raw" возвращается непосредственно содержимое body: HTML, Markdown или бинарный PNG. Поля status, http_code и warning доступны в x-debug_response. При недоступности сайта ответ всегда JSON с status: "error", даже при format: "raw" (см. раздел 7.11).

Форматы результата google_search описаны в разделах 8.3, 8.6 и 8.9.

5.3. Таймаут

Если задача не завершилась за указанное время:

http Copy
408 Request Timeout

При format: "json":

json Copy
{
  "error": "timeout",
  "response_id": "0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a",
  "status": "pending"
}

При format: "raw" тело ответа 408 содержит только ID задачи:

text Copy
0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a

Задача продолжает выполняться. Используйте полученный response_id для последующего запроса /tasks/result/:response_id.

5.4. Ошибка выполнения

Если задача завершилась с ошибкой:

http Copy
422 Unprocessable Entity

При ошибках GoogleParser, ScrapeParser и подключения CDP тело зависит от эндпоинта и format исходной задачи:

Запрос format задачи Content-Type ответа 422 Тело
POST /tasks/sync json application/json; charset=utf-8 JSON {"error":"…","status":"error"}
POST /tasks/sync raw text/plain текст ошибки без JSON-обёртки
GET /tasks/result/:response_id json или raw application/json; charset=utf-8 JSON {"status":"error","error":"…"}

Например, некорректный waitFor возвращает ScrapeParser: params.waitFor must be an object. Для GoogleParser текст начинается с GoogleParser:, а неуспешное подключение по cdpurl — с CDP connect failed. Ошибка max_restarts_exceeded означает, что задача исчерпала доступное число попыток выполнения.


6. Асинхронное выполнение

Асинхронный сценарий состоит из двух шагов:

  1. создать задачу через POST /tasks/request;
  2. получить результат через GET /tasks/result/:response_id.

6.1. Создание задачи: POST /tasks/request

Пример:

bash Copy
curl -X POST "$BASE_URL/tasks/request" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "scrape",
    "url": "https://example.com",
    "data_format": "raw",
    "format": "json"
  }'

Успешный ответ:

http Copy
201 Created
json Copy
{
  "response_id": "0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a",
  "status": "pending"
}

При format: "raw" тело содержит только ID:

text Copy
0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a

6.2. Вебхук

Для асинхронной задачи можно указать:

Поле Тип Описание
webhook_url string URL уведомления
webhook_method GET или POST по умолчанию GET
webhook_data string или object пользовательские данные вебхука

webhook_url — адрес, на который отправляется уведомление. Поле request_url в уведомлении содержит URL целевой страницы задачи.

Пример:

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com",
  "webhook_url": "https://client.example.com/scrape-callback",
  "webhook_method": "POST",
  "webhook_data": {
    "order_id": "A-10042"
  }
}

Для POST API отправляет:

json Copy
{
  "status": 200,
  "response_id": "0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a",
  "request_url": "https://example.com",
  "status_message": "OK",
  "webhook_data": {
    "order_id": "A-10042"
  }
}

Для GET (значение webhook_method по умолчанию) поля response_id, status, request_url и status_message добавляются к URL вебхука как query-параметры. Например:

text Copy
https://client.example.com/scrape-callback?response_id=0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a&status=200&request_url=https%3A%2F%2Fexample.com&status_message=OK

Числовое поле status уведомления не следует путать со строковым status в результате метода.

Ошибки доставки вебхука логируются, но не изменяют результат задачи.

6.3. Получение результата

bash Copy
curl "$BASE_URL/tasks/result/$RESPONSE_ID"

Эндпоинт не требует авторизации. На 200 он возвращает сохранённое содержимое результата как есть, без дополнительной обёртки; собственного параметра format у него нет. На 202, 404, 410 и 422 сервис возвращает JSON, независимо от формата, указанного при создании задачи.

Статус Значение Действие
200 OK задача успешно завершена прочитать результат
202 Accepted задача ещё выполняется повторить запрос через несколько секунд
404 Not Found ID не найден проверить response_id
410 Gone результат удалён по истечении срока хранения создать задачу заново
422 Unprocessable Entity задача завершилась с ошибкой прочитать поле error в JSON

При 202:

json Copy
{
  "status": "pending"
}

При 404:

json Copy
{
  "status": "error",
  "error": "Not found"
}

При 410 (запись о задаче сохранена, но тело результата уже удалено):

json Copy
{
  "status": "expired",
  "error": "Result expired"
}

При 422 (в том числе если исходный запрос имел format: "raw") приходит JSON с Content-Type: application/json; charset=utf-8:

json Copy
{
  "status": "error",
  "error": "ScrapeParser: params.waitFor must be an object"
}

Кроме x-debug, этот эндпоинт возвращает x-debug_request с параметрами найденной задачи и, когда доступны, данными задания. Пример для задачи scrape:

http Copy
x-debug_request: {"response_id":"0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a","ip":"1.2.3.4","add_datetime":1747983214000,"finish_datetime":1747983220000,"price":0.0005,"task_type":1,"params":{"task_type":"scrape","url":"https://example.com"}}
Поле Тип Описание
response_id string запрошенный ID задачи
ip string IP-адрес клиента
add_datetime number или null время создания, Unix milliseconds
finish_datetime number или null время завершения, Unix milliseconds
price number служебное поле; не использовать для расчётов, стоимость задачи определяет биллинг
task_type number числовой идентификатор типа задачи: 1 — scrape, 2 — google_search
params object или string параметры задания

Числовой task_type используется только в этом заголовке. В запросе метод по-прежнему передаётся строкой, например "task_type": "google_search". Заголовок завершённой задачи содержит поля из таблицы; поля time в этой структуре нет.

Поля задания присутствуют на 200, 410 и 422. На 202 и 404 заголовок содержит только response_id и ip. Заголовок x-debug_response может быть на 200 и 422; на 202 и 410 его нет (см. раздел 4.1).


7. Метод scrape

Метод загружает одну веб-страницу и возвращает HTML, Markdown или скриншот.

7.1. Параметры

Поле Тип Обязательно По умолчанию Описание
task_type string да — всегда scrape
url string да — полный URL с http:// или https://
data_format string нет raw raw, markdown или screenshot
format string нет json json или raw
waitFor JSON-объект нет — условие ожидания перед получением результата; максимум 30 секунд
fullPage boolean нет false полностраничный скриншот; применяется к screenshot
cdpurl string нет — собственный Chrome через CDP

Адрес без схемы, например example.com, не принимается: задача завершается с HTTP 422 и ScrapeParser: invalid URL "example.com". Адрес вида //example.com также даёт 422. Схемы data: и about: не подходят для загрузки веб-страницы: возможен пустой результат при http_code: 0. Адрес ftp:// может завершиться ошибкой загрузки. Для загрузки страницы используйте http:// или https://.

Поле uule принимается в запросе scrape, но его влияние на геопозицию браузера не описано. Не используйте его как единственный способ задать местоположение для этого метода.

waitFor передаётся JSON-объектом:

json Copy
{
  "waitFor": {
    "text": "Loaded"
  }
}

Строка, даже содержащая корректный JSON, не поддерживается и приводит к ошибке задачи.

7.2. data_format

Значение format: "json" format: "raw"
raw JSON с status, http_code, headers, HTML в body HTML напрямую
markdown JSON с Markdown в body Markdown напрямую
screenshot JSON с PNG в Base64-поле body бинарный PNG

Обычный успешный ответ при format: "json" имеет Content-Type: application/json; charset=utf-8. При format: "raw" тип зависит от data_format:

data_format Content-Type успешного ответа raw
raw text/html
markdown text/markdown
screenshot image/png

При недоступности сайта ответ в режиме raw меняет тип на application/json; charset=utf-8 (см. раздел 7.11). Проверяйте фактический заголовок перед обработкой тела.

При format: "json" результат загруженной страницы содержит:

Поле Тип Описание
status string success — без замечаний; warn — есть warning; error — сайт недоступен (см. раздел 7.11)
http_code integer HTTP-код целевого сайта; 0 при сетевой ошибке
headers object заголовки ответа целевого сайта
body string HTML, Markdown или PNG в Base64 согласно data_format
warning string только если условие waitFor не выполнено за 30 секунд; тогда status: "warn"

status: "success" означает, что метод получил страницу без замечаний, и не требует HTTP 2xx от сайта. Например, ответ сайта 404 возвращается с HTTP 200 от API, status: "success" и http_code: 404. Коды сайта 4xx, включая 403 и 404, не считаются недоступностью.

При format: "raw" тело содержит HTML, Markdown или бинарный PNG. Метаданные status, http_code, warning передаются в x-debug_response, а заголовки сайта headers в этом режиме недоступны. При недоступности сайта вместо содержимого приходит JSON (см. раздел 7.11).

7.3. Получить HTML в JSON

bash Copy
curl -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "scrape",
    "url": "https://example.com",
    "data_format": "raw",
    "format": "json"
  }'

Ответ:

json Copy
{
  "status": "success",
  "http_code": 200,
  "headers": {
    "content-type": "text/html; charset=utf-8"
  },
  "body": "<!DOCTYPE html>..."
}

7.4. Получить HTML напрямую

bash Copy
curl -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "scrape",
    "url": "https://example.com",
    "data_format": "raw",
    "format": "raw"
  }'

Ответ начинается с:

html Copy
<!DOCTYPE html>
<html>

7.5. Получить Markdown

JSON:

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com",
  "data_format": "markdown",
  "format": "json"
}

Напрямую:

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com",
  "data_format": "markdown",
  "format": "raw"
}

Пример Markdown:

md Copy
# Example Domain

[More information](https://www.iana.org/domains/example)

7.6. Сделать скриншот видимой области

Base64 в JSON:

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com",
  "data_format": "screenshot",
  "format": "json",
  "fullPage": false
}

Ответ:

json Copy
{
  "status": "success",
  "http_code": 200,
  "headers": {
    "content-type": "text/html; charset=utf-8"
  },
  "body": "iVBORw0KGgoAAAANSUhEUg..."
}

Поле body содержит Base64 без префикса data:image/png;base64,.

7.7. Получить бинарный PNG

bash Copy
curl -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "scrape",
    "url": "https://example.com",
    "data_format": "screenshot",
    "format": "raw"
  }' \
  --output screenshot.png

Первые восемь байт файла:

text Copy
89 50 4E 47 0D 0A 1A 0A

7.8. Сделать полностраничный скриншот

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com/long-page",
  "data_format": "screenshot",
  "format": "json",
  "fullPage": true
}

Для data_format: "raw" параметр fullPage фактически игнорируется и не изменяет HTML-результат.

7.9. waitFor

Условие задаётся JSON-объектом с text, element или state. Для CSS-селектора element параметр checkVisible по умолчанию равен false.

Ожидать текст

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com/dynamic",
  "waitFor": {"text": "Content loaded"}
}

Ожидание имеет смысл, если текст отсутствует в исходном HTML и появляется на странице позже.

Ожидать элемент

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com",
  "waitFor": {"element": "#content", "checkVisible": false}
}

Ожидать видимый элемент

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com",
  "waitFor": {"element": "#content", "checkVisible": true}
}

Ожидать полную загрузку

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com",
  "waitFor": {"state": "load"}
}

Ожидать построение DOM

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com",
  "waitFor": {"state": "domcontentloaded"}
}

Поддерживаемые значения state:

Значение Описание
load страница и зависимые ресурсы загрузились
domcontentloaded DOM построен, ресурсы ещё могут загружаться

Лимит ожидания и предупреждения

Ожидание waitFor ограничено 30 секундами. Увеличение общего timeout не увеличивает этот лимит; timeout в /tasks/sync задаёт время ожидания завершения задачи в HTTP-запросе.

Если условие не выполнено за 30 секунд, задача не завершается ошибкой: загруженная страница возвращается как есть. В результате появляются status: "warn" и warning:

json Copy
{
  "status": "warn",
  "http_code": 200,
  "headers": {
    "content-type": "text/html; charset=utf-8"
  },
  "body": "<!DOCTYPE html>...",
  "warning": "waitFor not met within 30s (element=#comments) — page returned as is"
}

Если условие выполнено или waitFor не задан, поля warning нет. При format: "raw" предупреждение доступно в x-debug_response. Перед page returned as is в значении warning используется длинное тире — (U+2014); в JSON-значении заголовка оно передаётся как —. Для обработки предупреждений не сравнивайте всю строку с жёстко заданным образцом.

Ошибки waitFor

Некорректное значение завершает задачу сразу, без повторов и без обращения к целевому сайту. Текст ошибки приходит с префиксом ScrapeParser: …:

Текст ошибки без префикса Причина
params.waitFor must be an object передана строка, в том числе с JSON внутри, число, массив или true
params.waitFor must have "element", "text" or "state" объект не содержит известных полей либо element пуст
params.waitFor.state must be one of: load, domcontentloaded неизвестное значение state

7.10. Собственный Chrome через cdpurl

json Copy
{
  "task_type": "scrape",
  "url": "https://example.com/account",
  "cdpurl": "wss://browser.example.com/devtools/browser/9b2c1f0a-..."
}

Требования:

  • поддерживается ws:// или wss://;
  • браузер должен быть доступен воркеру всё время выполнения;
  • задача использует куки, сессии, профиль, отпечаток и прокси этого браузера;
  • не публикуйте CDP URL: доступ к нему фактически даёт доступ к браузерной сессии.

Если cdpurl не указан, браузер подбирает воркер. Если он указан, но подключиться не удалось после двух попыток, задача завершается с HTTP 422, без дальнейших повторов и без замены вашего браузера браузером воркера. Текст ошибки начинается с:

text Copy
CDP connect failed (user cdpurl) after 2 attempts: Timeout 12000ms exceeded.
CDP connect failed (user cdpurl) after 2 attempts: WebSocket error: <endpoint> 401 Unauthorized deny_no_user
CDP connect failed (user cdpurl) after 2 attempts: WebSocket error: <endpoint> 500 Internal Server Error profile_locked

В тексте ошибки адрес CDP заменяется на <endpoint>, чтобы учётные данные из URL не попадали в ответ и историю задач. Проверьте, что браузер запущен и доступен извне, ссылка не устарела после перезапуска Chrome, а профиль не занят другим подключением.

7.11. Недоступность сайта

При сетевой ошибке (DNS, отказ соединения, ошибка TLS, таймаут загрузки) либо ответе сайта 5xx задача считается успешно завершённой на уровне API: HTTP-ответ имеет код 200, а результат метода содержит status: "error" и причину:

json Copy
{
  "status": "error",
  "http_code": 0,
  "error": "net::ERR_NAME_NOT_RESOLVED at https://example.invalid",
  "url": "https://example.invalid"
}
Поле Описание
status error — сайт недоступен
http_code 0 при сетевой ошибке или фактический HTTP-код 5xx сайта
error текст причины
url адрес, на котором произошла ошибка

В этом случае ответ всегда JSON, включая format: "raw". При сетевой ошибке Content-Type — application/json; charset=utf-8 как при format: "raw", так и при format: "json"; внешний HTTP-статус — 200. Заголовок x-debug_response дублирует status, http_code и error; поле url доступно только в теле. Не сохраняйте такой ответ как HTML или PNG без проверки Content-Type и результата.

Ответы сайта 4xx возвращаются обычным результатом с соответствующим http_code. Таймаут загрузки сайта отличается от HTTP 408 синхронного эндпоинта: при 408 сама задача продолжает выполняться (см. раздел 5.3).


8. Метод google_search

Метод выполняет запрос в Google и возвращает структурированную выдачу по группам либо документ Markdown. Авторизация, синхронное и асинхронное выполнение, вебхуки и получение результата работают по общим правилам из разделов 2–6.

8.1. Параметры

Все поля передаются плоско в JSON-теле, без вложенного params.

Поле Тип Обязательно По умолчанию Описание
task_type string да — всегда google_search
url string да — полный URL поиска; домен — только google.com и его поддомены, параметр q обязателен
format json или raw нет json формат тела; при data_format: "markdown" режим raw отдаёт документ напрямую. Без Markdown raw возвращает полный JSON результата (см. раздел 8.9)
data_format string нет обычная выдача группами markdown — документ; любое другое значение, включая json, — обычная выдача группами
page_num или pages integer нет 1 количество страниц выдачи, целое число от 1 до 10
gl string нет — двухбуквенный код страны; переопределяет gl внутри url
hl string нет — двухбуквенный код языка; переопределяет hl внутри url
with_ai boolean нет true собирать ответ ИИ на обычной выдаче
with_preview boolean нет false добавить превью к позициям news, video и books
with_html boolean нет false добавить HTML каждой страницы; только при format: "json"
saveScreenshot boolean нет false добавить скриншот каждой страницы; только при format: "json"
uule string нет — Canonical Name, lat,lon[,radius] или готовое значение Google; радиус в метрах, по умолчанию 200

Для числа страниц используйте одно из полей: page_num или pages. Параметр timeout синхронного запроса описан в разделе 5.1.

8.2. Параметры внутри url

Метод сохраняет следующие параметры Google; остальные удаляются, потому что неизвестный параметр в запросе повышает вероятность блокировки. Геолокация uule обрабатывается отдельно: её можно передать в URL или в теле запроса (см. раздел 8.7).

Параметр Пример Описание
q q=fastify+nodejs обязательный поисковый запрос
oq oq=fastify исходный запрос до автодополнения
hl hl=en язык страницы результатов
gl gl=us страна поиска; если gl указан в URL и геолокация не задана явно, API добавляет uule страны. Одно только поле gl в теле такой подстановки не вызывает (см. раздел 8.7)
start start=20 смещение выдачи; неотрицательное, кратно 10, не больше 90
tbm tbm=isch тип поиска из таблицы ниже
udm udm=2 тип поиска из таблицы ниже
tbs tbs=qdr:d фильтры Google, в том числе по времени
lr, cr lr=lang_en ограничение по языку и стране документов
safe safe=active безопасный поиск
pws pws=0 значение 0 отключает персонализацию выдачи
nfpr nfpr=1 искать без автозамены опечатки

Пробелы в значениях URL-параметров кодируются как %20 или +.

Типы поиска

Для обычной веб-выдачи не задавайте tbm и udm. В строках с двумя вариантами достаточно одного из них.

Выдача Параметр layout Основная группа
Обычная веб-выдача без tbm и udm organic organic
Места udm=1 или tbm=lcl places places
Изображения udm=2 или tbm=isch images images
Видео udm=7 или tbm=vid video video
Вакансии udm=8 jobs jobs
Новости udm=12 или tbm=nws news news
Книги udm=36 или tbm=bks books books
Shorts udm=39 shorts video
AI Mode udm=50 ai_mode ai_overview

Другие значения tbm и udm не поддерживаются и приводят к ошибке задачи. В частности, udm=14, udm=18 и udm=56 не входят в допустимый список. Покупки (udm=28, tbm=shop) временно недоступны.

Глубина выдачи

На странице выдачи — до 10 результатов. Для нескольких страниц задайте page_num от 1 до 10; параметры num и filter не поддерживаются.

start=20 начинает сбор с третьей страницы. Номера страниц и позиций при этом абсолютные (см. раздел 8.8).

json Copy
{
  "task_type": "google_search",
  "url": "https://www.google.com/search?q=fastify+nodejs&hl=en&gl=us&start=20",
  "page_num": 2,
  "format": "json"
}

Чтобы получить только вторую страницу, укажите start=10 без page_num:

json Copy
{
  "task_type": "google_search",
  "url": "https://www.google.com/search?q=fastify+nodejs&hl=en&gl=us&start=10"
}

8.3. Обычный веб-поиск

bash Copy
curl -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "google_search",
    "url": "https://www.google.com/search?q=fastify+nodejs&hl=en&gl=us",
    "page_num": 1,
    "format": "json",
    "data_format": "json",
    "with_html": false
  }'

Пример структуры ответа с одной позицией:

json Copy
{
  "status": "success",
  "results": 484000000,
  "layout": "organic",
  "task_param": {
    "url": "https://www.google.com/search?q=fastify+nodejs&hl=en&gl=us",
    "pages": 1
  },
  "real_location": "United States",
  "groups": {
    "organic": {
      "count": 1,
      "items": [
        {
          "url": "https://fastify.dev/",
          "title": "Fastify",
          "description": "Fast and low overhead web framework for Node.js",
          "position": 1,
          "page_position": {
            "page": 1,
            "position": 1
          }
        }
      ]
    }
  }
}
Поле Тип Описание
status string success — выдача собрана; no_results — позиций нет, это не ошибка; error — ошибка, её текст может быть в поле error
results number количество найденного по данным Google; -1, если счётчик прочитать не удалось
layout string тип основной выдачи из раздела 8.2; unknown, если вёрстка не опознана
task_param object параметры, полученные обработчиком, включая геолокацию, подставленную сервером
real_location string или null регион, фактически применённый Google; null, если подпись прочитать не удалось
groups object результаты по группам; обычная группа содержит count и массив items, ai_overview имеет отдельную структуру (см. раздел 8.8)
error string необязательный текст ошибки при status: "error"
html array HTML каждой страницы; только при with_html: true и format: "json"
screenshots array скриншоты страниц в Base64; только при saveScreenshot: true и format: "json"

Органические позиции находятся в groups.organic.items. Группы organic может не быть, например при поиске изображений. В одном ответе могут присутствовать группы разных типов.

results: 0 и results: -1 имеют разный смысл: 0 означает, что Google сообщил об отсутствии результатов; -1 — счётчик недоступен. В выдаче вакансий и AI Mode счётчика нет, поэтому там всегда -1. Наличие позиций проверяйте по status и groups, а не по одному results. Число найденного у Google может меняться между сессиями одного запроса.

При with_html: false поле html не добавляется. Чтобы получить HTML и скриншоты страниц вместе с результатом:

json Copy
{
  "task_type": "google_search",
  "url": "https://www.google.com/search?q=fastify+nodejs&hl=en&gl=us",
  "format": "json",
  "with_html": true,
  "saveScreenshot": true
}

Чтобы не собирать ответ ИИ на обычной выдаче, передайте with_ai: false. Наличие AI Overview при включённом сборе всё равно зависит от того, показал ли его Google.

8.4. Поиск изображений

json Copy
{
  "task_type": "google_search",
  "url": "https://www.google.com/search?q=modern+furniture&hl=en&gl=us&tbm=isch",
  "format": "json",
  "data_format": "json"
}

Альтернативно:

text Copy
https://www.google.com/search?q=modern+furniture&hl=en&gl=us&udm=2

Основная группа — groups.images, значение layout — images. Поле позиции url ведёт на страницу-источник, origin_image_url — на исходное изображение. Превью доступно в image_url либо image_base64.

8.5. Новости

json Copy
{
  "task_type": "google_search",
  "url": "https://www.google.com/search?q=technology&hl=en&gl=us&tbm=nws",
  "format": "json",
  "data_format": "json"
}

Основная группа — groups.news, значение layout — news. Вместо tbm=nws можно использовать udm=12.

Новости за последние сутки, с превью:

json Copy
{
  "task_type": "google_search",
  "url": "https://www.google.com/search?q=technology&hl=en&gl=us&udm=12&tbs=qdr:d",
  "format": "json",
  "with_preview": true
}

Поле time содержит давность текстом, как её показывает Google. Без with_preview полей image_url и image_base64 в новостных позициях нет.

8.6. Результат в Markdown

При data_format: "markdown" результаты собираются в один документ.

В JSON:

json Copy
{
  "task_type": "google_search",
  "url": "https://www.google.com/search?q=fastify+nodejs&hl=en&gl=us",
  "format": "json",
  "data_format": "markdown"
}

Ответ содержит status, results, layout, task_param, real_location и строку body с документом. Поля groups в этом режиме нет. При with_html: true к JSON-результату добавляется HTML страниц в поле html. При saveScreenshot: true к JSON-результату с Markdown добавляется поле screenshots; для одной страницы оно содержит один PNG в Base64.

Напрямую:

bash Copy
curl -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "google_search",
    "url": "https://www.google.com/search?q=fastify+nodejs&hl=en&gl=us",
    "format": "raw",
    "data_format": "markdown"
  }' \
  --output search.md

При успешном получении документа тело — Markdown с Content-Type: text/markdown; вложения html и screenshots не передаются. Перед использованием файла проверьте HTTP-статус и Content-Type: служебная ошибка не является Markdown-результатом.

Документ состоит из разделов ## <группа> с нумерованными позициями; изображения включаются в документ как картинки. Раздел ## Task information и блок yaml могут отсутствовать. Для параметров задания при format: "json" используйте task_param, а при format: "raw" не рассчитывайте на их присутствие в тексте документа.

8.7. Геотаргетинг

Геолокацию можно задать через uule в теле запроса или внутри url. Значение из URL извлекается автоматически, дублировать его отдельным полем не нужно. Если передать разные значения в обоих местах, ошибка не возвращается: приоритет имеет значение из URL. Применённую геолокацию смотрите в task_param и real_location.

Canonical Name

json Copy
{
  "task_type": "google_search",
  "url": "https://www.google.com/search?q=pizza&hl=en&gl=us",
  "uule": "New York,New York,United States"
}

Canonical Name — название местоположения из базы Google Ads Geo Targets. Оно должно дословно совпадать с записью в этой базе. Количество частей у разных городов разное: например, у Парижа каноническое имя состоит из четырёх частей.

text Copy
Paris,Paris,Ile-de-France,France
New York,New York,United States
London,England,United Kingdom

Внутри URL пробелы кодируются как обычно:

text Copy
https://www.google.com/search?q=pizza&gl=us&uule=New+York,New+York,United+States

Координаты

Для координат используйте строку uule в формате lat,lon или lat,lon,radius:

json Copy
{
  "task_type": "google_search",
  "url": "https://www.google.com/search?q=pizza&hl=en&gl=us",
  "uule": "40.7128,-74.0060,5000"
}

Широта и долгота указываются в десятичных градусах, радиус — в метрах. Если радиус не указан, используется 200 метров. В примере радиус равен 5 км; значение 10 означало бы 10 метров.

Готовое значение Google

google_search также принимает готовое значение uule, скопированное из адресной строки выдачи Google. Оно передаётся Google без изменений и не проверяется по справочнику регионов, поэтому так можно задать страну или регион любой глубины.

Префикс Содержимое
w+ название региона
a+ координаты latlng и радиус в метрах

Например, поиск с готовым значением для United Kingdom:

json Copy
{
  "task_type": "google_search",
  "url": "https://www.google.com/search?q=coffee+shops&hl=en",
  "uule": "w+CAIQICIOVW5pdGVkIEtpbmdkb20"
}

Готовое значение можно передать и внутри url; сервер извлечёт его автоматически.

Геолокация по gl

Если uule не задан явно, автоматическое дополнение срабатывает для gl в URL. И gl=gb, и gl=uk приводят к одному uule для United Kingdom. Для кода страны ISO используйте gb; значение uk API также принимает. Явно заданный uule имеет приоритет над автоматическим дополнением по gl.

Поле gl только в теле запроса не вызывает автоподстановку uule. Результирующий real_location в этом случае может отличаться от ожидаемой страны. Для воспроизводимого региона передавайте uule явно или указывайте gl в URL и проверяйте real_location. Без gl и геолокации регион выбирает Google по IP; real_location: null означает, что подпись региона прочитать не удалось.

8.8. Группы результатов и позиции

Обычные группы имеют структуру { "count": N, "items": [...] }. Группа ai_overview устроена отдельно.

Группа Поля позиции
organic url, title, description
news url, title, description, source, time; image_url, image_base64 — при with_preview
video url, title, description, is_shorts; image_url, image_base64 — при with_preview
images url (страница-источник), title, image_url, image_base64, origin_image_url, origin_image_width, origin_image_height
books url, title, description; image_url, image_base64 — при with_preview
ads url, title, advertiser, display_url
places title, url, place_id, rating, reviews, category, price_level, address, distance, hours, review_quote, image_url, image_base64
jobs title, company, location, source, tags, url, job_id, image_url, image_base64
ai_overview объект с text и массивом sources; источник содержит url, label и, если доступен, domain

На обычной выдаче рядом с органическими результатами могут быть новости, видео, места и реклама. В этом случае layout остаётся organic. Например, наличие groups.places само по себе не означает layout: "places".

Нумерация

Поле Описание
position сквозной номер позиции по собранным страницам с учётом start
page_position объект с абсолютным номером страницы и местом на ней, например { "page": 3, "position": 1 }

При сборе нескольких страниц позиции объединяются в общий список соответствующей группы. Смещение start применяется только к группе, по которой листается выдача: для обычного поиска это organic. Врезки новостей, видео и мест нумеруются с единицы независимо от start.

Превью и отсутствующие данные

Для превью заполняется либо image_url (ссылка), либо image_base64 (изображение, встроенное Google в страницу). Без with_preview этих полей в news, video и books нет.

В places, jobs и books часть полей может иметь значение null, если данных нет на странице. Например, это возможно для рейтинга, числа отзывов, уровня цен, расстояния, часов работы, цитаты отзыва и изображений места, логотипа вакансии или обложки книги. Отсутствующие превью в books учитывайте вместе с настройкой with_preview.

null не равен нулю или пустой строке: rating: null означает отсутствие оценки. Отдельный случай — places.url: при отсутствии идентификатора места в графе знаний Google поле может быть пустой строкой.

AI Overview

ai_overview.text содержит Markdown. Поле sources — массив источников с адресами и подписями. Если ссылку источника не удалось развернуть, url ведёт на редирект Google, а поля domain нет.

Google показывает обзор не для каждого запроса. Если обзора нет, соответствующей группы в результате тоже нет. Параметр with_ai управляет сбором ответа ИИ на обычной выдаче; отдельный AI Mode выбирается через udm=50.

8.9. Заголовок x-debug_response и format: "raw"

Метаданные задачи находятся в x-debug. Служебные поля поисковой выдачи доступны в x-debug_response при обоих значениях format, с общими условиями из раздела 4.1:

http Copy
x-debug_response: {"status":"success","results":484000000,"layout":"organic","real_location":"United States"}

Позиции и группы в заголовок не переносятся. При format: "raw" без data_format: "markdown" (как без data_format, так и с data_format: "json") API возвращает полный JSON-результат: status, results, layout, task_param, real_location и groups. Content-Type — application/json; charset=utf-8. Для Markdown напрямую используйте format: "raw" вместе с data_format: "markdown" (см. раздел 8.6); тогда тело — текст документа.

8.10. Ошибки параметров и повторное выполнение

Ошибки параметров завершают задачу сразу с HTTP 422, без автоматических повторов. Текст ошибки имеет префикс GoogleParser: ….

Текст ошибки без префикса Причина
params.url required поле url не передано
invalid URL "…" значение не разбирается как URL, например передана поисковая фраза или адрес без схемы
URL domain must be google.com, got "…" домен не google.com и не его поддомен
URL missing required parameter "q" отсутствует поисковый запрос q
params.pages must be an integer 1..10 число страниц дробное, нечисловое или вне диапазона 1–10
"start" must be a non-negative multiple of 10 смещение отрицательное или не кратно 10
"start" must be <= 90 смещение больше 90
сообщение о неподдерживаемом типе выдачи tbm или udm не входит в разрешённый список; допустимые значения перечислены в тексте ошибки

Для ошибок параметров метод передаёт status: "error" и описание в x-debug_response. Пример заголовка:

http Copy
x-debug_response: {"status":"error","error":"params.url required"}

Кроме ошибок параметров, задача может завершиться с 422 и текстом max_restarts_exceeded: её не удалось выполнить за отведённое число попыток. В этом случае повторите запрос. Обрабатывайте его как ошибку задачи, ориентируясь на HTTP-статус и фактический Content-Type.

Общие форматы ответа 422 описаны в разделах 5.4 и 6.3. При разборе ответа учитывайте HTTP-статус, Content-Type и описание ошибки в x-debug_response.

Капча, страница входа Google, обрыв соединения и таймаут не относятся к ошибкам параметров: такие задачи повторяются автоматически. Таймаут синхронного ожидания HTTP 408 обрабатывается по общим правилам — сохраните response_id и запросите результат позже, не создавая дубликат задачи.


9. История задач

bash Copy
curl "$BASE_URL/task_history?task_type=scrape&from=2026-01-01&to=2026-12-31&limit=50&offset=0" \
  -H "Authorization: Bearer $API_KEY"
Параметр Тип По умолчанию Описание
task_type string — фильтр по методу
from DateTime — нижняя граница add_datetime
to DateTime — верхняя граница add_datetime
limit integer 100 число записей
offset integer 0 смещение

Ответ:

json Copy
[
  {
    "response_id": "0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a",
    "add_datetime": 1747983214000,
    "finish_datetime": 1747983220000,
    "task_type": "scrape",
    "params": {
      "task_type": "scrape",
      "url": "https://example.com",
      "format": "json"
    },
    "price": 0.0005,
    "mime_type": "application/json",
    "error": "",
    "status": "done"
  }
]

Поле params содержит параметры запроса, включая task_type и поля метода; авторизационные email и password в задании не сохраняются.

История содержит только завершённые задачи. Её поле status определяется по полю error: пустая строка — done, текст ошибки — error. Значения pending в истории нет. Этот статус относится к завершению задачи и не заменяет status внутри результата scrape или google_search.


10. Основные ошибки

HTTP-статус Причина
400 Bad Request неверные параметры, неизвестный или отключённый task_type, тело больше 10 000 байт
401 Unauthorized неверные или отсутствующие учётные данные, заблокированный аккаунт либо исчерпанный баланс (≤ 0); причина указана в error
402 Payment Required баланс положительный, но недостаточен для новой задачи с учётом резервов; задача не создаётся
404 Not Found запрошенный ID задачи не найден
408 Request Timeout синхронное ожидание закончилось; задача продолжает выполняться, сервисный status — pending
410 Gone результат удалён по сроку хранения
422 Unprocessable Entity задача завершилась с ошибкой
503 Service Unavailable приём задач данного типа временно приостановлен — Service overloaded, повтор через Retry-After; на /health/ready — инфраструктура не готова

Пример ошибки в JSON:

json Copy
{
  "error": "Insufficient balance",
  "status": "error"
}

При format: "raw" API может вернуть только текст:

text Copy
Insufficient balance

При 400 JSON-ответ также содержит status: "error". Примеры значений error:

  • params exceeds 10 000 bytes;
  • Unknown task type: unknown_name;
  • Task type temporarily disabled.

Для GET /tasks/result/:response_id ответы ожидания и ошибок всегда JSON (см. раздел 6.3).

При создании задачи баланс проверяется с учётом уже зарезервированных средств и стоимости новой задачи. При 402 задача не создаётся. Фактическое списание выполняется после успешного завершения задачи; стоимость определяет биллинг, а x-debug.price не предназначен для расчётов.

Перегрузка сервиса

Если воркеры не успевают обрабатывать очередь, приём задач конкретного типа через /tasks/request или /tasks/sync может временно приостанавливаться. При создании задачи API возвращает:

http Copy
503 Service Unavailable
Retry-After: 60
json Copy
{
  "error": "Service overloaded",
  "status": "error"
}

При format: "raw" тело содержит Service overloaded с Content-Type: text/plain; при format: "json" ответ содержит JSON с Content-Type: application/json; charset=utf-8. Задача не создаётся, баланс не резервируется. Повторите запрос через число секунд из Retry-After (в примере — 60; фактическое значение берите из ответа). Ограничение снимается автоматически по мере обработки очереди.

Этот ответ отличается от 503 эндпоинта /health/ready, который сообщает о недоступности инфраструктурной зависимости.

Всегда проверяйте:

  1. HTTP-статус API;
  2. Content-Type;
  3. x-debug.status_code;
  4. x-debug.response_id;
  5. сервисный status и error, если ответ сформирован сервисом;
  6. для результата scrape — status, http_code и, при наличии, warning или error;
  7. для google_search — status, при наличии error, а также groups или body согласно data_format; значение results: -1 само по себе не означает отсутствие выдачи.

При format: "raw" используйте доступные поля x-debug_response, учитывая, что заголовок не гарантирован и его наличие не означает успех. При недоступности сайта прочитайте JSON-тело.


11. Служебные эндпоинты

Авторизация не требуется.

GET /health

bash Copy
curl "$BASE_URL/health"
json Copy
{
  "status": "ok",
  "ts": 1747983214000
}

GET /health/ready

bash Copy
curl "$BASE_URL/health/ready"

Успешный ответ:

json Copy
{
  "status": "ready",
  "deps": {
    "redis_streams": true,
    "redis_cache": true,
    "clickhouse": true,
    "s3": true
  }
}

Если зависимость недоступна, эндпоинт возвращает 503 и status: "degraded".


12. Рекомендации по интеграции

  • Используйте синхронный эндпоинт для коротких задач и асинхронный — для долгих или массовых. Если задача может выполняться дольше полуминуты, надёжнее асинхронный режим: не приходится держать соединение открытым.
  • При опросе GET /tasks/result/:response_id повторяйте запрос раз в несколько секунд, пока приходит 202.
  • При 408 не создавайте сразу дубликат задачи: сохраните response_id и запросите результат позже.
  • Не считайте внешний HTTP 200 подтверждением того, что целевой сайт тоже ответил 200.
  • Стоимость задачи определяет биллинг; служебное поле x-debug.price не используйте для расчётов.
  • Для scrape проверяйте результат в таком порядке: сначала status: "error" и поле error, затем status: "warn" и warning, затем http_code >= 400, и только после этого обрабатывайте body. Внешний HTTP 200 не отменяет ошибок метода или целевого сайта.
  • При 503 Service overloaded повторяйте создание задачи через интервал из Retry-After.
  • Для PNG с format: "raw" проверяйте Content-Type перед сохранением: при недоступности сайта вместо PNG приходит JSON с ошибкой.
  • Для PNG с format: "json" декодируйте Base64 из верхнеуровневого body.
  • Передавайте waitFor JSON-объектом. Для его поля text используйте строку, отсутствующую в исходном HTML и появляющуюся без действий пользователя.
  • Учитывайте лимит waitFor в 30 секунд: при его истечении возвращается страница с status: "warn" и полем warning.
  • Для google_search с обычной структурированной выдачей читайте groups из JSON. При format: "raw" без Markdown результат также содержит groups; ориентируйтесь на фактический Content-Type. При data_format: "markdown" читайте документ из body в JSON-режиме или получайте текст напрямую с format: "raw".
  • Не считайте google_search.status: "no_results" ошибкой; количество найденного Google в results не равно числу собранных позиций и может быть недоступно (-1).
  • Для воспроизводимой поисковой выдачи задавайте uule явно или указывайте gl внутри URL; одно лишь поле gl в теле не вызывает автоподстановку uule. Проверяйте фактический регион в real_location.
  • Не используйте внешние demo-сайты как постоянные фикстуры: их содержимое и доступность могут измениться.
  • Защищайте API-ключи, пароли, webhook URL и CDP URL.