Scraper API — руководство для пользователей API
Это руководство описывает работу с Scraper API напрямую по HTTP: создание задач, синхронное выполнение, получение результатов, просмотр истории, загрузку веб-страниц и поиск Google.
Quick Start — получить HTML страницы
Самый простой сценарий — синхронно загрузить страницу и получить её HTML в JSON.
Понадобятся:
- адрес API;
- API-ключ;
- URL страницы.
Задайте адрес API и ключ:
bash
BASE_URL="https://scraper.2captcha.com"
API_KEY="<ВАШ_API_КЛЮЧ>"
Отправьте запрос:
bash
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
{
"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
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
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
https://scraper.2captcha.com
bash
BASE_URL="https://scraper.2captcha.com"
3. Общие правила запросов
- Для POST-запросов используйте
Content-Type: application/json. - Параметры метода передаются плоско в JSON-теле, без вложенного объекта
params. task_typeможно передать в теле или в query string. Если он указан в обоих местах, приоритет имеет тело.- Размер JSON-тела не должен превышать 10 000 байт.
- Поля
emailиpasswordиспользуются только для авторизации и не сохраняются в задании. - Стоимость задачи определяет биллинг. Поле
priceв заголовкеx-debugслужебное; не используйте его для расчётов.
Правильно:
json
{
"task_type": "scrape",
"url": "https://example.com",
"data_format": "raw"
}
Не используйте вложенный params, если отдельная версия API явно не требует обратного:
json
{
"task_type": "scrape",
"params": {
"url": "https://example.com"
}
}
4. Заголовок x-debug
Все ответы API содержат заголовок x-debug. Его значение всегда JSON, независимо от format. В нём находятся метаданные задачи и самого ответа API:
http
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
HTTP 200 от Scraper API — задача завершена
├── status: "success" — метод scrape вернул страницу без замечаний
└── http_code: 404 — целевой сайт ответил 404
4.1. Заголовок x-debug_response
Заголовок содержит служебные поля результата конкретного метода. Он доступен при format: "json" и format: "raw":
http
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
{
"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
408 Request Timeout
При format: "json":
json
{
"error": "timeout",
"response_id": "0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a",
"status": "pending"
}
При format: "raw" тело ответа 408 содержит только ID задачи:
text
0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a
Задача продолжает выполняться. Используйте полученный response_id для последующего запроса /tasks/result/:response_id.
5.4. Ошибка выполнения
Если задача завершилась с ошибкой:
http
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. Асинхронное выполнение
Асинхронный сценарий состоит из двух шагов:
- создать задачу через
POST /tasks/request; - получить результат через
GET /tasks/result/:response_id.
6.1. Создание задачи: POST /tasks/request
Пример:
bash
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
201 Created
json
{
"response_id": "0193f2a4-1b2c-7d3e-8f4a-5b6c7d8e9f0a",
"status": "pending"
}
При format: "raw" тело содержит только ID:
text
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
{
"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
{
"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
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
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
{
"status": "pending"
}
При 404:
json
{
"status": "error",
"error": "Not found"
}
При 410 (запись о задаче сохранена, но тело результата уже удалено):
json
{
"status": "expired",
"error": "Result expired"
}
При 422 (в том числе если исходный запрос имел format: "raw") приходит JSON с Content-Type: application/json; charset=utf-8:
json
{
"status": "error",
"error": "ScrapeParser: params.waitFor must be an object"
}
Кроме x-debug, этот эндпоинт возвращает x-debug_request с параметрами найденной задачи и, когда доступны, данными задания. Пример для задачи scrape:
http
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
{
"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
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
{
"status": "success",
"http_code": 200,
"headers": {
"content-type": "text/html; charset=utf-8"
},
"body": "<!DOCTYPE html>..."
}
7.4. Получить HTML напрямую
bash
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
<!DOCTYPE html>
<html>
7.5. Получить Markdown
JSON:
json
{
"task_type": "scrape",
"url": "https://example.com",
"data_format": "markdown",
"format": "json"
}
Напрямую:
json
{
"task_type": "scrape",
"url": "https://example.com",
"data_format": "markdown",
"format": "raw"
}
Пример Markdown:
md
# Example Domain
[More information](https://www.iana.org/domains/example)
7.6. Сделать скриншот видимой области
Base64 в JSON:
json
{
"task_type": "scrape",
"url": "https://example.com",
"data_format": "screenshot",
"format": "json",
"fullPage": false
}
Ответ:
json
{
"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
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
89 50 4E 47 0D 0A 1A 0A
7.8. Сделать полностраничный скриншот
json
{
"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
{
"task_type": "scrape",
"url": "https://example.com/dynamic",
"waitFor": {"text": "Content loaded"}
}
Ожидание имеет смысл, если текст отсутствует в исходном HTML и появляется на странице позже.
Ожидать элемент
json
{
"task_type": "scrape",
"url": "https://example.com",
"waitFor": {"element": "#content", "checkVisible": false}
}
Ожидать видимый элемент
json
{
"task_type": "scrape",
"url": "https://example.com",
"waitFor": {"element": "#content", "checkVisible": true}
}
Ожидать полную загрузку
json
{
"task_type": "scrape",
"url": "https://example.com",
"waitFor": {"state": "load"}
}
Ожидать построение DOM
json
{
"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
{
"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
{
"task_type": "scrape",
"url": "https://example.com/account",
"cdpurl": "wss://browser.example.com/devtools/browser/9b2c1f0a-..."
}
Требования:
- поддерживается
ws://илиwss://; - браузер должен быть доступен воркеру всё время выполнения;
- задача использует куки, сессии, профиль, отпечаток и прокси этого браузера;
- не публикуйте CDP URL: доступ к нему фактически даёт доступ к браузерной сессии.
Если cdpurl не указан, браузер подбирает воркер. Если он указан, но подключиться не удалось после двух попыток, задача завершается с HTTP 422, без дальнейших повторов и без замены вашего браузера браузером воркера. Текст ошибки начинается с:
text
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
{
"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
{
"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
{
"task_type": "google_search",
"url": "https://www.google.com/search?q=fastify+nodejs&hl=en&gl=us&start=10"
}
8.3. Обычный веб-поиск
bash
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
{
"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
{
"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
{
"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
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
{
"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
{
"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
{
"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
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
{
"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
Paris,Paris,Ile-de-France,France
New York,New York,United States
London,England,United Kingdom
Внутри URL пробелы кодируются как обычно:
text
https://www.google.com/search?q=pizza&gl=us&uule=New+York,New+York,United+States
Координаты
Для координат используйте строку uule в формате lat,lon или lat,lon,radius:
json
{
"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
{
"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
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
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
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
[
{
"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
{
"error": "Insufficient balance",
"status": "error"
}
При format: "raw" API может вернуть только текст:
text
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
503 Service Unavailable
Retry-After: 60
json
{
"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, который сообщает о недоступности инфраструктурной зависимости.
Всегда проверяйте:
- HTTP-статус API;
Content-Type;x-debug.status_code;x-debug.response_id;- сервисный
statusиerror, если ответ сформирован сервисом; - для результата
scrape—status,http_codeи, при наличии,warningилиerror; - для
google_search—status, при наличииerror, а такжеgroupsилиbodyсогласноdata_format; значениеresults: -1само по себе не означает отсутствие выдачи.
При format: "raw" используйте доступные поля x-debug_response, учитывая, что заголовок не гарантирован и его наличие не означает успех. При недоступности сайта прочитайте JSON-тело.
11. Служебные эндпоинты
Авторизация не требуется.
GET /health
bash
curl "$BASE_URL/health"
json
{
"status": "ok",
"ts": 1747983214000
}
GET /health/ready
bash
curl "$BASE_URL/health/ready"
Успешный ответ:
json
{
"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. Внешний HTTP200не отменяет ошибок метода или целевого сайта. - При
503 Service overloadedповторяйте создание задачи через интервал изRetry-After. - Для PNG с
format: "raw"проверяйтеContent-Typeперед сохранением: при недоступности сайта вместо PNG приходит JSON с ошибкой. - Для PNG с
format: "json"декодируйте Base64 из верхнеуровневогоbody. - Передавайте
waitForJSON-объектом. Для его поля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.