Start free

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. 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. 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. 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

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.

Scraping Product Hunt: FAQ

Why do I get empty fields when I scrape a Product Hunt launch with a simple HTTP request?
Because producthunt.com is a JavaScript-rendered single-page app built on Next.js. A plain HTTP fetch only receives the initial shell, and the upvote count, topics, maker and tagline are populated by client-side hydration afterward. Extracto runs every request through a real headless browser, waits for the page to finish rendering, then maps the visible content to your schema, so those fields arrive populated instead of null.
Can I get the exact upvote number for a launch?
Yes, Extracto reads the rendered vote button after the page hydrates and returns the count as a typed number. Keep in mind upvotes change in real time on launch day, so the value is a snapshot at request time. Store the timestamp of each request if you are tracking a launch over its first 24 hours, because the same URL will legitimately return a higher number minutes later as votes come in.
How should I handle Product Hunt's rate limiting when scraping many launches?
Send one URL per call, keep concurrency low, and pace your requests so you respect the one-second crawl-delay implied by the robots.txt. Extracto's managed infrastructure handles rendering and the anti-bot layer for you, but you still control volume. For large or recurring pulls, Product Hunt's official GraphQL API is the sanctioned high-volume path; reserve page extraction for ad-hoc or supplementary fields.
Do I need to write CSS selectors for the hashed class names on Product Hunt?
No. Product Hunt uses CSS-module class names like styles_title__9aF2x that are regenerated on every front-end build, so any selector you write breaks at the next deploy. With Extracto you describe the fields you want in a JSON schema (product_name, upvotes, topics and so on) and the API returns typed, validated JSON. There are no selectors to maintain, and any field it cannot find comes back as null rather than a guessed value.
What happens to fields that are missing on a given launch, like a launch with no topics?
Extracto validates the output against your JSON schema and returns missing fields as null rather than inventing a value. A launch with no topics yields an empty array or null for that field, and a newly submitted launch with zero upvotes returns 0, so you can tell the difference between absent data and a real zero instead of getting a hallucinated guess.