Запрос вернулся с кодом 200, JSON на месте, строк ровно столько, сколько просили. Данные при этом — мусор. Так выглядит самый неприятный сбой в парсинге: сервер не ругается, не отдаёт ошибку, просто игнорирует часть параметров и возвращает дефолтную выдачу.
Как это выглядит на живом примере
Публичный поиск вакансий LinkedIn без логина идёт через guest-эндпоинт /jobs-guest/jobs/api/seeMoreJobPostings/search. Ему можно передавать те же параметры, что и на сайте: f_JT — тип занятости, f_E — уровень опыта, f_WT — удалёнка, sortBy=DD — сначала новые, f_TPR — за какой период, f_AL и f_EA — Easy Apply.
Примерно с 11 сентября 2026 эндпоинт принимает все эти параметры и возвращает один и тот же набор независимо от того, что вы отправили. Проверка: keywords=data analyst, location=United States, пять вариантов запроса, первые 50 ID сравниваются с выдачей без фильтров (повтор 13 сентября 2026).
| Вариант запроса | Пересечение с выдачей без фильтров |
|---|---|
| f_JT=C (контракт) | 50 / 50 |
| f_E=2 (начальный уровень) | 50 / 50 |
| f_WT=2 (удалёнка) | 50 / 50 |
| sortBy=DD (сначала новые) | 50 / 50 |
| f_TPR=r86400 (за 24 часа) | 4 / 50 — работает |
Только f_TPR, f_AL и f_EA меняют ответ. Остальное сервер молча проглатывает. Ни ошибки, ни предупреждения о том, что фильтр проигнорирован, — просто дефолтная первая страница.
Почему это опаснее явной поломки
Пятисотая ошибка или таймаут видны сразу: скрипт падает, вы идёте чинить. Здесь всё наоборот. Парсер, который доверяет параметрам, «успешно» работает с неправильными данными: логи чистые, метрики зелёные, а в базу льются удалённые вакансии в ответ на запрос «только контракт». Находится такое через месяцы, когда кто-то вручную сверяет выборку и удивляется, откуда в отчёте взялись лишние строки.
Правило, которое стоит повесить на стену: параметр, который принимается, — не то же самое, что параметр, который применяется. Проверять надо эффект, а не код ответа.
Фильтрация после выгрузки: где она ломается
Первый очевидный вариант — тянуть вакансии как есть, потом открывать карточку каждой (в детальной странице критерии ещё живут: уровень, тип занятости) и выбрасывать несовпадающие. Я так и сделал. Работало ровно до следующего аудита параметров, который показал баг: при maxJobs: 20 и jobTypes: ["contract"] прогон вернул 0 строк.
Не потому что контрактных вакансий нет. Просто до фильтра успело просканироваться 20 карточек, и ни одна из них не оказалась контрактом. В моей выборке первая такая вакансия нашлась на 49-й детальной странице.
Вывод: если фильтр применяется на клиенте, бюджет сканирования нужно отвязывать от бюджета результата. Иначе редкие фильтры всегда возвращают пустоту, и вы будете уверены, что таких вакансий не существует.
Как это устроено теперь: страница → проверка деталей → оставить совпадения → листать дальше, пока не наберётся maxJobs совпадений или не упрётся в лимит сканирования (по умолчанию 5× maxJobs, минимум 50, максимум 500). Несовпавшие вакансии не съедают бюджет результата. Прогон «удалённый data analyst, 8 штук» просматривает около 30 объявлений; «контракт» просматривает заметно больше — и честно об этом сообщает.
Когда клиентская фильтрация оправдана
Если условие пропускает большинство вакансий, цена почти не растёт: на каждую просмотренную карточку приходится совпадение. Когда под условие попадает одна вакансия из пятидесяти, число запросов к детальным страницам взлетает вместе с временем прогона. В такой ситуации дешевле сузить входной запрос по ключевым словам или географии, а тонкую фильтрацию уже делать на клиенте.
Регрессионная проверка, которая ловит это сама
Разовая проверка бесполезна: параметр могут отключить в любой момент, без анонса и записи в changelog. Нужен ежедневный прогон-«ворота» с заранее описанным ожидаемым эффектом. У меня это JSON-спека: вариант входа → что должно получиться.
- фильтр по контракту → все строки с employmentType, содержащим contract;
- фильтр по удалёнке → минимум 8 строк в выдаче;
- сортировка по дате → первые 5 ID отличаются от базовой выдачи.
Эту спеку я запускаю каждый день. Баг с нулём строк она поймала на следующий день после того, как появилась.
Чек-лист для любого скрапера с фильтрами
- Соберите эталон: выдача без фильтров, первые 50 ID. Каждый фильтр сравнивайте с ней по пересечению, а не по количеству строк. Совпало 50 из 50 — параметр, скорее всего, мёртвый.
- Проверяйте не только фильтры, но и сортировку. sortBy притворяется работающим так же убедительно.
- Разделите бюджеты: сколько страниц сканируем и сколько результатов хотим получить. Поставьте потолок на сканирование и логируйте, когда в него упираетесь.
- Пишите ожидаемый эффект в машинночитаемом виде. «Все строки подходят под условие» проверяется автоматически, «выглядит нормально» — нет.
- Показывайте цену фильтра. Если на 8 удалённых вакансий ушло 300 просканированных карточек, это должно быть видно вам через месяц, а не только в момент запуска.
Молчаливый сбой параметров — норма для публичных API без документации и без гарантий, а не редкая аномалия. Защита здесь одна: измерять эффект каждого параметра и делать это регулярно. Одна таблица с ID и один ежедневный прогон обходятся дешевле, чем квартальный отчёт, собранный на неверных данных.
Комментарии (0)
Войдите, чтобы комментировать.
Пока нет комментариев. Будьте первым.