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

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

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

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

Помочь может keyset-пагинация (курсорная): следующую страницу выбирают по значению ключа последней записи, а не по номеру позиции. Для этого API должен её поддерживать, а ключ сортировки — быть уникальным и неизменным; одного времени создания недостаточно. Обход всё равно собирает ответы в разные моменты, поэтому изменённая или удалённая после получения запись останется в выгрузке в старом виде.

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

Автор статьи собрал проверки в открытый инструмент PageTrace (лицензия MIT), который читает трассу в формате JSONL или HAR и ищет разрывы и повторы без сетевых запросов. В репозитории есть пример сломанной трассы; для практики пагинации на Python автор также собрал бесплатный практикум на Stepik с заданиями в браузере.