# Google Jobs Scraper — pay per job (`vibermacher/google-jobs`) Actor

One clean row per job from Google’s Jobs tab: parsed location, ISO posted date, salary split out, every apply link, the job’s own Google link. USD 0.004 a row, no subscription. US, UK, Canada, India.

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

## Pricing

from $4.00 / 1,000 job rows

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

## Google Jobs Scraper — one clean row per job, pay per result

Searches Google's Jobs tab for a query in a place and returns one normalised row per job: title, company, parsed location, posted date as an ISO date, salary split into minimum, maximum, currency and period, employment type, every apply link with its job board, the full description and highlights, Google's own job id, a dedupe key, and where and when the row came from.

You pay per job row returned, not per run or per month. A run that is blocked or cannot read the page fails and says which; it never ends "succeeded" with nothing.

### Contents

[How to use](#how-to-use) · [Input](#input) · [Output](#output) and a [sample row](#sample) · [Price](#price) · [Reliability](#reliability) · [Limits](#limits-honestly) · [FAQ](#faq)

### How to use

1. Enter a query ("registered nurse") and a place ("Houston,Texas,United States"); pick the Google site (`us`, `gb`, `ca`, `in`) and, if you want, a date window. Or paste a list of searches to run them in one go.
2. Set the maximum total charge for the run (10 jobs cost USD 0.04) and start it. Rows appear in the Output tab as they are read, with a Jobs view and a Salary view.
3. Export the dataset as JSON, CSV or Excel, or call the Actor from the API, a schedule or an integration like any other Apify Actor.

### Input

| Field | What it does | Default |
|---|---|---|
| `query` | What to search for, as you would type it: "registered nurse", "software engineer" | required |
| `location` | Where, as a place name. "City,Region,Country" ("Houston,Texas,United States") is the most precise | required |
| `country` | The Google site to search: `us` (google.com), `gb` (google.co.uk), `ca` (google.ca) or `in` (google.co.in) | `us` |
| `datePosted` | `any`, `week` (jobs Google lists as posted in the past week) or `day` (the past day) | `any` |
| `maxResultsPerSearch` | Stop a search at this many jobs (1 to 100) | 10 |
| `searches` | Several searches in one run: a list of `{"query", "location", "country"}`; overrides the three fields above. A job two searches both list is returned once | |

Google serves 10 jobs a page. For a maximum above 10 the actor also fetches Google's narrower date windows (past week, then past day) as further pages, so one search yields up to about 30 distinct jobs. For more, run more searches: other locations, other wordings of the query.

### Output

One row per job, in the dataset, with these fields:

| Field | Meaning |
|---|---|
| `title`, `company` | As Google shows them |
| `location`, `location_city`, `location_region`, `location_country`, `remote` | The location text and its parsed parts; `remote` is true for "Anywhere" or "work from home" |
| `posted_date`, `posted_date_approximate`, `posted_text` | The date resolved from Google's "3 days ago" against the retrieval time (`YYYY-MM-DD`); approximate when Google said "2 weeks ago", "a month ago" or "30+ days ago"; the original text beside it |
| `salary_min`, `salary_max`, `salary_currency`, `salary_period`, `salary_text` | The salary as Google shows it, split; `period` is `hour`, `day`, `week`, `month` or `year`; a bare "$" is the dollar of the site searched (`CAD` on google.ca); the original text beside it |
| `salary_plausible` | `false` when the amount does not fit its period, such as Google's own "£13 a year" for an hourly rate: the value is kept as shown and `notes` says why; `null` with no salary |
| `employment_type` | `full_time`, `part_time`, `contractor`, `internship`, `temporary`; several comma-joined when Google lists several ("Full-time and Internship") |
| `apply_links` | Every apply option, `{"board", "url"}`, with the direct destination where Google carries it |
| `via` | The board Google took the listing from |
| `description`, `job_highlights` | The full description text and Google's highlight sections (qualifications, benefits, responsibilities) |
| `google_job_id` | Google's id for the job, stable across searches and runs |
| `dedupe_key` | A 16-character hash of title, company and location with case, accents and punctuation removed: the same job found twice has the same key |
| `search_query`, `search_location`, `search_country` | The search that found the row |
| `source`, `source_url`, `as_of`, `retrieved_at` | "Google Jobs"; the job's own share link on Google, tracking parameters removed; the date the row was true; the retrieval time in UTC |
| `notes` | Why a value is empty or flagged, in words; `null` when nothing is |

Empty values are `null`, never "N/A". Dates are ISO 8601.

#### Sample

One row from a run of 2026-10-28 ("software engineer" in New York; four searches, 60 rows, USD 0.033 of platform usage). The description and highlights are cut short and six of the eight apply links are left out to fit here; the dataset holds them whole.

```json
{
  "title": "Software Engineer (Remote - US)", "company": "TixTrack",
  "location": "New York, NY", "location_city": "New York", "location_region": "NY", "location_country": "US", "remote": false,
  "posted_date": "2026-10-08", "posted_date_approximate": false, "posted_text": "20 days ago",
  "salary_min": 75000, "salary_max": 115000, "salary_currency": "USD", "salary_period": "year",
  "salary_text": "$75K–$115K a year", "salary_plausible": true,
  "employment_type": "full_time",
  "apply_links": [
    { "board": "Indeed", "url": "https://www.indeed.com/viewjob?jk=e3cf91577dccf37a&utm_campaign=google_jobs_apply&utm_source=google_jobs_apply&utm_medium=organic" },
    { "board": "TixTrack - Teamtailor", "url": "https://tixtrack.teamtailor.com/jobs/8287889-sr-software-engineer-ii-remote-us?utm_campaign=google_jobs_apply&utm_source=google_jobs_apply&utm_medium=organic" }
  ],
  "via": "Indeed",
  "description": "At TixTrack, we support our clients by creating superior ticketing solutions for performing arts and cultural institutions through a combination of modern technology, beautifully simple design, and the art of listening.\n\nHeadquartered in the heart of the Broadway Theatre District, NYC, we are a small, but growing team […]",
  "job_highlights": [
    { "title": "Qualifications", "items": ["You're curious about how things work and eager to keep growing […]", "You take pride in quality"] },
    { "title": "Benefits", "items": ["Our Software Engineer 1 has a base annual salary range of $75,000 - $95,000 and our Software Engineer 2 has a base annual salary range of $95,000 - $115,000"] },
    { "title": "Responsibilities", "items": ["We're looking for a full stack Software Engineer to help build and improve the nLiven platform, working in both the frontend and backend"] }
  ],
  "google_job_id": "KeSPkJQGSIHqbktDAAAAAA==", "dedupe_key": "8b5c59366211a68e",
  "search_query": "software engineer", "search_location": "New York,New York,United States", "search_country": "us",
  "source": "Google Jobs",
  "source_url": "https://www.google.com/search?ibp=htl;jobs&q=software+engineer+jobs+in+New+York%2C+New+York%2C+United+States&htidocid=KeSPkJQGSIHqbktDAAAAAA%3D%3D&hl=en",
  "as_of": "2026-10-28", "retrieved_at": "2026-10-28T05:54:44Z",
  "notes": null
}
```

### Price

USD 0.004 per job row, charged as each row is written, plus a small charge when a run starts (both shown on this page). Set a maximum total charge on the run; the actor stops cleanly at it, keeps every row written so far, and says so in the run's status message.

### Reliability

Google is fetched through Apify's Google SERP proxy. A blocked, challenged or failed page is fetched again through a new address with a growing wait, three attempts in all. Rows are written as they are read, so a run that stops early keeps what it found. The run's status message counts searches, pages, retries, blocks and rows, and names each search's end. Three outcomes are kept apart:

- **"Google returned no jobs for this query"**: every page was read and Google's own notice said there were none. The run succeeds with zero rows.
- **"Blocked by Google"** and **"Could not read the page"**: the run fails and says which, with zero rows rather than wrong rows.
- **Partial**: some searches read, some not; the run succeeds and the message names the ones that stopped early.

### Limits, honestly

- Up to about 30 jobs a search (see Input). Google's infinite scroll is not served through the SERP proxy, so that route is closed.
- Four countries. Google does not serve its Jobs tab through the proxy for Australia or Ireland, and shows no Jobs matches for English queries in Germany or France (tested 2026-10-27), so those are not offered.
- No employment-type filter: Google's job-type chips carry one-off tokens, so there is no stable URL for them. Filter on `employment_type` in the output instead.
- Salaries and posted dates are as Google shows them. Google sometimes labels an hourly rate "a year"; such rows carry `salary_plausible: false` and a note, and are never silently corrected.
- Google's Jobs tab ranks and chooses the jobs; what it does not list, this actor cannot return.

### FAQ

**How many jobs does one search return?** Up to about 30 distinct jobs: Google's first page plus its "past week" and "past day" pages. Set `maxResultsPerSearch` to stop earlier. For more coverage, add searches for nearby places or other wordings.

**Can I filter by job type or remote?** Not in the input: Google's filter chips have no stable URL. Filter the output on `employment_type` and `remote`; both are on every row.

**Is the salary reliable?** It is exactly what Google shows, split into numbers. When Google's own label does not fit the amount ("£13 a year"), `salary_plausible` is `false` and `notes` says why; nothing is silently corrected.

**Does it scrape Indeed or LinkedIn?** No. It reads Google's Jobs tab, which aggregates those boards and others; each row carries every apply link Google lists, with the board's name.

**What happens if Google blocks a run?** The run retries through new proxy addresses, then fails with "Blocked by Google" rather than returning wrong rows; you are charged only for the rows written. The Actor is self-tested every day.

# Actor input Schema

## `query` (type: `string`):

What to search for, such as "registered nurse".

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

Where, as a place name; the canonical form "City,Region,Country" (such as "Houston,Texas,United States") is the most precise.

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

The Google country site to search: google.com, google.co.uk, google.ca or google.co.in. These are the sites that serve the Jobs tab through the proxy; Australia, Ireland, Germany and France do not (tested 2026-10-27).

## `datePosted` (type: `string`):

Only jobs Google lists as posted within the last week or the last day; "any" takes Google's default list.

## `maxResultsPerSearch` (type: `integer`):

Google serves 10 jobs a page. Above 10, the actor also fetches the narrower date windows (past week, then past day) as further pages, so a search yields up to about 30 distinct jobs; more jobs come from more searches (other locations or query wordings).

## `searches` (type: `array`):

A list of {"query", "location", "country"} objects. A job two searches both list is pushed once.

## `concurrency` (type: `integer`):

How many searches run at the same time; each search's own pages are fetched one after another.

## `mode` (type: `string`):

"scrape" pushes job rows; "capture" saves every raw page fetched to the run's key-value store, with a manifest, for building the parser.

## `variant` (type: `string`):

Which Google page scrape mode fetches: udm8 (the Jobs tab), udm8-uule (the Jobs tab with the location encoded), udm8-jbr (the Jobs tab with jbr=sep:0), ibp (the older jobs panel) or web (the ordinary results page).

## `captureVariants` (type: `array`):

Capture mode fetches the first page of every search in each of these variants.

## `captureSecondPage` (type: `boolean`):

Capture mode also fetches page two of the first search in each variant, to learn whether paging works.

## `captureJobPage` (type: `boolean`):

Capture mode also fetches the first job's own page in both URL forms, to learn whether a description costs a request per job.

## `captureParams` (type: `array`):

Capture mode also fetches, for every search, one more first page per entry: the named variant's URL with these query parameters added, to test a filter form before any code depends on it. Entries are {"name", "variant", "params"}; the name goes into the saved page's key.

## `captureScrollPages` (type: `integer`):

Capture mode also requests the Jobs tab's infinite-scroll continuation this many pages deep after each search's first udm8 page, in each known URL format, to learn whether the proxy serves more than the first 10 jobs. 0 fetches none.

## `captureExtrasSearches` (type: `integer`):

The extra parameter pages and scroll continuations are fetched for this many leading searches only; 0 means every search.

## `proxyGroup` (type: `string`):

GOOGLE_SERP (billed per page, plain HTTP to Google) or RESIDENTIAL (billed per GB, HTTPS).

## `compareResidential` (type: `boolean`):

Capture mode fetches the first search's first page once more through the RESIDENTIAL group in every variant, for comparison.

## `maxAttempts` (type: `integer`):

A blocked, failed or unreadable page is fetched again, through a new proxy address, up to this many times in all.

## Actor input object example

```json
{
  "query": "software engineer",
  "location": "New York,New York,United States",
  "country": "us",
  "datePosted": "any",
  "maxResultsPerSearch": 10,
  "concurrency": 3,
  "mode": "scrape",
  "variant": "udm8",
  "captureVariants": [
    "udm8",
    "udm8-uule",
    "udm8-jbr",
    "ibp",
    "web"
  ],
  "captureSecondPage": true,
  "captureJobPage": true,
  "captureScrollPages": 0,
  "captureExtrasSearches": 0,
  "proxyGroup": "GOOGLE_SERP",
  "compareResidential": false,
  "maxAttempts": 3
}
```

# Actor output Schema

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

The job rows, one per job found, as JSON.

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

Searches attempted, pages fetched, retries, blocks, unreadable pages, jobs pushed, and how each search ended.

# 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 = {
    "query": "software engineer",
    "location": "New York,New York,United States"
};

// Run the Actor and wait for it to finish
const run = await client.actor("vibermacher/google-jobs").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 = {
    "query": "software engineer",
    "location": "New York,New York,United States",
}

# Run the Actor and wait for it to finish
run = client.actor("vibermacher/google-jobs").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 '{
  "query": "software engineer",
  "location": "New York,New York,United States"
}' |
apify call vibermacher/google-jobs --silent --output-dataset

```

## MCP server setup

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

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/4ORbyK68q8dV4hQrX/builds/h043t1EBScknS9fN4/openapi.json
