TypeScript SDK: Обход страниц и категорий
Задача обхода — пройти все нужные страницы, не пропустить товары и не зациклиться на последней странице. Сначала определите, откуда берётся номер страницы: из URL, кнопки «Далее» или ответа JSON. Затем задайте верхнюю границу и стабильный ключ записи.
Номер страницы в URL
Если адрес содержит параметр page, используйте ctx.url().withParam(). После каждого запроса проверяйте HTTP-статус, наличие карточек и отмену. Остановка по пустой странице и максимум страниц дополняют друг друга.
async function run(ctx: Akmelo.Context): Promise<void> {
const base = ctx.param('catalog_url', '');
if (!base) ctx.abort('Укажите адрес каталога');
const maxPages = Math.min(100, Math.max(1, Number(ctx.param('max_pages', 10))));
const seen = new Set();
for (let number = 1; number <= maxPages; number++) {
if (await ctx.cancelled()) ctx.abort('Сбор остановлен');
const url = ctx.url().withParam(base, 'page', number);
const response = await ctx.http().get(url);
if (!response.ok()) ctx.fail(`Страница ${number}: HTTP ${response.status}`);
const cards = response.dom().qa('.product-card');
if (!cards.length) break;
let added = 0;
for (const card of cards) {
const sku = card.attr('data-sku');
if (!sku || seen.has(sku)) continue;
seen.add(sku);
added++;
ctx.emit({ sku, name: card.q('.title')?.text() ?? '' }, sku);
}
ctx.progress(number, maxPages, 'Страницы каталога');
if (added === 0) break;
}
}
Селекторы .product-card, data-sku и .title — примеры. Выберите их на своём сайте и проверьте пробным прогоном. Set удаляет повторы только внутри текущего запуска; стабильный ключ в ctx.emit() связывает результаты разных запусков.
Несколько категорий
Список категорий может приходить из параметров сценария. Проверяйте тип: строка или пустое значение вместо массива не должны молча превращаться в один адрес. Ограничьте число категорий и страниц на категорию. Если разные категории содержат один артикул, используйте общий seen, если запись должна быть общей; добавьте категорию в ключ, если нужны отдельные записи.
async function run(ctx: Akmelo.Context): Promise<void> {
const categories = ctx.param('category_urls', []);
if (!Array.isArray(categories)) ctx.abort('Укажите список категорий');
const seen = new Set();
for (const categoryUrl of categories.slice(0, 20)) {
if (await ctx.cancelled()) ctx.abort('Сбор остановлен');
const response = await ctx.http().get(String(categoryUrl));
if (!response.ok()) ctx.fail(`Категория: HTTP ${response.status}`);
for (const card of response.dom().qa('.product-card')) {
const sku = card.attr('data-sku');
if (!sku || seen.has(sku)) continue;
seen.add(sku);
ctx.emit({ sku, category: String(categoryUrl), name: card.text() }, sku);
}
}
}
Если источник использует кнопку, бесконечную прокрутку или XHR, применяйте действия браузера и дождитесь новой карточки или ответа сети. Сам клик не доказывает, что данные изменились.