xmlsearch.ru — API поисковой выдачи Яндекса

Вход / регистрация

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 — Казань и т.д.
devicemobile | desktop
по умолч. mobile
Тип выдачи: мобильная или десктопная (ранжирование различается).
page0, 1, 2…
по умолч. 0
Номер страницы выдачи (0 — первая, 1 — следующая и т.д.). На странице ~10 результатов.
formatxml | json
по умолч. xml
Формат ответа. json — структурированный JSON (см. ниже).
related0 | 1
по умолч. 1
Дополнять related_queries автоподсказками Яндекса (включено по умолчанию) — полезно и для коммерческих запросов, где блока похожих в выдаче может не быть. Отключить: related=0. Без доплаты (1 лимит).
ai0 | 1
по умолч. 0
Полная Алиса — ответ, сгенерированный «на лету» (браузерный рендер), для запросов, где обычный ответ пустой (ai_answer=null). Медленнее (~7–40 с), ×10 лимита (списывается только при полном ответе). Несовместим с top/pages. Подробнее — в разделе «Ответ Алисы».
top130
по умолч. выкл
Цель: набрать N органических результатов, листая страницы (по умолч. до 5). Списывается по 1 лимиту за каждую пролистанную страницу. Только при ai=0. См. раздел «Больше результатов».
pages13
по умолч. выкл
Потолок глубины: не более N страниц. Без top — вернёт всю органику с N страниц; с top — ограничивает его N страницами. По 1 лимиту за страницу. Только при ai=0.
blocked0 | 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Число возвращённых результатов.
groupdoc idРезультат; id = позиция в выдаче.
urlСсылка результата.
domainДомен результата.
titleЗаголовок сниппета.
passagespassageТекст сниппета.
serp-posАбсолютная позиция в выдаче (с учётом рекламы и спецблоков).
cache-urlСсылка на сохранённую копию (кэш Яндекса). Присутствует в основном на device=desktop.
sitelinkssitelinkБыстрые ссылки под результатом: пары 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 и могут быть пустыми.

JSON-формат (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).

Ответ Алисы (LLM)

Для информационных запросов в ответе по умолчанию приходит ответ Алисы — на верхнем уровне и в wizards с type=AIdetails. Это быстрый ответ скоростным парсером: приходит всегда, без флагов и доплат — теперь и на 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-ответов.

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, 23
top=30&pages=3до 30 сниппетов, но не глубже 3 страницдо 3
top=10&pages=3стоп на 10 сниппетах (обычно 1 страница)1–2
top=20&page=420 сниппетов, начиная со страницы 4 (4, 5, 6…)сколько страниц пролистал
pages=3&page=5все результаты со страниц 5, 6, 73

Тарификация: списывается ровно столько лимитов, сколько страниц пришлось пролистать. Если лимит закончится посреди листания — вернётся набранное, списано за реально пролистанные страницы. Если стартовая страница глубже, чем у Яндекса есть результаты — вернётся ошибка (результатов нет).

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

Пакетные запросы (batch)

Один вызов на несколько запросов — обрабатываются параллельно на сервере, ответ приходит одним XML. Параметры lr, device, page, format, related, ai, top, pages, blocked применяются ко всем запросам пачки. При format=json ответ — JSON-массив по запросам. При ai=1 пачка идёт медленнее (браузерный рендер, ограниченная параллельность).

GET — несколько query

https://xmlsearch.ru/search_yandex/batch?user=USER&key=KEY&lr=213&device=desktop&query=пластиковые+окна&query=купить+велосипед&query=ремонт+холодильников

POST — по запросу на строку в теле

curl -X POST "https://xmlsearch.ru/search_yandex/batch?user=USER&key=KEY&lr=213" \
  --data-binary $'пластиковые окна\nкупить велосипед\nремонт холодильников'

Формат ответа batch

<?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>, остальные вернутся нормально.

Параллельные запросы

Обработать много запросов одновременно можно двумя способами.

Способ 1 — batch (рекомендуется)

Отправьте все запросы одним 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-соединение слишком долго.

Способ 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 успешно). Наращивание числа параллельных соединений сверх этого потолка скорость уже не повышает.

Как получать и сопоставлять ответы

Главное при параллельной работе — надёжно сопоставить каждый ответ с его запросом и обработать ошибки поштучно.

Приём параллельных ответов по мере готовности (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/>).