product-hunt-scraper-api
v0.1.0
Published
Product Hunt scraper API for Node. Pull ranked launches, names, slugs and URLs from Product Hunt as structured data.
Maintainers
Readme
product-hunt-scraper-api
Node client for scraping Product Hunt launches, built on ScrapingBee's Product Hunt scraper. Returns the ranked board as plain objects. No dependencies, Node 18+.
Run against the live site on 2026-08-25: 70 products back, 1 credit charged.
npm install product-hunt-scraper-apiGrab today's board
const { ProductHuntScraper } = require("product-hunt-scraper-api");
const scraper = new ProductHuntScraper(process.env.SCRAPINGBEE_API_KEY);
const products = await scraper.leaderboard();
products.slice(0, 5).forEach(p => console.log(p.rank, p.name, p.url));Every entry is { rank, name, slug, url }. The rank arrives glued to the name in the markup, as
"1. akta.pro", and gets split apart before it reaches you.
Pay 1 credit, not 25
Listing pages on Product Hunt render their links server-side, so a headless browser is wasted
spend. Instead of hardcoding that assumption, every call goes out with mode=auto: ScrapingBee
walks its configurations cheapest-first and bills only the one that worked.
const { products, cost } = await scraper.leaderboardWithCost();
console.log(`${products.length} products for ${cost} credit(s)`);
// 70 products for 1 credit(s)The upside is that a page which later starts fighting back gets handled without a code change. Auto-Mode simply climbs, up to whatever ceiling you allow:
await scraper.leaderboard("https://www.producthunt.com/", 75);Topics and other listings
const devTools = await scraper.topic("developer-tools");Any listing URL is fair game, since categories, topics and date archives share one structure.
Detecting rank changes
The board reshuffles through the day. At 1 credit a poll, checking every half hour costs under 50 credits daily, so the interesting part is diffing rather than budgeting:
const seen = new Map();
setInterval(async () => {
for (const p of await scraper.leaderboard()) {
const before = seen.get(p.slug);
if (before && before !== p.rank) console.log(`${p.name}: #${before} -> #${p.rank}`);
seen.set(p.slug, p.rank);
}
}, 30 * 60 * 1000);Going deeper than four fields
const html = await scraper.pageHtml("https://www.producthunt.com/products/akta-pro");That returns the page for your own parsing. Add { render_js: true } when a page genuinely needs
a browser, remembering it lifts the floor from 1 credit to 5.
Credits
| Call | Credits | | --- | --- | | Board via Auto-Mode | 1 observed | | Forced JavaScript rendering | 5 | | Premium proxy plus rendering | 25 | | HTTP 500 | 0 |
console.log(await scraper.usage());Free, six a minute. Plans.
Nearby tools
Google search and Google News cover the coverage side of a launch, and AI extraction is the escape hatch when a layout shifts and you would rather describe fields than fix selectors.
Notes
Public pages only; scraping behind a login is prohibited by the ScrapingBee terms. Keep your key out of AI coding assistants.
MIT licence. Source at ScrapingBee/product-hunt-scraper-api, selector syntax under data extraction.
