# Fake Google Reviews Check - Suspicious Reviews per Business (`that_red_bird/fake-google-reviews-detector`) Actor

Score a business's Google reviews for fake patterns: review bursts, one-review accounts, copy-paste text and sudden rating jumps. Works on any Google Maps reviews dataset.

- **URL**: https://apify.com/that_red_bird/fake-google-reviews-detector.md
- **Developed by:** [mohamed alaya](https://apify.com/that_red_bird) (community)
- **Categories:** Marketing, SEO tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 business scoreds

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

## Fake Google Reviews Check - Suspicious Reviews per Business

Give it the dataset of a Google Maps reviews scraper and it scores every business from 0 to 100 for suspicious review patterns: bursts of reviews in a few days, one-review accounts, copy-paste text and sudden rating jumps, with the reasons in plain words.
Made for local-SEO agencies, reputation-management shops and anyone vetting a business or a competitor's reviews.

It is a **suspicion score**, not a verdict. It does not scrape Google and cannot tell you that a review is fake; it shows where the numbers look unusual so you know where to read closer.

### Quick start (3 copy-paste examples)

**1. See it work (empty input):** run with `{}`. You get three labelled synthetic businesses (one organic, one with a bought 5-star burst, one with a small copy-paste cluster), free.

**2. Score a scraper's output:** run a Google Maps reviews scraper (for example `compass/Google-Maps-Reviews-Scraper`), then pick its dataset in the "Reviews dataset" field:

```json
{ "datasetId": "YOUR_DATASET_ID", "minReviews": 20, "includeReviewRows": true }
```

**3. Paste reviews (CSV or JSON), one business:**

```json
{ "businessName": "My Competitor", "rows": "stars,text,publishedAtDate,reviewerNumberOfReviews,isLocalGuide\n5,Great service highly recommend,2026-09-01T10:00:00Z,1,false\n..." }
```

### Use it with (pipelines)

- Google Maps reviews scraper -> this Actor. Reads one row per review: `name`, `reviewerId`, `reviewerNumberOfReviews`, `isLocalGuide`, `stars`, `text`, `publishedAtDate` (or `publishAt`), `likesCount`, `responseFromOwnerText`, `placeId`, `title`.
- Google Maps places scraper with reviews switched on (rows with a nested `reviews` array) -> this Actor. Same review fields inside each place.
- Any CSV with columns named like the ones above.

Field names come from the scrapers' public output documentation and common CSV exports. They have **not been verified against a fresh real export** (see limits). If your scraper uses other names, rename the columns.

### Input guide

| Field | What it does |
|---|---|
| Reviews dataset | Dataset picker (read-only). Reviews are grouped per business by `placeId`, then `title`. |
| Or paste reviews | JSON, JSON lines or CSV, one row per review. |
| Business name | Label for pasted rows that have no `placeId` or `title`. |
| Minimum reviews | Businesses below this get a free "not enough reviews" row (default 8). |
| Add a report row | One summary row with the ranking and the weights (separately priced). |
| Also output flagged reviews | Free rows for each review with at least one flag. |
| Maximum businesses | Safety cap (default 200). |

### How the score works (weights are in every output row)

Each signal has a weight and a strength from 0 to 1 that rises between a "normal" and an "extreme" level.

| Signal | Weight | Normal -> extreme |
|---|---|---|
| Share of reviewers with only one review | 20 | 35% -> 70% |
| Review burst: densest 7-day window (needs exact dates) | 22 | 15% -> 60% of reviews |
| Near-duplicate text (word 3-gram overlap 60%+, 6+ words) | 20 | 3% -> 25% of written reviews |
| Short generic praise ("great service", "highly recommend") | 10 | 10% -> 50% |
| Five-star reviews with no text | 6 | 50% -> 90% |
| Reviewers who are not Local Guides | 6 | 60% -> 95% |
| Rating jump: last 90 days vs before | 8 | +0.3 -> +1.2 stars |
| Share of five-star ratings | 8 | 80% -> 97% |

Score = sum(weight x strength) / sum(weight of the signals that could be measured) x 100, plus a convergence bonus (+8 for each signal beyond the second that is at least half-way to its extreme, max +16) and a peak bonus (up to +15 when one of the three heavy signals is at its extreme). Signals whose fields are missing are skipped and listed in `signalsNotMeasured`; with fewer than 3 measurable signals the score is capped at 40.
Levels: 0-24 low, 25-49 moderate, 50-74 high, 75+ very high suspicion. Confidence is high with 5+ signals and 30+ reviews.

### Output

| Field | Meaning |
|---|---|
| `business`, `placeId`, `reviewsAnalyzed` | Who and how many reviews. |
| `suspicionScore`, `level`, `confidence` | The 0-100 score and its label. |
| `topReasons` | Up to 4 plain-language reasons. |
| `signals` | Per signal: value, weight, strength, points, detail. |
| `signalsNotMeasured` | Signals skipped and why. |
| `sampleReviews` | Up to 5 reviews with the most flags (reviewer, stars, date, text start, flags). |

The last row (`type: "report"`) ranks the most suspicious businesses and repeats the weights.

Sample row (shortened):

```json
{ "type": "business", "business": "Demo Plumbing Pros (synthetic, bought 5-star burst)", "reviewsAnalyzed": 78, "suspicionScore": 79,
  "level": "very high suspicion", "confidence": "high",
  "topReasons": ["22 reviews are near-copies of each other", "45 of 78 reviews landed inside one 7-day window (from 2026-08-12)", "rating jumped by 0.8 stars in the last 90 days"] }
```

### Price

Pay per event: **$0.003 per business scored** and **$0.01 for the optional report**. Businesses with too few reviews and the free demo are never charged.
Example: 200 businesses with the report = 200 x $0.003 + $0.01 = **$0.61**.

### FAQ

**Can it prove a review is fake?** No. A legitimate viral moment can also create a burst, and real people write short praise. The score only shows patterns worth a closer look.

**Why was a business not scored?** It had fewer reviews than "Minimum reviews", or the rows had no stars or text.

**Do I need exact dates?** For the burst signal, yes (`publishedAtDate`). Relative dates such as "3 weeks ago" are used only for the rating-jump signal.

### Honest limits

- Heuristics only: no account verification, no photo analysis, no knowledge of which reviews Google already removed. The scrapers' review lists may be capped (often the newest N), which can hide older patterns.
- Tested only on synthetic data whose ground truth I controlled (60 generated businesses: 30 organic, including 6 with a legitimate viral burst; 30 manipulated, half obvious, half subtle). At the 25+ cut: precision 96%, recall 73%. At 50+: precision 100%, recall 43%; subtle manipulation is mostly missed. The same author wrote generator and scorer, so this is a consistency check, not an accuracy measurement on real Google reviews.
- Near-duplicate checking looks at the first 800 written reviews of a business.

# Actor input Schema

## `datasetId` (type: `string`):

Pick the dataset of a Google Maps reviews scraper run (for example compass/Google-Maps-Reviews-Scraper) or a Google Maps places scraper run that includes reviews. Reviews are grouped per business by placeId or title. Leave empty to use pasted rows below, or to see the built-in demo.

## `rows` (type: `string`):

Paste a JSON array, JSON lines or CSV with one row per review. Useful columns: stars, text, publishedAtDate, name or reviewerId, reviewerNumberOfReviews, isLocalGuide, placeId, title. Ignored when a dataset is picked above.

## `businessName` (type: `string`):

Label used when your rows have no placeId or title column, for example when you paste the reviews of a single business.

## `minReviews` (type: `integer`):

Businesses with fewer reviews than this get a 'not enough reviews' row that is not charged. Scores on very few reviews are unreliable. Minimum 5.

## `includeReport` (type: `boolean`):

Add one summary row at the end: ranking of the most suspicious businesses, level counts and the weights behind every score. This is a separately priced event.

## `includeReviewRows` (type: `boolean`):

Add one free row per review that has at least one suspicion flag (single-review account, inside a burst, near-duplicate text and so on). Can make the dataset large.

## `maxBusinesses` (type: `integer`):

Safety cap on how many businesses are scored in one run.

## Actor input object example

```json
{
  "rows": "",
  "businessName": "",
  "minReviews": 8,
  "includeReport": true,
  "includeReviewRows": false,
  "maxBusinesses": 200
}
```

# Actor output Schema

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

No description

## `downloadCsv` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `count` (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 = {
    "rows": "",
    "businessName": "",
    "minReviews": 8,
    "includeReport": true,
    "includeReviewRows": false,
    "maxBusinesses": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("that_red_bird/fake-google-reviews-detector").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 = {
    "rows": "",
    "businessName": "",
    "minReviews": 8,
    "includeReport": True,
    "includeReviewRows": False,
    "maxBusinesses": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("that_red_bird/fake-google-reviews-detector").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 '{
  "rows": "",
  "businessName": "",
  "minReviews": 8,
  "includeReport": true,
  "includeReviewRows": false,
  "maxBusinesses": 200
}' |
apify call that_red_bird/fake-google-reviews-detector --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,that_red_bird/fake-google-reviews-detector"
        }
    }
}
```

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/12yxAHAneSnFh52JG/builds/9zdj5M45tbo7tO8V1/openapi.json
