← Все статьи

Поиск билетов: async-API и гибрид

Поиск билетов: async-API и гибрид

Поиск билетов выглядит как простая задача, пока не сядешь её автоматизировать. Тут же вылезают две неприятности. Первая: у авиапоиска API есть, но он не отвечает мгновенно - ты запускаешь поиск, а цены прилетают потом, отдельным запросом. Вторая: у РЖД открытого API просто нет, и сходить за расписанием поезда привычным запросом не выйдет вообще.

Обычно из-за этого автоматизация и буксует. Один источник работает не так, как ты привык (синхронный запрос - синхронный ответ), а другой не работает совсем без браузера. И кажется, что надо городить два разных инструмента, держать их в голове и каждый раз вспоминать, куда лезть за самолётом, а куда за поездом.

На самом деле всё это прекрасно прячется в один скил. Разберём на живом примере: скил, который по запросу “найди билеты Москва - Сочи на завтра” сам сходит и в авиа, и в РЖД, и сведёт ответ в одну выдачу.

В чём идея

Скил - это короткая инструкция для ассистента: как называется, когда применять и что сделать. Тут он сводит под одним описанием два разных приёма работы с данными, и в этом весь смысл урока.

Первый приём - асинхронный API. Это когда сервис не возвращает результат сразу. Ты делаешь первый запрос с маршрутом и датой - в ответ приходит не цена, а идентификатор поиска. Это всего лишь “талончик в очередь”: сервис принял задачу и пошёл считать. Дальше ты по этому идентификатору опрашиваешь результат - раз, другой, пока не вернутся реальные предложения. Главная ловушка тут именно в том, чтобы не перепутать идентификатор с результатом: на первый запрос цен ещё нет, они появляются при опросе. Ровно так устроен авиапоиск Aviasales (через Travelpayouts): сперва получаешь идентификатор, потом по нему забираешь готовые билеты. Честная оговорка: доступ к поисковому API Aviasales выдают только партнёрским проектам Travelpayouts с подтверждённой аудиторией (порядка 50 000 уникальных пользователей в месяц) и ручным одобрением поддержки, плюс запросы надо подписывать. Обычный зритель этот ключ не получит, так что авиа-часть тут - демонстрация самого приёма (асинхронный поиск через идентификатор), а не готовая выдача цен. Приём универсален: так же опрашивают любой сервис с REST-доступом к “джобам” и их результату.

Второй приём - гибрид с браузером. У РЖД открытого API нет, и это нормальная, частая ситуация: данные на свете есть, а удобной двери к ним - нет. Тогда скил идёт туда, куда ходит человек, - на сайт. Он открывает страницу браузером (через Playwright или любой драйвер), заполняет форму, дожидается таблицы рейсов и считывает её. Не так быстро и чисто, как API. Единственный нюанс: rzd.ru защищён от ботов, поэтому при первой попытке страница может попросить подтверждение “вы не робот” - в этом случае запусти браузер с видимым окном (headed) и пройди проверку вручную один раз; дальше сессия обычно работает.

И вот ключевая мысль: где есть API - берём API, где его нет - идём браузером, и оба способа живут в одном скиле. Скил говорит, ЧТО сделать (“найди авиа асинхронным поиском, а ж/д со страницы”), а ЧЕМ именно сходить - каким HTTP-инструментом, каким браузерным драйвером, на какой системе - решает сам ассистент по тому, что у тебя установлено.

Почему это работает? Потому что современная LLM умеет и опрашивать API в цикле с паузой, и водить браузер по странице. Ей не хватало одного - понятной инструкции, которая разводит два источника по двум приёмам и сводит результат вместе. Скил ровно это и задаёт.

Как себе сделать

Не пиши этот скил руками и не разбирайся в чужих API по документации. Отдай задачу своему ассистенту - Claude Code, Codex, Cursor, любому. Он сам сообразит, как запустить асинхронный поиск, как водить браузер и куда положить файл. Для авиа нужен токен Travelpayouts - регистрация бесплатная на travelpayouts.com, ключ появляется после подтверждения аккаунта; но боевой доступ к поисковому API партнёрка открывает только проектам с большой аудиторией, поэтому без одобрения авиа-часть остаётся учебной иллюстрацией приёма. Просто скопируй промпт:

Создай мне скил, который ищет билеты - авиа и ж/д - по маршруту и дате.
Срабатывай на просьбы вроде "найди билеты", "рейсы", "сколько лететь или ехать",
"билеты Москва-Сочи на завтра". Сначала разбери запрос: откуда, куда, дата, тип;
если тип не указан - ищи и авиа, и поезд.
Авиа бери асинхронным API (Aviasales/Travelpayouts): первый запрос возвращает
идентификатор поиска, а не цены, - дальше опрашивай результат по этому идентификатору
с паузой и лимитом попыток, пока не придут предложения, и возьми самые дешёвые.
Ж/д бери через браузер, потому что открытого API у РЖД нет: открой сайт, заполни форму,
дождись таблицы рейсов и считай первые строки. Учти, что rzd.ru защищён от ботов:
если вместо таблицы прилетела проверка "вы не робот" - запусти браузер с видимым окном
и дай пройти капчу вручную один раз.
Токен авиа-API (Travelpayouts) держи в переменной окружения, а не в тексте скила;
если ключа нет - честно сообщи об этом, а не выдумывай цены.
Не завязывайся на конкретную ОС: инструмент и браузер выбирай сам по системе.
Ответ сведи в одну короткую выдачу: цена, перевозчик или поезд, время в пути.

Как понять, что заработало: напиши ассистенту по-человечески “найди билеты Москва - Сочи на завтра”. Если он сам запустит авиапоиск по идентификатору, а за поездом сходит браузером и сведёт обе строки в один ответ - скил живой. Авиа-часть потребует токена Travelpayouts: без него ассистент честно скажет, что ключ не найден (это не баг скила, это нормально - сам приём опроса по идентификатору он при этом всё равно показывает). Дальше проси любой маршрут теми же словами.

И всё. Ты только что собрал скил, который аккуратно прячет две неприятности сразу: API, который отвечает не сразу, и источник, у которого API нет вовсе. Точно так же заворачивается любой кейс, где данные лежат в разных местах и достаются по-разному: один раз описал приёмы - дальше ассистент сводит всё сам.

Смотреть полностью

Коротко