Пагинация в Python: Как собирать данные с помощью пагинированных страниц
TL;DR
Пагинация — это контракт между вашим клиентом и источником данных. Проверьте ответ на наличие номера страницы, смещения, курсора или ссылки на следующую страницу перед написанием цикла.
Для API с лимитом/смещением продвигаетесь по количеству фактически возвращенных записей и останавливайтесь на указанном общем числе или пустой странице. Добавьте защиту от максимальной страницы и уберите дубликаты по стабильному ID.
API с курсорами являются непрозрачными по дизайну. Отправляйте точно следующий курсор, возвращаемый сервером; не создавайте номера страниц из него.
Этот учебник использует DummyJSON в качестве демонстрационного API. Он полезен для изучения механики пагинации, а не как производственный бизнес-набор данных или контракт на гарантированную доступность.
Что означает пагинация в Python для сбора данных
Пагинация в Python — это логика, которая запрашивает большой набор результатов маленькими страницами. API и веб-сайты используют пагинацию, потому что возврат каждой записи в одном ответе может быть медленным, требовательным к памяти или превышать серверные лимиты.
Парсер, который получает только первый ответ, может выглядеть здоровым, оставаясь при этом без большинства набора данных. Правильный цикл зависит от стиля пагинации источника: номера страниц, смещение и лимит, токены курсоров, URL следующей страницы или контент, загружаемый по мере прокрутки пользователя. Всегда читайте документацию API провайдера и проверяйте пример ответа перед выбором цикла. Для обнаружения URL на уровне веб-сайта и извлечения страниц Nstdata Crawl отделен от конкретной пагинации с курсором или смещением.
Попробуйте Nstproxy Crawl - Начните Бесплатно Уже Сегодня
Пустая страница, нет поля следующей страницы или известный итог
Предположение, что каждая конечная точка начинается с 1 страницы
Смещение + лимит
?skip=60&limit=30
Возвращенный итог или нет записей
Увеличение неправильного размера страницы
Курсор / токен продолжения
?cursor=opaque-value
Нет следующего курсора
Рассмотрение курсора как числа или изменение его
HTML ссылка на следующую страницу
Якорь с надписью Далее
Нет доступной ссылки на следующую страницу
Построение следующего URL путем конкатенации строк
Бесконечная прокрутка
Браузер запрашивает больше после прокрутки
Конкретный маркер окончания или исчерпанный задокументированный API
Прокрутка вечно или вызов недокументированных конечных точек без разрешения
Имена параметров являются примерами, а не универсальными стандартами API. Некоторые API возвращают полный следующий URL; другие возвращают курсор внутри вложенного объекта. Используйте имена и условия остановки, которые задокументированы источником.
Постройте скрипт пагинации с лимитом/смещением на Python
Документация конечной точки продуктов DummyJSON определяет параметры limit и skip и возвращает общее количество результатов. Пример ниже запрашивает ограниченные страницы, валидирует форму ответа, убирает дубликаты по ID продукта и записывает в CSV.
import csv
import time
from typing import Any
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
API_URL ="https://dummyjson.com/products"PAGE_SIZE =30MAX_PAGES =100defmake_session()-> requests.Session: retry = Retry( total=3, connect=3, read=2, status=3, backoff_factor=0.5, status_forcelist=(429,500,502,503,504), allowed_methods=frozenset({"GET"}), respect_retry_after_header=True,) session = requests.Session() session.mount("https://", HTTPAdapter(max_retries=retry)) session.headers.update({"User-Agent":"pagination-demo/1.0"})return session
deffetch_all_products()->list[dict[str, Any]]: session = make_session() products:list[dict[str, Any]]=[] seen_ids:set[int]=set() skip =0 total:int|None=Nonefor page_number inrange(1, MAX_PAGES +1): response = session.get( API_URL, params={"limit": PAGE_SIZE,"skip": skip}, timeout=(5,20),) response.raise_for_status() payload = response.json() batch = payload.get("products") page_total = payload.get("total")ifnotisinstance(batch,list)ornotisinstance(page_total,int):raise ValueError("Неожиданный ответ API: ожидались products[] и целочисленный total") total = page_total
ifnot batch:breakfor item in batch: item_id = item.get("id")ifnotisinstance(item_id,int):continueif item_id notin seen_ids: seen_ids.add(item_id) products.append(item)# Продвигайтесь на количество, возвращенное, а не на запрашиваемый лимит. skip +=len(batch)if skip >= total:break time.sleep(0.1)else:raise RuntimeError(f"Остановлено на предельном лимите в {MAX_PAGES} страниц")if total isnotNoneandlen(products)< total:print(f"Предупреждение: получено {len(products)} уникальных продуктов; API сообщил {total}")return products
defmain()->None: products = fetch_all_products() fields =["id","title","category","price"]withopen("products.csv","w", newline="", encoding="utf-8")as output: writer = csv.DictWriter(output, fieldnames=fields, extrasaction="ignore") writer.writeheader() writer.writerows(products)print(f"Сохранено {len(products)} уникальных строк в products.csv")if __name__ =="__main__": main()
Этот пример использует политику повторных попыток для временных ошибок HTTP и учитывает Retry-After, когда сервер предоставляет его. Он не повторяет каждую ошибку: ошибка 400 или 401 обычно требует исправления запроса или учетных данных, а не повторения его.
Почему цикл продолжается по возвращенному количеству
API может вернуть меньше записей, чем запрашивалось, в конце коллекции или когда он применяет максимальный размер страницы. Обновление skip на len(batch) сохраняет следующую заявку в согласованности с ответом. Цикл заканчивается, когда смещение достигает total или раньше, если API возвращает пустую партию.
Защита максимальной страницы защищает от поврежденного поля total или конечной точки, которая продолжает возвращать одну и ту же страницу. Если появляется предупреждение, сравните идентификаторы и смещения в сохраненных журналах перед увеличением предела. Дублирующиеся идентификаторы могут означать, что источник изменился во время сбора или что параметры постраничной навигации не были применены так, как ожидалось.
Как обрабатывать API с номерами страниц
Для API с номерами страниц начните с страницы, определенной в его документации, а затем остановитесь на документированном конечном условии источника. Если ответ включает в себя общее количество страниц, используйте его. Если он включает URL следующей страницы, следуйте этому URL. Если API возвращает короткую финальную страницу, но без общего числа, остановитесь, когда партия станет пустой или короче запрашиваемого размера, только если это правило задокументировано.
Избегайте предположений, таких как “страница 1 всегда существует” или “последняя страница – это первая пустая страница.” Некоторые API возвращают явный флаг has_more. Предпочитайте это, а не делайте выводы о завершении по количеству строк.
Как обрабатывать постраничную навигацию на основе курсора
API на основе курсоров часто возвращают данные, как показано ниже:
Названия полей выше иллюстративны. В реальной интеграции копируйте точные поля ответа и параметры запроса из документации API. Цикл концептуально выглядит так:
Сделайте первоначальный запрос без курсора.
Обработайте и сохраните возвращенные элементы.
Прочитайте следующий курсор из ответа.
Остановитесь, если провайдер не возвращает следующий курсор.
В противном случае отправьте этот курсор без изменений с последующим запросом.
Сохраните курсор вместе с состоянием задания, если вам нужно восстановить его после сбоя. Сделайте записи о записях идемпотентными, чтобы страница, повторно запрашиваемая после таймаута, не дублировала строки позже.
Как следовать HTML-ссылкам на страницах
Когда источник является разрешенным HTML-сайтом, а не API, используйте Requests для загрузки страницы, анализируйте её с помощью Beautiful Soup и разрешайте относительные URL относительно текущего URL ответа. Не добавляйте номер страницы, если схема URL сайта стабильна и задокументирована.
from urllib.parse import urljoin
from bs4 import BeautifulSoup
import requests
defget_next_url(page_url:str)->str|None: response = requests.get(page_url, timeout=(5,20)) response.raise_for_status() soup = BeautifulSoup(response.text,"html.parser") link = soup.select_one("a.next")if link isNone:returnNone href = link.get("href")return urljoin(response.url, href)if href elseNone
Официальная документация Beautiful Soup объясняет, как работают селекторы и атрибуты, которые были разобраны. Добавьте набор виденных URL и максимальный лимит страниц вокруг этой функции. Если следующая ссылка указывает за пределы вашего одобренного доменного диапазона, остановитесь и проверьте её, прежде чем следовать ей автоматически.
бесконечная прокрутка и страницы с навигацией на основе JavaScript
Бесконечная прокрутка является поведением презентации, а не универсальным протоколом постраничной навигации. Сначала проверьте, документирует ли сайт API или предоставляет разрешенный следующий курсор в своем интерфейсе. Если вы владеете приложением, используйте тот же контракт API, что и фронтенд. Если нет, получите разрешение перед автоматизацией браузера или вызовом конечной точки, которая не предназначена для вашего использования.
Для авторизованной страницы, требующей JavaScript, Playwright для Python может ждать известного элемента и выполнять ограниченное количество действий прокрутки. Определите максимальное количество элементов или стабильный маркер окончания; цикл, который прокручивает, пока "ничего не меняется", может зависнуть на странице с периодическим контентом или временной сетевой ошибкой.
Предотвращение пропуска записей и петель пагинации
Надежная работа по сбору данных требует больше, чем просто условия цикла:
Стабильная идентичность: дублируйте записи по ID или каноническому URL.
Отслеживание прогресса: журналируйте номер страницы, смещение/курсор, возвращаемое количество и накопительное количество.
Ограниченная работа: задайте максимальное количество страниц, прошедшее время и скорость запроса.
Проверки схемы: убедитесь, что поле коллекции и метаданные пагинации существуют.
Дисциплина повторов: повторяйте временные тайм-ауты и серверные ошибки с наращиванием; остановитесь при ошибках авторизации или валидации.
Проверки консистентности: сравните уникальные записи, полученные с сообщенной общей суммой, когда источник предоставляет таковую.
Состояние резюме: сохраните последнюю успешную страницу или курсор, если работа может продолжаться после перезапусков.
Пагинация с использованием смещения все еще может пропускать или повторять записи, когда исходный набор данных изменяется во время длительной работы. Если источник поддерживает токен снимка или стабильный курсор, используйте его для изменяемых данных. В противном случае запишите время сбора и используйте более поздний проход согласования.
Когда использовать фреймворк для обхода вместо ручной пагинации
Короткий цикл со смещением/лимитом часто достаточен для одного API. Используйте фреймворк, такой как Crawlee для Python, когда вам нужны постоянные очереди, обнаружение страниц, несколько обработчиков страниц, повторы по большому набору URL или многократное хранилище. Держите эти концепции раздельными: API пагинация получает страницы в одной конечной точке данных; обход сайта открывает и посещает связанные URL.
Nstdata Crawl нацелен на сбор на уровне URL и веб-сайта, включая ограниченное обнаружение страниц/глубины и нормализованные выходные данные страниц. Он не знает автоматически о бизнес-специфическом параметре курсора произвольного приложения. Для парсинга страниц и динамического контента смотрите руководства Nstdata по извлечению HTML-текста в Python, страницам, отрисованным с помощью JavaScript и автоматизированному сбору данных.
Пагинация в Python надежна, если вы следуете фактическому контракту источника, делаете каждую страницу ограниченной и проверяете, что собранные ID и итоги имеют смысл. Начните с небольшой пробы и сохраните необработанный ответ и состояние страницы. Используйте курсор точно так, как он возвращается, разрешайте следующие ссылки HTML с помощью urljoin() и переходите к фреймворку для обхода только тогда, когда рабочий процесс требует больше, чем последовательные запросы к API.
Держите обход сайта отдельным от API пагинации
Nstdata Crawl может помочь в разрешенном обнаружении URL веб-сайта и извлечении страниц. Продолжайте следовать контракту на пагинацию каждой API, смещению или курсору в коде приложения.
В: Как мне сделать скрапинг нескольких страниц в Python?
Читать поля пагинации источника, циклом используя его номер страницы, смещение, курсор или следующий URL, и останавливайтесь на документированном терминальном условии. Установите максимальный лимит страниц и устраните дубликаты записей по стабильному ключу.
В: Как мне работать с пагинацией с помощью запросов?
Используйте requests.Session, задайте тайм-ауты, проверяйте каждый ответ и обновляйте параметр пагинации в соответствии с контрактом ответа. Сохраняйте прогресс, если сбор данных нужно возобновить.
В: Как мне сделать скрапинг бесконечной прокрутки?
Для сайта, который вы владеете или к которому вы авторизованы получить доступ, используйте его документированный API или автоматизацию браузера с четким конечным условием и лимитом элементов. Не автоматизируйте обход ограничений доступа.
В: Как мне остановить цикл пагинации в Python?
Остановитесь, когда API говорит, что больше нет страниц, курсор отсутствует, следующая ссылка отсутствует, смещение достигает общего числа страниц или ответ пуст — в соответствии с задокументированным поведением этого API. Установите жесткий предел страниц в качестве меры предосторожности.
В: Как мне выполнить скрейпинг постраничной навигации с использованием курсора?
Отправьте непрозрачный курсор, возвращенный одним ответом, в следующем запросе и остановитесь, когда API не вернет следующий курсор. Не увеличивайте и не декодируйте курсор, если провайдер не задокументировал это поведение.
Ivy Lin
Oct. 19th 2026
Сканируйте целые сайты одним API-запросом
Успешность 99,8% с рендерингом JavaScript
Получайте чистые данные для LLM в разных форматах
Преобразуйте любой сайт в Markdown, HTML, JSON, ссылки, PDF и другие форматы без управления инфраструктурой сканирования.