# Google Jobs Scraper $3/1K — Salary, Apply Links, Full Text (`oswaldocarabano/google-jobs-scraper`) Actor

Scrape Google Jobs in 18 countries and 7 languages: full job description, parsed salary (min, max, currency, period), posting date, employment type, highlights and the real application URLs (LinkedIn, Indeed, company sites). Pay only for jobs delivered: empty searches are free.

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

## Pricing

from $3.00 / 1,000 job listing delivereds

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

## Google Jobs Scraper — salary, apply links and full job descriptions

**Scrape Google Jobs listings in 18 countries and 7 languages, with the full job
description, the parsed salary, the posting date, the employment type, the job
highlights and the real application URLs (LinkedIn, Indeed, Glassdoor, the
company's own careers page) in every row.**

**$0.003 per job delivered ($3 per 1,000).** Searches that return nothing,
duplicates, filtered-out jobs and error rows are **never charged**.

No login. No account. No cookies to paste.

***

### What you can use this for

| If you are… | You want |
|---|---|
| Building a **job board** or a **job aggregator** | fresh listings with the full description and the direct application URL, deduplicated by a stable job id |
| Doing **salary research** or pay benchmarking | `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryPeriod` already parsed, in any currency |
| Running **recruitment lead generation** | which companies are hiring for which roles, where, and on which job boards |
| Tracking the **labor market** for a role or a city | a schedule that runs the same searches every day; the stable `id` tells new listings from old ones |
| Feeding an **HR tech** product or an AI agent | clean JSON with every key present on every row, over the Apify API |

***

### What arrives in a row

Every figure below was measured on **13 Oct 2026** on **n = 439 unique jobs** from
49 Google Jobs result pages, 33 searches, **15 countries and 5 languages**. We do
not list a field whose fill rate we have not measured.

**Re-measured on the Apify platform on 14 Oct 2026:** n = **2,085 jobs** from 50
private test runs covering all 18 countries and 7 languages. A job row carries on
average **34.1 filled fields** (median 34) and **25.1 filled job-data fields**
(not counting the echo of your input and run metadata). Description 98.2% (full
text 83.4%), resolved application URLs 99.5% with 4.92 options per job,
employment type 97.2%, posting date 82%, salary 45% (US 51.7%, n = 1,221).

#### Always there — 100% (n = 439)

`id` · `title` · `company` · `applyLinks` · `applyUrls` · `shareUrl` ·
`employmentTypes` *(97.5%)* · `location` *(99.8%)*

> **The application links are the part nobody else resolves.** Google wraps every
> "Apply on …" button in a redirect. This actor follows each one and gives you the
> real destination (`applyUrl`), the same URL without tracking parameters
> (`applyUrlClean`) and its domain (`applyDomain`). On average a job has
> **4.87 application options** (n = 439).

#### Usually there

| Field | Fill rate | n |
|---|---:|---:|
| `description` | 98.4% | 439 |
| …of which the full text (not a preview) | 82.9% | 439 |
| `employmentType` | 97.9% | 439 |
| `via` (job board) | 95.4% | 439 |
| `postedAt` (absolute ISO date) | 87.7% | 439 |
| `logoUrl` | 68.6% | 439 |
| `qualifications` | 42.1% | 439 |
| `responsibilities` | 41.7% | 439 |
| `noDegreeMentioned` = true | 37.1% | 439 |
| `benefits` | 36.2% | 439 |
| `salaryMin` / `salaryMax` | 34.6% | 439 |

**Salary depends on the country**, so one blended number would mislead you.
Measured share of jobs with a salary: **40.4% in the US** (n = 203), 46.7% in
Canada, 33.3% in the UK, 20% in Germany, 6.3% in India, 10% in South Africa.

**Job highlights** (qualifications, benefits, responsibilities) are rendered by
Google **only on English (US) pages**: 91.1% of US jobs have them and 0% elsewhere
(n = 439). In Spanish, French and German Google serves a ~400-character
description preview instead of the full text; those rows say so with
`descriptionIsPreview: true`.

#### The salary, parsed

`"$140K–$200K a year"` → `salaryMin 140000`, `salaryMax 200000`, `salaryCurrency
"USD"`, `salaryPeriod "year"`. The parser handles K, M and lakh suffixes, European
and English separators, and the period wording in English, Spanish, Portuguese,
French and German (checked by hand on 34 of 34 cases). When Google shows no
salary badge but the job text contains a clear salary range, it is used and
`salarySource` says where it came from.

***

### How it compares

Competitors measured on 13 Oct 2026 through the Apify API (their public stats and
published output schemas; no third-party actor was run). This actor measured on
14 Oct 2026 in 50 private runs on the platform.

| | **This actor** | Most-used Google Jobs actor | Second by users | Third by users |
|---|---|---|---|---|
| Users, last 30 days | new | 424 | 122 | 112 |
| Runs that failed or timed out | **0 of 50** test runs (20–500 rows, 18 countries) | **18.9% timed out** (3,373 of 17,872 in 30 days) | 8.8% | 4.2% |
| Price per job | **$0.003** | $0.035 per page of up to 10 ($0.09 on the free plan) | $0.006 | $0.15 + $0.14 per start |
| Empty searches charged | **never** | the page is charged | — | — |
| Full description in the row | ✅ 98.2% (n = 2,085) | not declared | ✅ | not declared |
| Application URLs | ✅ resolved to the real page, 99.5% | not declared | declared | not declared |
| Salary field | ✅ min / max / currency / period | not declared | declared | not declared |
| Output fields declared in its schema | 44 | 0 | 24 | 0 |
| Filled fields per job row | **34.1 on average** (measured, n = 2,085) | — (no schema) | at most 24 (declared) | — (no schema) |

"Not declared" means the field does not exist in that actor's published output
schema; we read the schemas, not the marketing. For competitors, "at most" is the
number of fields they declare: a declared field can still be empty. 12 of the 20
Google Jobs actors with 5+ monthly users publish no output schema at all.

Every one of the 50 test runs charged exactly the rows it delivered (2,085 jobs,
2,085 charges) with 0 duplicate ids. The same job keeps the same `id` across runs:
167 of 167 ids seen in more than one run had the same title and company.

***

### Input

| Field | Default | What it does |
|---|---|---|
| `queries` | `["software engineer"]` | Job titles or keywords, one search each |
| `locations` | `["Austin"]` | Cities or regions; every query runs in every location. Empty = whole country |
| `country` | `us` | Google country (21 available) |
| `language` | `en` | Google interface language (en, es, pt, fr, de, nl, it) |
| `maxJobsPerQuery` | `10` | Upper limit of rows per query × location |
| `expandQueries` | `true` | When a search needs more than its first page, also searches up to 4 variants of the query; duplicates are removed by id |
| `resolveApplyUrls` | `true` | Follow Google's redirects to the real application pages (same price either way) |
| `remoteOnly`, `employmentTypes`, `postedWithinDays` | off | Filters applied after the search; filtered jobs are not charged |
| `deepPagination` | `false` | **Experimental.** Also fetches result pages 2+ (needs 2048 MB of memory) |
| `maxConcurrency` | `10` | Parallel searches |

Example:

```json
{
  "queries": ["registered nurse", "data analyst"],
  "locations": ["Chicago", "Houston"],
  "country": "us",
  "language": "en",
  "maxJobsPerQuery": 30
}
```

***

### Output example (one row, shortened)

```json
{
  "rowType": "job",
  "id": "PIaBVaFi308wULA3AAAAAA==",
  "title": "Senior Software Engineer",
  "company": "Example Corp",
  "location": "Austin, TX",
  "via": "LinkedIn",
  "postedAt": "2026-10-12T14:00:00.000Z",
  "employmentTypes": ["full-time"],
  "salaryMin": 140000,
  "salaryMax": 200000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salarySource": "listing",
  "applyUrls": ["https://www.linkedin.com/jobs/view/0000000000", "https://careers.example.com/jobs/1234"],
  "applyDomains": ["linkedin.com", "careers.example.com"],
  "qualifications": ["5+ years of experience with distributed systems"],
  "description": "We are looking for…",
  "descriptionIsPreview": false,
  "isRemote": false,
  "isHybrid": true,
  "query": "software engineer",
  "searchLocation": "Austin",
  "country": "us",
  "language": "en"
}
```

Every row carries **all 44 keys**, with `null` where Google shows nothing, so
CSV and spreadsheet exports keep their columns.

Searches that produce no data add one **error row** (`rowType: "error"`) that
explains what happened and what to try. **Error rows are never charged.** The
`ERRORS` and `RUN_SUMMARY` records of the run's key-value store list them too.

***

### Pricing

| Event | Price | When |
|---|---:|---|
| Job listing delivered | **$0.003** | one job row with description, salary, highlights and resolved application URLs |
| Deep pagination session | $0.20 | only with `deepPagination` on, and only when the session actually delivered jobs from page 2 or later |
| Actor start | $0.00001 | once per run |

1,000 jobs cost **$3**. With deep pagination, 50 jobs from deeper pages cost
50 × $0.003 + one $0.20 session = $0.35. Duplicates within a run, filtered-out jobs,
empty searches and error rows cost nothing. A run stops cleanly when it reaches the maximum cost
you set.

***

### Reliability and speed

- Google answers a Jobs search in about 20 seconds through the route this actor
  uses (median 19.8 and 21.1 s in two platform runs of 20 searches each, 13 Oct
  2026\). Searches run in parallel, rows are delivered as each page arrives and the
  status message updates every few seconds.
- The default input finishes in under two minutes: 8 to 82 s on the platform in 4
  runs (14 Oct 2026, 10 jobs each). 500 jobs from 50 searches took 209 s at the
  default 512 MB.
- If Google pushes back, the actor retries with a fresh connection, then slows
  down, and always stops in time to finish inside your run timeout with the rows
  it already delivered.

### Deep pagination (experimental)

Google only serves result pages 2+ to a browser session. With `deepPagination`
on, the actor opens a short browser session to unlock them. In our platform test
(13 Oct 2026) a working session gave 4 pages on 7 of 10 searches, but sessions
could not always be started. When none can be started, the run keeps the first
pages, says so in an error row (not charged) and finishes normally. Most users
get more jobs per search with `expandQueries` and more locations instead.

***

### FAQ

**Do I need a Google account or cookies?** No. The actor never logs in.

**Is the job id stable?** Yes. It is Google's own job id, the same across runs,
so you can deduplicate or detect new listings between scheduled runs.

**Are duplicates charged?** Not within a run: a job found by two searches or two
query variants is delivered and charged once. Between separate runs the same job
can appear again, and it is charged again.

**Why are some descriptions short?** In Spanish, French and German Google shows
only a preview of the description; `descriptionIsPreview` tells you which rows.
Search in English (`language: "en"`) for full text and job highlights.

**What about personal data?** Recruiter email addresses and phone numbers that
appear inside descriptions are replaced with `[email]` and `[phone]`.

***

### Disclaimer

This actor extracts publicly available job listings shown by Google Jobs — no
login, no account. You are responsible for how you use the data, including
compliance with privacy laws and with the terms of the sites involved. It is not
affiliated with or endorsed by Google.

# Actor input Schema

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

One search per line, for example "software engineer" or "registered nurse". Each query is combined with each location. Up to 50 queries and 200 query × location searches per run.

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

City or region names added to each query (for example "Austin", "London", "Madrid"). Leave empty to search the whole country.

## `country` (type: `string`):

Google country (gl): which Google Jobs index is searched and which local currency a bare "$" means. Australia, Ireland and New Zealand are not listed because Google does not serve its Jobs results there.

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

Google interface language (hl). Google renders the "Job highlights" lists (qualifications, benefits, responsibilities) only in English, and in Spanish, French and German it serves a ~400-character description preview instead of the full text.

## `maxJobsPerQuery` (type: `integer`):

Upper limit of job rows for each query × location search. One Google results page gives up to 10 jobs; more come from query expansion and, when enabled, deep pagination. You are charged only for rows delivered.

## `expandQueries` (type: `boolean`):

When a search needs more rows than its first page gave, also search up to 4 variants of the query (for example "<query> jobs", "<query> hiring", "<query> remote"). Duplicates are removed by job id and never charged.

## `deepPagination` (type: `boolean`):

Also fetch result pages 2+ of each search. Needs a short browser session with Google (run memory 2048 MB) that is charged as a separate event only when it delivers jobs. Off by default; see the README for measured success rates.

## `resolveApplyUrls` (type: `boolean`):

Follow each Google redirect to the real application page (LinkedIn, Indeed, the company's careers site...). Turn off to keep only the Google links; it does not change the price.

## `remoteOnly` (type: `boolean`):

Keep only jobs detected as remote from their location or description. Google has no server-side remote filter on this route, so this is applied after the search; filtered jobs are not charged.

## `employmentTypes` (type: `array`):

Keep only these normalized employment types (applied after the search; filtered jobs are not charged). Empty keeps all.

## `postedWithinDays` (type: `integer`):

Keep only jobs posted within the last N days, using postedAt (applied after the search; filtered jobs are not charged). Jobs without a posting date are dropped when this is set. 0 keeps all.

## `outputType` (type: `string`):

Row type delivered by this run.

## `maxConcurrency` (type: `integer`):

How many searches run at the same time. Measured on the platform: 10 parallel searches gave 16/20 and 19/20 first pages in 60 s; lower values only make the run slower.

## Actor input object example

```json
{
  "queries": [
    "software engineer"
  ],
  "locations": [
    "Austin"
  ],
  "country": "us",
  "language": "en",
  "maxJobsPerQuery": 10,
  "expandQueries": true,
  "deepPagination": false,
  "resolveApplyUrls": true,
  "remoteOnly": false,
  "employmentTypes": [],
  "postedWithinDays": 0,
  "outputType": "job",
  "maxConcurrency": 10
}
```

# Actor output Schema

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

Job listings with salary, apply URLs, description and highlights (plus error rows for searches that returned nothing; those are never charged).

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

Searches that produced no data and why. Always present, empty when everything worked.

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

Counts of delivered, filtered and duplicate jobs, charges and timing.

# 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": [
        "software engineer"
    ],
    "locations": [
        "Austin"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("oswaldocarabano/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 = {
    "queries": ["software engineer"],
    "locations": ["Austin"],
}

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

```

## MCP server setup

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