Product Hunt, это площадка, где каждый день запускаются и ранжируются новые программные продукты, аппаратные устройства и побочные проекты, а публичная сторона этого ранжирования является чистым сигналом для всех, кто отслеживает, что выпускают создатели. Основатели следят за ней, чтобы оценить конкурентов, исследователи используют её для выявления трендов в категориях, а команды роста изучают, какие запуски набирают больше всего голосов. Данные прямо перед вами на каждой странице категории: названия продуктов, слоганы, счётчики голосов и ссылка на каждый листинг.

В этом руководстве показано, как парсить Product Hunt с помощью JavaScript и Node.js с использованием Cheerio. Вы создадите небольшой работающий скрапер, который загружает страницу категории Product Hunt через Crawling API, разбирает название продукта, слоган, голоса и ссылку для каждого листинга, опционально извлекает базовые публичные сведения о создателе со страницы профиля, обрабатывает пагинацию и экспортирует результат в форматах JSON и CSV. Всё руководство ограничено публичными данными о продуктах, а раздел о законности в конце не является формальностью, так что прочитайте его прежде, чем запускать парсер в промышленных объёмах.

Что вы создадите

Скрипт на Node.js, который принимает публичный URL категории Product Hunt, загружает отрендеренный HTML через Crawling API и извлекает структурированную запись для каждого продукта в списке. В качестве основного примера используется страница категории инженерия и разработка; для каждого объекта извлекаются следующие поля:

  • Название название продукта, отображаемое на карточке листинга.
  • Слоган короткая строка описания, расположенная под названием.
  • Голоса счётчик голосов, разобранный в число при его наличии.
  • Отзывы текст с числом отзывов, например «151 reviews».
  • Ссылка URL страницы отдельного продукта.

Далее в руководстве мы добавляем отдельный проход, который читает базовые публичные поля профиля создателя, такие как имя, заголовок, число подписчиков и подписок, а также очки, чтобы показать, как тот же подход распространяется от листингов на один публичный профиль.

Почему обычный запрос не работает на Product Hunt

При запросе URL категории Product Hunt с помощью обычного HTTP-клиента список продуктов редко возвращается. Два фактора работают против вас. Во-первых, Product Hunt рендерит карточки листингов в браузере с помощью JavaScript, поэтому исходный HTML является почти пустым каркасом до выполнения скриптов страницы. Во-вторых, платформа следит за автоматизированным трафиком: IP-адреса дата-центров и паттерны запросов, не характерные для реального браузера, ограничиваются по скорости или блокируются ещё до достижения отрендеренных данных о продуктах.

Таким образом, работающий скрапер Product Hunt требует двух вещей в одном запросе: браузера, который реально рендерит страницу, и IP-адреса, который платформа воспринимает как адрес настоящего посетителя. Можно собрать это самостоятельно с помощью headless-браузера плюс пула ротирующихся жилых прокси, однако их объединение и поддержание в рабочем состоянии составляет большую часть усилий. Crawling API объединяет оба компонента в одном вызове: вы передаёте ему URL, он рендерит страницу за доверенным IP и возвращает готовый HTML для разбора с помощью Cheerio.

Зачем нужен JavaScript-токен

Поскольку Product Hunt формирует листинги на стороне клиента, вы запрашиваете эти страницы через Crawling API с включённым рендерингом JavaScript. В официальном Node-клиенте это означает инициализацию с вашим JavaScript-токеном. Запросы с рендерингом стоят больше кредитов, чем обычные, но бесплатный тариф позволяет протестировать весь поток до масштабирования.

Предварительные требования

Перед написанием кода нужно подготовить несколько вещей. Ни одна из них не займёт много времени.

Базовые знания JavaScript и Node.js. Вы должны уметь писать и запускать Node-скрипт, устанавливать пакеты с помощью npm и работать с DOM-концепциями, которые зеркалирует Cheerio. Если вы только знакомитесь с Node, руководство по созданию веб-скрапера с Node.js закрывает все базовые темы, которые предполагаются в этом туториале.

Node.js 16 или новее. Проверьте вашу версию командой node --version. Если Node.js не установлен, установите его с сайта Node.js или через менеджер версий, например nvm.

Аккаунт Crawlbase и токен. Зарегистрируйтесь, откройте дашборд и скопируйте JavaScript-токен со страницы документации аккаунта. Бесплатный тариф предоставляет до 20 000 запросов без банковской карты. Обращайтесь с токеном как с паролем: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий.

Настройка проекта

Создайте папку проекта, инициализируйте его и установите две библиотеки, необходимые для скрапера.

bash
node --version

mkdir producthunt-scraper && cd producthunt-scraper
npm init -y

npm install crawlbase cheerio

Две зависимости выполняют основную работу: crawlbase является официальным Node-клиентом для Crawling API, а cheerio разбирает возвращаемый HTML с jQuery-подобным API, позволяя извлекать отдельные поля по CSS-селектору без привязанного браузера. Создайте файл scraper.js в этой папке и добавьте в него код из шагов ниже.

Шаг 1: Загрузка отрендеренной страницы категории

Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его с помощью JavaScript-токена и запросите URL категории. Сохранение тела на диск позволяет один раз проверить реальную разметку, а затем итерировать по селекторам без траты запроса каждый раз.

javascript
const { CrawlingAPI } = require('crawlbase');
const fs = require('fs');

const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' });

const producthuntPageURL =
  'https://www.producthunt.com/categories/engineering-development';

api
  .get(producthuntPageURL)
  .then((response) => {
    if (response.statusCode === 200) {
      fs.writeFileSync('response.html', response.body);
      console.log('HTML saved to response.html');
    }
  })
  .catch((error) => console.error('API request error:', error));

Запустите скрипт командой node scraper.js и вы должны увидеть реальную разметку Product Hunt, записанную в response.html, а не упрощённый каркас. Это подтверждает, что рендеринг работает ещё до написания единственного селектора. Замените пример URL на любую нужную страницу категории; все категории находятся в разделе /categories/ того же домена.

Crawlbase Crawling API

Этот первый запрос только что вернул полностью отрендеренную страницу категории Product Hunt без headless-браузера или прокси на вашей стороне. Crawling API запускает страницу в настоящем браузере, ротирует жилые IP на стороне сервера и обрабатывает ограничения скорости, которые платформа применяет к скраперам, так что вы получаете готовый HTML из одного вызова. Начните с категории инженерии и разработки на бесплатном тарифе.

Шаг 2: Разбор каждого продукта с помощью Cheerio

Получив отрендеренный HTML, загрузите его в Cheerio и обойдите карточки продуктов. Product Hunt размещает каждый листинг в повторяющемся контейнере, поэтому вы выбираете все карточки, затем читаете из каждой название, слоган, голоса, отзывы и ссылку. Защитное чтение каждого поля предотвращает сбой запуска из-за одного отсутствующего значения.

javascript
const fs = require('fs');
const cheerio = require('cheerio');

function parseProducts(html) {
  const $ = cheerio.load(html);
  const products = [];

  const containers = $(
    'div.flex.direction-column.mb-mobile-10.mb-tablet-15.mb-desktop-15.mb-widescreen-15'
  );

  containers.each((index, element) => {
    const card = $(element);

    const name = card
      .find('div.color-blue.fontSize-18.fontWeight-600')
      .text()
      .trim();

    // Each filled star is one label element inside the rating row
    const upvotes = card.find(
      'div.flex.direction-row.align-center label'
    ).length;

    const reviews = card
      .find('div.ml-3.color-lighter-grey.fontSize-14.fontWeight-400')
      .text()
      .trim();

    const tagline = card
      .find(
        'div.color-lighter-grey.fontSize-mobile-14.fontSize-tablet-16.fontSize-desktop-16.fontSize-widescreen-16.fontWeight-400'
      )
      .text()
      .trim();

    const href = card.find('a').first().attr('href');
    const link = href
      ? new URL(href, 'https://www.producthunt.com').href
      : '';

    if (name) {
      products.push({ rank: index + 1, name, tagline, upvotes, reviews, link });
    }
  });

  return products;
}

Несколько деталей обеспечивают точность воспроизведения страницы. Название продукта берётся из блока color-blue fontSize-18 fontWeight-600, а слоган из длинной цепочки классов color-lighter-grey со шрифтовыми размерами, которую Product Hunt использует для строки описания. Строка голосов и рейтинга рендерит один элемент label для каждого заполненного маркера, поэтому подсчёт этих элементов label даёт число без разбора текста. Текст с числом отзывов находится в своём блоке ml-3 color-lighter-grey, а ссылка читается из первого анкора карточки и преобразуется в абсолютный URL, чтобы работать за пределами страницы.

Селекторы смещаются

Имена классов Product Hunt (длинные цепочки fontSize-* и остальные) генерируются и меняются без предупреждения. Относитесь к приведённым выше селекторам как к начальному шаблону, а не как к постоянному договору. Когда поле возвращается пустым, повторно проверьте живую страницу в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание является нормой для любого промышленного скрапера, а не признаком неисправности. Более широкий технический контекст см. в руководстве по краулингу JavaScript-сайтов.

Шаг 3: Сборка полного скрипта с экспортом в JSON и CSV

Теперь соедините загрузку и парсинг в один работающий скрипт, затем запишите записи на диск в форматах JSON и CSV.

javascript
const fs = require('fs');
const { CrawlingAPI } = require('crawlbase');
const cheerio = require('cheerio');

const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' });

async function crawl(pageUrl) {
  const response = await api.get(pageUrl);
  if (response.statusCode === 200) return response.body;
  console.error(`Request failed: ${response.statusCode}`);
  return null;
}

function toCsv(rows) {
  const headers = ['rank', 'name', 'tagline', 'upvotes', 'reviews', 'link'];
  const escape = (value) =>
    `"${String(value).replace(/"/g, '""')}"`;
  const lines = [headers.join(',')];
  for (const row of rows) {
    lines.push(headers.map((h) => escape(row[h])).join(','));
  }
  return lines.join('\n');
}

async function main() {
  const url =
    'https://www.producthunt.com/categories/engineering-development';
  const html = await crawl(url);
  if (!html) return;

  const products = parseProducts(html);
  fs.writeFileSync('products.json', JSON.stringify(products, null, 2));
  fs.writeFileSync('products.csv', toCsv(products));
  console.log(`Saved ${products.length} products to JSON and CSV`);
}

main();

Вставьте функцию parseProducts из шага 2 в тот же файл, чтобы main могла её вызвать. Запустите командой node scraper.js и получите два файла: products.json с полными структурированными записями и products.csv, готовый к открытию в таблицах. Вспомогательная функция toCsv заключает каждое поле в кавычки и удваивает встроенные кавычки, что важно здесь, поскольку слоганы продуктов длинные и часто содержат запятые.

Как выглядит результат

JSON-файл содержит по одному объекту на продукт в порядке листинга, каждый с рангом, названием, слоганом, числом голосов, текстом числа отзывов и ссылкой.

json
[
  {
    "rank": 1,
    "name": "The Free Website Guys",
    "tagline": "A free website program that has helped over 10,000 entrepreneurs.",
    "upvotes": 5,
    "reviews": "151 reviews",
    "link": "https://www.producthunt.com/products/the-free-website-guys"
  },
  {
    "rank": 2,
    "name": "Zipy",
    "tagline": "A debugging platform with session replay and network monitoring.",
    "upvotes": 5,
    "reviews": "132 reviews",
    "link": "https://www.producthunt.com/products/zipy"
  }
]

CSV зеркалирует те же строки со строкой заголовка, поэтому он напрямую вставляется в Excel, Google Sheets или любой конвейер данных, читающий файлы с разделителями.

csv
rank,name,tagline,upvotes,reviews,link
"1","The Free Website Guys","A free website program that has helped over 10,000 entrepreneurs.","5","151 reviews","https://www.producthunt.com/products/the-free-website-guys"
"2","Zipy","A debugging platform with session replay and network monitoring.","5","132 reviews","https://www.producthunt.com/products/zipy"

Обработка пагинации по всей категории

Одна страница является демонстрацией; реальная задача обходит всю категорию. Product Hunt загружает дополнительные листинги при прокрутке, а страницы категорий также принимают параметр страницы, который можно перебирать. Наиболее простой надёжный паттерн состоит в загрузке последовательных страниц, разборе каждой той же функцией и остановке, когда страница возвращает пустой результат. Пауза в цикле с короткой задержкой поддерживает спокойный темп запросов.

javascript
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function scrapeCategory(slug, maxPages = 5) {
  const all = [];
  for (let page = 1; page <= maxPages; page++) {
    const url =
      `https://www.producthunt.com/categories/${slug}?page=${page}`;
    const html = await crawl(url);
    if (!html) break;

    const rows = parseProducts(html);
    if (rows.length === 0) break;

    all.push(...rows.map((p) => ({ category: slug, ...p })));
    await sleep(2000);
  }
  return all;
}

scrapeCategory('engineering-development').then((rows) => {
  console.log(`Collected ${rows.length} products`);
});

Поскольку все страницы категорий имеют одинаковую структуру карточек, уже написанный парсер работает для всех них без изменений. Пометьте каждую строку её категорией перед экспортом, и вы сможете сравнить, что выпускается, скажем, в инженерии, дизайне и AI в одном наборе данных. Этот паттерн напрямую переходит в исследование продуктов; более глубокий взгляд на превращение ранжированных листингов в решения см. в руководстве по автоматизации исследования продуктов в электронной коммерции.

Парсинг базовых публичных данных профиля

Тот же подход «загрузить, затем разобрать» работает для отдельного публичного профиля создателя. Профили содержат базовые публичные поля, такие как отображаемое имя, заголовок, число подписчиков и подписок, очки и продукты, которые создатель выпустил. Устаревшая версия этого руководства читала их со страницы профиля; следующий фрагмент переносит те же поля. Ограничивайте сбор данных только базовыми публичными данными профиля и ознакомьтесь с разделом о законности перед запуском.

javascript
function parseProfile(html, handle) {
  const $ = cheerio.load(html);
  const profile = {};

  profile.name = $(
    'h1.color-darker-grey.fontSize-24.fontWeight-600'
  ).text().trim();
  profile.headline = $(
    'div.color-lighter-grey.fontSize-18.fontWeight-300'
  ).text().trim();
  profile.followers = $(
    `a[href="/@${handle}/followers"]`
  ).text().trim();
  profile.following = $(
    `a[href="/@${handle}/following"]`
  ).text().replace(/\n\s+/g, ' ').trim();
  profile.points = $(
    'span.color-lighter-grey.fontSize-14.fontWeight-400:contains("points")'
  ).text().trim();

  // Public list of products the maker has shipped
  profile.products = [];
  $('.styles_even__Qeyum, .styles_odd__wazk7').each((i, el) => {
    profile.products.push({
      name: $(el).find('img.styles_thumbnail__Y9ZpZ').attr('alt'),
    });
  });

  return profile;
}

async function scrapeProfile(handle) {
  const html = await crawl(`https://www.producthunt.com/@${handle}`);
  return html ? parseProfile(html, handle) : null;
}

Селекторы зеркалируют устаревшие поля: отображаемое имя из заголовка h1.color-darker-grey, заголовок из строки fontSize-18 fontWeight-300, числа подписчиков и подписок из анкоров, ведущих на страницы подписчиков и подписок хендла, и общее число очков из тега span, содержащего слово «points». Список продуктов читает атрибут alt каждой миниатюры. Извлекайте только эти базовые публичные данные и не углубляйтесь в то, что может идентифицировать частное лицо.

Как оставаться незаблокированным

Даже при отработанном рендеринге Product Hunt следит за трафиком, характерным для скраперов. Несколько привычек помогут сохранить работоспособность запуска и применимы к любой коммерческой цели.

  • Задавайте темп запросов. Добавляйте задержку между загрузками страниц вместо перегрузки страниц в быстром цикле. Распределение запросов является единственным наиболее эффективным способом оставаться ниже лимитов платформы.
  • Используйте ротацию. Пул жилых IP распределяет запросы по многим реальным пользовательским адресам, чтобы ни один из них не превысил лимит. Crawling API берёт это на себя; если вы собираете собственную инфраструктуру, именно этому аспекту стоит уделить особое внимание.
  • Обращайте внимание на коды статусов. Запуск, который начинает возвращать CAPTCHA или ответы не с кодом 200, сигнализирует о том, что текущей частоты запросов или уровня IP уже недостаточно. Воспринимайте это как сигнал снизить нагрузку, а не как шум, который можно игнорировать.

Подробная инструкция приведена в руководстве по скрапингу без блокировок.

Законно ли парсить Product Hunt?

Допустимость парсинга Product Hunt зависит от условий обслуживания Product Hunt, вашей юрисдикции и того, как вы используете данные. Условия Product Hunt ограничивают автоматизированный доступ, поэтому парсинг может нарушать эти условия независимо от тщательности вашего инструментария. Ни один из приведённых здесь кодов этого не меняет; он лишь обеспечивает техническую работоспособность. Ознакомьтесь с Условиями использования Product Hunt и файлом robots.txt, и рассматривайте оба документа как границу для собираемых данных.

Несколько правил, которых стоит придерживаться. Собирайте только публичные данные о продуктах: названия, слоганы, счётчики голосов, числа отзывов и ссылки, которые может видеть любой пользователь на странице категории без аккаунта. Не собирайте персональные данные о создателях, выходящие за рамки базовых публичных данных профиля, отображаемых на странице, и никогда не составляйте профили в набор данных, который нацелен на частных лиц или идентифицирует их. Соблюдайте заявленные ожидания Product Hunt по частоте запросов и держите объём запросов достаточно низким, чтобы не создавать нагрузку на серверы. Не распространяйте охраняемые авторским правом материалы Product Hunt, такие как изображения продуктов или аватары создателей, как свои собственные. Если вы планируете повторно использовать данные в коммерческих целях, получите разрешение или официальное соглашение, а не предполагайте, что молчание означает согласие.

Для объёмного или коммерческого использования Product Hunt располагает официальным API. Он использует аутентификацию с OAuth2-токеном и применяет собственные ограничения скорости, а коммерческое использование по умолчанию ограничено, поэтому вы связываетесь с Product Hunt для получения разрешения перед созданием бизнеса на его основе. Этот официальный API является правильным инструментом, когда вам нужны большие объёмы, гарантированная структура или коммерческие права. Данное руководство намеренно ограничено публичными листингами и базовыми публичными данными профилей, поскольку это та граница, которая делает работу обоснованной. Оно не охватывает ничего, что находится за авторизацией, приватных данных пользователей или любых попыток обойти аутентификацию. Если ваш проект требует большего, чем публичные данные, официальный API Product Hunt или соглашение о данных является правильным путём, а не более изощрённый скрапер.

Итоги

Ключевые выводы

  • Product Hunt рендерит листинги на стороне клиента. Обычный запрос возвращает пустой каркас, поэтому страницу необходимо отрендерить за доверенным IP до разбора.
  • Crawling API делает оба в одном вызове. Он рендерит страницу с JavaScript-токеном, ротирует жилые IP и возвращает готовый HTML для разбора с помощью Cheerio.
  • Cheerio извлекает поля. Выберите каждый контейнер продукта, затем читайте название, слоган, голоса, отзывы и ссылку, получая ранг из индекса цикла, и ожидайте смещения сгенерированных имён классов.
  • Пагинация и экспорт. Перебирайте страницы категорий до тех пор, пока одна не вернёт пустой результат, затем записывайте структурированные записи в JSON и CSV, заключая поля CSV в кавычки, чтобы слоганы с запятыми оставались целыми.
  • Оставайтесь в рамках публичных данных. Соблюдайте условия использования Product Hunt и файл robots.txt, берите только базовые публичные данные профиля без персональных данных, и предпочитайте официальный API Product Hunt для объёмного или коммерческого использования.

Часто задаваемые вопросы

Какие данные можно парсить с Product Hunt?

Со страницы публичной категории можно извлечь название, слоган, счётчик голосов, текст числа отзывов и ссылку на листинг каждого продукта. Из публичного профиля создателя можно читать базовые публичные поля, такие как отображаемое имя, заголовок, числа подписчиков и подписок, очки и продукты, которые создатель выпустил. Ограничивайте сбор этими базовыми публичными данными и избегайте всего, что идентифицирует частное лицо сверх того, что страница открыто показывает.

Почему обычный запрос возвращает неполные данные с Product Hunt?

Потому что Product Hunt рендерит листинги на стороне клиента с помощью JavaScript и следит за автоматизированным трафиком. Обычный HTTP-запрос с IP-адреса дата-центра обычно возвращает почти пустой каркас вместо карточек продуктов. Для получения полной страницы необходимо отрендерить её за доверенным IP, что и делает Crawling API при использовании JavaScript-токена.

Нужен ли JavaScript-токен для Product Hunt?

Да. Product Hunt строит страницы категорий и профилей в браузере, поэтому вы запрашиваете их с включённым рендерингом JavaScript, что в официальном Node-клиенте означает инициализацию Crawling API с вашим JavaScript-токеном. Запросы с рендерингом стоят больше кредитов, чем обычные, но бесплатный тариф всё равно позволяет протестировать полный поток до масштабирования.

Мои селекторы возвращают пустые значения. Что изменилось?

Почти наверняка разметка Product Hunt. Сгенерированные имена классов, длинные цепочки fontSize-* и хешированные классы styles_*, меняются без предупреждения, поэтому работавшие в прошлом месяце селекторы могут сломаться. Повторно проверьте живую страницу в инструментах разработчика браузера и обновите селекторы. Периодическое обслуживание является нормой для любого промышленного скрапера.

Есть ли официальный API Product Hunt?

Да. Product Hunt предоставляет официальный API, использующий аутентификацию с OAuth2-токеном и применяющий ограничения скорости, причём коммерческое использование по умолчанию ограничено, поэтому вы запрашиваете разрешение перед использованием в деловых целях. Когда вам нужны большие объёмы, гарантированная структура или коммерческие права, официальный API является правильным путём, а не парсинг.

Как избежать блокировок при парсинге Product Hunt?

Держите скорость запросов на каждый IP низкой, добавляйте задержки между загрузками страниц и маршрутизируйте через ротирующиеся жилые IP, чтобы ни один адрес не превысил лимит. Crawling API управляет ротацией и доверенным пулом IP за вас; если вы собираете собственную инфраструктуру, именно в это стоит инвестировать. Следите за кодами статусов и снижайте нагрузку, когда начинают появляться CAPTCHA.

Начать создавать

Обходите любой сайт в масштабе, без борьбы с инфраструктурой.

Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.

Самообслуживание · Звонок отдела продаж не требуется · Доступны корпоративные объёмы краулинга