# Trustpilot Reviews Scraper (`rastriq/trustpilot-reviews-scraper`) Actor

Public Trustpilot reviews of any company by domain: rating, title, text, language, date, useful votes and the company reply. Reviewer data hashed, emails and phones redacted.

- **URL**: https://apify.com/rastriq/trustpilot-reviews-scraper.md
- **Developed by:** [Rastriq — Structured data from the world](https://apify.com/rastriq) (community)
- **Features:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

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

**Trustpilot Reviews Scraper** extracts the public reviews of any company on Trustpilot by domain: star rating, title, text, language, publication date, useful votes and the **company reply** when there is one. Give it domains such as `booking.com` or Trustpilot profile URLs and get clean, deduplicated JSON, CSV or Excel. It is a Trustpilot reviews API alternative for reputation monitoring, customer-experience analytics, competitor tracking and sentiment analysis, and you need no Trustpilot account.

Reviewer names are included and can be hidden with one switch for GDPR (`hideAuthorNames`). A keyed `author_hash` is always output; avatars, profile URLs and raw reviewer ids never are. Company replies are the company's own text, as written. Emails and phone numbers typed into a review are redacted.

### What is Trustpilot Reviews Scraper?

It has two routes, chosen with the `mode` input:

- **Fast**: Trustpilot's public widget data (`widget.trustpilot.com/trustbox-data/`, which that host's `robots.txt` allows). No browser and, normally, no proxy. It returns the **newest ~100 reviews per star rating and language**, without company replies, useful votes or reviewer country. Trustpilot only serves this widget data for companies on a paid Trustpilot plan; for every other company the Actor switches to the deep route automatically and remembers it for 30 days.
- **Deep**: the full profile pages, described below. Every field and up to 2000 reviews per company and language, but slower, and it uses a residential proxy.
- **Auto** (default): the widget first; the deep route only when you ask for more reviews than the widget gives, or for fields it lacks (`withRepliesOnly`, `reviewerCountries`).

Widget rows are marked `ext_route = "widget"`; profile-page rows `ext_route = "browser"`. In widget rows `developer_reply`, `developer_reply_at`, `helpful_count`, `country`, `ext_updated_at`, `ext_business_categories`, `ext_business_is_claimed` and `ext_business_review_count_12m` are `null` (the widget does not carry them) and `author_hash` is `null` (it has no stable reviewer id); `author_name` is still the published display name. The first time a company is scraped, the Actor loads one profile page to learn its Trustpilot id; it is then cached in the named key-value store `rastriq-trustpilot-bu-ids` of your account.

The deep route: Trustpilot protects its pages with a JavaScript connection check. This Actor opens each public company page in a real headless browser, lets the page finish its own check, and reads the review data the page ships with. It opens the first page of each company in the browser and reads the next pages over a lighter HTTP connection in the same session, which keeps runs fast and cheap. It never solves CAPTCHAs and only requests the two page forms Trustpilot's `robots.txt` allows: `/review/<domain>` and `/review/<domain>?page=N`.

It shares one output contract with [App Store Reviews Scraper](https://apify.com/rastriq/app-store-reviews-scraper) and [Google Play Reviews Scraper](https://apify.com/rastriq/google-play-reviews-scraper), so you can ingest all three sources with a single integration.

### What data does Trustpilot Reviews Scraper extract?

| Field | What it is |
|---|---|
| ⭐ `rating` | Stars, 1 to 5 |
| 📝 `title`, `text` | Review title and body (emails and phones redacted) |
| 🕒 `published_at` | Publication time, ISO 8601, UTC |
| 💬 `developer_reply`, `developer_reply_at` | The company's reply, exactly as written, and when it was posted, or `null` |
| 👍 `helpful_count` | Useful votes |
| 🌍 `lang`, `country` | Review language and the **reviewer's** country (`null` when Trustpilot shows none). It is not a filter |
| 🏷️ `app_id`, `app_name` | Company domain as Trustpilot knows it and company display name |
| 🔑 `review_id` | Durable review id, used to deduplicate |
| 👤 `author_name` | Reviewer display name. `null` if you enable *Hide author names* |
| 🔒 `author_hash` | Keyed HMAC-SHA256 pseudonym of the reviewer id. Stable across runs while the salt is unchanged |
| 🔗 `source_url`, `scraped_at` | The Trustpilot page that was read, and extraction time (UTC) |
| ➕ `ext_experienced_at`, `ext_review_verified`, `ext_review_source`, `ext_updated_at` | Date of the experience, verified label, how the review was collected, last update |
| 🏢 `ext_business_trust_score`, `ext_business_stars`, `ext_business_review_count`, `ext_business_review_count_12m` | The company's TrustScore, stars, total reviews and reviews of the last 12 months when the page was read |
| 🏬 `ext_business_id`, `ext_business_website`, `ext_business_country`, `ext_business_categories`, `ext_business_is_claimed` | Company profile data: Trustpilot id, website, country, category names and whether the profile is claimed (`null` when Trustpilot gives none) |
| ✅ `ext_review_verification_level` | Trustpilot's verification level of the review (for example `not-verified`) |
| 🌐 `ext_language_requested` | The `languages` code this row was collected under |
| 🛣️ `ext_route` | `widget` (fast route) or `browser` (full profile pages) |
| 🚩 `_quality_*` | Flags for missing text, rating or date. Flagged, never filtered |

`app_version` is always `null` (Trustpilot reviews have no version). Fields the source does not provide are `null`, never invented.

### How to scrape Trustpilot reviews with Trustpilot Reviews Scraper

1. Open **Trustpilot Reviews Scraper** on Apify and click **Try for free**.
2. In **Companies**, add domains such as `www.booking.com` or profile URLs such as `https://www.trustpilot.com/review/booking.com`.
3. Set **Max reviews** (per company and language, 20 per page, newest first).
4. Optionally pick **Review languages**, a **Date range** or **Reviews since** (the run stops as soon as a whole page is older) and any of the filters.
5. Click **Start** and download the results as JSON, CSV, Excel or XML, or read them through the API.

#### Input parameters

| Parameter | Type | Description |
|---|---|---|
| `companies` | array, required | Domains or Trustpilot profile URLs. Invalid entries are skipped and listed in the run summary |
| `mode` | string | `auto` (default: widget first, deep only when needed), `fast` (widget only: newest ~100 reviews per star rating and language; an input error if `withRepliesOnly` or `reviewerCountries` is set) or `deep` (full profile pages in a browser) |
| `maxReviews` | integer | Max reviews **delivered per company and per language**. If empty, 100. Hard cap 2000 |
| `languages` | array | Languages to collect, from Trustpilot's localized sites: `all` (every language below, English first), `en`, `de`, `fr`, `es`, `it`, `nl`, `da`, `sv`, `nb` (`no` also accepted), `fi`, `pl`, `pt`, `ja`. If empty, `en`. Unsupported codes are skipped and listed in the run summary |
| `dateRange` | string | `last30days`, `last3months`, `last6months` or `last12months`, counted from today (UTC). Empty = any time. If `dateFrom` is also set, the later date wins |
| `dateFrom` | date `YYYY-MM-DD` | Only reviews published on or after this date (UTC) |
| `stars` | array | Only these star ratings (1 to 5). Empty = all |
| `verifiedOnly` | boolean | Only reviews Trustpilot labels as verified (`ext_review_verified` is true) |
| `withRepliesOnly` | boolean | Only reviews with a company reply (`developer_reply` is not `null`) |
| `keyword` | string, max 100 | Only reviews whose title or text contains it (case-insensitive) |
| `reviewerCountries` | array | Only reviews whose reviewer `country` is one of these ISO 3166 alpha-2 codes (case-insensitive) |
| `incrementalKey` | string `[A-Za-z0-9-]`, max 40 | Only reviews newer than the previous run with the same key. State is kept per company and language in the named key-value store `rastriq-trustpilot-incremental` |
| `hideAuthorNames` | boolean | If on, `author_name` is `null` in every row (GDPR). Default off |

#### Example input

```json
{
  "companies": ["www.booking.com"],
  "maxReviews": 100,
  "languages": ["en", "de"],
  "stars": ["1", "2"],
  "dateRange": "last3months"
}
```

### Output example

```json
{
  "review_id": "000000000000000000000001",
  "store": "trustpilot",
  "app_id": "www.example.com",
  "app_name": "Example",
  "country": "gb",
  "lang": "en",
  "rating": 2,
  "title": "Support never answered",
  "text": "I wrote three times and nobody replied.",
  "author_hash": "3f1c0c5a0a6d6b7e9d2b8f4c1a7e5d3b9c0f2a6e8d4b1c7a5e3f9d0b2c4a6e81",
  "author_name": "Reviewer 1",
  "app_version": null,
  "published_at": "2026-11-09T15:42:15Z",
  "developer_reply": "Thank you for your feedback. Please contact our support team.",
  "developer_reply_at": "2026-11-09T16:30:00Z",
  "helpful_count": 0,
  "source_url": "https://www.trustpilot.com/review/www.example.com",
  "scraped_at": "2026-11-09T18:00:00Z",
  "ext_pii_redactions": 0,
  "ext_sort": null,
  "ext_author_hash_weak": false,
  "ext_experienced_at": "2026-11-09T00:00:00Z",
  "ext_review_verified": false,
  "ext_review_source": "Organic",
  "ext_updated_at": null,
  "ext_business_trust_score": 3.1,
  "ext_business_stars": 3,
  "ext_business_review_count": 1234,
  "ext_business_id": "46d3f1f8000064000500b592",
  "ext_business_website": "https://www.example.com",
  "ext_business_country": "nl",
  "ext_business_categories": ["Travel Agency"],
  "ext_business_is_claimed": true,
  "ext_business_review_count_12m": 321,
  "ext_review_verification_level": "not-verified",
  "ext_language_requested": "en",
  "_quality_missing_text": false,
  "_quality_missing_rating": false,
  "_quality_missing_published_at": false,
  "_quality_missing_app_name": false,
  "_quality_bad_number": false,
  "_quality_pii_redacted": false
}
```

Every run also writes a `SUMMARY` record with, for each company: reviews scanned, emitted and dropped, pages read, browser sessions used, Trustpilot's own total count and the reason it stopped.

### Limits

- **Languages come from Trustpilot's localized sites.** `languages` loads each company from `de.trustpilot.com`, `fr.trustpilot.com` and so on, which show only the reviews written in that language. Each company-language pair is its own group in the summary with its own `maxReviews` quota, and takes its own browser session. If Trustpilot already told us a company has no reviews in a language, that group ends without loading a page. If Trustpilot serves another language than requested, rows are kept and a note is added to the summary.
- **Other filters are applied after download.** `stars`, `verifiedOnly`, `withRepliesOnly`, `keyword` and `reviewerCountries` never change the URL: the Actor reads the pages and keeps the matching reviews. Only delivered reviews are charged, but a strict filter has to read more pages. Each company-language group stops at a scan limit of about five times the pages `maxReviews` would need (at least 10, at most 200 pages) and says so in the summary notes. Filtered-out reviews are counted as `filtered` in the summary, not as losses.
- **Not supported, because `robots.txt` disallows it:** sort by relevance or any `?sort=`, topics, searching inside a company's reviews, Trustpilot's own star/verified/reply filters, company search, and review transparency pages. Reviews are always newest first.
- **Incremental runs** remember, per company and language, the newest review delivered. If a run stops early (for example at `maxReviews`) older reviews it did not reach are not collected by later incremental runs; raise `maxReviews` or run more often. The marker only moves for groups that ended normally.
- **Fast route depth.** The widget gives at most the newest 100 reviews per star rating and language (a request without a `stars` filter and `maxReviews` up to 100 is a single request; otherwise one request per star rating). Reviews older than the oldest review of a full request are not guaranteed complete, so they are not taken from the widget. In **Fast** mode the group then ends as `widget_limit` (not an error) with a note; in **Auto** the Actor continues with the deep route from the right page. A `dateRange`, `dateFrom` or incremental bound newer than that point is fully served by the widget.
- **Access levels.** The widget is called directly (N0); if it answers 403/429/5xx, non-JSON or JSON without reviews it is retried once through the Apify datacenter proxy (N1); if that fails too it is skipped for the rest of the run and the deep route (N2) is used. `levels_used` in the summary lists the levels actually used.
- **How deep it goes.** The deep route reads up to 200 pages (4000 reviews) per company and language, newest first, within your `maxReviews` (hard cap 2000). The fast route stops at about 100 reviews per star rating and language. If Trustpilot ever stops serving older pages, the run ends at that point with a note (`public_page_limit`) and still finishes successfully. Schedule it with `incrementalKey` or `dateFrom` to keep collecting new reviews as they arrive.
- **Polite by design:** one page at a time, 1 to 3 seconds between pages. The first page of each company takes longer because a real browser passes the connection check.
- If Trustpilot blocks a company, the Actor retries the page, then retries the company once with a fresh session, and finally marks it `blocked` in the summary. Reviews already delivered are kept. It never escalates to CAPTCHA solving.

### How much does it cost to use Trustpilot Reviews Scraper?

Trustpilot Reviews Scraper uses pay-per-event pricing:

- **$0.30 per 1,000 reviews** delivered (event `review-record`); $0.27 on the Silver plan and $0.24 on Gold and above.
- **$0.015 per company** (event `company-scraped`), charged once per company per run, however many languages you pick, and **only when the deep route (full profile pages) was used** for that company. Companies served entirely by the fast route are not charged it.

For example, 1,000 reviews of one company cost $0.30 through the fast route and $0.315 through the deep route. Residential proxy traffic is included. Blocked or empty companies, duplicates, reviews older than `dateFrom`, reviews removed by your filters or already delivered in an incremental run, and invalid rows are never charged. Start with 20 reviews to see the exact bill, and set a maximum charge per run in the Apify Console to cap spending.

### Privacy & GDPR

- **Reviewer name.** `author_name` is the display name published with the review. To minimise personal data, switch on **Hide author names** (`hideAuthorNames`): every row then has `author_name = null`.
- **Author hash.** `author_hash` is a keyed pseudonym: HMAC-SHA256 of the reviewer id, computed with the secret `AUTHOR_HASH_SALT` of the Actor. It is stable across runs while the salt does not change, so you can count repeat reviewers, but it is pseudonymisation, not anonymisation: whoever holds the salt can recompute it, and if no salt is set the Actor flags every row with `ext_author_hash_weak = true`.
- **Never output.** Avatars, profile URLs and raw reviewer ids are never output, in either mode. Emails and phone numbers written inside the review text are redacted (`[email]`, `[phone]`). Company replies are the company's own text, exactly as written.
- **Your responsibility.** Review text is free text and may contain personal information. You remain responsible for your lawful basis, retention and use of the data under GDPR and Trustpilot's terms.

Trustpilot Reviews Scraper only collects review data that is publicly visible on Trustpilot.

### FAQ

**Does Trustpilot Reviews Scraper return company replies?**
Yes, in `developer_reply` and `developer_reply_at`, when the company answered, but only from the deep route. Widget rows (`ext_route = "widget"`) do not carry replies; use `mode: "deep"`, or set `withRepliesOnly`, which switches to the deep route in Auto. The field name is shared with the other stores in the suite.

**Why is `country` sometimes null?**
It is the country shown with the reviewer, and Trustpilot does not always show one.

**Can I filter by language or stars?**
Yes. `languages` uses Trustpilot's localized sites and `stars` (with `verifiedOnly`, `withRepliesOnly`, `keyword`, `reviewerCountries`) filters the reviews after download. See Limits for how filtered runs read pages and what is not supported.

**How do I get only new reviews each day?**
Set an `incrementalKey` and schedule the Actor: each run delivers only reviews newer than the last run with that key. Alternatively schedule it with `dateFrom` or `dateRange`; reviews come newest first, so the run stops at the first page that is entirely older.

**What happens if Trustpilot blocks the run?**
The run always ends cleanly with a `SUMMARY` record: `completed`, `no_results`, `partial` (some companies failed) or `failed` (all of them), with a `stopped_reason` per company.

### Related Actors

- [Google Play Reviews Scraper](https://apify.com/rastriq/google-play-reviews-scraper): same output contract for Android apps.
- [App Store Reviews Scraper](https://apify.com/rastriq/app-store-reviews-scraper): same output contract for iOS apps.

# Changelog

This Actor's version history is a separate document: https://apify.com/rastriq/trustpilot-reviews-scraper/changelog.md

# Actor input Schema

## `companies` (type: `array`):

Company domains (<code>booking.com</code>, <code>www.booking.com</code>) or Trustpilot profile URLs (<code>https://www.trustpilot.com/review/booking.com</code>). One per line. Companies are scraped one after another.

## `mode` (type: `string`):

<b>Auto</b>: Trustpilot's public widget data first (fast, cheap, newest ~100 reviews per star rating and language), full profile pages only when more reviews or other fields are needed. <b>Fast</b>: widget only (a browser page is loaded once for a company never seen before, to learn its Trustpilot id, which is then cached); no company replies, useful votes or reviewer country; fails with an error if <b>Only reviews with a company reply</b> or <b>Reviewer countries</b> is set. <b>Deep</b>: full profile pages in a real browser with a residential proxy; every field, any depth, slower. If empty, Auto. The widget data is only available for companies on a paid Trustpilot plan; other companies always use the full profile pages (Auto switches by itself; Fast returns nothing for them).

## `maxReviews` (type: `integer`):

Max reviews delivered per company and per language, newest first. If empty, 100. Hard cap 2000. With several languages, each company-language pair gets its own quota.

## `languages` (type: `array`):

Languages to collect, one Trustpilot localized site per language (e.g. German reviews come from de.trustpilot.com). Pick <b>All languages</b> for every supported language, English first. If empty, English. Each language is a separate group with its own <b>Max reviews</b> quota; a language with no reviews for a company is skipped without loading a page. Unsupported codes are skipped and reported in the run summary.

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

Only reviews from the last 30 days, 3 months (90 days), 6 months (180 days) or 12 months (365 days), counted from today (UTC). If <b>Reviews since</b> is also set, the later of the two dates is used.

## `dateFrom` (type: `string`):

Only reviews published on or after this date (UTC), YYYY-MM-DD. The run stops as soon as a whole page is older.

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

Only reviews with these star ratings. If empty, all ratings.

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

Only reviews that Trustpilot labels as verified (<code>ext_review_verified</code> is true).

## `withRepliesOnly` (type: `boolean`):

Only reviews the company has answered (<code>developer_reply</code> is not null). Needs the deep route: Auto switches to it, Fast refuses.

## `keyword` (type: `string`):

Only reviews whose title or text contains this text, case-insensitive. Up to 100 characters.

## `reviewerCountries` (type: `array`):

Only reviews from reviewers in these countries, as ISO 3166 alpha-2 codes (<code>GB</code>, <code>DE</code>), case-insensitive. Reviews without a country never match. Needs the deep route: Auto switches to it, Fast refuses. If empty, all countries.

## `incrementalKey` (type: `string`):

Collect only reviews newer than the previous run that used the same key. The newest delivered review per company and language is remembered in the named key-value store <code>rastriq-trustpilot-incremental</code>. Letters, digits and hyphens, up to 40 characters. Leave empty for a normal run.

## `hideAuthorNames` (type: `boolean`):

Replace reviewer names with null (author_hash is kept) to minimise personal data under GDPR.

## Actor input object example

```json
{
  "companies": [
    "www.booking.com"
  ],
  "maxReviews": 20,
  "languages": [
    "en"
  ]
}
```

# Actor output Schema

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

No description

## `summary` (type: `string`):

No description

# 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 = {
    "companies": [
        "www.booking.com"
    ],
    "maxReviews": 20,
    "languages": [
        "en"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rastriq/trustpilot-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 = {
    "companies": ["www.booking.com"],
    "maxReviews": 20,
    "languages": ["en"],
}

# Run the Actor and wait for it to finish
run = client.actor("rastriq/trustpilot-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 '{
  "companies": [
    "www.booking.com"
  ],
  "maxReviews": 20,
  "languages": [
    "en"
  ]
}' |
apify call rastriq/trustpilot-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rastriq/trustpilot-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/VKMtLArpRTk0yZ0BO/builds/OcRrK82nKZtKSPDAp/openapi.json
