Оформление
Синхронизация и постраничность
Оба списочных метода устроены одинаково: страница по курсору плюс необязательный фильтр «что изменилось после».
json
{
"items": [ /* ... */ ],
"nextCursor": "MjAyNi0wOC0wMVQxMjowMDowMC4wMDBafGNseDEyMw"
}nextCursor равен null, когда страница последняя. Пока он не null — идите дальше, подставляя его в параметр cursor.
Почему курсор, а не смещение
Параметра offset здесь нет намеренно. Пока вы идёте по страницам, данные меняются: вакансию правят, кандидат переезжает по доске. Со смещением это означает, что часть записей вы получите дважды, а часть не получите вовсе — и узнаете об этом не сразу.
Курсор указывает на позицию в сортировке, а не на номер строки, и от вставок не страдает.
Параметры
| Параметр | Значение |
|---|---|
limit | размер страницы, 1..500, по умолчанию 100 |
cursor | курсор из nextCursor предыдущей страницы |
since | ISO 8601; вернутся записи, изменённые после этого момента |
Инкрементальная синхронизация
since сравнивается с отметкой правки, а не создания. Это важнее, чем кажется: без этого закрытая вакансия никогда не доехала бы до вас, и портал показывал бы людям позицию, на которую уже не берут.
У кандидатов сравнение идёт по двум отметкам сразу — по самой карточке и по её резюме. Резюме живёт отдельно и обновляется при переимпорте с hh.ru; без второй отметки обновлённое резюме осталось бы у нас.
Схема регулярной синхронизации:
bash
SINCE=$(cat .last-sync) # отметка предыдущего успешного прохода
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
CURSOR=""
while :; do
RESP=$(curl -s -H "X-API-Key: $KEY" \
"$BASE/vacancies?status=all&since=$SINCE&limit=200${CURSOR:+&cursor=$CURSOR}")
echo "$RESP" | jq -c '.items[]' >> vacancies.ndjson
CURSOR=$(echo "$RESP" | jq -r '.nextCursor // empty')
[ -z "$CURSOR" ] && break
done
echo "$NOW" > .last-sync # отметку берём ДО прохода, а не послеОтметку следующего прохода берите до начала выгрузки, а не после её окончания: запись, изменившаяся во время обхода, иначе потеряется.
Первая синхронизация вытянет всё
Отметка правки появилась в платформе вместе с этим API, и существующим записям она проставлена моментом обновления. Поэтому первый проход с since вернёт всю базу целиком — один раз. Дальше объёмы будут обычными.
Удаления не приходят
Удалённая запись просто перестаёт появляться в выдаче — отдельного признака «удалено» в API нет.
Если вам важно не показывать то, чего у нас больше нет, раз в сутки делайте полный проход без since и сверяйте множество идентификаторов. Вакансий обычно сотни, такой проход стоит недорого.
Архив — другое дело: снятая вакансия приходит штатно, со статусом archived, и по ней видно, что позиция закрыта.