How to scrape Product Hunt
Extract Product Hunt launch data (name, tagline, upvotes, topics, maker, launch date) as typed JSON from any post URL, no CSS selectors.
- Site:
- producthunt.com
- Difficulty:
- Medium
- Rendering:
- Single-page app
- Anti-bot:
- rate limiting on rapid sequential requests, Cloudflare edge protection on some routes, client-side hydration that hides data from naive HTML fetches
What you can extract from Product Hunt
These are the fields teams most often pull from producthunt.com, and why a hand-written selector for each one tends to break over time.
| Field | Type | Why selectors break |
|---|---|---|
| product name | string | The launch title sits inside a hydrated React component whose wrapper class is a hashed CSS-module token like styles_title__9aF2x that is regenerated on every front-end build, so a class-based selector silently returns nothing after the next deploy. |
| tagline | string | The 60-character tagline is rendered next to the name in the same hashed-class header block and is sometimes split across a name span and a separate tagline span, so a positional CSS selector grabs the wrong half or concatenates both depending on viewport. |
| upvotes | number | The upvote count lives in the vote button, which is injected and updated client-side after hydration, so a raw-HTML CSS selector reads an empty or stale aria-label, and the number is wrapped in markup that mixes the count with the word Upvote. |
| topics | array | Topics are a variable-length list of pill links under hashed wrapper classes; a CSS selector cannot know how many pills exist and breaks when Product Hunt renders zero, one or six tags, and the same class is reused for unrelated related-launch chips. |
| maker | string | The maker name is a link into the /@username space, but that profile path is disallowed in robots and the avatar/name block shares its hashed class with commenters and upvoters, so a selector cannot reliably distinguish the actual maker from other people shown on the page. |
| launch date | string | The launch date is often shown as relative text (Launched 8 months ago) in the visible DOM while the absolute date hides in a title attribute or JSON-LD, so a text selector captures an unusable relative string instead of an ISO date. |
The schema-first approach
Instead of maintaining selectors, you hand Extracto a JSON schema describing the shape you want. Here is a schema that works for Product Hunt:
{
"product_name": "string",
"tagline": "string",
"upvotes": "number",
"topics": "array",
"maker": "string",
"launch_date": "string"
} - 1
Describe the Product Hunt fields you want
Write a small JSON schema listing the fields to pull from producthunt.com (for example product name, tagline, upvotes). No CSS selectors or XPath needed.
- 2
Send the URL and schema to Extracto
POST the Product Hunt page URL and your schema to the Extracto API. Extracto renders the page in a real browser, handling JavaScript and dynamic content automatically.
- 3
Get schema-validated JSON back
Extracto returns JSON that matches your schema exactly, validated before it leaves the API, so your pipeline never receives broken or partial data.
Common pitfalls when scraping Product Hunt
- Product Hunt is a Next.js single-page app, so fetching the raw HTML with a plain HTTP client returns the shell before hydration and the upvote count, topics and maker block are missing; you need a real headless browser that runs the page's JavaScript before reading the values.
- Upvote counts are live and tick up during a launch day, so two requests minutes apart for the same launch can legitimately return different numbers; capture a timestamp alongside the count rather than treating it as a fixed attribute.
- Firing many launch URLs back to back trips Product Hunt's rate limiting and you start getting throttled or challenged responses; space requests out, stay under a modest concurrency, and honor the one-second crawl-delay implied by robots.
- The visible launch date is usually relative (Launched 5 months ago) while the precise date sits in structured metadata, so normalize to an absolute ISO date at extraction time instead of storing the relative phrase.
- Maker profiles live under the /@username path that robots.txt disallows, so resolve the maker name from the launch page itself and do not follow links into the profile space to enrich it.
Is it legal to scrape Product Hunt?
Product Hunt's robots.txt (as of 2026) leaves individual launch and product pages crawlable while explicitly disallowing /search, /auth, /my, /notifications and user profile paths such as /@username, and it sets a one-second crawl-delay for named audit bots. Treat that as the contract: only request public launch URLs (the /products and /posts space surfaced through the public XML sitemaps), keep request volume modest, and never touch the disallowed account or search routes. Product Hunt also exposes an official GraphQL API with documented terms, so for high-volume programmatic access the API is the sanctioned path. The site's Terms of Service prohibit reselling or redistributing Product Hunt content wholesale and prohibit scraping that degrades the service. Scrape only the fields you genuinely need, attribute the source, respect makers' and voters' data, and do not reconstruct a competing directory from the harvested data.
Extracto follows robots.txt by default and rate-limits politely. You are responsible for compliance with Product Hunt's terms and applicable law (GDPR, CCPA) in your use case.