Город «Wedge» оказался Клином. Как мы научили сервер самому читать кабинет партнёра без API
Ставки по городам мы до сих пор забирали именно так: раз в неделю по понедельникам на рабочем компьютере запускался скрипт, который открывал браузер, входил под живой учётной записью и кликал по интерфейсу кабинета партнёра, снимая цифры с экрана. Работало, но с двумя слабыми местами. Первое — без человека, который хоть иногда сам логинится в этот же браузер, сессия слетает, а Пн 09:30 иногда превращалось в «а кто сегодня за компьютером». Второе — любое обновление вёрстки кабинета партнёра могло сломать скрипт в произвольном месте, и мы узнавали об этом не сразу.
Разбирая, что именно скрипт вытаскивает с экрана, мы заметили странность: часть городов в выгрузке была на английском, и явно машинным переводом. «Mines» — это, как выяснилось, Шахты. «Carpet» — Ковров. А «Wedge» — Клин: площадка переводила название города как топоним-слово, а не как собственное имя. Чтобы наши сводки показывали Роману нормальные русские названия, кто-то каждую неделю вручную сверял список подстановок — и это было не забавным курьёзом, а сигналом, что мы читаем кабинет партнёра не с той стороны, с какой нужно.
Та же сводка с ответом сервера заодно показала, откуда эти английские названия берутся: они приходят не с отрисованной страницы, а из JSON, который кабинет партнёра запрашивает у себя же в фоне, чтобы заполнить таблицу. То есть у кабинета есть собственный внутренний API — он просто не документирован и не предназначен для посторонних, но браузер к нему обращается при каждом открытии страницы, с обычными куками и CSRF-токеном сессии.
Решили развернуть это не как разовую хитрость, а как отдельный сервис на сервере. Браузер в контейнере (headless Chromium) теперь держит ровно одну задачу — вход. Он открывает страницу кабинета партнёра, авторизуется и оставляет сессию жить в собственном профиле на диске, отдельном для каждой площадки. А все данные сервис берёт не кликами по интерфейсу, а вызовом `fetch()` прямо внутри открытой страницы — к тому же самому внутреннему JSON API, которым пользуется сам кабинет, с теми же куками и CSRF-заголовком. Вёрстка кабинета может меняться сколько угодно — нас это не касается, потому что мы с ней не взаимодействуем, мы читаем её же данные.
Входить в кабинет всё равно должен человек: в учётной записи площадки лежат реальные деньги и реальные данные курьеров. Для этого сервис показывает удалённый экран браузера внутри нашей собственной платформы — владелец видит ровно ту страницу логина, что увидел бы в обычном браузере, вводит пароль и код, а дальше уходит из этого экрана. Доступ к нему — только у владельца, остальным ролям эта кнопка не видна. С этого момента сервис сам следит за тем, жива ли сессия, и сообщает, если площадка разлогинила его и нужен новый вход.
Отдельно поставили ограничители на саму частоту обращений — не чтобы быть вежливыми абстрактно, а потому что реальный лимит нашёлся опытным путём: после восьми запросов подряд к одной и той же точке API (карточкам телефонов конкретных лидов) площадка начинала отвечать отказом. Теперь сервис сам держит паузу между пачками запросов и ограничивает их количество за один проход — лимит нашёлся не в документации (её и не было), а в самих ответах сервера.
Первую площадку перевели на эту схему за один день. Сейчас в синхронизации 172 заявки от потенциальных курьеров (телефон подтягивается у свежих — по старым площадка его больше не отдаёт) и 791 ставка по 207 городам, уже на русском — потому что мы наконец читаем те же названия, что видит сам партнёр в своей базе, а не их перевод для отображения. Заявки обновляются каждые 30 минут, ставки — раз в неделю, как и раньше, но уже без человека у клавиатуры в конкретный понедельник.
Вывод, который я забираю для себя и для любого, кто интегрируется с площадкой без официального API: не пытайтесь парсить то, что отрисовано на экране, и не кликайте по кнопкам программно, если можно этого не делать. Откройте вкладку «Сеть» в инструментах разработчика при обычном посещении кабинета — почти наверняка интерфейс сам ходит за данными в свой же внутренний JSON API. Если вы можете держать его сессию живой (через постоянный профиль браузера и разовый человеческий вход), вызывать этот же API можно напрямую — и тогда обновление вёрстки у партнёра перестаёт быть вашей проблемой, а частота обновления данных перестаёт зависеть от того, кто сегодня на месте по понедельникам в 09:30.