# G2 Reviews Scraper - Likes, Dislikes & Reviewer Role, No Login (`lanternlab318/g2-reviews`) Actor

G2 software reviews by product URL: star rating, title, what reviewers like and dislike, problems solved, reviewer role, industry and company size (never names), plus product rating, review count, vendor and categories. No login or cookies. Partial results are marked, never shown as empty.

- **URL**: https://apify.com/lanternlab318/g2-reviews.md
- **Developed by:** [Lantern Lab](https://apify.com/lanternlab318) (community)
- **Categories:** Business, Marketing, Lead generation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.99 / 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.

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

## G2 Reviews Scraper

A Lantern Lab Actor. Give it G2 product URLs and get the public software reviews back: star rating, title, **what reviewers like and dislike**, the problems the product solves for them, and the reviewer's role, industry and company size. Each product also gets its **G2 star rating**, review count, vendor and categories. No G2 login, no cookies.

**Price:** you pay per review collected. Product rows, status rows and the run summary are free, and so is a URL that finds nothing.

### Quick example

Input:

```json
{
  "productUrls": ["https://www.g2.com/products/notion/reviews"],
  "maxReviewsPerProduct": 100,
  "sort": "most_recent"
}
```

One review row (`recordType: "review"`, shortened):

```json
{
  "recordType": "review",
  "reviewId": "13469971",
  "reviewUrl": "https://www.g2.com/products/notion/reviews/notion-review-13469971",
  "productName": "Notion",
  "rating": 5,
  "title": "Notion Replaced Three Tools with One Powerful, Fast Workspace",
  "likes": "Notion replaced three separate tools for us: wiki, task tracker and CRM with one workspace. ...",
  "dislikes": "Permissions are still coarse - sharing a single database view with a client without exposing the rest of the workspace takes more workarounds than it should. ...",
  "problemsSolved": "Notion gives each of our client client one page with brief, lead lists and results linked together, ...",
  "reviewerRole": "Founder",
  "reviewerIndustry": "Computer Software",
  "companySize": "Small-Business (50 or fewer emp.)",
  "reviewSource": "Organic",
  "incentivized": false,
  "validatedReviewer": true,
  "publishedAt": "2026-09-15",
  "updatedAt": null,
  "partial": false,
  "collectedAt": "2026-10-23T17:05:12.408Z"
}
```

One product row (`recordType: "product"`, free):

```json
{
  "recordType": "product",
  "name": "Notion",
  "vendor": "Notion",
  "category": "AI Chatbots",
  "categories": ["AI Chatbots", "Knowledge Base", "Note-Taking Software", "Project Management", "..."],
  "rating": 4.6,
  "reviewCount": 13885,
  "url": "https://www.g2.com/products/notion/reviews",
  "reviewsAvailable": 100,
  "reviewsReturned": 100,
  "partial": false,
  "statusText": "Collected 100 review(s) of 13885 on G2."
}
```

### Limits

- **Up to 100 reviews per product in each order.** G2 shows a signed-out visitor 10 pages of 10 reviews; asking for page 11 returns page 10 again. Choose the order (`most_recent`, `most_helpful`, `highest_rated`, `lowest_rated` or G2's default) to decide which 100.
- Public data only. The reviewer's name, avatar and profile link are never collected, even where G2 shows them. A reviewer who hid their name appears on G2 as "Verified User in <industry>"; that industry is kept.
- G2 shows role, industry and company size only when the reviewer gave them, so any of the three can be `null`. When G2 shows a single line besides the company size it is written as `reviewerRole`.
- Each G2 page takes 20-50 seconds to load through the anti-bot layer; a product with 100 reviews took about 4 minutes in our test runs. Products are collected one after another.
- Vendor name and categories come from G2's full product page, which for a few products (Canva, for example) is too slow to load through the anti-bot layer. Those products are still collected in full from the review list - name, rating, review count and every review - with `vendor` and `categories` `null` and `profileComplete: false`.
- `most_recent` is G2's own order, by the date a review was last updated: a 2023 review the reviewer refreshed this month is near the top, with `publishedAt` 2023 and `updatedAt` this month.
- English G2 pages only. No vendor responses, screenshots, pricing, feature scores, comparisons or G2 Grid data.
- No scheduling or monitoring built in; use Apify schedules.

### What it does

- Accepts product URLs (`https://www.g2.com/products/<product>/reviews`, with or without `/reviews`, or just the slug).
- Returns the three answers of a G2 review as their own fields (`likes`, `dislikes`, `problemsSolved`), all of them as one `text`, and `sections` with each question and answer in page order. The full answer is returned, not the "Show More" preview.
- Keeps G2's own review signals: `reviewSource` (for example `Organic`, `Seller invite`, `G2 invite`), `incentivized`, `validatedReviewer`, `currentUser`, `aiTranslated` and the raw `badges` list.
- Deduplicates across the whole run: a product reached by two URLs is collected once, and a review is written and charged once.
- Keeps the source identifier (`reviewId`), G2's review date and update date (`publishedAt`, `updatedAt`) and our collection time (`collectedAt`) as separate fields.
- Says what happened to every input and product. A product with no reviews, a URL that is not a product, and a product that was cut short are different records and are never confused: a cut-short product is marked `partial: true` with the reason.
- Gets past G2's bot protection (DataDome) through Apify's Unblocker proxy, and retries a refused or failed page up to 5 times, with a short wait in between.
- Caps itself: `maxUpstreamRequests` is a hard ceiling on requests to G2 for the whole run, retries included, derived from your input when you leave it empty, so a run is never unbounded.

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `productUrls` | — | G2 product URLs or slugs, one per line. |
| `maxReviewsPerProduct` | `100` | Upper bound on reviews per product. `0` collects only the product profile. Max 100 (what G2 shows a signed-out visitor). |
| `sort` | `most_recent` | `most_recent`, `g2_default`, `most_helpful`, `highest_rated` or `lowest_rated`. Decides which reviews a limit keeps. |
| `maxUpstreamRequests` | derived | Hard ceiling on requests to G2 for the whole run, retries included. |

### Output

One row per review, `recordType: "review"`, charged:

| Field | Meaning |
| --- | --- |
| `reviewId`, `reviewUrl` | G2's own review ID (the dedupe key) and the public review page. |
| `productId`, `productName`, `productSlug` | The product reviewed. |
| `rating` | Star rating out of 5, in half stars. |
| `title` | The review headline. |
| `likes`, `dislikes` | "What do you like best about ...?" and "What do you dislike about ...?" |
| `problemsSolved` | "What problems is ... solving and how is that benefiting you?" |
| `text`, `sections` | Every answer as one text, and as `{question, answer}` pairs. |
| `reviewerRole`, `reviewerIndustry`, `companySize` | The reviewer's role and company profile, as G2 shows them. Never the name. |
| `reviewSource`, `incentivized`, `validatedReviewer`, `currentUser`, `aiTranslated`, `badges` | G2's review signals. |
| `publishedAt`, `updatedAt` | The review's date and, when the reviewer updated it, the update date (`YYYY-MM-DD`). |
| `partial`, `partialReason` | Always `false` / `null` on a review row: the row exists because the review was collected. |
| `collectedAt` | Our own collection time. |

One row per product, `recordType: "product"`, free: `name`, `vendor`, `vendorId`, `category` (the first of `categories`), `categories`, `rating` (average stars out of 5), `reviewCount` (all reviews G2 counts), `url`, `profileComplete` (false when the product page did not load and vendor / categories are null), then `reviewsAvailable` (what G2 shows a signed-out visitor, at most 100) / `reviewsRequested` / `reviewsReturned` / `duplicatesSkipped`, and `partial` / `partialReason` (`upstream_request_cap`, `charge_limit_reached`, `blocked`, `upstream_error`, `not_collected`) with `statusText`.

One row per product URL, `recordType: "input_status"`, free: `outcome` is `found`, `not_found` (no such product on G2), `invalid_url` (not a G2 product URL) or `partial`, with `productSlug` and `statusText`.

The last row is `recordType: "RUN_SUMMARY"` — what the inputs resolved to, products and reviews collected and charged, products with no review, partial products by reason, requests sent (retries included) and why collection stopped. The same object is also in the run's key-value store under `RUN_SUMMARY`.

# Actor input Schema

## `productUrls` (type: `array`):

G2 product pages, one per line, for example https://www.g2.com/products/notion/reviews. The /reviews suffix is optional, and a bare product slug such as "notion" also works. A URL that is not a G2 product URL, or a product that no longer exists, gets its own status row and is not charged.

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

Upper bound on the reviews collected for each product. 0 collects only the product profile (rating, review count, vendor, categories). G2 shows a signed-out visitor at most 100 reviews (10 pages) per product in each order.

## `sort` (type: `string`):

Which reviews come first, and so which ones a limit keeps. These are G2's own orders.

## `maxUpstreamRequests` (type: `integer`):

Hard ceiling on the HTTP requests this run sends to G2, retries included. At the ceiling collection stops - what was not reached is written with partialReason "upstream_request_cap" and the run then ends normally. Leave empty and the Actor works a ceiling out from your input, so a run is never unbounded.

## Actor input object example

```json
{
  "productUrls": [
    "https://www.g2.com/products/notion/reviews"
  ],
  "maxReviewsPerProduct": 100,
  "sort": "most_recent"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `runSummary` (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 = {
    "productUrls": [
        "https://www.g2.com/products/notion/reviews"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/g2-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 = { "productUrls": ["https://www.g2.com/products/notion/reviews"] }

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/g2-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 '{
  "productUrls": [
    "https://www.g2.com/products/notion/reviews"
  ]
}' |
apify call lanternlab318/g2-reviews --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lanternlab318/g2-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/oekPiBXFsSb32mRet/builds/3EIe0fx6MiOHRVgf6/openapi.json
