# Product Hunt Reviews Scraper - Ratings & Pros Cons (`eiv/producthunt-reviews-scraper`) Actor

Product Hunt reviews scraper that returns full review text, five separate rating dimensions, and the AI pro/con tags Product Hunt generates itself. Includes why each reviewer chose the product over alternatives. No login, no API token, no browser.

- **URL**: https://apify.com/eiv/producthunt-reviews-scraper.md
- **Developed by:** [Eimantas V](https://apify.com/eiv) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 review scrapes

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Product Hunt Reviews Scraper — Ratings & Pros/Cons

Product Hunt reviews scraper that returns the **full review text**, **five
separate rating dimensions**, and the **AI pro/con tags Product Hunt generates
itself**. No login, no API token, no browser.

Software review data is what competitive-intelligence buyers actually pay for,
and Product Hunt's review corpus is the one nobody is extracting: every existing
Product Hunt Actor scrapes launches, leaderboards and maker emails. This one
scrapes the reviews.

### What you get

One row per review. Fill rates below are **measured over 260 real reviews from
5 products** — not estimated, and not rounded up.

| Field | Type | Fill | Notes |
|---|---|---|---|
| `overallRating` | number | null | **100%** | 1–5 |
| `createdAt` | string | null | **100%** | A real UTC instant, normalised from PH's offset |
| `reviewerName` / `reviewerUsername` | string | null | **100%** | |
| `reviewerUrl` / `reviewerHeadline` | string | null | | |
| `overallExperience` | string | null | **62%** | The quick-review paragraph, median 359 chars |
| `selectedPros` / `selectedCons` | array | **53%** | Per-review AI tags with a polarity |
| `alternativesFeedback` | string | null | **32%** | **Why they chose this over competitors** |
| `easeOfUseRating` | number | null | **23%** | |
| `reliabilityRating` | number | null | **23%** | |
| `valueForMoneyRating` | number | null | **23%** | |
| `customizationRating` | number | null | **23%** | |
| `positiveFeedback` | string | null | **22%** | What the reviewer liked |
| `negativeFeedback` | string | null | **16%** | What they didn't |
| `productReviewsCount` | integer | null | 100% | PH's own total, so you can see what you missed |
| `productReviewsRating` | number | null | 100% | Aggregate, 2dp |
| `commentsCount` / `votesCount` | integer | null | 100% | |
| `reviewType` / `status` | string | null | 100% | `personal` or `founder` |

#### Two review formats, and you can pick

Product Hunt collects reviews in two shapes, and the difference matters enough
that it is an input option rather than a footnote:

- A **quick review** carries an overall rating and one written paragraph
  (`overallExperience`). Measured at **62%** of reviews.
- A **structured review** adds four more ratings — ease of use, reliability,
  value for money, customization — and splits the prose into what the reviewer
  liked, what they didn't, and why they chose this over alternatives. Measured
  at **22%**.

Every review carries an overall rating, and over 260 rows **100% carried some
written text**. Set `reviewDetail` to `structured` when you need the rating
breakdown; rows that do not match are never written and never charged, and the
coverage row tells you how many were examined and how many were dropped.

Founder reviews are worth knowing about too: on the products measured they were
101 of 260 rows, and they carry the structured form far less often (6% against
33% for user reviews). Use `reviewTypes: ["personal"]` to exclude them.

#### Five rating dimensions where most sites give one star

When a review is structured, you get ease of use, reliability, value for money
and customization as separate scores alongside the overall. That is the
difference between "users rate it 4.2" and "users rate it 4.8 on reliability and
3.1 on value".

#### `alternativesFeedback` has no equivalent elsewhere

Product Hunt asks reviewers why they picked this product **over the ones they
considered instead**. It is the field a competitive-intelligence buyer would
otherwise have to infer, and it comes back as prose.

#### Pro/con tags, already extracted

Product Hunt runs its own model over its reviews and attaches pro/con tags with
a polarity — per review, and aggregated per product. A scraper pointed at other
review sites has to synthesise that downstream with an LLM, at a per-row cost.
Here it ships in the payload.

#### A per-product coverage row

Each product also gets one summary row, and it is **never charged**: how many
reviews were collected against the total Product Hunt reports, the aggregate
rating, the product-level pro/con tags, pages read, and why the run stopped.
That row is what lets you tell *"this product has 12 reviews"* from *"we only
read the first page"* — a distinction an empty dataset cannot make.

### Input

```json
{
  "products": [
    "slack",
    "https://www.producthunt.com/products/notion",
    "https://www.producthunt.com/products/linear"
  ],
  "maxReviewsPerProduct": 200,
  "maxTotalReviews": 5000
}
```

Product URLs or bare slugs. You can also chain from another Actor's dataset with
`sourceDatasetId`.

**A launch URL (`/posts/<slug>`) is rejected with a message**, not guessed at.
Launches and products are separate namespaces on Product Hunt and their slugs do
not line up, so coercing one into the other would scrape the wrong thing — or
nothing, which is worse because you could not tell.

### Pricing

| Event | Price | When |
|---|---|---|
| `review-scraped` (primary) | **$1.50–$3.00 per 1,000** | Per review row written |
| `product-processed` | $0.002 | Per product coverage row |
| `apify-actor-start` | $0.00005 | Once per run |

**You are never billed twice for the same review.** Product Hunt repeats a fixed
block of top reviews on every page — measured, page two returned 18 review
objects of which only 8 were new — and this Actor deduplicates by review id, so
only genuinely new reviews are charged.

Also never charged: coverage rows, error rows of any kind, and requests that
came back challenged and were retried.

### What this does NOT do

- **No product discovery.** You supply the products. Pair this with a discovery
  Actor if you need to find them first.
- **No launches, makers, upvotes or hunter data.** Those are a different corpus
  and several Actors already cover them well.
- **No comment threads under reviews.** The counts are returned; the thread text
  is not.
- **No G2 or Capterra.** Different sites, different shapes.
- **Reviewer profile pages are not crawled** — Product Hunt's `robots.txt`
  disallows `/@*/*`, so this Actor takes the reviewer fields that come embedded
  in the review itself and goes no further.

### How it works, and the honest part

One plain HTTP GET per page of reviews. Product Hunt server-renders its review
data into Apollo SSR transport scripts, so there is no browser and no API token.
The public GraphQL API at `api.producthunt.com` is deliberately **not** used: it
requires an OAuth developer token, which is exactly the kind of credential that
expires and takes an Actor down with it. The HTML path needs no credential at
all.

#### This Actor requires the Apify Unblocker proxy

That is measured, not cautious. From an Apify run:

| proxy | result |
|---|---|
| Datacenter | **0 of 15 usable** — HTTP 403, `cf-mitigated: challenge` |
| Residential | 0 of 3 usable — same challenge |
| **Unblocker** | **HTTP 200, full payload** |

Cloudflare here checks both the TLS fingerprint and the IP reputation, and only
Unblocker satisfies both. Leave the proxy setting at its default. Unblocker is
billed per request rather than per gigabyte, which is why this is affordable.

#### Reliability

- **Deleted or non-existent products are distinguishable from blocks.** A
  missing product returns HTTP 404 with a Next.js error document and no
  `reviewsCount` key; a block returns a Cloudflare challenge; a product with
  genuinely zero reviews returns HTTP 200 **with** `"reviewsCount":0`. That key
  is the positive control — no block page and no 404 carries it — so "no reviews
  yet" is never confused with "we were stopped".
- **The response gate never keys on size.** The 404 page measured 113 KB against
  a real page's 1.05 MB, but a small real page is still valid, so only shape is
  used.
- **Pagination terminates on zero new review ids**, not on a page returning no
  rows — because pages keep returning rows long after the new ones run out.
- **Challenged requests are retried on a fresh IP**, twice, with a hard backoff.
  A product-not-found is never retried: the answer is the same everywhere.

#### robots.txt

`https://www.producthunt.com/robots.txt` permits the paths this Actor uses. The
reviews path is not disallowed. Relevant lines it **does** disallow, none of
which this Actor touches: `/auth/*`, `/search*`, `/my/*`, `/yours*`,
`/notifications`, `/r/*`, `/@*/*` (reviewer profile pages, which is why this
Actor does not crawl them) and the `?review=` single-review deep-link parameter,
which is a query parameter rather than the `/reviews` path segment.

This Actor reads publicly visible reviews, sends no credentials, and bypasses no
authentication or access control. Whether that suits your purpose is a decision
for you and your legal advisers.

# Actor input Schema

## `products` (type: `array`):

Product URLs (`producthunt.com/products/<slug>`) or bare slugs. A LAUNCH url (`/posts/<slug>`) is rejected with a message rather than guessed at: launches and products are separate namespaces on Product Hunt and their slugs do not line up, so coercing one into the other would scrape the wrong thing or nothing at all.

## `startUrls` (type: `array`):

The same products in the shape other Actors and the Apify UI hand over. Merged with the list above and deduplicated by slug.

## `sourceDatasetId` (type: `string`):

Read product URLs out of another run's dataset, for chaining after a discovery Actor.

## `sourceDatasetField` (type: `string`):

Which field in that dataset holds the product URL or slug. Defaults to `url`.

## `maxReviewsPerProduct` (type: `integer`):

Bounds the **output**. Product Hunt repeats a fixed block of top reviews on every page, so this Actor deduplicates by review id and counts only genuinely new ones — measured, page 2 returned 18 review objects of which only 8 were new.

## `maxPagesPerProduct` (type: `integer`):

Bounds the **work**, where the cap above bounds the output. Needed because the repeated top-review block means pages keep returning rows long after the new ones run out; the run also stops on its own when a page yields no new review ids.

## `reviewDetail` (type: `string`):

Product Hunt has two review formats. A QUICK review carries an overall rating and one written paragraph — measured at 62% of reviews. A STRUCTURED review adds four more ratings (ease of use, reliability, value for money, customization) and splits the prose into what the reviewer liked, what they didn't, and why they chose the product over alternatives — measured at 22%. Every review carries an overall rating and, measured over 260 rows, 100% carry some written text. Choose `structured` when you need the rating breakdown; filtered rows are never written and never charged, and the coverage row reports how many were examined and dropped.

## `reviewTypes` (type: `array`):

Limit to `personal` (from users) or `founder` (from the product's own founders). Leave empty for both. Founder reviews are a large share of some products — on one measured product they were 567 of 983 — and they carry written feedback far less often, so excluding them raises the density of useful rows.

## `maxTotalReviews` (type: `integer`):

A ceiling across every product. Reached mid-product, the run stops cleanly and the summary rows say so.

## `requestDelayMs` (type: `integer`):

Applied across the whole run rather than per worker, so this is the real interval between requests whatever the concurrency.

## `maxConcurrency` (type: `integer`):

How many products are scraped at once. Pages within a product are sequential because each page's new-review check depends on the ones before it.

## `proxyConfig` (type: `object`):

Leave this at the default. Product Hunt is behind Cloudflare and challenges every plain datacenter IP — measured 0 usable responses out of 15 — so this Actor uses the Apify UNBLOCKER group, which clears the challenge and is billed per request rather than per gigabyte.

## Actor input object example

```json
{
  "products": [
    "slack",
    "https://www.producthunt.com/products/notion"
  ],
  "sourceDatasetId": "aBcDeFgHiJkLmNoPq",
  "sourceDatasetField": "url",
  "maxReviewsPerProduct": 200,
  "maxPagesPerProduct": 40,
  "reviewDetail": "any",
  "reviewTypes": [
    "personal"
  ],
  "maxTotalReviews": 5000,
  "requestDelayMs": 1000,
  "maxConcurrency": 3,
  "proxyConfig": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row per review with five rating dimensions, the full prose and per-review pro/con tags. Plus one coverage row per product saying how many reviews were collected against the total Product Hunt reports, and why the run stopped.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "products": [
        "slack",
        "https://www.producthunt.com/products/notion"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("eiv/producthunt-reviews-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "products": [
        "slack",
        "https://www.producthunt.com/products/notion",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("eiv/producthunt-reviews-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "products": [
    "slack",
    "https://www.producthunt.com/products/notion"
  ]
}' |
apify call eiv/producthunt-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,eiv/producthunt-reviews-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/qVGolSgBW8GeONtwn/builds/yeQK8ImINHgdNryhe/openapi.json
