GET-запрос → XML-ответ в формате Яндекс XML. Органическая выдача с позициями, доменами, заголовками и сниппетами. Мобильная и десктопная выдача, выбор региона.
Базовый эндпоинт принимает параметры в query-строке и возвращает XML. Авторизация — по паре user + key. Один вызов = один поисковый запрос; для пакетной обработки есть отдельный batch-эндпоинт.
GEThttps://xmlsearch.ru/search_yandex/xml
Пример:
https://xmlsearch.ru/search_yandex/xml?user=USER&key=KEY&query=пластиковые+окна&lr=213&device=desktop
| Параметр | Значения | Описание |
|---|---|---|
user обяз. | ID | Идентификатор пользователя. |
key обяз. | строка | API-ключ пользователя. |
query обяз. | текст | Поисковый запрос. Символ & кодировать как %26. |
lr | число по умолч. 213 | Регион (код Яндекса). 213 — Москва, 2 — СПб, 54 — Екатеринбург, 65 — Новосибирск, 43 — Казань и т.д. |
device | mobile | desktopпо умолч. mobile | Тип выдачи: мобильная или десктопная (ранжирование различается). |
page | 0, 1, 2… по умолч. 0 | Номер страницы выдачи (0 — первая, 1 — следующая и т.д.). На странице ~10 результатов. |
format | xml | jsonпо умолч. xml | Формат ответа. json — структурированный JSON (см. ниже). |
related | 0 | 1по умолч. 1 | Дополнять related_queries автоподсказками Яндекса (включено по умолчанию) — полезно и для коммерческих запросов, где блока похожих в выдаче может не быть. Отключить: related=0. Без доплаты (1 лимит). |
ai | 0 | 1по умолч. 0 | Полная Алиса — ответ, сгенерированный «на лету» (браузерный рендер), для запросов, где обычный ответ пустой (ai_answer=null). Медленнее (~7–40 с), ×10 лимита (списывается только при полном ответе). Несовместим с top/pages. Подробнее — в разделе «Ответ Алисы». |
top | 1–30по умолч. выкл | Цель: набрать N органических результатов, листая страницы (по умолч. до 5). Списывается по 1 лимиту за каждую пролистанную страницу. Только при ai=0. См. раздел «Больше результатов». |
pages | 1–3по умолч. выкл | Потолок глубины: не более N страниц. Без top — вернёт всю органику с N страниц; с top — ограничивает его N страницами. По 1 лимиту за страницу. Только при ai=0. |
blocked | 0 | 1по умолч. 0 | По умолчанию выдача приводится к тому, что видит пользователь из РФ (заблокированные в РФ домены скрыты). blocked=1 — международный вид, где заблокированные в РФ сайты (serpstat, netpeak, ahrefs…) видны. Работает и в /batch. Не увеличивает стоимость. |
XML в формате Яндекс XML. Результаты идут группами <group> в порядке ранжирования; позиция — атрибут id у <doc>.
<?xml version="1.0" encoding="utf-8"?>
<yandexsearch version="1.0">
<request>
<query>пластиковые окна</query>
<page>0</page>
<groupings><groupby attr="d" mode="deep" groups-on-page="10" docs-in-group="1"/></groupings>
</request>
<response date="20260730T041500">
<found priority="all">10</found>
<results>
<grouping attr="d" mode="deep" groups-on-page="10" docs-in-group="1">
<page first="1" last="10">0</page>
<group>
<doccount>1</doccount>
<doc id="1">
<url>https://www.mosokna.ru/</url>
<domain>www.mosokna.ru</domain>
<title>Пластиковые окна от производителя в Москве</title>
<passages><passage>Устанавливаем пластиковые окна и балконные двери…</passage></passages>
</doc>
</group>
<!-- ещё group … -->
</grouping>
</results>
</response>
</yandexsearch>
| Тег | Описание |
|---|---|
found | Число возвращённых результатов. |
group → doc id | Результат; id = позиция в выдаче. |
url | Ссылка результата. |
domain | Домен результата. |
title | Заголовок сниппета. |
passages → passage | Текст сниппета. |
serp-pos | Абсолютная позиция в выдаче (с учётом рекламы и спецблоков). |
cache-url | Ссылка на сохранённую копию (кэш Яндекса). Присутствует в основном на device=desktop. |
sitelinks → sitelink | Быстрые ссылки под результатом: пары title + url (до 8). Тег появляется, только если ссылки есть в выдаче. |
path | Видимый путь-крошки результата, напр. domclick.ru › arenda/kvartiry. Только при наличии. |
price | Цена из товарного сниппета (напр. 55 180 ₽). Только при наличии. |
meta | Мета-строка сниппета (просмотры, дата и т.п.). Только при наличии. |
Новые поля добавляются аддитивно: в JSON — необязательные ключи, в XML — теги только при наличии данных. Существующие поля и их порядок не изменились.
Помимо органики ответ (и XML, и JSON) содержит:
| Блок | Что внутри |
|---|---|
ads (реклама) | Объявления с атрибутом block: top / middle / bottom; поля pos, serp_pos, domain (домен рекламодателя), title, text. Включая объявления РСЯ-блоков, мимикрирующих под органику (в органику они не попадают). |
wizards (спецблоки с содержимым) | Теперь здесь только блоки, несущие данные: AI (ответ Алисы — ai-text/ai-meta/sources) и MARKET (details.products: title, price, shop). Голые маркеры прочих типов больше не дублируются в wizards — их полный список с позициями и именами лежит в categories. На запросах без Алисы и Маркета wizards пустой. Поля: type, side, serp_pos, display_name + details. |
categories (в XML — <categories><category>) | Самодостаточный список всех спецблоков выдачи — уникальные (по типу), отсортированы по тематической значимости: характеризующая тему — первой, «шум» (RELATED, MISSPELL, ADS) — в хвост. Каждая запись несёт атрибуты side (LEFT/RIGHT), serp_pos, display_name; сам тип — в значении. JSON: массив объектов {"type","side","serp_pos","display_name"}; XML: <category side="…" serp-pos="…" display-name="…">TYPE</category>. Отдельно: ADULT — синтетическая метка 18+ (не колдунщик, в wizards её нет). Ставится первой, когда выдача адалтная. Детект — по собственному флагу 18+ Яндекса (адалт-режим SafeSearch «Контент 18+»/«Размывать 18+», появляется только когда Яндекс сам классифицировал выдачу как 18+): на невинных запросах не срабатывает; метит порно/видео/вебкам с медиа, но не дейтинг/аптеки/секс-шопы в обычных магазинах. Возможные type: ADULT, IMAGES, VIDEOS, MARKET, AI (Алиса/LLM), ENTITIES, RELATED, COMPANIES (организации на карте), WEATHER (погода), CURRENCY (конвертер валют), SPORT (расписания/результаты), REALTY (недвижимость), TICKETS (билеты), LEGAL (юр. информация), USLUGI (Яндекс Услуги), MISSPELL (исправление опечатки), NEWS, MAPS, ADS (рекламные блоки РСЯ/галереи), STOCKS (котировки), BANK (банк), TIME (время), TRANSLATE (переводчик), CALCULATOR (калькулятор), MEDICINE (медицина), MORTGAGE (ипотека), APPS (приложения), DEPOSIT (вклады), CREDIT (кредиты), CREDIT_CARD (кредитные карты), DEBIT_CARD (дебетовые карты), MFO (микрозаймы), BONDS (облигации), KEY_RATE (ключевая ставка ЦБ), INSURANCE (страхование/ОСАГО), COUNTERPARTY (реквизиты юрлица), CONVERTER (конвертер единиц), COLORS (палитра цветов), RANDOM (генератор чисел), COUNTDOWN (отсчёт до даты), TIMER (таймер на N минут), POST_INDEX (почтовые индексы), INTERNET (IP-инструменты), CALLER_ID (определитель номера), FACT (быстрый факт), EASTER_EGG (пасхалка), SUGGEST (подсказки субпоиска), NOTIFICATION (блок-уведомление), EDUCATION (образование/курсы), HOLIDAY (праздничное оформление). Содержимое AI/MARKET (ответ/товары) — в wizards. |
related_queries | Похожие запросы. Для информационных запросов приходят в обычном ответе (из выдачи), бесплатно. related=1 добавляет автоподсказки Яндекса — в т.ч. для коммерческих запросов, где в выдаче похожих нет. |
misspell (в XML — <misspell>) | Пометка об исправлении опечатки. Появляется, только когда Яндекс исправил запрос или не нашёл его слов; иначе блока нет (в JSON ключ отсутствует). Вариант — в атрибуте source:• source="yandex" — Яндекс явно исправил опечатку. Поля: source_query (что вы отправили), fixed (исправленный запрос, по которому реально построена выдача), message (текст пометки Яндекса, напр. «Исправлена опечатка в слове «рабтает»»).• source="missing_words" — слова запроса не найдены ни в одном органическом результате (Яндекс их фактически не искал). Поля: source_query и not_found (список ненайденных слов); исправленный вариант в этом случае не возвращается.XML: <misspell source="yandex"><source-query>…</source-query><fixed>…</fixed><message>…</message></misspell> либо <misspell source="missing_words"><source-query>…</source-query><not-found>…</not-found></misspell>. JSON: {"source","source_query","fixed","not_found":[…],"message"}. Вариант missing_words отдаётся в основном на device=desktop. |
| счётчики | results_count (сколько сниппетов отдано — при top=N это N), first_page_organic (сколько органики реально на 1-й странице Яндекса — не зависит от top; в XML — <first-page-organic>), ads_count, wizards_count = categories_count (число спецблоков выдачи = длина categories; не число элементов wizards), index_size (примерное число результатов). |
| выделения | title_bolds / text_bolds — массивы слов, выделенных жирным в заголовке/сниппете. |
Товары Маркета (details.products) — на mobile и desktop. Домен рекламы — на mobile всегда; на desktop в HTML отсутствует. cache-url — на desktop. Отдельные is_*-флаги и index_size — best-effort и могут быть пустыми.
format=json)При format=json ответ — JSON-массив объектов (по объекту на запрос):
[
{
"word": "что такое seo",
"position": 0,
"index_size": 104000000,
"results_count": 9, "first_page_organic": 9, "ads_count": 13, "wizards_count": 3, "categories_count": 3,
"related_queries": ["что такое seo простыми словами", "…"],
"results": [
{
"pos": 1, "serp_pos": 9,
"domain": "ru.wikipedia.org",
"url": "https://ru.wikipedia.org/wiki/…",
"title": "Поисковая оптимизация — Википедия",
"text": "<b>SEO</b> может генерировать…",
"cache_url": "https://yandexwebcache.net/…",
"title_bolds": [], "text_bolds": ["SEO"],
"is_verified": false, "is_main_page": false
}
],
"ads": [
{"block":"top","pos":1,"serp_pos":1,"domain":"direct.yandex.ru","title":"…","text":"…"}
],
"wizards": [
{"type":"MARKET","side":"LEFT","serp_pos":1,"display_name":"Маркет",
"details":{"products":[{"title":"Диван-кровать «Жасмин»","price":"12 790","shop":"mnogomeb.ru"}]}},
{"type":"AI","side":"RIGHT","serp_pos":26,"display_name":"Алиса (LLM-ответ)",
"details":{"text":"SEO (Search Engine Optimization) — это…","sources":["https://…","https://…"]}}
],
"categories": [
{"type":"MARKET","side":"LEFT","serp_pos":1,"display_name":"Маркет"},
{"type":"IMAGES","side":"LEFT","serp_pos":12,"display_name":"Картинки"},
{"type":"AI","side":"RIGHT","serp_pos":26,"display_name":"Алиса (LLM-ответ)"}
],
"ai_answer": "SEO (Search Engine Optimization) — это…",
"ai_sources": ["https://…", "https://…"]
}
]
title и text сохраняют разметку <b>…</b>; *_bolds дублируют выделенные слова списком. Поля ai_answer/ai_sources и details у wizards с type=AI приходят для информационных запросов (быстрый ответ; на mobile и desktop).
Для информационных запросов в ответе по умолчанию приходит ответ Алисы — на верхнем уровне и в wizards с type=AI → details. Это быстрый ответ скоростным парсером: приходит всегда, без флагов и доплат — теперь и на device=desktop напрямую (~88%+ запросов), не только на mobile. Может быть неполным. Если Алисы для запроса нет — ai_answer = null.
Текст ответа отдаётся с сохранённой разметкой (заголовки, списки, жирный); технические вставки (inline-сноски, реклама, обвязка, встроенные видео/медиа-карточки) вырезаны, а ссылки-источники идут отдельными полями:
| JSON / XML | Что |
|---|---|
ai_answer / <ai-text> | Ответ с разметкой: <h2> <h3> <p> <ul> <ol> <li> <strong> |
ai_meta.footnote_refs / <ai-meta><footnotes> | Inline-сноски: пары {n, url} (номер → URL источника) |
ai_sources / <sources> | Плоский список URL источников |
В XML: <wizard type="AI"><ai-text>…</ai-text><ai-meta><footnotes><footnote n="1">URL</footnote>…</footnotes></ai-meta><sources><source>…</source></sources></wizard>.
Ответ Алисы приходит по умолчанию, когда он есть в выдаче — на mobile и desktop, без флагов и доплат. Если Алисы для запроса нет — ai_answer = null. Примеры запросов:
https://xmlsearch.ru/search_yandex/xml?user=USER&key=KEY&query=что+такое+seo&device=mobile&format=json
https://xmlsearch.ru/search_yandex/xml?user=USER&key=KEY&query=что+такое+seo&device=desktop&format=json
ai=1 — полная Алиса (генерация «на лету»)Часть запросов (обычно длинные, вопросительные, редкие) Яндекс не отдаёт готовым текстом — в HTML только каркас-заглушка (стрим-тизер), а сам ответ генерируется в реальном времени и дописывается в браузере JS-стримом. Быстрый (бесплатный) режим такой ответ вернуть не может → ai_answer = null.
ai=1 открывает страницу в настоящем headless-браузере, дожидается завершения генерации и возвращает полный текст (тот, что пользователь увидел бы в браузере). Работает и для «нестабильных» desktop-ответов.
top/pages (только при ai=0).ai_answer=null, но нужен именно сгенерированный ответ Алисы.https://xmlsearch.ru/search_yandex/xml?user=USER&key=KEY&query=длинный+вопросительный+запрос&device=desktop&ai=1&format=json
top / pages / page)Управление глубиной выдачи. Результаты идут в results со сквозной нумерацией pos = 1…N; у каждого есть поле page (с какой страницы взят). Реклама, спецблоки и related_queries берутся с первой снятой страницы. В ответе — pages_fetched (сколько страниц реально пролистано) и first_page_organic (сколько органики было именно на 1-й странице — при top=N в results_count будет N, а тут — фактическое число органики Яндекса на первой странице). Обычная навигация page работает всегда. Ответ Алисы (ai_answer) при top/pages сохраняется — берётся с первой страницы. alice=1 с top/pages сочетается.
| Параметр | Смысл |
|---|---|
top=N (1–30) | Цель по количеству: набрать N органических сниппетов, листая страницы (по умолч. до 5) и останавливаясь, как только набралось N. |
pages=N (1–3) | Потолок глубины: снять не более N страниц. Без top — вернёт всю органику с этих N страниц. |
page=K | Стартовая страница листания (по умолч. 0). |
Параметры комбинируются. Стоп — по тому, что срабатывает раньше: цель top или потолок pages.
| Запрос | Что делает | Спишется |
|---|---|---|
top=30 | до 30 сниппетов, максимум 5 страниц с 0-й | сколько страниц пролистал (обычно 3–4) |
pages=3 | все результаты со страниц 0, 1, 2 | 3 |
top=30&pages=3 | до 30 сниппетов, но не глубже 3 страниц | до 3 |
top=10&pages=3 | стоп на 10 сниппетах (обычно 1 страница) | 1–2 |
top=20&page=4 | 20 сниппетов, начиная со страницы 4 (4, 5, 6…) | сколько страниц пролистал |
pages=3&page=5 | все результаты со страниц 5, 6, 7 | 3 |
Тарификация: списывается ровно столько лимитов, сколько страниц пришлось пролистать. Если лимит закончится посреди листания — вернётся набранное, списано за реально пролистанные страницы. Если стартовая страница глубже, чем у Яндекса есть результаты — вернётся ошибка (результатов нет).
https://xmlsearch.ru/search_yandex/xml?user=USER&key=KEY&query=пластиковые+окна&device=mobile&top=30
https://xmlsearch.ru/search_yandex/xml?user=USER&key=KEY&query=пластиковые+окна&device=mobile&top=30&pages=3
https://xmlsearch.ru/search_yandex/xml?user=USER&key=KEY&query=пластиковые+окна&device=mobile&top=20&page=4
Один вызов на несколько запросов — обрабатываются параллельно на сервере, ответ приходит одним XML. Параметры lr, device, page, format, related, ai, top, pages, blocked применяются ко всем запросам пачки. При format=json ответ — JSON-массив по запросам. При ai=1 пачка идёт медленнее (браузерный рендер, ограниченная параллельность).
queryhttps://xmlsearch.ru/search_yandex/batch?user=USER&key=KEY&lr=213&device=desktop&query=пластиковые+окна&query=купить+велосипед&query=ремонт+холодильников
curl -X POST "https://xmlsearch.ru/search_yandex/batch?user=USER&key=KEY&lr=213" \
--data-binary $'пластиковые окна\nкупить велосипед\nремонт холодильников'
<?xml version="1.0" encoding="utf-8"?>
<batch>
<item query="пластиковые окна">
<yandexsearch version="1.0"> … </yandexsearch>
</item>
<item query="купить велосипед">
<yandexsearch version="1.0"> … </yandexsearch>
</item>
</batch>
Если отдельный запрос не удалось снять — у его <item> будет блок <error>, остальные вернутся нормально.
Обработать много запросов одновременно можно двумя способами.
Отправьте все запросы одним POST на /search_yandex/batch — сервер сам распараллеливает их у себя и возвращает ответы одной пачкой. Это самый эффективный путь: одно соединение, минимум накладных расходов, максимальная параллельность на нашей стороне.
curl -X POST "https://xmlsearch.ru/search_yandex/batch?user=USER&key=KEY&lr=213&device=mobile&format=json" \
--data-binary @queries.txt
queries.txt — по одному запросу на строку. Одна пачка может содержать тысячи запросов; очень длинные списки (десятки тысяч строк) лучше слать частями по 1000–5000, чтобы не держать одно HTTP-соединение слишком долго.
Если удобнее слать по одному запросу на соединение — открывайте несколько соединений одновременно, соблюдая правила:
code=500 и таймаутах (1–2 повтора). Это временная неудача съёма; за неуспешный съём лимит не списывается, так что повтор безопасен.Пример на Python — 100 параллельных запросов с пулом соединений и ретраем:
import concurrent.futures as cf, requests
USER, KEY = "USER", "KEY"
BASE = "https://xmlsearch.ru/search_yandex/xml"
session = requests.Session() # пул соединений + keep-alive
def fetch(query):
for _ in range(3): # до 3 попыток
try:
r = session.get(BASE, params={
"user": USER, "key": KEY, "query": query,
"lr": 213, "device": "mobile", "format": "json",
}, timeout=60)
data = r.json()[0] # format=json -> массив из одного объекта
if "error_code" not in data:
return data
except Exception:
pass
return None
queries = [s.strip() for s in open("queries.txt", encoding="utf-8") if s.strip()]
with cf.ThreadPoolExecutor(max_workers=100) as pool: # 100 одновременно
results = list(pool.map(fetch, queries))
Пропускная способность ограничена не нашим сервером, а скоростью прокси-пула и ответом Яндекса — порядка 1000–1500 запросов в минуту с одного клиента при высокой конкурентности (в тесте 1000 запросов сняты за ~38 с, 1000/1000 успешно). Наращивание числа параллельных соединений сверх этого потолка скорость уже не повышает.
Главное при параллельной работе — надёжно сопоставить каждый ответ с его запросом и обработать ошибки поштучно.
word = исходный запрос. В XML — <item query="…"> с тем же порядком и текстом запроса в атрибуте. Сопоставляйте по индексу или по word/query (надёжнее, если не полагаться на порядок).format=json — это массив из одного объекта. Привязывайте ответ к запросу, который вы отправили в данном соединении (например, через словарь future → query). Чтобы обрабатывать ответы по мере готовности, не дожидаясь самого медленного, используйте as_completed.error_code (вместо results), в XML внутри его <item> — блок <error code="…">. Проверяйте это до чтения results; остальные элементы пачки при этом валидны.Приём параллельных ответов по мере готовности (Python, as_completed):
import concurrent.futures as cf, requests
session = requests.Session()
BASE = "https://xmlsearch.ru/search_yandex/xml"
def fetch(query):
r = session.get(BASE, params={"user":"USER","key":"KEY","query":query,
"lr":213,"device":"mobile","format":"json"}, timeout=60)
return r.json()[0]
queries = [s.strip() for s in open("queries.txt", encoding="utf-8") if s.strip()]
answers = {}
with cf.ThreadPoolExecutor(max_workers=100) as pool:
fut2q = {pool.submit(fetch, q): q for q in queries} # запоминаем, какой future чей
for fut in cf.as_completed(fut2q): # обрабатываем, как только готов
q = fut2q[fut]
try:
data = fut.result()
except Exception:
answers[q] = None; continue # сеть/таймаут -> можно ретраить
if "error_code" in data:
answers[q] = None # съём не удался (лимит не списан)
else:
answers[q] = data["results"] # успех: список результатов
В batch ничего этого не нужно — сервер отдаёт весь массив разом в исходном порядке; достаточно пройтись по нему и проверить error_code у каждого объекта.
При ошибке возвращается XML с тегом <error>:
<yandexsearch version="1.0"><response><error code="100">wrong user or key</error></response></yandexsearch>
| Код | Значение |
|---|---|
100 | Неверный user или key. |
15 | Пустой запрос. |
500 | Не удалось снять выдачу (после нескольких попыток). |
GEThttps://xmlsearch.ru/health — проверка доступности (возвращает <ok/>).