JavaScript SDK: Журнал, прогресс и безопасный повтор
Во время длинного сбора пользователь должен понимать, что происходит. ctx.log() добавляет объяснение в журнал, ctx.progress() показывает текущий этап, ctx.metric() собирает числовые показатели. Эти методы локальны и не требуют await.
Понятный журнал
Записывайте начало категории, число найденных страниц, пропуски и причину остановки. Не записывайте пароль, токен, текст личного письма или полное содержимое приватного документа. Для ошибки используйте ctx.fail(reason) с конкретным наблюдением: например, «страница вернула HTTP 403», а не «что-то пошло не так».
Прогресс и отмена
Если известна общая длина, передайте done и total. Если неизвестна, передайте null вместо общего числа и обновляйте название этапа. Не показывайте 100% до завершения сохранения всех записей.
async function run(ctx) {
const urls = ctx.param('urls', []);
if (!Array.isArray(urls)) ctx.abort('Укажите список адресов');
let processed = 0;
ctx.log('Начинаем обход', { pages: urls.length });
for (const url of urls) {
if (await ctx.cancelled()) ctx.abort('Сбор остановлен пользователем');
const response = await ctx.http().get(String(url));
if (!response.ok()) ctx.fail(`HTTP ${response.status}: ${url}`);
processed++;
ctx.metric('pages_processed', 1);
ctx.progress(processed, urls.length, 'Загрузка страниц');
}
ctx.log('Обход завершён', { processed });
}
Состояние между запусками
await ctx.state().get(key, fallback) читает сохранённый курсор; set() и incr() обновляют его. Состояние не заменяет таблицу результатов. Для товара используйте стабильный ключ ctx.emit(record, key). Повторите пробный запуск с теми же входами и проверьте, что дубликатов и пропусков нет.
async function run(ctx) {
const state = ctx.state();
const nextPage = Number(await state.get('next_page', 1));
const url = ctx.url().withParam(ctx.param('catalog_url'), 'page', nextPage);
const response = await ctx.http().get(url);
if (!response.ok()) ctx.fail(`HTTP ${response.status}`);
const cards = response.dom().qa('.product-card');
for (const card of cards) {
const sku = card.attr('data-sku');
if (sku) ctx.emit({ sku, name: card.text() }, sku);
}
if (cards.length) await state.set('next_page', nextPage + 1);
}
Курсор стоит менять после успешного чтения страницы. ctx.flush() отправляет накопленные записи на обработку, но сам по себе не подтверждает их окончательное сохранение. Проверяйте итоговый статус прогона и делайте повтор безопасным за счёт стабильных ключей записей.