Данные Ozon в Google Таблицах: скрипт на Apps Script и обновление по расписанию

Как выгрузить заказы, остатки и начисления Ozon в Google Таблицу через Apps Script: где хранить API-ключ, как написать пагинацию для трёх разных методов, как повесить триггер на обновление и что проверить, чтобы сумма в листе сошлась с кабинетом.

Каждое утро заходить в кабинет, скачивать отчёт, вставлять в свою таблицу, поправлять формулы — на это уходит 15 минут в день и всё равно к вечеру данные устаревают. Google Таблицы умеют ходить в Ozon Seller API сами: Apps Script встроен в таблицу, бесплатен и запускается по расписанию без вашего участия. Ниже — рабочая схема: какие методы тянуть, куда положить ключ, как написать пагинацию (она у Ozon разная в трёх вариантах), как настроить триггер и почему первые версии таких скриптов почти всегда врут на несколько процентов.

Что реально стоит тянуть в таблицу

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

Данные Метод Seller API Пагинация Как часто обновлять
Отправления FBS /v3/posting/fbs/list offset + limit (до 1000) Раз в час в рабочее время
Отправления FBO /v2/posting/fbo/list offset + limit 2–3 раза в день
Начисления и услуги /v3/finance/transaction/list page + page_size, в ответе page_count Раз в сутки ночью
Остатки по складам /v2/analytics/stock_on_warehouses offset + limit Дважды в день
Список товаров /v3/product/list last_id Раз в сутки
Воронка: показы, заказы /v1/analytics/data offset, ограничение по строкам Раз в сутки

Номера версий в этой таблице — ориентир на момент написания. Ozon выкатывает новую версию метода, какое-то время держит обе, потом старую отключает: остатки и список товаров так уже переезжали. Перед тем как писать код, откройте документацию Seller API и сверьте путь, набор полей в теле запроса и структуру ответа. Метод со скриншота в чужой статье может не существовать уже полгода.

Опрос

Как вы сейчас забираете данные из Ozon?

Распределение для этой статьи · выберите свой ответ

Ключ: в свойствах скрипта, а не в коде и не в ячейке

Для запроса нужны два заголовка: Client-Id (номер магазина) и Api-Key (секрет, создаётся в кабинете в разделе настроек Seller API). Самая частая ошибка новичка — записать ключ прямо в текст функции или в ячейку служебного листа. Оба варианта означают, что ключ увидит любой, кому вы дали доступ к таблице, и он останется в истории версий даже после удаления.

Правильное место — Script Properties: в редакторе Apps Script откройте настройки проекта, добавьте свойства OZON_CLIENT_ID и OZON_API_KEY, а в коде читайте их через PropertiesService.getScriptProperties().getProperty('OZON_API_KEY'). Свойства принадлежат проекту скрипта, а не документу, и в обычный доступ к таблице не попадают.

Дальше три привычки, которые обходятся дешевле, чем разбор последствий. Под каждую интеграцию — свой ключ: скрипт, сервис аналитики, репрайсер. Тогда один можно отозвать, не уронив остальные. Таблицу с выгрузкой не открывайте ссылкой в режиме «редактор для всех, у кого есть ссылка». И перевыпускайте ключ, когда уходит сотрудник или таблица уезжает подрядчику — не разбирая, успел он что-то скопировать или нет. Отзыв в кабинете срабатывает сразу: старый скрипт после этого начнёт получать 401, вы просто пропишете в свойствах новое значение.

Скелет: один запрос и запись в лист

Вся работа с API сводится к одной функции-обёртке. Она делает UrlFetchApp.fetch с method: 'post', contentType: 'application/json', заголовками из свойств, телом через JSON.stringify и обязательным muteHttpExceptions: true — иначе при 429 или 500 скрипт упадёт с исключением, вместо того чтобы показать вам текст ошибки Ozon. Дальше смотрите getResponseCode(): 200 — парсим, 401 и 403 — проблема с ключом или его правами, 404 — метод переехал на новую версию, 429 — упёрлись в лимит, 5xx — ждём и повторяем.

Запись в лист — второе место, где скрипты тормозят. appendRow в цикле означает отдельное обращение к таблице на каждую строку: несколько тысяч отправлений так пишутся десятки минут и съедают весь лимит времени выполнения ещё до того, как вы дочитаете API. Соберите массив массивов и отдайте одним getRange(row, col, rows.length, rows[0].length).setValues(rows) — тот же объём уезжает за секунды.

Два типа данных, которые ломают формулы молча. Цены и суммы Ozon часто отдаёт строкой вида «1990.0000» — в листе это текст, СУММ его игнорирует и вы неделю смотрите на заниженный оборот; приводите через Number(). Даты приходят в UTC в формате ISO — если просто записать строку, сортировка будет по алфавиту, а заказ, сделанный в 23:40 по Москве, попадёт во вчерашний день. Конвертируйте в объект Date и выставьте таймзону таблицы в настройках файла, а не в голове.

Пагинация: три механизма, и написать придётся все три

Ozon не завёл единый способ листать страницы, поэтому под каждый метод пишется свой цикл.

offset и limit. Запрашиваете limit: 1000, после каждого ответа увеличиваете offset на 1000. Крутите, пока приходит ровно limit записей; пришло меньше — страница была последней. Ловушка: у части методов сумма offset и limit ограничена сверху, и на длинном периоде вы упираетесь в потолок независимо от того, сколько ещё данных осталось. Лечится нарезкой периода на недели, а не попыткой поднять offset.

page и page_size. Так работают финансовые начисления: в первом ответе приходит page_count, дальше просто крутите страницы до этого числа. Здесь важно не перепутать нумерацию — страницы считаются с единицы, а не с нуля.

last_id или cursor. Ответ содержит идентификатор последней записи, его кладёте в следующий запрос. Цикл заканчивается, когда список пустой или last_id не изменился. Именно тут чаще всего получается бесконечный цикл, который выедает всю дневную квоту вызовов: обязательно ставьте предохранитель вроде «не больше 200 итераций» и проверяйте, что идентификатор реально поменялся.

И общее для всех трёх: Utilities.sleep(300) между запросами. Триста миллисекунд на сорок страниц — это двенадцать секунд сверху, зато 429 на объёмах обычного магазина почти пропадают.

Проверьте себя

Первая заливка истории: 38 000 отправлений FBS за квартал. Скрипт тянет их страницами по 1000 и на 23-й итерации падает по таймауту Apps Script. Что чинить первым?

Триггеры: обновление без вашего участия

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

Лист Интервал триггера Глубина окна Почему так
Отправления FBS Каждый час, 8:00–21:00 3 дня Статусы и отмены меняются в течение дня
Отправления FBO Раз в 8 часов 30 дней Доставка и возвраты приходят с лагом
Начисления Раз в сутки, ночью 45 дней Услуги начисляются задним числом
Остатки 2 раза в день Текущий срез Решение о поставке принимается не ежечасно
Товары и артикулы Раз в сутки Полный список Справочник меняется редко

Две страховки ставятся сразу, до первого боевого запуска. LockService.getScriptLock().tryLock(0) — чтобы часовой триггер, стартовавший поверх недоработавшего предыдущего, не писал в тот же лист параллельно: так рождаются дубли. И уведомления об ошибках триггеров на почту, они включаются там же, в списке триггеров. Без них скрипт умирает молча, а вы неделю принимаете решения по вчерашним цифрам и не догадываетесь об этом.

Про третье вспоминают обычно уже после падения — это квоты Apps Script. Ограничено время одного запуска, суммарное время работы триггеров за сутки и число вызовов UrlFetchApp; два последних лимита зависят ещё и от типа аккаунта Google. Значения Google периодически пересматривает, поэтому откройте актуальную таблицу квот в документации Apps Script до того, как поставите триггер на каждые пять минут.

Как не превратить лист в свалку

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

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

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

Разбор: как наш отчёт врал на 43 тысячи

Первая версия скрипта у нас делала ровно то, от чего я отговариваю выше: раз в сутки тянула вчерашние отправления и дописывала их в конец листа. Через два месяца в листе было 11 400 строк, и при сверке за июнь мы получили выручку 1 246 000 рублей против 1 203 000 в отчёте о реализации. Расхождение — 43 000 рублей, 3,6 % от отчёта.

Разбирали два вечера, и сумма разложилась на две части. 28 000 — отмены и возвраты, случившиеся после выгрузки: в листе эти заказы навсегда остались продажами, потому что строку больше никто не перечитывал. Ещё 15 000 — обычные дубли. 14 июня ночной триггер упал по таймауту, скрипт перезапустили руками в обед, окно наложилось, часть отправлений записалась дважды.

Чинилось тремя правками: ключ строки «номер отправления + артикул», окно 30 дней с удалением и перезаписью блока вместо дописывания, LockService на входе в функцию. Расхождение с отчётом о реализации после этого держится ниже 0,5 %. Остаток — лаг начислений: когда пересчитываешь закрытый месяц числа десятого, он схлопывается сам.

Чек-лист

Перед тем как доверять цифрам в листе

Где скрипт ломается и когда таблицы перестают справляться

Три поломки чинятся за вечер. 429 в цикле без пауз — самая частая и самая безобидная: sleep плюс повтор с увеличенной задержкой. Таймаут выполнения — курсор в свойствах скрипта и продолжение со следующего триггера. 403 на финансовых методах, когда остатки той же связкой тянутся нормально, — вопрос к ключу: проверьте, что он выпущен под этот магазин и не отозван, при сомнении выпустите новый.

Четвёртая опаснее трёх предыдущих, потому что молчит. Метод переехал на новую версию, старый отдаёт 404, а скрипт код ответа не смотрит — лист перестаёт обновляться и выглядит при этом абсолютно нормальным. Заведите служебную ячейку «последнее успешное обновление» и смотрите на неё раньше, чем на цифры.

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

Apps Script закрывает задачу «вижу свежие заказы и остатки без ручной выгрузки» и закрывает её хорошо. Буксовать он начинает, когда в расчёт заходят себестоимость по партиям, разнесение рекламы по SKU, сведение начислений с заказами и история за год: лист на десятки тысяч строк тормозит, а каждое новое правило — это ещё один блок кода, который поддерживаете только вы. На этой границе выбор простой: либо разносить оперативную и архивную таблицы, либо отдать расчёт прибыли, рекламы и остатков готовому сервису. Мы в Starbox пришли ко второму, а скрипт оставили для точечных выгрузок под конкретные вопросы.

FAQ

Можно ли выгрузить данные Ozon в Google Таблицы без программирования?

Частично. Отчёты из кабинета скачиваются в XLSX и заливаются в таблицу через «Файл → Импорт» — это работает, но каждый раз руками. Автообновление без кода дают готовые коннекторы и сервисы аналитики с интеграцией в Google Таблицы. Бесплатный вариант, который сам ходит за данными по расписанию, — это всё-таки Apps Script.

Нужен ли платный аккаунт Google для такого скрипта?

Нет, Apps Script работает и на обычном личном аккаунте. Разница в квотах: у бесплатного заметно ниже суммарное время работы триггеров за сутки и число вызовов UrlFetchApp. Магазину с сотнями заказов в день этого хватает с запасом, а вот минутный опрос нескольких методов в такой лимит уже не влезает.

Как часто можно обновлять данные, чтобы не ловить 429?

Раз в час для заказов и раз в сутки для финансов — безопасный режим почти для любого объёма. Лимиты у Ozon заданы по методам и периодически меняются, актуальные значения смотрите в документации Seller API. Пауза 300 миллисекунд между запросами внутри цикла снимает большую часть проблем.

Почему сумма в таблице не сходится с отчётом о реализации?

Три типовые причины: в листе остались заказы, которые позже отменили или вернули; строки задвоились после повторного запуска; периоды считаются по разным датам — заказа и отгрузки. Сверять стоит закрытый месяц и по одному признаку даты, а не текущую неделю.

Безопасно ли хранить API-ключ Ozon в Google Таблице?

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

Статья была полезна?

Вы развиваете магазин. ИИ разбирается с рутиной.

Поручите Starbox AI поиск потерь в прибыли, проверку рекламы и планирование запасов. Задавайте вопросы в чате и получайте рекомендации по своему магазину. Попробуйте ИИ-менеджера для Ozon 14 дней бесплатно, без карты.

Попробовать ИИ-менеджера бесплатно