Парсинг данных API: лимиты, пагинация и дубли
← Все материалы
Разбор

Парсинг данных через API на Python: лимиты, пагинация, дубли

Парсинг данных через API — это когда вы забираете информацию не со страницы, а из штатного интерфейса сервиса: запрос, JSON, понятные поля. Сложность здесь не в первом запросе (он пишется за десять минут), а в том, что происходит на трёхсотой тысяче записей: ключи, лимиты, пагинация и повторные запуски, которые норовят удвоить данные. Ниже — как это устроено и где обычно ломается.

5–10 днейот доступа к API до регулярной выгрузки
от 30 000 ₽интеграция с одним API и загрузка в базу
60 vs 5000запросов в час без ключа и с ключом (пример GitHub)

Чем официальный API лучше парсинга HTML

Если у источника есть открытый API, спорить не о чем: брать данные надо оттуда. Парсер HTML читает то, что нарисовано для человека, и ломается от смены дизайна. API отдаёт то, что предназначено для программы, и меняется по правилам.

ПАРСИНГ HTMLОФИЦИАЛЬНЫЙ APIЛомается от смены вёрсткиПоля вытаскиваются из текстаСкрытая нагрузка на чужой сайтСерая зона в условиях использованияНет способа узнать, что изменилосьКонтракт полей и версия эндпоинтаГотовые типы: числа, даты, идентификаторыЗаявленные лимиты и легальная квотаПравила прописаны в документацииФильтр по дате изменения, вебхуки
Главная разница не в скорости, а в том, узнаете ли вы о поломке до того, как данные испортятся

Есть и бонус, о котором вспоминают поздно: у API почти всегда есть фильтр по дате изменения. Повторная выгрузка тянет не весь каталог, а только изменившееся со вчера — разница между сорока минутами и четырьмя секундами.

Обратная сторона честная: API отдаёт только то, что решил отдать вендор. Нужного поля в ответе нет — придётся запрашивать расширенный доступ или добирать данные другим путём.

Парсинг данных API на Python: из чего состоит клиент

Разница между «скриптом с requests» и рабочей интеграцией — в слоях, отвечающих за поведение при сбоях. Библиотека тут почти не важна: requests для простого случая, httpx — когда нужны асинхронные запросы.

Авторизация: ключ или токен, обновление по срокуТранспорт: таймауты, повторы, ограничение частотыПагинация: обход всех страниц до концаРазбор ответа: проверка схемы и типовЗагрузка: upsert по ключу, а не вставкаСостояние: с какого момента продолжатьЛоги и уведомление о сбое
Первые два слоя обычно пишут сразу, а из-за отсутствия двух последних потом переделывают весь проект

Ключи и лимиты: как не поймать 429

Почти любой публичный API ограничивает частоту запросов, и разница между анонимным и авторизованным доступом огромна. Пример — REST API GitHub: без ключа 60 запросов в час, с персональным токеном 5000. Отсюда первое действие перед выгрузкой: получить ключ, даже если данные публичные.

Когда лимит всё же выбран, сервис отвечает кодом 429 Too Many Requests, а иногда сразу говорит, сколько ждать, в заголовке Retry-After. Многие API дополнительно присылают остаток квоты в заголовках вида X-RateLimit-Remaining и время её сброса. Правильный клиент читает эти заголовки и притормаживает сам, а не долбит сервер повторами.

  1. Держать свой лимит ниже заявленного. Разрешено 10 запросов в секунду — ходите на 6–7: у чужого счётчика своя логика округления.
  2. Уважать Retry-After. Пришёл заголовок — ждём ровно столько, сколько сказали.
  3. Повторять с экспоненциальной задержкой и случайной добавкой. 1, 2, 4, 8 секунд плюс разброс: без него параллельные задачи синхронно ударят по серверу в один момент.
  4. Различать 4xx и 5xx. 500-е — временная проблема на их стороне, повтор оправдан. 400-е (кроме 429) значат, что запрос неправильный, повторять бессмысленно.
  5. Ключ хранить вне кода. Переменные окружения или секрет-хранилище: токен, попавший в репозиторий, отзывается — иногда автоматически и в самый неудобный момент.

Отдельно про квоты: у части сервисов лимит считается не в запросах, а в «стоимости» — сложный запрос списывает больше. Это всегда написано в документации и всегда обнаруживается на второй день работы теми, кто её не прочитал.

Пагинация: почему offset врёт

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

ТипКак выглядитЧем опасен
Offset / page?page=3&per_page=100При вставках и удалениях между запросами записи дублируются или пропадают; на глубоких страницах резко замедляется
Cursor?cursor=eyJpZCI6...Курсор нельзя собрать вручную и нельзя прыгнуть на середину — только последовательный обход
Keyset?updated_after=2026-08-01&id_gt=15320Требует стабильной сортировки, зато устойчив к изменениям данных

Классическая ошибка: выгрузка идёт полчаса, за это время в источник добавляют записи, содержимое сдвигается на страницу — и часть данных вы не увидели, а часть скачали дважды. Именно поэтому Stripe, Slack и GitHub используют курсоры. И если сервис отдаёт ссылку на следующую страницу сам (GitHub кладёт её в заголовок Link с признаком rel="next"), идти надо по ней, а не конструировать адрес самому.

Запрос страницыРазбор записейСохранениекурсораЕсть next?Следующаястраница
Курсор сохраняется до обработки следующей страницы — тогда прерванная выгрузка продолжается с места обрыва

И правило, которое стоит дороже всего: выгрузка завершена не тогда, когда кончились ожидаемые страницы, а когда сервер не вернул следующий курсор. Всё остальное — тихая потеря хвоста данных.

Пока читаете — можно сразу проверить свою задачу. Опишите процесс, и я скажу, решается ли он и во сколько обойдётся.

Как не собрать дубли при повторном запуске

Любая интеграция запускается повторно: сеть отвалилась, задачу перезапустили, понадобилось догрузить прошлый месяц. Требование звучит просто — повторный запуск за тот же период должен давать тот же результат, а не второй комплект строк. Достигается это тремя приёмами.

Ключ вместо порядкового номера

У каждой записи есть естественный идентификатор из источника: id заказа, номер документа, ИНН. Загрузка делается не вставкой, а upsert — «обнови по этому ключу, а если записи нет — создай». В PostgreSQL это INSERT ... ON CONFLICT (external_id) DO UPDATE. Уникальный индекс здесь важнее самого запроса: он физически не даст появиться дублю, даже если две задачи запустятся одновременно.

Инкремент с перехлёстом

Хранится отметка, до какого момента данные забраны. Следующий запуск просит записи, изменённые после неё, но с запасом назад — на несколько минут или час. Это закрывает расхождение часов между серверами и записи, не зафиксированные в момент прошлой выгрузки. Повторы при этом безвредны ровно потому, что загрузка идёт через upsert.

Идемпотентность на запись

Когда интеграция не только читает, но и создаёт объекты на чужой стороне, вопрос встаёт зеркально. Хороший API даёт штатный механизм: в Stripe это заголовок Idempotency-Key — строка до 255 символов (рекомендуется UUID v4), по которой сервер узнаёт повтор и возвращает результат первого запроса вместо создания второго платежа. Ключи живут 24 часа, а тот же ключ с другими параметрами вернёт ошибку. Если механизма нет, его роль играет ваш собственный внешний номер в создаваемом объекте.

Вставка без ключадубли после каж…Проверка «есть ли уже»гонки при парал…Upsert по индексуповтор безопасе…
Относительная частота разбора «откуда взялись двойные строки» на одинаковом объёме

Что делать, когда API нет

Ситуация обычная: данные нужны, публичного API нет. Порядок действий от дешёвого к дорогому такой.

  1. Спросить. У множества сервисов API есть, но не вынесен в публичную документацию — доступ дают по запросу или на партнёрском тарифе. Письмо не стоит ничего и часто закрывает вопрос.
  2. Поискать фид и экспорт. Прайс в YML, выгрузка в CSV по ссылке, отчёты на почту по расписанию — у поставщиков это встречается чаще, чем API, и им самим выгодно, чтобы ваши цены были актуальны.
  3. Посмотреть, чем питается сам сайт. Современный интерфейс почти всегда получает данные отдельными запросами к внутренним эндпоинтам с JSON — это видно в панели разработчика на вкладке сети. Удобнее HTML, но обязательств у такого эндпоинта нет: он может измениться без предупреждения, и считать его контрактом нельзя.
  4. Парсить HTML. Последний вариант по надёжности, но рабочий — со своими правилами вежливости и юридическими рамками. Разобрано в статье про парсер сайтов.
  5. Взять посредника. Для популярных источников есть агрегаторы с собственным API: дороже за объём, зато чужая забота о поломках.

Собранное обычно едет не в файл, а в базу или CRM — эта часть разобрана в статье про интеграцию с CRM. Если же результат нужен людям в привычном виде, поверх базы делается выгрузка в таблицу: про это — в разборе автоматизации Python и Excel.

Сроки, цена и когда заказывать не нужно

ЗадачаЧто входитСрок и цена
Разовая выгрузкаОдин эндпоинт, пагинация, результат в CSV или таблицу1–2 дня, от 15 000 ₽
Регулярная интеграцияКлючи, лимиты, инкремент, upsert в базу, расписание, уведомления5–10 дней, 30 000–90 000 ₽
Несколько источников со сверкой3–10 API, приведение справочников, история измененийот 150 000 ₽
ПоддержкаРеакция на смену версии API и новые поляот 8 000 ₽ в месяц

А теперь часть, из-за которой я иногда отговариваю от разработки. Интеграция не нужна, если:

Если данные нужны ежедневно, источников больше одного и на них завязаны деньги — интеграция окупается за первые недели. Основная работа там не в запросах, а в трёх вещах: лимиты, пагинация, повторные запуски. Они и отличают выгрузку, которая живёт год, от скрипта, сломавшегося на второй неделе.

Частые вопросы

Чем парсинг через API лучше парсинга сайта?

API отдаёт данные в структурированном виде с заявленным контрактом полей и не ломается от смены дизайна. У него есть легальная квота, фильтр по дате изменения и предсказуемое поведение при ошибках. Парсинг HTML — вариант для случаев, когда API нет вообще.

Что делать, если API отдаёт ошибку 429?

Это превышение лимита частоты. Нужно посмотреть заголовок Retry-After и подождать указанное время, а в клиенте заранее держать скорость ниже разрешённой и повторять запросы с экспоненциальной задержкой и случайной добавкой. Долбить повторами бесполезно — счётчик от этого только растёт.

Как выгрузить все данные, если API отдаёт их страницами?

Идти по пагинации до конца и определять конец не по числу страниц, а по отсутствию следующего курсора или ссылки next. Если сервис поддерживает курсорную пагинацию, использовать её: постраничный offset при изменении данных во время выгрузки теряет и дублирует записи.

Как избежать дублей при повторном запуске выгрузки?

Загружать данные не вставкой, а upsert по естественному ключу из источника (id заказа, номер документа) с уникальным индексом в базе. Тогда повторный запуск за тот же период обновит существующие строки, а не создаст вторые. Для записи в чужой сервис используется идемпотентный ключ запроса.

Нужен ли ключ, если данные публичные?

Как правило да, и он резко расширяет лимиты. У GitHub, например, анонимно доступно 60 запросов в час против 5000 с токеном. Ключ хранится в переменных окружения или секрет-хранилище, а не в коде.

Сколько стоит интеграция с API?

Разовая выгрузка из одного эндпоинта — от 15 000 ₽ и 1–2 дня. Регулярная интеграция с лимитами, инкрементальной догрузкой и загрузкой в базу — 30 000–90 000 ₽ за 5–10 дней. Сборка из нескольких источников со сверкой справочников — от 150 000 ₽.

парсинг данных apiпарсинг данных api pythonинтеграция с apiвыгрузка данных через apiпагинация и лимиты api

Читайте дальше

Нужны данные из чужого сервиса?

Скажите, что за источник и куда должны приезжать данные — проверю, есть ли у него API, и назову срок и цену выгрузки.

Обсудить задачу