# Google Jobs Scraper - Adverts, Salaries and Apply Links (`s-r/google-jobs-scraper`) Actor

Read Google's jobs results as a table. One row per advert with the company, the location, the board it came from, the posted date, the salary where Google has one, the full description and every site the job can be applied on.

- **URL**: https://apify.com/s-r/google-jobs-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Google Jobs Scraper

Google's jobs results as a table. Give it a job title and a city and you get
one row per advert: the company, the location, the board the advert came from,
how long ago it was posted, the salary where Google has one, the full job
description, and every site the job can be applied on.

The reason to read jobs through Google rather than one board at a time is that
Google has already merged them. A single advert here typically carries six to
eight apply links, one per board that listed it, so you see LinkedIn, Indeed,
the company's own careers page and four aggregators in one row instead of
deduplicating four scrapes by hand.

### Ten jobs per search, and why that is not a setting

This is the one limit to plan around. A Google jobs search returns ten adverts
and there is no page two: the surface does not page, and asking for more
returns nothing rather than the next ten. Measured, twice, on the same query.

So the way to a bigger dataset is more searches, and the input is shaped for
exactly that. **Job searches** takes a list, **Locations** takes a list, and
every search runs once per location. Four titles across five cities is twenty
queries and up to two hundred adverts.

That works in your favour more than it sounds: "python developer" in Austin and
"python developer" in Denver return genuinely different companies, where page
two of a single search would have returned more of the same.

### What you get per advert

- `title`, `company`, `location`, `via`, `posted`, `employment_type`
- `salary_text`, `salary_min`, `salary_max`, `salary_currency`, `salary_period`
- `description` — the advert's own text
- `qualifications`, `responsibilities`, `benefits` — Google's own extraction
  from the original post, as three separate lists
- `apply_options` — every board the job can be applied on, with its link
- `share_url`, `company_logo`, `job_id`
- `search`, `search_location`, `market`, `language`, `position`

`via` is worth a second look. It names the board Google took the advert from,
which is where the job actually lives, and it is often the company's own
careers site rather than an aggregator. Filtering on it is the quickest way to
separate first-party postings from reposts.

`qualifications` and `responsibilities` are the fields that make this cheaper
than reading the boards yourself. Google has already pulled the requirements
out of the prose, so a screen for "3+ years" or "Kubernetes" is a filter on a
list rather than a search through paragraphs.

### Salary is present on a minority of adverts

Two adverts in ten carried a salary on the page this was measured against, and
that is Google's doing rather than a gap here. The rest genuinely do not publish one. `salary_text` keeps the
line exactly as advertised because the same figure gets written a dozen ways
across boards, and the parsed `salary_min` / `salary_max` / `salary_period`
sit next to it as a convenience. When the two disagree, trust the text.

`posted` is similar: Google dates some adverts and leaves others undated. A
null means undated, not missed.

### Apply links

Every apply link comes back as Google's own redirect. Turn on **Resolve apply
links** to follow each one through to the board it lands on and get the real
URL instead, capped by **Maximum apply links to resolve**.

It is off by default because it costs an extra request per link and adverts
carry up to eight of them, which on a forty-advert run is a couple of hundred
requests to learn what the board's name already told you. Turn it on when you
are going to open the links, leave it off when you are analysing the market.

### Market and language

**Country** is the market the search is made for and **Language** is what the
page comes back written in. They are separate on purpose: an English-language
search of the German market is a normal thing to want, and pinning both to `de`
would silently change the results as well as the wording.

Job results are regional, so the same title returns different companies per
market. Berlin, London, Toronto, Chicago and Singapore all returned a full ten
adverts when this was measured. Some city-and-title pairs return nothing at
all, which the run reports as `queriesWithNoJobs` rather than as an error, so
check that number before concluding a market is unavailable.

If you are comparing two countries, run them as two runs and keep `market` on
every row, which it already is.

### Reading the run summary

`summary` in the key-value store is worth a glance after every run:

- `queriesSent` against `jobs` tells you the yield per search
- `queriesWithNoJobs` is how many searches Google answered with nothing
- `requestsRetried` is how much work the run spent getting served; a high
  number against a healthy `jobs` count is normal, a high number against zero
  jobs is not
- `withSalary` says how much of the salary column is actually populated before
  you build anything on it

### Errors

| Code | Meaning |
|---|---|
| `blocked` | The search could not be read after every attempt |
| `bad_input` | No search term was given |
| `no_sessions` | The run has no Google session configured |

A search Google answered with **no jobs** is not an error and does not appear
here. It is counted in the run summary as `queriesWithNoJobs`, because a search
nobody is hiring for and a search that was refused look identical if you only
count rows, and only one of them is worth running again.

Raise **Attempts per query** if you see `blocked`. Google serves this surface
to a fraction of attempts, so retries are normal and the default clears it
comfortably.

### Related actors

For the boards themselves, see the LinkedIn job actors and the XING job scraper
for the German market. For the companies behind the adverts, the LinkedIn
company actors.

# Actor input Schema

## `search` (type: `array`):

One search per line, written the way you would type it into Google, such as "react developer" or "warehouse manager".

## `locations` (type: `array`):

One location per line. Each search is run once per location, so three searches and four cities is twelve queries. Leave empty to let Google decide.

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

Stop after this many jobs. Also the cost ceiling for the run.

## `language` (type: `string`):

Two-letter interface language, such as en, nl or de.

## `region` (type: `string`):

Two-letter country code the search is made for. Job results are regional, so the same search returns different adverts per market.

## `resolveApplyLinks` (type: `boolean`):

Follow each apply link through to the board it lands on. Off by default because it costs one extra request per link, and adverts carry up to eight.

## `maxApplyLinksResolved` (type: `integer`):

The ceiling on that extra work when the option above is on.

## `attempts` (type: `integer`):

How often to retry a query before giving up on it. Roughly one attempt in three comes back with a page, so a handful of retries is normal and the default clears it comfortably.

## Actor input object example

```json
{
  "search": [
    "product manager"
  ],
  "locations": [
    "Amsterdam",
    "Berlin"
  ],
  "maxItems": 100,
  "language": "nl",
  "region": "nl",
  "resolveApplyLinks": false,
  "maxApplyLinksResolved": 60,
  "attempts": 15
}
```

# Actor output Schema

## `jobs` (type: `string`):

One row per advert.

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

Queries sent, jobs returned, how many carried a salary, and how many searches Google answered with nothing.

## `errors` (type: `string`):

Searches that could not be read.

# 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 = {
    "search": [
        "software engineer"
    ],
    "locations": [
        "New York"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/google-jobs-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 = {
    "search": ["software engineer"],
    "locations": ["New York"],
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/google-jobs-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 '{
  "search": [
    "software engineer"
  ],
  "locations": [
    "New York"
  ]
}' |
apify call s-r/google-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/google-jobs-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/2NbGCGNQhndhkaxwO/builds/2qVa8VsBEGyPstIPu/openapi.json
