# Google Search Scraper: SERP, Ads & AI Overview (`tortuga/google-search-scraper`) Actor

Scrape Google search results for any keyword and country: organic rank, title, URL, snippet, date, sitelinks, People also ask, related searches, AI Overview and ads. Rank tracking, SEO research.

- **URL**: https://apify.com/tortuga/google-search-scraper.md
- **Developed by:** [Trevor Ortega](https://apify.com/tortuga) (community)
- **Categories:** SEO tools
- **Features:** Limited permissions
- **Stats:** 2 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.08 / 1,000 search results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Google SERP Scraper: Rankings, Ads & AI Overview

Scrape **Google search results** for any keyword, country, language or city. For every search you get **one row
per organic result** (position, title, URL, displayed URL, domain, snippet, date, sitelinks), plus **one summary
row per results page** with **People also ask** questions, **related searches**, the **AI Overview** text and its
sources, **Top stories** and the list of SERP features Google showed. Turn on **ads** to get every text ad with its
landing URL, at no add-on fee.

Use it for SEO rank tracking, keyword and content research, competitor and brand monitoring, building URL and domain
lists, or feeding fresh Google results to an AI agent. No Google API key, no browser, no login.

### What data does it extract?

Every row has a `type` (`organic`, `serp` or `ad`), `searchTerm`, `page`, `country`, `language`, `googleDomain`,
`searchUrl` (the Google URL, to open in a browser) and `scrapedAt`.

| Field | Rows | Description |
|---|---|---|
| `position` | organic | Organic rank for the search, continuing across pages (page 2 starts after page 1's last result) |
| `positionOnPage` | organic | Rank on its own results page |
| `title` | organic, ad | Result title |
| `url` | organic, ad | Target URL. Ads: the landing URL, read from the page (ads are never clicked) |
| `displayedUrl` | organic, ad | The URL / breadcrumb Google prints under the title (`https://www.cnet.com › ... › VPN`) |
| `domain` | organic, ad | Host of the result (`www.cnet.com`), filled even when Google hides the URL |
| `siteName` | organic, ad | Site name Google shows (`CNET`, `Reddit · r/running`, `YouTube · Channel`), or the advertiser |
| `description` | organic, ad | Snippet text (without the date) |
| `date` | organic | Date Google shows with the result (`Oct 20, 2026`, `6 days ago`, `06.10.2026`) |
| `emphasizedKeywords` | organic | Words Google printed in bold in the snippet |
| `siteLinks` | organic, ad | Sitelinks under the result: `[{title, url, description}]` |
| `richSnippet` | organic | Extra line under some results: ratings, prices, views, comments (`4.7 (5,077)`, `330+ views · 1 year ago`) |
| `resultType`, `videoDuration` | organic | `web` or `video`; video length (`4:56`) |
| `urlSource`, `googleRedirectUrl` | organic | Where `url` came from: `page` (printed in the page) or `googleRedirect` (see FAQ); `googleRedirectUrl` is kept only if the URL could not be read |
| `adPlacement`, `adPosition` | ad | `top` or `bottom`, and the rank within that block |
| `organicResultsCount`, `adsCount`, `firstPosition`, `lastPosition` | serp | What the page contained |
| `peopleAlsoAsk` | serp | Questions in the "People also ask" box |
| `relatedSearches` | serp | `[{query, url}]` from "People also search for" |
| `aiOverview` | serp | `{text, sources: [{title, url}]}` when Google shows an AI Overview (the text rendered in the page) |
| `topStories` | serp | `[{title, source, publishedAt, dateText, section, url, googleRedirectUrl}]`: Top stories and "What people are saying" cards |
| `serpFeatures` | serp | Blocks on the page: `AI Overview`, `People also ask`, `Videos`, `Top stories`, `Ads`, ... |
| `hasNextPage` | serp | Whether Google offers another page |
| `detectedLocation` | serp | The location Google says it used, from the page footer, as Google words it: `{name, source}` (`Austin, Texas` / `From your IP address`, or `Unknown` / `Can't determine location`) |
| `changeType`, `previousValues` | organic (monitoring) | `new` or `changed`, and the old `position` |

#### Example output

An organic result:

```json
{
  "type": "organic",
  "position": 1,
  "positionOnPage": 1,
  "title": "Sourdough Bread: A Beginner's Guide",
  "url": "https://www.theclevercarrot.com/2014/01/sourdough-bread-a-beginners-guide/",
  "displayedUrl": "https://www.theclevercarrot.com › 2014/01 › sourdoug...",
  "domain": "www.theclevercarrot.com",
  "siteName": "The Clever Carrot",
  "description": "In a large bowl, add the sourdough starter, water and olive oil. Mix with a fork to combine, then add the flour and salt. Continue mixing until...",
  "date": "Oct 17, 2025",
  "emphasizedKeywords": ["In a large bowl, add the sourdough starter, water and olive oil"],
  "siteLinks": [],
  "richSnippet": "4.9 (1,444) · 14 hr",
  "resultType": "web",
  "videoDuration": null,
  "urlSource": "page",
  "googleRedirectUrl": null,
  "searchTerm": "how to make sourdough bread",
  "page": 1,
  "country": "us",
  "language": "en",
  "googleDomain": "google.com",
  "searchUrl": "https://www.google.com/search?q=how+to+make+sourdough+bread&hl=en&gl=us&uule=w%2BCAIQICINVW5pdGVkIFN0YXRlcw",
  "scrapedAt": "2026-11-05T15:17:44Z"
}
```

The SERP summary row of the same page (shortened):

```json
{
  "type": "serp",
  "organicResultsCount": 6,
  "adsCount": 0,
  "firstPosition": 1,
  "lastPosition": 6,
  "peopleAlsoAsk": [
    "What is the secret to making good sourdough bread?",
    "What is the biggest mistake beginners make with sourdough bread?",
    "What is the 3 ingredient sourdough?",
    "How difficult is sourdough bread to make?"
  ],
  "relatedSearches": [
    {"query": "Sourdough starter Kit", "url": "https://www.google.com/search?q=Sourdough+starter+Kit"},
    {"query": "How to make sourdough bread without starter", "url": "https://www.google.com/search?q=How+to+make+sourdough+bread+without+starter"}
  ],
  "aiOverview": {
    "text": "Classic artisan sourdough bread uses just four simple ingredients: active sourdough starter, bread flour, water, and sea salt. ...",
    "sources": [
      {"title": "Farmhouse on Boone", "url": "https://www.farmhouseonboone.com/beginners-sourdough-bread-recipe/"},
      {"title": "The Best Sourdough Bread Recipe, Video, watch on YouTube", "url": "https://www.youtube.com/watch?v=McqmnU1vVe4&t=6"}
    ]
  },
  "topStories": [],
  "serpFeatures": ["AI Overview", "Recipes", "People also ask", "Videos", "Short videos"],
  "hasNextPage": true,
  "searchTerm": "how to make sourdough bread",
  "page": 1
}
```

### How to scrape Google search results

1. Add one or more **search terms** (one per line). Google operators work: `site:example.com pricing`,
   `"exact phrase"`, `intitle:review`, `filetype:pdf`, `-word`, `after:2026-01-01`. You can also paste a full
   **Google search URL** (`https://www.google.co.uk/search?q=coffee&hl=en`); it keeps its own domain, country,
   language, start page and filters.
2. Pick the **country** and, if you want, the **interface language**, an **exact location** (city) and a
   **time period**.
3. Set **Results pages per search** (1 page = up to 10 organic results) and start the run.
4. Download the dataset as JSON, CSV, Excel or HTML, or read it through the Apify API. The dataset has three
   views: **Organic results**, **SERP summary** and **Ads**.

Input example:

```json
{
  "queries": ["best running shoes", "site:reddit.com marathon training plan"],
  "countryCode": "us",
  "maxPagesPerQuery": 3,
  "includeAds": true
}
```

### How much does it cost?

You pay **$0.08 per 1,000 rows** (`result` event: an organic result, an ad or a page's summary row) plus Apify's
platform usage, which is almost all the **Google SERP proxy**: Apify bills **$0.0025 per results page** on the Free
and Starter plans ($0.002 on Scale, $0.0017 on Business), plus about $0.0002 of compute per page.

A typical page (about 9 organic results + 1 summary row) costs about **$0.0035 in total** on the Free plan, about
22% less than the $0.0045 per page that the most used Google search actor charges on the Free plan, and ads are
included instead of being a $0.0065-per-page add-on. 1,000 keywords x 1 page ≈ $3.5.

### Why do some results come with `urlSource: googleRedirect`?

Google currently serves many results pages with encrypted redirect links (`/goto?url=...`) instead of the real
target URLs; which pages get them depends on the network route of each request. For those pages the actor asks
Google for each redirect's target (one small request per result, from the run's own IP, not through the paid
proxy) and fills `url` with it. In our test runs 199 of 204 organic results (97.5%) got their URL this way or
straight from the page.
If Google refuses a redirect, the row keeps `url: null`, the `domain` and `displayedUrl` from the page, and a
`googleRedirectUrl` that opens the result in a browser. Ads are never followed.

### Can I get results for another country, language or city?

Yes. **Country** sets the Google domain (google.de, google.co.uk, ...), `gl`, and a country-level location
(Google's `uule`), so Google ranks for that country instead of wherever Apify's proxy exits (a German search saved
without it was placed in Delhi). About 50 countries work through pasted Google URLs
(`https://www.google.com.ar/search?q=...`).

**Exact location** (best effort) sends Google's canonical location name (`Austin,Texas,United States`) for local
searches like "plumber near me". Google applies it on some requests only: in our tests 3 of 7 requests came back
with Austin results, the others with results from all over the US. The summary row's `detectedLocation` shows the
location Google reported (not a guarantee either). Ads are chosen by Google's ad system and can follow the proxy's
own location (a UK ad on a US search was seen).

### Why do two runs of the same search give different results?

Google does not serve one fixed ranking. In our tests, six requests for "best project management software" (US, one
minute apart) came back with two clearly different top-10s, three times each; Google's data centers and ranking
experiments differ by network route, and Apify's Google SERP proxy picks a new route for every request. That is
true for any tool that scrapes Google, and for real users. Treat a single run as a sample: for rank tracking, run
on a schedule and compare positions over several runs; `detectedLocation` on the summary row shows which location
Google said it used.

### Monitor changes on a schedule

Turn on **Only new or changed items** and save the input as a task with a Schedule (for example daily). The actor
remembers the results of earlier runs of the same search (term, country, language) and outputs only results that
are **new** in the search or whose **position changed**, so you get a lightweight rank tracker. Unchanged results
are not saved and not charged (the proxy cost per page still applies). Because Google's ranking varies between
requests (see above), expect some "new" results that are really route-to-route variation. Each row gets `changeType` (`new` or
`changed`) and, for moved results, `previousValues` with the old position. Use **Monitoring key** to keep several
schedules on the same search separate. Ads and summary rows are not output in this mode.

### Does it need a Google account or a browser?

No. It reads the same public results page anyone sees without logging in, through Apify's Google SERP proxy, and
parses the HTML. No CAPTCHA solving and no cookies from your account.

### Is it legal to scrape Google search results?

The actor collects what Google shows publicly to anyone: result titles, URLs, snippets, questions and ads. Some
results show public names (for example the author of a forum post or a news byline), which are exported as shown.
Check Google's terms and the laws that apply to you for your use case, and use the data responsibly.

### Limits

- Up to 10 organic results per page (Google no longer supports `num=100`); 6-10 is common because Google mixes in
  videos, forums and other blocks. Deeper pages often broaden the query (page 3 of "best vpn" is about "vpn").
- People also ask: questions only (Google loads the answers when you click).
- The AI Overview is the text Google renders in the page; very long overviews can be cut where Google shows "Show
  more". AI Mode answers are not collected.
- Knowledge panels, local map packs, shopping and image carousels are listed in `serpFeatures` but not parsed.
- Google changes its page layout often; the daily health check of this actor watches for it.

### Related scrapers

- [Google Autocomplete Scraper](https://apify.com/tortuga/google-autocomplete-scraper): Keyword Ideas, YouTube & Bing
- [Google Trends Scraper](https://apify.com/tortuga/google-trends-scraper): Interest, Regions, Queries & Trending
- [Google News Scraper](https://apify.com/tortuga/google-news-scraper): Articles & Headlines
- [Google Images Scraper](https://apify.com/tortuga/google-images-scraper): Full-Size Image URLs & Sources
- [Google Shopping Scraper](https://apify.com/tortuga/google-shopping-scraper): Prices & Offers

### Found it useful?

A ★ review on this Actor's Apify Store page helps other users find it and tells us what to improve. Issues and feature requests are welcome in the Actor's **Issues** tab.

# Actor input Schema

## `queries` (type: `array`):

One Google search per line: a search term as you would type it on Google ("best running shoes", "site:example.com pricing", ""your brand" reviews") or a full Google search URL such as https://www.google.co.uk/search?q=coffee\&hl=en. Google operators (site:, intitle:, filetype:, quotes, -word, after:2026-01-01) work. A URL keeps its own domain, country (gl), language (hl), start page and filters.

## `maxPagesPerQuery` (type: `integer`):

How many Google results pages to read per search term (page 1, 2, 3, ...). A page has up to 10 organic results (often 6-10, because Google mixes in videos, forums and other blocks). Each page is one request through Apify's Google SERP proxy. The actor stops earlier when Google has no next page.

## `countryCode` (type: `string`):

Country whose Google results you get: sets the Google domain (google.co.uk for United Kingdom, ...), Google's gl parameter and a country-level location, so results do not depend on where the proxy happens to exit. For another country, paste a Google search URL with its domain or gl= (e.g. https://www.google.com.ar/search?q=...\&gl=ar; about 50 countries are supported that way).

## `languageCode` (type: `string`):

Google interface language, e.g. en, de, fr, es, pt-BR. It changes snippets, dates and labels and can change rankings. Leave empty to use the country's main language (English for the US, UK, India, ...).

## `location` (type: `string`):

Best effort: search as if you were in a city or region, using Google's canonical location name: "Austin,Texas,United States", "Manchester,England,United Kingdom", "Munich,Bavaria,Germany" (sent as Google's uule parameter). Google applies it on some requests only (local results on 3 of 7 test requests for "plumber near me"); the summary row's detectedLocation shows the location Google reported. Leave empty for the whole country.

## `timePeriod` (type: `string`):

Only results Google dates within this period (Google's Tools > Any time filter).

## `searchLanguage` (type: `string`):

Optional: only pages written in this language (Google's lr parameter), e.g. de, fr, en. Leave empty for all languages.

## `includeSerpFeatures` (type: `boolean`):

Add one row per results page (type 'serp') with People also ask questions, related searches ("People also search for"), the AI Overview text and its sources, Top stories / posts, the list of SERP features Google showed and how many organic results and ads the page had. Charged as one result. Not output in monitoring mode.

## `includeAds` (type: `boolean`):

Also output Google's text ads (type 'ad'): placement (top/bottom), position, title, advertiser, displayed URL, domain, landing URL and description. Each ad is one result (no add-on fee). Ads are read from the page, never clicked.

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

Stop after this many rows in total (organic results, ads and SERP summary rows of all searches). You pay per row, so this caps the cost.

## `safeSearch` (type: `boolean`):

Filter explicit results (Google's safe=active).

## `includeUnfilteredResults` (type: `boolean`):

Show the results Google hides as 'very similar to the ones already displayed' (filter=0).

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new items or items whose price/status changed. Use with a Schedule; unchanged items are not charged. For Google: a result is 'new' when its URL was not in earlier runs of this watch (same term, country and language) and 'changed' when its position moved; previousValues holds the old position. Ads and SERP summary rows are not output in this mode.

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input

## `proxyConfiguration` (type: `object`):

Google results pages load only through Apify's Google SERP proxy (group GOOGLE_SERP), which Apify bills per results page: $0.0025 per page on the Free and Starter plans ($0.002 on Scale, $0.0017 on Business). The actor always uses it. Resolving Google's redirect links uses the run's own IP (free) and, as a fallback, Apify datacenter proxy.

## Actor input object example

```json
{
  "queries": [
    "best running shoes"
  ],
  "maxPagesPerQuery": 1,
  "countryCode": "us",
  "timePeriod": "any",
  "includeSerpFeatures": true,
  "includeAds": false,
  "maxItems": 1000,
  "safeSearch": false,
  "includeUnfilteredResults": false,
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "GOOGLE_SERP"
    ]
  }
}
```

# Actor output Schema

## `results` (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 = {
    "queries": [
        "best running shoes"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "GOOGLE_SERP"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/google-search-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 = {
    "queries": ["best running shoes"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["GOOGLE_SERP"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/google-search-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 '{
  "queries": [
    "best running shoes"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "GOOGLE_SERP"
    ]
  }
}' |
apify call tortuga/google-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tortuga/google-search-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/MYuMwyg9J9C3djbYF/builds/bRufNUqdl1CjgktMd/openapi.json
