Scrapers

Capterra reviews

The Capterra scraper pulls product detail + review pages from capterra.com for a given list of product identifiers. Reviews are paginated server-side; the actor walks them while keeping the session fingerprint stable, parses both the embedded JSON-LD and the rendered DOM, and yields one dataset row per product header and per review.

Updated today

Overview

At a glance
Worker
ultron-capterra-reviews
Runtime
Python, anti-fingerprint Firefox engine, headless by default
Targets
Capterra product detail + paginated reviews
Proxy
Residential US (mandatory). Datacenter IPs trigger anti-bot.
Defaults
25 reviews per product, 4000 ms per request, headless on
Max reviews / product
500
Webhook target
scraper-webhook.51ultron.com/apify

Pulled when an Ultron skill needs review signal for a vendor evaluation, a competitive teardown, or a buyer-intent enrichment for a CRM contact. Sibling to the G2 reviews scraper; the two share the same dataset row shape so downstream consumers can merge them on product identity.

Input schema

One required field, four knobs.

.actor/INPUT_SCHEMA.jsonjson
1{
2 "products": [
3 { "capterraId": "112233", "slug": "linear-app" },
4 { "capterraId": "445566", "slug": "notion" }
5 ], // required, both keys per item
6 "maxReviewsPerProduct": 25, // 1 to 500
7 "headless": true,
8 "proxyConfiguration": {
9 "useApifyProxy": true,
10 "apifyProxyGroups": ["RESIDENTIAL"],
11 "apifyProxyCountry": "US"
12 },
13 "requestDelayMs": 4000 // 1000 to 30000, +50% jitter
14}
Warning
The product URL is built from both keys: capterra.com/p/<capterraId>/<slug>/reviews/. Wrong slug for the right id returns a 200 with the wrong product. Validate inputs against a known-good source before firing at volume.

Crawl flow

Five phases per product.

src/__main__.pypy
1# 1. For each { capterraId, slug }:
2# URL = f"https://capterra.com/p/{capterraId}/{slug}/reviews/"
3# 2. Open URL in the anti-fingerprint browser, wait for JSON-LD <script type="application/ld+json">.
4# 3. Extract product header: name, rating, reviewCount, vendor, category, pricing.
5# Push kind="product" row.
6# 4. Iterate /reviews/?page=N until reviewCount or maxReviewsPerProduct is reached.
7# Parse each card: rating, title, body, role, industry, useCase, pros, cons,
8# overallScore, publishedAt.
9# Push kind="review" rows in batches.
10# 5. Sleep requestDelayMs + 50% jitter between page loads.

The actor reads JSON-LD first because it carries authoritative aggregateRating + reviewCount. The DOM walk that follows is the only path to per-review prose, so missing JSON-LD silently does not abort; the actor falls back to DOM-only product info and continues to reviews.

Output shape

One row per product header, then one row per review.

dataset-row.jsonjson
1// kind === "product"
2{
3 "kind": "product",
4 "capterraId": "112233",
5 "slug": "linear-app",
6 "name": "Linear",
7 "vendor": "Linear Orbit, Inc.",
8 "category": "Project Management",
9 "rating": 4.6,
10 "reviewCount": 1842,
11 "scrapedAt": "2026-05-19T12:00:00Z"
12}
13
14// kind === "review"
15{
16 "kind": "review",
17 "capterraId": "112233",
18 "reviewId": "r-9382-1842",
19 "rating": 5,
20 "title": "Best PM tool we have used",
21 "body": "We migrated from Jira six months ago...",
22 "pros": "Speed, keyboard-first UX, clean API",
23 "cons": "Limited reporting in v1",
24 "useCase": "Engineering ops",
25 "reviewerRole": "Engineering Manager",
26 "industry": "Software",
27 "publishedAt": "2026-04-12T00:00:00Z",
28 "scrapedAt": "2026-05-19T12:00:14Z"
29}

Anti-bot strategy

Capterra fronts DataDome. Datacenter IPs are blocked at the TCP layer.

DefenseCounter
DataDome challengeAnti-fingerprint Firefox engine bypasses the JS challenge with a real browser DOM.
IP reputation gatingResidential US proxy. Datacenter or non-US IPs trigger captcha walls.
Rate-limit per IP4000 ms per request with 50 percent jitter is the empirically safe floor. Below that, the actor hits soft-block within 50 requests.
TLS fingerprint sniffingThe anti-fingerprint browser exposes a Firefox ClientHello, not the default Python or Node TLS fingerprint.
Cookie consent gateAuto-accepted on first page load with a deterministic click selector.

Webhook contract

Terminal-status callback to the central scraper-webhook worker.

actor.json (webhook block)json
1{
2 "eventTypes": [
3 "ACTOR.RUN.SUCCEEDED",
4 "ACTOR.RUN.FAILED",
5 "ACTOR.RUN.TIMED_OUT",
6 "ACTOR.RUN.ABORTED"
7 ],
8 "requestUrl": "https://scraper-webhook.51ultron.com/apify",
9 "payload": {
10 "eventType": "<event>",
11 "actorName": "capterra-reviews",
12 "source": "capterra",
13 "actorRunId": "<run-id>",
14 "datasetId": "<dataset-id>",
15 "status": "SUCCEEDED|FAILED|TIMED_OUT|ABORTED",
16 "startedAt": "<RFC3339>",
17 "finishedAt": "<RFC3339>",
18 "actorTaskId": "<task-id>"
19 }
20}
Note
The webhook does not carry rows. It carries the dataset id so the receiving worker can pull rows in bulk and route them to the correct workspace and skill run. See /docs/ingest/webhook.

How to trigger it

From chat v2, a skill, or the orchestrator.

Direct chat: the user names a product set and any Ultron skill with the reviews-capture tool fires a single run with that product list. The actor name and dataset routing are baked into the skill metadata, so the model does not need to know the worker name.

From a scheduled task: a daily cron entry under the review-monitor schedule replays the workspace product set against the actor, so net-new reviews land in the brain within 24 hours.

From a multi-source run: the scraper orchestrator can fan out across capterra-reviews + g2-reviews + similarweb-bulk in parallel and merge their outputs into a unified competitor dossier.

File map

actors/capterra-reviews
.actor
actor.jsonmanifest + webhook config
INPUT_SCHEMA.jsonfive-field input
src
__main__.pyentry point: products loop, pagination, parsers
DockerfilePython + anti-fingerprint browser image
requirements.txtpinned deps
README.mdoperator notes