Building a Review Monitoring Pipeline: Trustpilot Business Search to Reviews in One Workflow
Chain Trustpilot Business Search into the Reviews Scraper to go from a category search to full review data for every company found, in one Python workflow.
The actor referenced in this article. Pay only for results delivered.
Most Trustpilot monitoring requests start the same way: “pull reviews for every company in this space.” The problem is that “every company in this space” isn’t a list you already have. You have to discover it first. That’s a two-step job: find the companies, then pull their reviews. Doing it manually means searching Trustpilot’s category pages by hand, copying domains into a spreadsheet, then running a separate review pull for each one.
Try it live: Trustpilot Business Search, 13 Fields, TrustScore & Domain. Pay per result delivered. Failed and empty results are never charged.
The Trustpilot Business Search Scraper and the Trustpilot Reviews Scraper are built specifically to chain together; they share a domain key by design. This post walks through the actual pipeline: search a category, take the domain field from every result, and feed it straight into the Reviews Scraper.
Why Chain Instead of Scraping Reviews Directly
The Reviews Scraper needs a companyDomain as input: it doesn’t discover companies on its own, it pulls reviews for a domain you already know. If your job is “monitor everyone in fintech” rather than “monitor this one company,” you need a discovery step first. Business Search is that discovery step: it browses a category or keyword and returns a list of companies with their domain, trustScore, reviewsCount and verification status. You decide which of those companies are worth pulling full reviews for, and pass their domains downstream.
This also controls cost. Business Search is $2 per 1,000 businesses; Reviews is $5 per 1,000 reviews. Discovering 200 businesses costs $0.40. Blindly pulling 100 reviews for all 200 of them costs $10, whether or not most of those companies turn out to be irrelevant. Filtering the discovery results first, by TrustScore, review volume, or verification status, before triggering the Reviews run is the difference between a targeted pipeline and burning budget on companies you didn’t need.
Why a Plain HTTP Scraper Won’t Work Here
Trustpilot puts an AWS WAF “Verifying your connection” challenge in front of every page. A requests.get() or curl call gets the challenge page back, with no reviews and no rating. Reading Trustpilot reliably takes a real browser session that can clear that check, routed through residential proxies, which is how the Reviews Scraper works. You don’t need a Trustpilot login, cookies or an API key.
For each domain it returns one row per review (reviewer_name, rating, title, text, review_date, reply_text, review_url, review_id, business_domain) plus one business-level row tagged "_type": "business" with overall_rating and total_reviews. Filter on the tag so the business row doesn’t end up in your review list.
The Pipeline
Step 1: Search a category
import os
from apify_client import ApifyClient
apify = ApifyClient(os.environ["APIFY_TOKEN"])
search_run = apify.actor("themineworks/trustpilot-business-search").call(run_input={
"query": "electronics_technology",
"searchMode": "category",
"maxResults": 100,
"proxyConfiguration": {
"useApifyProxy": True,
"apifyProxyGroups": ["RESIDENTIAL"],
"apifyProxyCountry": "US",
},
})
businesses = list(apify.dataset(search_run["defaultDatasetId"]).iterate_items())
print(f"Found {len(businesses)} businesses")
Step 2: Filter to the companies worth monitoring
# Only pull reviews for verified, claimed businesses with meaningful review volume
targets = [
b for b in businesses
if b.get("domain")
and b.get("isVerified")
and b.get("reviewsCount", 0) >= 100
]
print(f"{len(targets)} businesses qualify for review monitoring")
Step 3: Chain each domain into the Reviews Scraper
all_reviews = []
for business in targets:
reviews_run = apify.actor("themineworks/trustpilot-reviews").call(run_input={
"companyDomain": business["domain"],
"maxResults": 100,
})
rows = apify.dataset(reviews_run["defaultDatasetId"]).iterate_items()
reviews = [r for r in rows if "review_id" in r] # skip business and summary rows
for r in reviews:
r["source_business_name"] = business["name"]
r["source_trust_score"] = business.get("trustScore")
all_reviews.extend(reviews)
print(f" {business['domain']}: {len(reviews)} reviews pulled")
print(f"Total reviews collected across {len(targets)} businesses: {len(all_reviews)}")
The loop above is the easiest version to read. The Reviews Scraper also takes a companyDomains list, and one run covering many domains costs less per review than one run per domain, because the bot check is cleared once and that clearance covers every company in the run. maxResults counts per company, not per run. Each review carries business_domain, so you can join the names back:
names = {b["domain"]: b["name"] for b in targets}
scores = {b["domain"]: b.get("trustScore") for b in targets}
batch_run = apify.actor("themineworks/trustpilot-reviews").call(run_input={
"companyDomains": list(names),
"maxResults": 100, # per company
})
rows = list(apify.dataset(batch_run["defaultDatasetId"]).iterate_items())
all_reviews = [r for r in rows if "review_id" in r]
for r in all_reviews:
r["source_business_name"] = names.get(r["business_domain"])
r["source_trust_score"] = scores.get(r["business_domain"])
business_rows = [r for r in rows if r.get("_type") == "business"]
Step 4: Surface what needs attention
The Reviews Scraper returns rating, text, review_date and reply_text per review. Unanswered negative reviews are the highest-priority signal in a monitoring pipeline. A business with a low rating and no reply_text has a live reputation problem it hasn’t responded to:
unanswered_negative = [
r for r in all_reviews
if r.get("rating", 5) <= 2 and not r.get("reply_text")
]
for r in unanswered_negative[:10]:
print(f"[{r['source_business_name']}] {r['rating']}★: {r.get('title', '')}")
print(f" {r['text'][:120]}...")
Running It on a Schedule
A one-time pull tells you where things stand today. The value of this pipeline compounds when it runs on a schedule, daily or weekly, and you diff against the previous run to catch new reviews as they land, rather than re-reading everything each time:
import json
from datetime import datetime
# Load domains + last-seen review IDs from a previous run
try:
with open("seen_review_ids.json") as f:
seen_ids = set(json.load(f))
except FileNotFoundError:
seen_ids = set()
new_reviews = [r for r in all_reviews if r["review_id"] not in seen_ids]
print(f"{len(new_reviews)} new reviews since last run")
seen_ids.update(r["review_id"] for r in all_reviews)
with open("seen_review_ids.json", "w") as f:
json.dump(list(seen_ids), f)
Wire this into a cron job or an Apify scheduled task, and route new_reviews (especially the unanswered negative ones) to Slack, email, or a CRM webhook.
Scheduling the scan on Apify
Apify’s Scheduler runs the actor on a cron expression, so you don’t need a server of your own. In the Apify Console, open the actor, go to Schedules, and use something like 0 8 * * * for a daily 8am run, or 0 */4 * * * for every four hours on a brand where response time matters. Your alerts arrive as fast as that interval and no faster.
If you only want alerts on complaints, add "starsFilter": ["1", "2"] to the scheduled input. That cuts noise and cost at the source, since you are only charged for reviews that are delivered.
Diffing the latest run and posting to Slack
The scheduled run doesn’t know what is new. That logic lives in whatever reads its dataset afterwards. Run the script below as a second scheduled job an hour later, or trigger it from an Apify webhook that fires when the actor run succeeds. The loop is: scheduled scrape, diff against known ids, alert on what is new, update the known set.
import json
import os
import requests
from apify_client import ApifyClient
apify = ApifyClient(os.environ["APIFY_TOKEN"])
SLACK_WEBHOOK = os.environ["SLACK_WEBHOOK_URL"]
try:
with open("seen_review_ids.json") as f:
seen_ids = set(json.load(f))
except FileNotFoundError:
seen_ids = set()
# Read the dataset from the most recent run of the actor
run = apify.actor("themineworks/trustpilot-reviews").last_run().get()
reviews = [r for r in apify.dataset(run["defaultDatasetId"]).iterate_items() if "review_id" in r]
new_reviews = [r for r in reviews if r["review_id"] not in seen_ids]
for r in new_reviews:
rating = r.get("rating") or 0
label = "NEGATIVE" if rating <= 2 else "new"
replied = "already replied" if r.get("reply_text") else "no reply yet"
requests.post(SLACK_WEBHOOK, json={
"text": (
f"[{label}] {r.get('business_domain')} {rating}★ review, {replied}: "
f"\"{r.get('title', '')}\" {r.get('text', '')[:200]}\n{r.get('review_url', '')}"
)
})
seen_ids.update(r["review_id"] for r in reviews)
with open("seen_review_ids.json", "w") as f:
json.dump(list(seen_ids), f)
print(f"{len(new_reviews)} new reviews found and alerted.")
This only tracks additions. If you also need to catch edited reviews, store the full record rather than just the id, and compare text and rating on later runs.
Your Own Brand vs Competitors
The same pipeline covers two jobs with small input changes.
For your own brand, scan every few hours with starsFilter set to 1 and 2 stars, and send alerts to whichever channel your support team actually watches. An empty reply_text on a 1-star review is your queue of unanswered complaints.
For competitors, a daily or weekly scan is usually enough, and you want all ratings rather than just complaints. Store overall_rating and total_reviews from the business row on each run to see whether their satisfaction is trending up or down, and read their review text for the complaints and praise that keep repeating. That tells you where their product falls short and which messages land with their customers.
Cost of a Realistic Run
Discovering 100 companies in a category and pulling 100 reviews each for the 40 that pass a quality filter:
- Business Search: 100 businesses × $0.002 = $0.20
- Reviews: 40 companies × 100 reviews × $0.005 = $20.00
- Total: ~$20.20 for a full category sweep with review data for every qualified competitor
Re-running weekly to catch new reviews costs the same per sweep, though in practice you’d lower maxResults on the Reviews step for repeat runs since you’re mostly catching new activity, not re-pulling the full history each time.
Related Reading
For the discovery step in isolation, see Trustpilot Business Search: Discover Companies by Category with TrustScore Data. Both actors, Trustpilot Business Search and Trustpilot Reviews, are also exposed as MCP tools, so an AI agent can run this same discovery-then-monitor logic conversationally for smaller one-off checks.
Explore the scraper referenced in this article: inputs, outputs, and pricing, then run it on Apify.
Trustpilot Business Search: Discover Companies by Category with TrustScore Data
Search Trustpilot by category or keyword in Python to build company lists with TrustScore, review count and verification status, no login required.
Trustpilot vs Google Maps Reviews vs TripAdvisor: Choosing the Right Review Data Source
Trustpilot, Google Maps and TripAdvisor cover different business types and different fields. A field-by-field comparison to pick the right review data source.
How to Scrape Trustpilot Reviews by Company Domain (Python Guide)
Trustpilot has no public API for review data. Learn how to pull business reviews, star ratings, trust scores, and business replies from any Trustpilot company page using Python.
Compare an App's App Store Reviews Across Countries
Read one app's App Store reviews in many countries in one run with App Store Reviews Scraper, then compare ratings and top complaints country by country.