Интеграция с WordPress REST API часто отлично работает на тестовом сайте и начинает терять данные после роста проекта. Причина простая: коллекции возвращаются страницами, а клиент ошибочно считает первый ответ полным списком.
Для импорта статей, товаров, категорий, пользователей или кастомных сущностей нужно корректно обходить pagination, учитывать лимит per_page, читать заголовки X-WP-Total/X-WP-TotalPages и не создавать лишнюю нагрузку запросами «до пустого ответа» без контроля.
Почему REST API не возвращает всё сразу
WordPress REST API специально ограничивает размер коллекций. Официальный handbook указывает, что параметр per_page принимает от 1 до 100 записей. Ограничение защищает сайт от слишком тяжёлых запросов и больших ответов.
Поэтому запрос вида /wp-json/wp/v2/posts — это первая страница, а не «все записи сайта».
Основные параметры pagination
page— номер страницы;per_page— количество элементов на странице, максимум 100;offset— смещение относительно начала выборки.
GET /wp-json/wp/v2/posts?per_page=100&page=1
Для последовательного обхода обычно проще использовать page. Offset полезен для отдельных сценариев, но при изменяющейся коллекции требует такой же осторожности.
X-WP-Total и X-WP-TotalPages
WordPress возвращает два ключевых HTTP-заголовка: X-WP-Total содержит общее количество элементов, а X-WP-TotalPages — количество страниц при текущих параметрах запроса.
Правильный клиент делает первую страницу, читает X-WP-TotalPages, затем запрашивает страницы от 2 до указанного значения. Не нужно заранее угадывать число записей.
Пример на JavaScript
async function fetchAllPosts(baseUrl) {
const all = [];
const perPage = 100;
const first = await fetch(`${baseUrl}/wp-json/wp/v2/posts?per_page=${perPage}&page=1`);
if (!first.ok) throw new Error(`HTTP ${first.status}`);
all.push(...await first.json());
const totalPages = Number(first.headers.get('X-WP-TotalPages') || 1);
for (let page = 2; page <= totalPages; page++) {
const response = await fetch(`${baseUrl}/wp-json/wp/v2/posts?per_page=${perPage}&page=${page}`);
if (!response.ok) throw new Error(`Page ${page}: HTTP ${response.status}`);
all.push(...await response.json());
}
return all;
}
Для production-кода добавьте timeout, retry только для временных ошибок и журнал последней успешно обработанной страницы.
Не запрашивайте поля, которые не используете
Глобальный параметр _fields позволяет ограничить набор возвращаемых полей. Если интеграции нужны только ID, slug, modified и title, нет смысла передавать полный content, links и лишние метаданные.
GET /wp-json/wp/v2/posts?per_page=100&page=1&_fields=id,slug,modified,title
Это уменьшает размер JSON, время сериализации и сетевой трафик. Особенно заметно на больших текстах и кастомных REST-полях.
Почему параллельно запрашивать сотни страниц опасно
После получения X-WP-TotalPages легко запустить все страницы одновременно через Promise.all. На большом сайте это может создать всплеск PHP workers и SQL-запросов.
Для интеграции обычно лучше ограниченная concurrency: например, несколько запросов одновременно, а не десятки. Точное число зависит от VPS, endpoint и сложности запросов.
Стабильная сортировка важна
Если во время выгрузки редакторы публикуют новые материалы, выборка может сдвинуться. При сортировке по дате новый объект попадает в начало, а элементы на границе страниц меняют позиции. Результатом могут стать дубль одной записи и пропуск другой.
Для массового экспорта полезно заранее определить стабильный порядок и фиксировать ID уже обработанных сущностей. Для инкрементальной синхронизации лучше работать по дате изменения плюс ID как дополнительному техническому ключу, а не полагаться только на номер страницы.
Pagination и изменяющиеся данные
Номер страницы — транспортный механизм, а не курсор состояния бизнеса. Если выгрузка занимает долгое время, данные между page 1 и page 20 могут измениться.
Надёжная интеграция хранит собственную контрольную точку: например, последнее подтверждённое modified_gmt и ID. Следующий запуск запрашивает только изменённые записи с небольшим overlap и дедупликацией по ID.
Не используйте page как idempotency key
Страница 3 сегодня и страница 3 завтра — разные наборы объектов. В CRM, индексе поиска или локальной базе сущность нужно связывать по WordPress ID либо по другому стабильному бизнес-идентификатору.
Тогда повторный импорт страницы обновит существующую запись, а не создаст дубль.
Что делать с HTTP 400 на последней странице
Если клиент запрашивает номер страницы больше доступного, WordPress может вернуть ошибку вроде invalid page number. Поэтому лучше ориентироваться на X-WP-TotalPages, а не бесконечно увеличивать page до первого сбоя.
Если общее количество страниц изменилось во время синхронизации, завершите текущий проход по понятному snapshot-плану и затем сделайте reconciliation изменённых сущностей.
Retry только для временных ошибок
HTTP 429, 502, 503 или сетевой timeout часто допускают повтор с backoff. Ошибка 400 из-за неверного параметра не исправится после десяти повторов. 401/403 требует проверки авторизации и прав.
Для защищённых endpoint способ авторизации нужно проектировать отдельно. Для WordPress Application Passwords есть отдельная инструкция.
CORS не связан с pagination
Если запрос работает через curl, но блокируется в браузере, увеличение per_page не поможет. Это другая проблема — политика origin и разрешённых заголовков. Её я разбирал в статье про CORS WordPress REST API.
Как синхронизировать категории и теги
Taxonomy endpoints также являются коллекциями. Если категорий или тегов больше одной страницы, нужно читать те же pagination headers и обходить все страницы. Нельзя считать первые 10 или 100 терминов полным справочником.
При импорте сохраняйте numeric ID и slug, но учитывайте, что ID относятся к конкретному WordPress-сайту. ID категории 15 на staging не обязан означать ту же категорию на production.
Пагинация WooCommerce и WordPress
WooCommerce REST API построен поверх WordPress REST API и многие collection endpoints используют аналогичную модель страниц. Но параметры конкретного ресурса всегда нужно сверять с его актуальной схемой, а не автоматически переносить все фильтры от wp/v2/posts.
Практическая схема большого импорта
- Сделать первый запрос с нужными фильтрами, сортировкой и
_fields. - Прочитать
X-WP-TotalиX-WP-TotalPages. - Последовательно или с ограниченной concurrency пройти страницы.
- Дедуплицировать объекты по ID.
- Фиксировать прогресс после успешно сохранённой страницы.
- Повторять только временные ошибки с backoff.
- После полного прохода выполнить reconciliation изменившихся объектов.
- Для следующих запусков перейти на инкрементальную синхронизацию.
Типовые ошибки
- обработать только первую страницу;
- поставить
per_page=1000и ожидать, что WordPress примет значение; - не читать response headers;
- запускать все страницы одновременно без ограничения;
- качать полный content, когда нужны три поля;
- считать page стабильным идентификатором;
- не учитывать изменения коллекции во время долгой выгрузки;
- повторять любую 4xx ошибку бесконечно.
Когда нужна кастомная интеграция
Если нужно синхронизировать тысячи записей с CRM, поисковым индексом, мобильным приложением или внешним каталогом, простой цикл по страницам — только начало. Нужны checkpoint, idempotency, ограничение нагрузки, безопасная авторизация и повторяемая обработка ошибок.
В A.S Groups можно заказать API/CRM-интеграцию или доработку WordPress REST endpoint под конкретную структуру данных. Для оценки пришлите тип сущностей, пример ответа API и объём записей без секретных ключей. Связь — через контакты.
Обсуждение
Вопросы и комментарии
Можно уточнить детали статьи или поделиться своим опытом. Первый комментарий проходит проверку.