# G2 Category Scraper — Ranked Software Listings (`thenetaji/g2-category-scraper`) Actor

Export a software category in its ranked order, with every product's name, rank, aggregate rating, review count and price band. Each row also carries what reviewers praise and complain about, summarised — a field published on the category listing and on no product page.

- **URL**: https://apify.com/thenetaji/g2-category-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Business, Marketing, Automation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.40 / 1,000 product listeds

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

## G2 Category Scraper

A software category's ranked listing, exported as a spreadsheet of products. Each row
carries the product's rank within the category, its name and slug, its aggregate rating and
review count, the published price band, its logo and its page address — and the two fields
that make the listing worth reading in its own right: what reviewers praise the product for,
and what they complain about, each summarised as a short list of traits.

Those two summaries are published on the category listing and on no product page. A run that
opens every product individually will not find them.

### Accepted input

`categorySlugs` is required and takes one or more category slugs, one per line, such as
`crm`. A pasted category page link works too and is reduced to the slug inside it. A slug is
lowercase and carries no spaces and no query string; the page to start from is a separate
field rather than something appended to the slug, because a slug with `?page=2` on the end
would be reduced to the plain slug and quietly serve page 1.

`maxItems` bounds each category separately and defaults to `45`. Products arrive fifteen to
a page, so `45` is three pages per category and `0` removes the bound. The cap is per
category rather than per run because a run given five categories is asking for a comparable
slice of each; a single shared cap would return the first category and nothing else.

`startPage` sets the page each category begins at and defaults to `1`. It resumes a walk
that stopped and splits a large category across several runs.

```json
{
  "categorySlugs": ["crm", "marketing-automation"],
  "maxItems": 45,
  "startPage": 1
}
```

### Response fields

```json
{
  "category_slug": "crm",
  "category_name": "Best CRM Software",
  "category_url": "https://www.g2.com/categories/crm",
  "page": 1,
  "position": 1,
  "name": "Pipedrive",
  "slug": "pipedrive",
  "url": "https://www.g2.com/products/pipedrive/reviews",
  "image": "https://images.g2crowd.com/uploads/product/image/pipedrive.png",
  "application_category": "BusinessApplication",
  "rating": 4.3,
  "rating_best": 5,
  "review_count": 3118,
  "rating_count": 3118,
  "liked_for": ["Ease of Use", "Features", "Lead Management"],
  "disliked_for": ["Learning Curve", "Expensive"],
  "price_currency": "USD",
  "price_low": "25",
  "price_high": "330",
  "price_offer_count": 4
}
```

Fields absent from a listing are returned as null rather than omitted, so every row has the
same shape. `liked_for` and `disliked_for` are empty lists rather than null when G2 states
none.

`slug` is the key. It is the exact value
[G2 Reviews Scraper](https://apify.com/thenetaji/g2-reviews-scraper) accepts, which is what
makes the two Actors compose: a category is walked here, and any product's reviews are then
read there without a link ever being constructed. That is not a convenience. G2 slugs run to
`agentforce-sales-formerly-salesforce-sales-cloud`, so a slug guessed from a product's name
is a slug that does not exist, and a run built on twenty guesses returns nothing while
looking like a fault.

`rating` is stated on the scale `rating_best` names, and it is not rescaled. On a category
listing that scale is 5. On a product's own review page the same aggregate is stated out of
10\. Both are G2's own numbers, which is why the top of the scale travels beside every rating
in this family rather than being assumed.

`position` is the rank within its own page, counting from 1. Page 2 therefore starts again
at 1, and `page` is the field that separates them.

`price_low` and `price_high` are strings, because that is how the price band is stated. A
product with no published pricing — common for enterprise software — returns nulls rather
than zeros.

### Behaviour on partial results

A category slug that G2 serves nothing for is logged and skipped, and the run continues with
the next one. A category that exists and lists no products returns a successful run with no
rows for it.

The walk over a category ends when a page comes back with fewer than fifteen products, and
that is the only signal there is: this listing states no total and publishes no next
control. Pages do not overlap — measured, page 2 of a category held fifteen products, none
of them on page 1 — so a full page is genuinely fifteen new products and the walk continues.

### Frequently asked questions

**Why did the first row take so much longer than the rest?**
Because the first request of a run is where G2 checks the connection before it will serve
anything, and that check takes time. Measured on 2026-08-22, a first category read answered
in 23.1 seconds and the read that followed it answered in 15.5. A run that appears to sit
still for twenty seconds before its first row is behaving normally, not stalling.

**How many products does one category hold?**
G2 does not say, and neither does this Actor. There is no stated total anywhere on the
listing, so the honest answer is that a category is as long as it turns out to be: the walk
asks for the next page until one comes back short. Setting `maxItems` to `0` walks a
category to its end.

**Why is `rating_best` sometimes 5 and sometimes 10?**
Because G2 states the same product's aggregate on two different scales depending on where it
is published — 5 on a category listing, 10 on the product's own review page — and an
individual review is stated out of 5 alongside a product aggregate out of 10 in one
response. Nothing here is rescaled, because rescaling would publish a figure G2 never
stated. Divide `rating` by `rating_best` for a comparable number.

**Where do `liked_for` and `disliked_for` come from, and why are they empty on some rows?**
They are G2's own summary of what reviewers say, derived across all of a product's reviews
and published on the category listing. They are empty when G2 states none, which happens on
products with few reviews. They are not available from a product's own pages, so a category
run is the only way to collect them.

**Can a product be found without knowing its category?**
Not through this Actor. A category listing is the discovery surface here, and the
`related_categories` a category links to are printed in the run log at the start of each
category so the next slug to try is visible without another request. Once a product's slug
is known, [G2 Reviews Scraper](https://apify.com/thenetaji/g2-reviews-scraper) takes it
directly.

**Is a G2 account, cookie or API key required?**
No. No account, session cookie or key of any kind is supplied to the Actor or needed by it.

### Related Actors

[G2 Reviews Scraper](https://apify.com/thenetaji/g2-reviews-scraper) reads any product's
reviews from the `slug` on any row here, with the reviewer's job title and company size band
on every review and the three questions behind each one kept apart. It is the evidence
behind the summaries this Actor publishes.

# Actor input Schema

## `categorySlugs` (type: `array`):

Software category slugs, one per line — for example crm. A pasted page link works too and is reduced to the slug inside it. Slugs are lowercase and can be long, so copy them rather than typing them.

## `startPage` (type: `integer`):

Which page each item in the list starts from. It resumes a walk that stopped, and it splits a long product across several runs.

## `maxItems` (type: `integer`):

Maximum products to save for each category in the list. Set 0 for no limit. Products come 15 to a page, so 45 is three pages per category.

## Actor input object example

```json
{
  "categorySlugs": [
    "crm"
  ],
  "startPage": 1,
  "maxItems": 15
}
```

# Actor output Schema

## `dataset` (type: `string`):

All records scraped by this run

# 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 = {
    "categorySlugs": [
        "crm"
    ],
    "startPage": 1,
    "maxItems": 15
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/g2-category-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 = {
    "categorySlugs": ["crm"],
    "startPage": 1,
    "maxItems": 15,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/g2-category-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 '{
  "categorySlugs": [
    "crm"
  ],
  "startPage": 1,
  "maxItems": 15
}' |
apify call thenetaji/g2-category-scraper --silent --output-dataset

```

## MCP server setup

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