Smart Scrape
Smart Scrape выбирает самый быстрый способ получить пригодный документ и запускает Chromium только при необходимости. Возвращаемое тело всегда сопровождается дискриминатором contentKind, который говорит, как читать content.
curl -X POST "https://proxy.unoapi.ru/v1/browser/smart-scrape?timeout=30000" \ -H "Authorization: Bearer $UNOAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "formats": ["content", "markdown"] }'const res = await fetch('https://proxy.unoapi.ru/v1/browser/smart-scrape?timeout=30000', { method: 'POST', headers: { Authorization: `Bearer ${process.env.UNOAPI_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://example.com', formats: ['content', 'markdown'], }),});
const data = await res.json();if (data.ok) { console.log(data.contentKind); // "html" | "json" | "xml" | "text" console.log(data.markdown);} else { console.error(`${data.message} (strategy: ${data.strategy})`);}import httpx, os
r = httpx.post( "https://proxy.unoapi.ru/v1/browser/smart-scrape", params={"timeout": 30000}, json={"url": "https://example.com", "formats": ["content", "markdown"]}, headers={"Authorization": f"Bearer {os.environ['UNOAPI_KEY']}"}, timeout=35,)data = r.json()if data["ok"]: print(data["contentKind"]) # "html" | "json" | "xml" | "text" print(data["markdown"])else: print(f"{data['message']} (strategy: {data['strategy']})")| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
url |
string |
— | Обязательный HTTP(S) URL |
formats |
string[] |
["content"] |
content, markdown, links, screenshot, pdf, seo; html принимается как псевдоним content |
Query-параметр timeout задаётся в миллисекундах и применяется отдельно к HTTP- и browser-попыткам. Попытка сохранённой копии из поискового кэша ограничена двумя секундами.
Форматы screenshot и pdf сразу запускают браузер. Если запрошены оба, они создаются из одной загрузки страницы.
contentKind
Заголовок раздела «contentKind»Дискриминатор присваивается один раз — в момент получения ответа — и сохраняется вместе с телом. Клиент обязан ветвиться по contentKind и не должен переклассифицировать contentType или анализировать содержимое.
contentKind описывает сам ресурс, а не поле content, поэтому он возвращается всегда, когда документ получен, — в том числе если формат content не запрашивался. Иначе запрос только с markdown получил бы дословный неочищенный исходник и ничего, по чему ветвиться.
contentKind |
MIME-типы | Что лежит в content |
|---|---|---|
html |
text/html, application/xhtml+xml |
Очищенный результат извлечения: скрипты, стили, обработчики событий и javascript:/data: ссылки удалены |
json |
application/json, text/json, application/*+json |
Точный декодированный исходник, без санитизации |
xml |
application/xml, text/xml, image/svg+xml, application/*+xml (кроме XHTML) |
Точный декодированный исходник, без санитизации |
text |
application/robots+txt и любой другой text/* — включая text/plain, text/markdown, text/csv, text/tab-separated-values, text/css, text/javascript |
Точный декодированный исходник, без санитизации |
pdf |
application/pdf |
Извлечённый текстовый слой — не байты файла. Скан без текстового слоя — неудача с message: "PDF has no text layer" |
spreadsheet |
application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel.sheet.macroEnabled.12, application/vnd.ms-excel.sheet.binary.macroEnabled.12, application/vnd.oasis.opendocument.spreadsheet, application/vnd.openxmlformats-officedocument.spreadsheetml.template, application/vnd.ms-excel.template.macroEnabled.12, application/vnd.oasis.opendocument.spreadsheet-template |
CSV по листам — один лист как чистый CSV, несколько разделяются строками === Sheet: имя === |
text/event-stream не поддерживается: потоковый ответ не имеет конца и не может быть прочитан как документ. Не распознанные типы — image/png, application/msword, application/octet-stream без подсказки в URL — документом не считаются.
pdf и spreadsheet — третий класс наряду с извлечением HTML и точным исходником: content для них — текст, извлечённый из бинарного файла. Не путайте с форматом запроса pdf, который рендерит страницу в PDF, — это разные поля. Лимит чтения для бинарных документов — 10 МиБ (текстовые — 4 МиБ).
Как определяется вид
Заголовок раздела «Как определяется вид»- Распознанный MIME-тип ответа имеет приоритет всегда, даже если противоречит расширению в URL.
- Если заголовок
Content-Typeотсутствует, используется расширение конечного URL, иначеhtml. - Если тип указан, но не описывает формат (
application/octet-stream), используется только расширение конечного URL. - Любой другой распознанный, но неподдерживаемый тип документом не является.
Расширение берётся из конечного URL после редиректов. Подсказки: .html/.htm, .json, .xml/.rss/.atom/.svg, .txt/.md/.markdown/.csv/.tsv, .pdf, .xls/.xlsx/.xlsm/.xlsb/.ods/.xlt/.xltx/.xltm/.ots.
- Общий 12-часовой кэш документов.
- Сохранённая копия из кэша поисковой системы — только если URL не оканчивается явным «сырым» расширением и сама копия является HTML.
- Анонимный HTTP-запрос к оригиналу.
- Изолированный headless Chromium для JavaScript-страниц и некачественных HTTP-ответов.
Поле strategy содержит фактически сработавшую стратегию: cache, yandex-cache, http-fetch или browser; значение yandex-cache соответствует уровню сохранённой копии из поискового кэша. attempted перечисляет сделанные попытки по порядку.
Правила перехода к браузеру:
- «сырой» или бинарный MIME-тип на 2xx возвращает результат и никогда не эскалирует: Chromium не прочитает исходник или бинарный файл лучше прямого запроса. Более того, headless-браузер вообще не открывает PDF и таблицы (навигация превращается в скачивание), поэтому извлечение из бинарных файлов работает только на дешёвом уровне;
- «сырой» MIME-тип на не-2xx останавливается на дешёвом уровне;
- URL с явным «сырым» расширением, который упал или ответил не-2xx, останавливается на дешёвом уровне — даже если тело ошибки является HTML;
- HTML-тип на 2xx перекрывает «сырое» расширение и идёт обычным путём извлечения, включая переход в браузер;
- 2xx с распознанным, но неподдерживаемым типом (
image/png,application/msword) завершается на дешёвом уровне: Chromium прочитает тот же заголовок и ответит так же, поэтому переход в браузер оплачивался бы впустую. Исключение —application/octet-stream: формат не назван, и источник, меняющий ответ по User-Agent, может отдать браузеру что-то читаемое; - когда Chromium доходит до JSON, XML или текста, возвращается тело основного навигационного ответа, а не DOM встроенного просмотрщика.
{ "ok": true, "statusCode": 200, "content": "<html>...</html>", "contentKind": "html", "contentType": "text/html; charset=utf-8", "headers": {"content-type": "text/html; charset=utf-8"}, "strategy": "http-fetch", "attempted": ["cache", "http-fetch"], "message": null, "screenshot": null, "pdf": null, "markdown": "# Article\n...", "links": ["https://example.com/next"], "seo": { "title": "Article", "metadata": {}, "canonical": "https://example.com/article", "language": "en", "headings": [], "links": [], "images": [], "jsonLd": [], "diagnostics": [] }}| Случай | content |
contentKind |
|---|---|---|
Успех, формат content запрошен |
строка | html, json, xml, text, pdf или spreadsheet |
Успех, формат content не запрошен |
null |
html, json, xml, text, pdf или spreadsheet |
Только screenshot/pdf |
null |
null |
| Исчерпанные стратегии, не-2xx, неподдерживаемый тип | null |
null |
Для json, xml, text, pdf и spreadsheet поля links и seo всегда null. Производный markdown: для text — сам исходник; для xml — таблица sitemap или список записей RSS/Atom, иначе инертный блок ```xml; для json — инертный блок ```json с точным исходником, без переформатирования; для pdf — извлечённый текст как есть; для spreadsheet — Markdown-таблица по каждому листу, не более 100 строк на лист с пометкой [… ещё N строк] (CSV в content при этом не усечён).
Тело декодируется по объявленной кодировке: сначала BOM, затем charset из заголовка, затем <meta charset> или объявление XML, иначе UTF-8. Это касается и дешёвого HTTP-уровня, и браузерного, поэтому windows-1251 читается одинаково на обоих.
Внешние Set-Cookie, авторизационные и другие небезопасные заголовки не возвращаются.
Если все стратегии исчерпаны, HTTP-статус остаётся 200, а тело содержит ok: false и диагностическое message. При этом сохраняются статус, MIME-тип, безопасные заголовки, редиректы и X-Response-URL последней попытки. Смешанный запрос вида ["content", "screenshot"] к ресурсу без документа вернёт ok: false, но скриншот останется на месте. Ошибки самого запроса — невалидный URL, формат или таймаут — возвращаются до выполнения и не тарифицируются.
Примеры
Заголовок раздела «Примеры»Все примеры проверены против production. Ключ передаётся через переменную окружения UNOAPI_KEY.
Свежие данные
Заголовок раздела «Свежие данные»Cache-Control: no-cache пропускает кэш ответов, кэш документов и сохранённую копию из поискового кэша и идёт сразу к источнику. Ответ приходит с X-Cache: BYPASS, а свежий результат обновляет кэши. Свежесть имеет цену: страница, которая обычно закрывается сохранённой копией за 1 копейку, без неё может дойти до Chromium и browser-уровня.
curl -s -D - -X POST "https://proxy.unoapi.ru/v1/browser/smart-scrape?timeout=30000" \ -H "Authorization: Bearer $UNOAPI_KEY" \ -H "Content-Type: application/json" \ -H "Cache-Control: no-cache" \ -d '{"url": "https://example.com", "formats": ["content"]}'# в заголовках ответа: x-cache: BYPASSconst res = await fetch('https://proxy.unoapi.ru/v1/browser/smart-scrape?timeout=30000', { method: 'POST', headers: { Authorization: `Bearer ${process.env.UNOAPI_KEY}`, 'Content-Type': 'application/json', 'Cache-Control': 'no-cache', // мимо кэшей — сразу к источнику }, body: JSON.stringify({ url: 'https://example.com', formats: ['content'] }),});console.log(res.headers.get('x-cache')); // BYPASSconst data = await res.json();console.log(data.strategy); // "http-fetch" или "browser" — кэш не участвовалimport httpx, os
r = httpx.post( "https://proxy.unoapi.ru/v1/browser/smart-scrape", params={"timeout": 30000}, json={"url": "https://example.com", "formats": ["content"]}, headers={ "Authorization": f"Bearer {os.environ['UNOAPI_KEY']}", "Cache-Control": "no-cache", # мимо кэшей — сразу к источнику }, timeout=35,)print(r.headers["x-cache"]) # BYPASSprint(r.json()["strategy"]) # "http-fetch" или "browser" — кэш не участвовалSEO и ссылки
Заголовок раздела «SEO и ссылки»Форматы seo и links работают только для HTML-документов; для json, xml и text оба поля равны null. Элементы seo.headings — объекты с полями level и text.
curl -s -X POST "https://proxy.unoapi.ru/v1/browser/smart-scrape?timeout=30000" \ -H "Authorization: Bearer $UNOAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com", "formats": ["seo", "links"]}'const res = await fetch('https://proxy.unoapi.ru/v1/browser/smart-scrape?timeout=30000', { method: 'POST', headers: { Authorization: `Bearer ${process.env.UNOAPI_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://example.com', formats: ['seo', 'links'] }),});
const data = await res.json();if (data.ok && data.seo) { console.log(data.seo.title, data.seo.canonical, data.seo.language); console.log(data.seo.headings.map((h: { text: string }) => h.text)); console.log(data.links);}import httpx, os
r = httpx.post( "https://proxy.unoapi.ru/v1/browser/smart-scrape", params={"timeout": 30000}, json={"url": "https://example.com", "formats": ["seo", "links"]}, headers={"Authorization": f"Bearer {os.environ['UNOAPI_KEY']}"}, timeout=35,)data = r.json()if data["ok"] and data["seo"]: seo = data["seo"] print(seo["title"], seo["canonical"], seo["language"]) print([h["text"] for h in seo["headings"]]) print(data["links"])Скриншот
Заголовок раздела «Скриншот»Визуальные форматы идут сразу в браузер и тарифицируются по browser-уровню. PNG приходит в поле screenshot в base64. content равен null, если формат content не запрошен; contentKind равен null, когда документ не запрашивался вовсе (как здесь) или получить его не удалось (ok: false) — см. таблицу в разделе «Ответ». Запрос ["screenshot", "markdown"] вернёт contentKind вместе со скриншотом.
curl -s -X POST "https://proxy.unoapi.ru/v1/browser/smart-scrape?timeout=30000" \ -H "Authorization: Bearer $UNOAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com", "formats": ["screenshot"]}' \ | jq -r '.screenshot // empty' | base64 -d > page.pngimport { writeFile } from 'node:fs/promises';
const res = await fetch('https://proxy.unoapi.ru/v1/browser/smart-scrape?timeout=30000', { method: 'POST', headers: { Authorization: `Bearer ${process.env.UNOAPI_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://example.com', formats: ['screenshot'] }),});
const data = await res.json();if (data.ok) await writeFile('page.png', Buffer.from(data.screenshot, 'base64'));import base64, httpx, os
r = httpx.post( "https://proxy.unoapi.ru/v1/browser/smart-scrape", params={"timeout": 30000}, json={"url": "https://example.com", "formats": ["screenshot"]}, headers={"Authorization": f"Bearer {os.environ['UNOAPI_KEY']}"}, timeout=35,)data = r.json()if data["ok"]: with open("page.png", "wb") as f: f.write(base64.b64decode(data["screenshot"]))PDF и таблицы
Заголовок раздела «PDF и таблицы»content для бинарных документов — извлечённый текст: у PDF — текстовый слой, у таблиц — CSV по листам. Запрос ничем не отличается от обычного; вид придёт в contentKind.
curl -s -X POST "https://proxy.unoapi.ru/v1/browser/smart-scrape?timeout=30000" \ -H "Authorization: Bearer $UNOAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/annual-report.pdf", "formats": ["content"]}'# contentKind: "pdf", content — текстовый слой документаcurl -s -X POST "https://proxy.unoapi.ru/v1/browser/smart-scrape?timeout=30000" \ -H "Authorization: Bearer $UNOAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/stats.xlsx", "formats": ["content", "markdown"]}'# contentKind: "spreadsheet", content — CSV, markdown — таблицы по листамОграничения: файл больше 10 МиБ, извлечённый текст больше 1,5 МБ (такой результат не поместился бы в кэш, и каждый повторный запрос парсил бы файл заново), скан без текстового слоя или повреждённый файл — ok: false на дешёвом уровне с диагностическим message. Если прямой HTTP-запрос к файлу недоступен, headless-браузер не поможет: Chromium скачивает бинарные файлы, а не открывает их.
Тарификация и кэш
Заголовок раздела «Тарификация и кэш»- Кэш документов, сохранённая копия из поискового кэша и прямой HTTP: 1 копейка.
- Попытка Chromium, включая неуспешную: 10 копеек.
- Идентичный 12-часовой response-cache hit: 1 копейка, без обращения к browser-сервису.
В cache identity входят канонический URL и нормализованный набор форматов. timeout и порядок/дубликаты форматов на identity не влияют. Неизвестные поля тела игнорируются, поэтому снятый в этой версии proxy у старых клиентов не меняет ни ответ, ни ключ кэша. Ответы с no-store, ошибки, скриншоты, PDF и слишком большие envelopes не сохраняются.
Тело больше 4 МиБ не возвращается. На HTTP-уровне и в /v1/browser/cheerio чтение прерывается по достижении лимита, в браузере — отклоняется по заявленному Content-Length, а тело без объявленной длины буферизуется один раз и отбрасывается (Playwright не даёт потокового доступа). Слишком большой ответ origin завершает запрос на дешёвом уровне: Chromium не сделает документ меньше, поэтому переход в браузер только удвоил бы расход и стоимость.
Миграция с Browserless
Заголовок раздела «Миграция с Browserless»Endpoint принимает запросы Browserless Smart Scrape без изменений и расширяет ответ:
https://production-sfo.browserless.io/smart-scrapehttps://proxy.unoapi.ru/v1/browser/smart-scrapeЗапросы работают как есть: formats: ["html"] принимается (как псевдоним content), поле proxy игнорируется. Что стоит знать при переходе:
- ответ содержит обязательное поле
contentKind— ветвитесь по нему, а не поContent-Type; для HTML-документовcontentприходит очищенным, а не сырым; - неудача — это HTTP 200 с
ok: falseи заполненнымиstatusCode,contentType, безопасными заголовками и редиректами последней попытки, а не код ошибки; - добавлены форматы
seoиlinks, каскад с общим кэшем документов и сохранённой копией из поискового кэша и двухуровневая тарификация — дешёвый уровень без запуска Chromium.
Остальные Browserless-эндпоинты (/screenshot, /content, /scrape, /pdf, /performance) сохраняют совместимость — см. обзор Browser API.