G2 Reviews
Pricing
from $2.00 / 1,000 per-run start fees
G2 Reviews
Give it a G2 product slug and get that product's reviews as rows: title, full body, star rating, pros, cons, date and the source URL. Page back through the review history and cap the count per run. G2 blocks unauthenticated requests, so bring your own proxy.
G2 Reviews Scraper
Pull real customer reviews for any software product listed on G2 and get them back as clean, structured rows: who wrote the review, what they do for a living, how big their company is, the star rating they gave, the headline, the full text split into what they liked and what they did not, and the date. Point it at a product, say how many reviews you want, and press start.
G2 is where B2B software buyers go before they buy, which makes its review corpus the closest thing the category has to a public record of what customers actually think. This Actor turns that record into a dataset you can query, chart, feed into a model, or drop into a spreadsheet.
What you get
One row per review, de-duplicated, with these fields:
| Field | What it holds |
|---|---|
review_id | G2's own identifier for the review. Stable across runs. |
review_url | Direct link to that single review on G2. |
title | The headline the reviewer wrote. |
rating | Their star rating, on a 0 to 5 scale. Half stars occur, and 0 is a real rating. |
rating_scale_min, rating_scale_max | Always 0 and 5, stated so you never have to guess the scale. |
reviewer | Display name. Anonymous reviewers appear as "Verified User in ...". |
role | Their job title, when they chose to show it. |
industry | Their industry, when they chose to show it. |
company_size | Small-Business, Mid-Market or Enterprise, with the headcount band. |
pros | Their answer to "What do you like best?" |
cons | Their answer to "What do you dislike?" |
problems_solved | Their answer to "What problems is this solving for you?" |
body | The three answers joined, for when you just want the text. |
date_published | Full timestamp, ISO 8601. |
date_displayed | The date exactly as G2 prints it on the card. |
product_name | The product's name as G2 states it. |
product_rating | The product's overall score, on a 0 to 10 scale. |
product_rating_best | Always 10, for the same reason as above. |
product_review_count | How many reviews G2 holds for the product in total. |
view, page, source_url | Where the row came from, so any row is reproducible. |
About those two scales
G2 publishes two different numbers that both look like ratings, and mixing them up is the most common mistake in G2 data. Each individual review is scored out of 5. The product's headline score is out of 10. A product showing 9.1 is not a 9.1-star product, and a review showing 4.5 is not 4.5 out of 10. This Actor reports both exactly as G2 states them and ships the scale bounds alongside each one, so nothing downstream has to infer it.
The review scale starts at 0, not at 1. Zero-star and half-star reviews
are real and they cluster in the lowest-satisfaction listing, which is
precisely the listing anyone doing a weakness analysis will select. Filtering
on rating >= 1 would silently drop them.
Input
| Field | Type | Default | Meaning |
|---|---|---|---|
productSlug | string, required | slack | The product's identifier, taken from its own G2 address. For https://www.g2.com/products/slack/reviews the slug is slack. |
maxReviews | integer | 100 | How many distinct reviews to return, at most. |
views | array | ["default"] | Which listings to walk. See below. |
region | string | empty | Optional preferred region. Leave empty unless you have a reason. |
Getting more than 100 reviews
G2's review listing caps out at 100 reviews for any one ordering. That is a property of G2, not of this Actor, and no scraper can page past it on a single listing. What you can do is read a different listing of the same product, which holds its own 100 and largely different reviews.
Four listings are supported, and you can select any combination:
- Default ordering, G2's own sort.
- Newest first, which surfaces reviews written in the last weeks.
- Highest rated first, which surfaces the strongest advocates.
- Lowest satisfaction scores, which surfaces the unhappiest customers.
Selecting all four gives you up to 400 distinct reviews for one product.
Overlap between listings is removed for you, so maxReviews: 400 means 400
different reviews, not 400 rows with repeats in them. In our own runs, the
newest-first listing overlapped the default ordering by about 6 per cent and
the two rating-based listings barely overlapped it at all, so the fourth
listing is still adding new material rather than repeating the first.
If the product has fewer reviews than you asked for, you simply get what exists and the run reports why it stopped. Nothing is padded.
Output and the run summary
Reviews go to the dataset. The run also writes a summary record that tells you what happened, which matters more here than it does for most scrapers:
{"productSlug": "slack","status": "ok","reviewsReturned": 200,"productName": "Slack","productReviewCount": 39649,"viewsWalked": [{"view": "default", "pagesFetched": 10, "rowsNew": 100, "stoppedBecause": "view ceiling reached"},{"view": "most_recent", "pagesFetched": 10, "rowsNew": 94, "rowsDuplicate": 6, "stoppedBecause": "view ceiling reached"}],"dataTrustworthy": true}
status is the field to check. It is one of:
ok— reviews were returned.no_reviews— the product exists and genuinely has none matching your selection.not_found— there is no product at that slug. Check the slug against the product's own G2 address.blocked— the source did not serve this run. No conclusion should be drawn about the product from this.failed— the run could not start properly, usually a configuration problem.
That distinction is deliberate. A run that returns zero rows because a product
has no reviews and a run that returns zero rows because the source refused it
look identical if you only count rows, and they mean opposite things. This
Actor never lets the second one masquerade as the first: dataTrustworthy
tells you in one boolean whether the emptiness is a fact about the product.
The summary also reports rowsMissingReviewer, rowsMissingRating and
rowsMissingBody. On a healthy run those are zero or near it. If they are
large, the rows are suspect and you will know before you use them.
Worked examples
A quick look at one product.
{"productSlug": "notion", "maxReviews": 25} returns 25 recent reviews in a
single short run.
A full competitive read.
{"productSlug": "hubspot-crm", "maxReviews": 400, "views": ["default", "most_recent", "highest_rated", "lowest_nps"]}
returns up to 400 distinct reviews spanning both the advocates and the
detractors, which is the shape you want for a win-loss analysis or a
positioning deck.
Tracking sentiment over time.
Run with views: ["most_recent"] on a schedule and de-duplicate on
review_id in your own store. Each run brings back whatever is new.
Finding what to fix.
views: ["lowest_nps"] and read the cons column. It is the shortest path
from a review corpus to a product backlog.
Notes and limits
- The 100-per-listing ceiling is G2's. Four listings is the honest maximum for a single product, which is 400 reviews.
- Very large products have far more reviews than 400. This Actor does not
claim otherwise, and
product_review_counttells you the real total so you can see the gap yourself. - Reviewer job title and industry are optional on G2. Roughly half of all reviewers leave one or both blank, and anonymous reviewers leave the name blank too. Blank means the reviewer withheld it, not that the Actor missed it.
- Reviews are returned as written, including typos and formatting. Nothing is rewritten, summarised or scored.
- Free-plan Apify accounts receive the first 10 rows of each run. Paid plans are uncapped.
Operations
The Actor replays a session that is created outside the platform and handed
to it, because the platform's own browser is refused the session every time.
Three values travel together and the Actor refuses a partial set: the session
cookie, the user agent of the browser that created it, and the address
reference it was created through (region:id). They can be attached to the
Actor version as secret environment variables (G2_MINT_COOKIE,
G2_MINT_USER_AGENT, G2_MINT_PROXY_SESSION) or supplied on the input under
"Advanced", where the input wins.
To refresh the session, attach it and rebuild in one go:
set -a && source ~/.apify-agents.env && set +a~/CascadeProjects/scraping_script/.venv/bin/python scripts/g2_mint_session.py --attach
Things to know:
- A session lives for a limited time and a limited number of requests, and the
run summary says which of the two ran out (
status: blocked, with the reason in theerrorsrecord). See the lifetime measured below. credentialSourcein the summary says whether the run used the supplied session (preminted) or fell back to its own browser (browser). On the platform the fallback does not work, sobrowserthere means the session was not attached.- A redeploy with
scripts/deploy.pyreplaces the version's environment variables and drops the three session values. Run the refresh command again after every deploy. - The address reference is only valid for as long as the gateway keeps that address; a session whose address has gone reports itself as expired.