# Software Reviews: Capterra, GetApp, G2 (`vonsensey/software-reviews-scraper`) Actor

Software reviews from the review pool Capterra, GetApp and Software Advice share, by product or category, filtered by star rating and date and checked against the site's own count; plus G2's newest reviews.

- **URL**: https://apify.com/vonsensey/software-reviews-scraper.md
- **Developed by:** [Blackcube](https://apify.com/vonsensey) (community)
- **Categories:** Business, Marketing, Automation
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 reviews

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

## Software Reviews Scraper: Capterra, GetApp, Software Advice, G2

Reads software reviews from the review pool Capterra, GetApp and Software Advice share (one pool: each review is
published on all three, so it is read once), by product or by Capterra category, newest first (G2's feed only
roughly so), filtered by star
rating and date, each walk checked against the site's own count. Also reads G2's newest reviews per product.

Unofficial; not affiliated with Capterra, GetApp, Software Advice, Gartner or G2. We never log in.

### Input

| Field | What it does |
|---|---|
| `products` | Capterra product URLs (`https://www.capterra.com/p/135003/Slack/`): the shared pool's reviews. G2 product URLs (`https://www.g2.com/products/slack/reviews`): G2's newest reviews. A GetApp or Software Advice URL gets a free row asking for the Capterra URL (the same reviews) |
| `categories` | Capterra category URLs (`https://www.capterra.com/team-communication-software/`): the listed products in the site's order, up to `maxProductsPerCategory` (default 20) |
| `ratings` | Star ratings, e.g. `["1","2","3"]`. Capterra: each star read separately, checked against the site's count for that star. G2: applied here (its feed has no filter; a half star counts to the star below) |
| `since` | Reviews written on or after this day (`YYYY-MM-DD`) |
| `maxReviewsPerProduct` | Default 100, per star when stars are selected; in change-only mode, reviews an earlier run read do not count. Each run starts from the newest review: reviews past the cap that a run did not read are not read later (raise the cap to read further back) |
| `maxReviewsTotal` | Default 1,000 for the run |
| `includeProfiles` | Default on: one row per Capterra product (rating, review count, the four sub-ratings) |
| `personalData` | Default off: reviewer name, job title, LinkedIn verification. Company size, industry and time used are always included |
| `onlyNewSinceLastRun`, `stateName` | Per feed, remember what each product and star has read; the next run delivers only what is new, plus any part an interrupted run left unread. If the memory cannot be read, the run stops with a free `state-read-failed` row |

Recipe, the research use: the 1 to 3 star reviews of the top team-chat tools, with switching reasons.

```json
{ "categories": ["https://www.capterra.com/team-communication-software/"], "ratings": ["1", "2", "3"], "maxReviewsPerProduct": 200 }
```

### Output rows

- `review`: `source` (`capterra` or `g2`), `originSite` (where it was written: Capterra, GetApp, SoftwareAdvice, G2),
  `productId`, `productName`, `reviewId`, `reviewUrl` (Capterra has no per-review link: the reviews page), `rating`,
  the four sub-ratings and `recommendationRating` (0-10), `title`, `text`, `pros`, `cons`, `adviceToOthers`,
  `switchingReasons` and `switchedFrom`, `chosenReasons` and `alternativesConsidered`, `date` (a day for Capterra, a
  time for G2), `incentive` and `reviewSource` (the site's incentive label and wording), `companySize`, `industry`,
  `timeUsedProduct`, `reviewerRole` (G2's stated role; Capterra's job title only with `personalData`),
  `reviewerName` / `reviewerLinkedInVerified` (only with `personalData`), `vendorResponse` (as given; null when none).
- `product`: the Capterra profile above.
- `coverage` (free): one per product and star (Capterra) or per product (G2), with the site's `total`, what was read,
  and why it stopped; a free-plan run's row carries a `note` when its preview ran out.
- `error` (free): every input, source or run failure, with a stable `reason`.
- `summary` (free): one per run, with `renderedReads`.

### How Capterra is read, and what a free plan gets

Capterra's pages are behind Cloudflare, and from Apify's network our own browser does not pass it. The Actor opens each
product's reviews page through a paid render service and, inside that page, walks the page's own review list exactly
as the page does: 25 reviews a call, newest first or one star at a time. One paid read covers a product's run (up to
\~2,000 reviews; more reads continue where it stopped). That service costs the owner per read, so on a **free Apify plan**
this listing reads up to 25 Capterra reviews a month per account, and the coverage row says so; a paid plan reads them
all. G2's feed is read directly, free on every plan. Because every Capterra read costs the same whatever it yields,
each read that served Capterra data carries a small read fee (the `check` event), on top of its reviews: usually one
per product a run (a product over ~2,000 reviews takes more), and one per category page listed. A read that served
nothing (a refused, missing or unreadable page) is free, and a read the cost ceiling (`maxCostUsd`) cannot pay its fee
for is never made.

### What it promises, and the test that holds it

| Promise | Test |
|---|---|
| A Capterra walk is finished only at exactly the site's count for its filter; a list that ends short is a free `review-count-mismatch` row | `walk.test.ts` "one read walks a star filter to the site's count (48 of 48)", "a list that ends before the count" |
| A star filter returns only that star; a page that ignored it is a free row, before anything is counted | `walk.test.ts` "a page that ignored the star filter"; `parse.test.ts` "a whole 1-star walk" |
| `since` returns exactly the reviews written on or after the day | `walk.test.ts` "since: exactly the reviews written on or after the day" |
| A walk longer than one read continues from the cursor in the next read | `walk.test.ts` "a walk that needs more pages than one read gave continues in the next read" |
| Reviews from Capterra, GetApp and Software Advice come once, each saying where it was written | `parse.test.ts` "from all three Gartner sites" |
| Reviewer names and job titles stay out unless asked | `walk.test.ts` "reviewer names and job titles only with personalData" |
| A free plan gets the 25-review preview and is told what a paid plan reads; a run without the route says so and reads nothing | `walk.test.ts` "a free-plan preview stops at its rows", "a run without the render route" |
| A missing product, a refused read, a site release that hid the review list, an unreadable answer: free rows that say so | `walk.test.ts` "a release that hid the review action, a refused read, a missing product, an unreadable answer" |
| G2: the feed's newest 25, the star and date filters applied here, no count claimed | `walk.test.ts` "G2: the feed's newest 25"; `parse.test.ts` "the G2 feed" |
| Change-only: a re-run delivers nothing it already delivered; an interrupted run leaves no gap and sends nothing twice | `walk.test.ts` "a second run delivers nothing", "a walk the run cut short leaves its gap to the next run"; `state.test.ts` "a cut walk keeps only what it read and marks a gap" |
| No row is billed twice; caps and the cost ceiling hold under concurrency; failures are free | `billing.test.ts` (the Sink contract) |

### Limits

- G2 without a login shows only its newest reviews and page 1 of anything else; this Actor reads its public feed (the
  newest 25 per product) and never logs in. Its coverage rows carry no count.
- Capterra gives each review a day, not a time.
- GetApp and Software Advice URLs are not translated to Capterra yet: give the Capterra URL.

### Known limits (chosen, not built)

- Capterra can briefly refuse the review call for a product read several times within minutes (measured: three refused
  reads of one product in 20 minutes while other products read cleanly). The run then writes a free `blocked` row (no
  second paid read: it was refused too every time) and a later run reads it.
- A free-plan account's monthly 25-row preview is claimed by a write and a read-back in the account's own store (no
  atomic lock exists there): two free runs of one account started within the same moment could both claim it, at most
  doubling that month's preview (shared with every listing's free-plan rule since 2026-10-05).
- Two runs of the same change-only feed at once (overlapping schedules with one `stateName`) each start from the
  memory as it was and may both deliver the same new reviews; give each schedule its own `stateName`, or keep its runs
  from overlapping.
- Capterra gives days, not times: reviews past the cap that were written on the same day as the newest one a change-only
  run delivered can come in the next run.
- A slow render service can make one read cost more than its fee covers: typical reads take 7-36 s, and a read near the
  90 s limit (or a retried one) on the Hobby plan falls below 85%; the owner's cost, never the buyer's.
- A read's fee is charged before its rows are written: if the dataset then refuses them, the fee stays charged.
- On a migration or abort the memory is saved after the walks in progress stop (the save waits up to 5 s for them); a
  walk still waiting on a slow page after that is saved as unread and its rows may come again next run.
- Change-only memory records each walk when it ends, and is saved once a minute, on migration and abort, and at the
  end. A hard crash with no warning (no migration or abort event) loses the memory of walks still in progress and of up
  to the last minute, and a final save that fails three times loses that run's part: the next run may then deliver,
  and bill, those rows again (the second case says so in a free `state-save-failed` row). Checkpointing every page and
  a write-ahead protocol were weighed and not built: platform migrations and aborts are announced and saved first, and
  no unannounced crash was seen in any run.

Build evidence: `docs/BUILD-REPORT.md`. Measured source facts: `spike/NOTES.md`.

### Use it from n8n, MCP, the API or a schedule

Built to be called by a workflow, not only from the Store form. The Actor is `vonsensey/software-reviews-scraper`; every snippet below sends `{}`, which runs the defaults shown on the form — replace it with your own input.

#### n8n

Install the **Apify** community node (`@apify/n8n-nodes-apify` under *Settings → Community Nodes*, or search "Apify" on n8n Cloud). Add **Apify → Run Actor** with Actor `vonsensey/software-reviews-scraper` and your input JSON, then **Apify → Get Dataset Items** on the run's `defaultDatasetId` and pipe the rows anywhere. For scheduled runs, the **On new Apify Event** trigger fires when a run of this Actor finishes.

#### MCP (Claude, Cursor, VS Code, any MCP client)

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=vonsensey/software-reviews-scraper",
      "headers": {
        "Authorization": "Bearer <YOUR_APIFY_TOKEN>"
      }
    }
  }
}
```

Your agent then calls `vonsensey/software-reviews-scraper` as a tool with the same input the form takes and reads the dataset back.

#### REST API (one call, rows in the response)

```bash
curl -X POST "https://api.apify.com/v2/acts/vonsensey~software-reviews-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" -d '{}'
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("vonsensey/software-reviews-scraper").call(run_input={})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row)
```

#### JavaScript

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('vonsensey/software-reviews-scraper').call({});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Make, Zapier, LangChain, CrewAI

The Apify app in **Make** and **Zapier** has a *Run an Actor* module: pick `vonsensey/software-reviews-scraper`. In **LangChain** and **CrewAI** the Apify tool wrappers take the same Actor id. A daily **schedule** needs nothing but the Console: *Schedules → Create → this Actor → cron*, and the dataset fills on its own.

# Actor input Schema

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

Capterra product URLs (e.g. https://www.capterra.com/p/135003/Slack/): the reviews Capterra, GetApp and Software Advice share, once. G2 product URLs (e.g. https://www.g2.com/products/slack/reviews): G2's newest reviews. A GetApp or Software Advice URL: give the product's Capterra URL instead (the same reviews).

## `categories` (type: `array`):

Capterra category URLs, e.g. https://www.capterra.com/team-communication-software/. The listed products are read in the site's order, up to the per-category limit.

## `maxProductsPerCategory` (type: `integer`):

Most products read from each category.

## `ratings` (type: `array`):

Only reviews with these star ratings. Empty reads every rating. Capterra: each star is read separately and checked against the site's count for that star. G2: its feed has no filter, so the star is applied here (a half star counts to the star below: 4.5 is 4).

## `since` (type: `string`):

Only reviews written on or after this day (YYYY-MM-DD).

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

Most reviews read per product (per star when stars are selected). With 'Only new' on, reviews an earlier run read do not count. Each run starts from the newest review: reviews past the cap that a run did not read are not read later (raise the cap to read further back).

## `maxReviewsTotal` (type: `integer`):

The run stops once this many reviews are delivered.

## `includeProfiles` (type: `boolean`):

One row per Capterra product: rating, review count and the four sub-ratings.

## `personalData` (type: `boolean`):

Include the reviewer's name, job title and LinkedIn verification. Off by default; company size, industry and time used are always included.

## `onlyNewSinceLastRun` (type: `boolean`):

Remember how far this feed has read each product and star; the next run delivers only what is new, plus any part an interrupted run left unread. If the memory cannot be read, the run stops with a free row instead of re-sending old reviews.

## `stateName` (type: `string`):

Separate memory per feed when 'Only new' is on.

## `maxCostUsd` (type: `number`):

Stop once the run's charges would pass this amount.

## `concurrency` (type: `integer`):

How many products are read at the same time.

## Actor input object example

```json
{
  "products": [
    "https://www.capterra.com/p/135003/Slack/"
  ],
  "categories": [],
  "maxProductsPerCategory": 20,
  "ratings": [],
  "maxReviewsPerProduct": 100,
  "maxReviewsTotal": 1000,
  "includeProfiles": true,
  "personalData": false,
  "onlyNewSinceLastRun": false,
  "concurrency": 2
}
```

# Actor output Schema

## `reviews` (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 = {
    "products": [
        "https://www.capterra.com/p/135003/Slack/"
    ],
    "categories": [],
    "ratings": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("vonsensey/software-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": ["https://www.capterra.com/p/135003/Slack/"],
    "categories": [],
    "ratings": [],
}

# Run the Actor and wait for it to finish
run = client.actor("vonsensey/software-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": [
    "https://www.capterra.com/p/135003/Slack/"
  ],
  "categories": [],
  "ratings": []
}' |
apify call vonsensey/software-reviews-scraper --silent --output-dataset

```

## MCP server setup

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