# Google Jobs Scraper & API - New Jobs, Salary & Apply Links (`suerte_enero/google-jobs-scraper`) Actor

Google Jobs (Google for Jobs) scraper & API: job listings from LinkedIn, Indeed, Glassdoor, ZipRecruiter and career sites with real apply links, salary and full description. Only-new-jobs monitoring. $2 per 1,000 jobs.

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

## Pricing

from $2.00 / 1,000 jobs

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 & API — new jobs, salary & direct apply links

Scrape **Google Jobs** (also called *Google for Jobs* — the job listings panel Google builds from LinkedIn, Indeed, Glassdoor, ZipRecruiter and company career sites) into clean JSON, CSV or Excel, or call it as a **Google Jobs API** from your code, Zapier, Make, n8n or an AI agent. Every job comes with **real apply links** (employer careers page / ATS, Indeed, LinkedIn…), **normalized salary**, full description and highlights.

Built for scheduled monitoring: **only new jobs** since the last run, **closed-job alerts**, Slack / webhook notifications. You pay only for unique jobs that have an apply link.

### Why this scraper

| | This Actor |
|---|---|
| Run success | Designed for 99%+: every search is retried when Google serves a captcha or plain web results instead of jobs; runs finish before the timeout and keep everything collected |
| More than 10 results | Google shows ~10 jobs per search and has no next page. The Actor automatically runs search variants (full time, part time, contract, remote, entry level, senior, hybrid, internship) and de-duplicates |
| Real apply URLs | Google redirect links are resolved to the actual job URL. `hasDirectApply`, `companyDomain` and `atsProvider` (Greenhouse, Lever, Workday, Ashby, SmartRecruiters, iCIMS…) come for free |
| Salary you can sort by | `salary.min`, `max`, `currency`, `period` plus `annualMinUsd` / `annualMaxUsd` |
| Monitoring | `onlyNewJobs` remembers what each feed already returned; `trackClosedJobs` reports postings that disappeared |
| Stable schema | Fields are only ever added; every record carries `schemaVersion` |
| Fair billing | Duplicates, filtered-out jobs, already-known jobs, empty searches and retries are free |

### Quick start

```json
{
  "queries": ["registered nurse", "python developer"],
  "locations": ["Houston, TX", "New York, NY"],
  "country": "US",
  "maxItems": 500
}
```

Google Jobs matches on **job titles**: short titles ("data analyst") work better than long descriptions.

### Daily monitoring in 3 steps

1. Set `onlyNewJobs: true` and give the feed a name, e.g. `"monitorId": "nurses-houston"`.
2. Optional: add a `webhookUrl` (Slack incoming webhook with `webhookFormat: "slack"`, or Zapier / Make / n8n with JSON).
3. Create a **Schedule** in Apify Console (e.g. every morning). Each run returns only jobs this feed has not returned before; known jobs are skipped and not charged.

Turn on `trackClosedJobs` to also receive records with `recordType: "closed"` when a posting disappears. Because Google rotates which jobs it shows for a search, a job is reported as closed only after it is missing from 2 complete runs in a row **and** has not been seen for `closedAfterDays` (default 3).

### Output example

```json
{
  "recordType": "job",
  "jobId": "Iw1WSt-URMCIKrSHAAAAAA==",
  "title": "Registered Nurse II (RN) Outpatient Infusion | BMT & Cellular Therapy",
  "companyName": "Houston Methodist",
  "companyDomain": "houstonmethodistcareers.org",
  "location": "Houston, TX, United States",
  "city": "Houston",
  "region": "TX",
  "remoteType": "onsite",
  "employmentType": "FULLTIME",
  "employmentTypes": ["FULLTIME"],
  "seniority": "mid",
  "postedRaw": "7 days ago",
  "postedAt": "2026-10-21T13:19:22.374Z",
  "salary": null,
  "via": "Houston Methodist Careers",
  "hasDirectApply": true,
  "primaryApplyUrl": "https://www.houstonmethodistcareers.org/job/120638/registered-nurse-ii-rn-outpatient-infusion-bmt-cellular-therapy-rn-ot",
  "applyLinks": [
    { "board": "Houston Methodist Careers", "url": "https://www.houstonmethodistcareers.org/job/120638/…", "isDirect": true, "ats": null, "resolved": true },
    { "board": "Indeed", "url": "https://www.indeed.com/viewjob?jk=0d7070e21feb6f45", "isDirect": false, "ats": null, "resolved": true }
  ],
  "atsProvider": null,
  "qualifications": ["Twelve months registered nurse experience in a healthcare environment", "…"],
  "responsibilities": ["…"],
  "benefits": [],
  "description": "At Houston Methodist, the Registered Nurse (RN) II position is …",
  "tags": ["Full-time"],
  "isNew": true,
  "firstSeenAt": "2026-10-28T17:40:01.000Z",
  "searchQuery": "registered nurse",
  "searchLocation": "Houston, TX",
  "searchCountry": "US",
  "scrapedAt": "2026-10-28T17:40:01.120Z",
  "schemaVersion": 1
}
```

With `outputFormat: "flat"` salary and apply links become plain columns (`salaryMin`, `salaryAnnualMinUsd`, `applyBoards`, `directApplyUrls`…) for Excel / Google Sheets.

Every run also stores a `RUN_SUMMARY` record in the key-value store: searches, retries, jobs found, duplicates, new / known / closed jobs and warnings.

### Filters

`postedWithinHours`, `employmentTypes`, `remoteOnly`, `salaryOnly`, `directApplyOnly`, `excludeCompanies`. Jobs removed by filters are not charged.

### Use cases

- **Recruiting agencies:** fresh openings every morning with the hiring company, its domain and the direct apply link — a ready lead list of companies that are hiring.
- **Job boards & aggregators:** a daily feed of new postings per title and city, with closed-job signals to expire listings.
- **Salary research:** normalized annual USD ranges by title, city and seniority.
- **AI agents (MCP):** structured, de-duplicated jobs with a stable schema.

### Countries

Works where Google shows its jobs panel. Verified on 28 Oct 2026 with 200 searches: **United States, United Kingdom, Canada, India, Philippines** — 200/200 searches returned jobs or Google's own "no matches" answer. Many other countries are in the list. In markets where Google does not show the jobs panel (reported for Australia and several EU countries) the Actor detects it after two searches, stops, explains it in the run status and charges nothing.

Keep `language` at `en`: parsing is verified on English pages.

### Pricing

Pay per event:

- **Job** — a unique job with at least one apply link: **$2.00 per 1,000**.
- **Closed job** — only with `trackClosedJobs`: **$0.50 per 1,000**.

Not charged: duplicates, filtered-out jobs, jobs already known to the feed, searches with no results, retries.

### Limitations (from Google, not the Actor)

- About 10 jobs per search; volume comes from more titles, more locations and `expandSearch`.
- Salary is shown on roughly 40% of postings and posting date on about two thirds.
- Google rotates which jobs it shows, so consecutive runs return slightly different subsets — monitoring mode turns this into a steady stream of jobs you have not seen yet.

### FAQ

**Is a run ever charged when nothing was found?** No. Only jobs with an apply link are charged.

**Why fewer jobs than `maxItems`?** Google has a limited pool per title and city. Add titles, nearby cities or keep `expandSearch` on.

**Do I need proxies or an API key?** No.

### Changelog

- **0.1 (Oct 2026)** — First release: jobs vertical parser, retries and timeout budget, search expansion, apply-link resolution with ATS and employer domain, only-new / closed-job monitoring, Slack and JSON webhooks, flat CSV output.

# Actor input Schema

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

Job titles or keywords, one per line. Google Jobs matches on job titles, so short titles work best (e.g. "python developer", "registered nurse").

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

Cities or regions, one per line (e.g. "New York, NY"). Leave empty to search the whole country.

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

Country of the Google Jobs results.

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

Hard limit of unique jobs saved (and charged) per run.

## `expandSearch` (type: `boolean`):

Google shows about 10 jobs per search. When a search returns a full page, automatically run variants (full time, part time, contract, remote, entry level, senior, hybrid, internship) and de-duplicate.

## `postedWithinHours` (type: `integer`):

Keep only jobs posted in the last N hours (e.g. 24). Filtered jobs are not charged.

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

Keep only these employment types.

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

Keep only remote jobs.

## `salaryOnly` (type: `boolean`):

Keep only jobs that show a salary.

## `directApplyOnly` (type: `boolean`):

Keep only jobs that can be applied to on the employer's own careers site / ATS.

## `excludeCompanies` (type: `array`):

Skip jobs from these companies (substring match).

## `onlyNewJobs` (type: `boolean`):

Return only jobs that this feed has not returned before (Google shows a rotating subset per search, so regular runs also surface listings you have not seen yet). Known jobs are skipped and not charged. Ideal for scheduled daily runs.

## `trackClosedJobs` (type: `boolean`):

Also output jobs that disappeared from Google Jobs as records with recordType "closed" (a 'position filled' signal). A job counts as closed only when it is missing from 2 complete runs in a row and has not been seen for at least the number of days below, because Google rotates which jobs it shows per search.

## `monitorId` (type: `string`):

Optional name of the monitoring feed (e.g. "nurses-houston"). Runs with the same name share memory. Leave empty to derive it from country, job titles and locations.

## `memoryDays` (type: `integer`):

How long a job is remembered after it was last seen.

## `closedAfterDays` (type: `integer`):

Minimum days a job must be unseen before it is reported as closed.

## `webhookUrl` (type: `string`):

POST a summary with new (and closed) jobs here when the run finishes. Works with Slack incoming webhooks, Zapier, Make, n8n.

## `webhookFormat` (type: `string`):

Payload format for the webhook.

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

Turn Google redirect links into the real job URLs (employer careers page / ATS, Indeed, LinkedIn…), detect the ATS (Greenhouse, Lever, Workday…) and the employer's domain.

## `outputFormat` (type: `string`):

Flat format turns salary and apply links into plain columns.

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

Google interface language (hl). Keep "en": the parser is verified on English pages.

## `maxQueriesPerRun` (type: `integer`):

Upper bound on searches, including automatic variants.

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

Parallel searches (1-10).

## Actor input object example

```json
{
  "queries": [
    "python developer"
  ],
  "locations": [
    "New York, NY"
  ],
  "country": "US",
  "maxItems": 500,
  "expandSearch": true,
  "employmentTypes": [],
  "remoteOnly": false,
  "salaryOnly": false,
  "directApplyOnly": false,
  "excludeCompanies": [],
  "onlyNewJobs": false,
  "trackClosedJobs": false,
  "memoryDays": 60,
  "closedAfterDays": 3,
  "webhookFormat": "json",
  "resolveApplyUrls": true,
  "outputFormat": "nested",
  "language": "en",
  "maxQueriesPerRun": 200,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

Scraped jobs (and closed-job records when "Report closed jobs" is on).

## `allFields` (type: `string`):

No description

## `runSummary` (type: `string`):

Searches, retries, new / known / closed jobs, charged jobs and warnings.

# 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": [
        "python developer"
    ],
    "locations": [
        "New York, NY"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("suerte_enero/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": ["python developer"],
    "locations": ["New York, NY"],
}

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

```

## MCP server setup

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