# Trustpilot Reviews Scraper - Verified Filters & Replies (`diopside/trustpilot-reviews`) Actor

Trustpilot profiles and reviews: ratings, full text, company replies, verification labels, reviewer country and language. Clears the AWS WAF challenge once per run with a real browser. Star/language/date filters are verified against what Trustpilot returned, so a dropped filter fails loudly.

- **URL**: https://apify.com/diopside/trustpilot-reviews.md
- **Developed by:** [DIOPSIDE AI](https://apify.com/diopside) (community)
- **Categories:** Business, Marketing, Agents
- **Stats:** 2 total users, 1 monthly users, 87.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 review records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Trustpilot Reviews Scraper

Company profiles and customer reviews from [Trustpilot](https://www.trustpilot.com) — ratings, full
review text, company replies, verification labels, reviewer country and language — from any company
profile, with star / language / date filters that are **checked, not assumed**.

Public profile pages only. No login, no personal contact data.

### Why this one

Trustpilot changed twice in ways that quietly break most scrapers in this category. This actor is
built around both.

**1. Every page is behind an AWS WAF JavaScript challenge.** Since September 2026 trustpilot.com
answers `403` with a "Verifying your connection" interstitial to any plain HTTP client. A Chrome TLS
fingerprint is not enough — `curl_cffi` impersonating Chrome, Chrome 124 and Safari 17 all get the
same 403. This actor launches a real browser **once per run**, clears the challenge in ~2 seconds,
and then reuses the resulting `aws-waf-token` for ordinary HTTP requests (the token stays valid for
days). You pay for one browser launch, not one per page — a 400-review run finishes in about 20
seconds.

**2. `?page=1` silently returns the wrong reviews.** Trustpilot serves the canonical profile page
from a CDN cache whenever `page=1` appears in the query string, and that cached page **ignores every
filter you asked for** — while still answering `200 OK`. Request 1-star reviews with `page=1` and you
get back the default mix of 4- and 5-star reviews, with nothing in the response to tell you the
filter was dropped. This actor never emits `page=1`, and after every request it re-reads the filter
state Trustpilot echoes back. If a filter did not survive the round trip, the run fails loudly
instead of writing wrong data into your dataset.

Two more things that come out of that:

- **Non-English reviews are included by default.** Trustpilot's own pages default to English only —
  on a profile with 741 reviews, 51 of them are invisible unless you override the language. This
  actor defaults to `language: "all"`.
- **Coverage past 200 reviews.** Trustpilot's public pagination stops at page 10 (200 reviews per
  filter combination); page 11 redirects to a login wall. Rather than pretend otherwise, the actor
  re-queries the company one star rating at a time — each rating has its own page window — and
  de-duplicates by review ID, reaching ~1,000 reviews per company without logging in. When a company
  still has more reviews than the public site will serve, the run says so in its status message
  rather than silently returning short.

#### vs other Trustpilot scrapers

The category leader, `trustpilot-scraper-ppe` (706 monthly users), is reliable (4% failed runs) —
we're not trying to underprice a healthy leader. A smaller alternative,
`trustpilot-review-scraper`, failed on **41%** of its last 4,754 runs, which lines up with the
WAF challenge and the `page=1` filter bug above: both are easy to get *mostly* working and easy
to get subtly wrong.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `companyDomains` | array of strings | `["apify.com"]` | Company domains as they appear on Trustpilot. |
| `startUrls` | array | `[]` | Trustpilot profile URLs; merged with `companyDomains`. |
| `maxReviewsPerCompany` | integer | `200` | Reviews to return per company. |
| `stars` | array of `"1"`–`"5"` | `[]` (all) | Only return these star ratings. |
| `language` | string | `"all"` | ISO review language, e.g. `en`, `de`. `all` returns every language. |
| `dateRange` | enum | `"any"` | `last30days`, `last3months`, `last6months`, `last12months`. |
| `sortBy` | enum | `"recency"` | `recency` or `relevance`. |
| `verifiedOnly` | boolean | `false` | Only reviews Trustpilot marks verified. |
| `repliesOnly` | boolean | `false` | Only reviews the company replied to. |
| `includeCompanyProfile` | boolean | `true` | Also emit one company record per profile. |
| `deepCoverage` | boolean | `true` | Slice by star rating to get past the 200-review public limit. |
| `proxyConfiguration` | object | Apify proxy | Escalate to `RESIDENTIAL` if challenges start failing. |

```json
{
  "companyDomains": ["apify.com"],
  "maxReviewsPerCompany": 200,
  "stars": ["1", "2"],
  "language": "all",
  "dateRange": "last12months"
}
```

### Output

One record per review (`record_type: "review"`), plus one per company (`record_type: "company"`)
when `includeCompanyProfile` is on.

```json
{
  "record_type": "review",
  "review_id": "6aad08bdde1d5fc31500cfe1",
  "review_url": "https://www.trustpilot.com/reviews/6aad08bdde1d5fc31500cfe1",
  "company_domain": "apify.com",
  "company_name": "Apify",
  "rating": 5,
  "title": "I use Apify a lot and I do not regret it at all",
  "text": "I use Apify actors to scrape trending topics for my blog...",
  "language": "en",
  "experienced_date": "2026-08-02T00:00:00.000Z",
  "published_date": "2026-09-18T11:47:41.000Z",
  "likes": 0,
  "source": "BasicLink",
  "is_verified": false,
  "verification_level": "invited",
  "consumer_name": "Righteousness",
  "consumer_country": "GB",
  "consumer_review_count": 1,
  "reply_text": null,
  "reply_published_date": null,
  "scraped_at": "2026-09-18T12:20:00+00:00"
}
```

The company record carries `trust_score`, `number_of_reviews`,
`number_of_reviews_last_12_months`, `categories`, `country_code`, `is_claimed`,
`has_incentivised_reviews`, `consumer_alert` and the contact details the business publishes on its
own profile.

### Sizes and run time

| Input | Records | Time |
| --- | --- | --- |
| 1 company, 60 reviews | 61 | ~10 s |
| 1 company, 200 reviews (one page window) | 201 | ~15 s |
| 1 company, 400 reviews (star-sliced) | 401 | ~20 s |
| 10 companies × 200 reviews | ~2,010 | ~3 min |

One browser launch per run (~2 s), then ~0.9 s per 20-review page.

### Use cases

- Track TrustScore and review volume for your own brand and your competitors over time.
- Pull every 1- and 2-star review for a company and cluster the complaints.
- Monitor whether a business replies to negative reviews, and how fast.
- Compare sentiment across languages and countries for a multi-market brand.
- Feed review text into a summarisation or topic model.

### Limits, stated plainly

- Trustpilot's public pages stop at **200 reviews per filter combination**; `deepCoverage` slices by
  star rating to reach roughly 1,000 per company. Past that there is no public route, and this actor
  will not log in to fake one. Runs that hit the ceiling say so in the status message.
- `relevance` sorting is Trustpilot's own ranking and is not stable between runs.
- Reviewer display names and countries are whatever the reviewer chose to publish; no email,
  phone or other private data is collected.

# Actor input Schema

## `companyDomains` (type: `array`):

Company domains as they appear on Trustpilot, e.g. "apify.com". One profile per entry.

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

Trustpilot profile URLs, e.g. https://www.trustpilot.com/review/apify.com. Merged with Company domains; filters below still apply.

## `maxReviewsPerCompany` (type: `integer`):

Reviews to return per company. Trustpilot's public pages stop at 200 per filter combination; above that the actor slices by star rating to keep going (see Deep coverage).

## `stars` (type: `array`):

Only return reviews with these ratings. Empty = all ratings.

## `language` (type: `string`):

ISO code of the review language, e.g. en, de, fr. Defaults to "all" — note Trustpilot's own pages default to English only, so "all" returns reviews other scrapers miss.

## `dateRange` (type: `string`):

Only return reviews published within this window.

## `sortBy` (type: `string`):

Order reviews by most recent or by Trustpilot's relevance ranking.

## `verifiedOnly` (type: `boolean`):

Only reviews Trustpilot marks as verified.

## `repliesOnly` (type: `boolean`):

Only reviews the company has replied to.

## `includeCompanyProfile` (type: `boolean`):

Push one extra record per company with TrustScore, review counts, categories and contact details.

## `deepCoverage` (type: `boolean`):

When more reviews are wanted than Trustpilot's 200-per-filter public limit allows, re-query the company one star rating at a time — each rating has its own page window — and de-duplicate. Turn off to stop at 200.

## `proxyConfiguration` (type: `object`):

Trustpilot challenges datacenter IPs unevenly; the default Apify proxy is recommended. Escalate to RESIDENTIAL if runs start failing the challenge.

## Actor input object example

```json
{
  "companyDomains": [
    "apify.com"
  ],
  "startUrls": [],
  "maxReviewsPerCompany": 200,
  "stars": [],
  "language": "all",
  "dateRange": "any",
  "sortBy": "recency",
  "verifiedOnly": false,
  "repliesOnly": false,
  "includeCompanyProfile": true,
  "deepCoverage": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `reviews` (type: `string`):

All review and company records. Append ?format=csv for CSV.

## `datasetUrl` (type: `string`):

The default dataset.

# 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 = {
    "companyDomains": [
        "apify.com"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("diopside/trustpilot-reviews").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 = {
    "companyDomains": ["apify.com"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("diopside/trustpilot-reviews").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 '{
  "companyDomains": [
    "apify.com"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call diopside/trustpilot-reviews --silent --output-dataset

```

## MCP server setup

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

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/IEdvWVa9eTedManBy/builds/3lFhmGaFzq7Nc29Yp/openapi.json
