# Google Jobs Scraper | Jobs by City, Apply Links, Full Coverage (`alexmorain/google-jobs-scraper`) Actor

Scrape Google Jobs without a browser or SerpApi: the jobs of the city you ask (not the proxy's), salary, every apply link and the full description. Complete coverage goes past Google's 10 per request; duplicates and jobs outside your filters are never charged. New-jobs-only mode and webhook.

- **URL**: https://apify.com/alexmorain/google-jobs-scraper.md
- **Developed by:** [Alexis Morain](https://apify.com/alexmorain) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Security:** Limited permissions
- **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.
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

### What does Google Jobs Scraper do?

**Google Jobs Scraper** collects the job listings that [Google Jobs](https://www.google.com/search?q=software+engineer+jobs+in+austin\&udm=8) shows for a job title and a city. It is a Google Jobs API alternative that needs **no browser and no SerpApi key**. For every job you get the company, location, posting age, salary when Google shows it, **every apply link**, the highlights and the **full description**.

It does not scrape the employer sites behind the links. It returns what Google Jobs itself lists, with the apply links Google gives.

### Why use Google Jobs Scraper?

Google Jobs gathers listings from employer career pages and from job boards such as Indeed, LinkedIn, Glassdoor and ZipRecruiter into one list. Two things get in the way of collecting it, and this Actor handles both.

**1. Google picks the city for you when the query has none.** Through a proxy, Google localizes on the IP address of the proxy, not on your location field. We ran the same search, `software engineer`, three times without a place in the query, then twice with `Austin, TX` in the query (one Google request each, 10 jobs each, measured on 31 October 2026):

| Run | Location in the query | Cities of the 10 jobs | Jobs in common |
|---|---|---|---|
| A | none | United States, Colorado, Colorado Springs | A and B: 0 of 10 |
| B | none | Eagan, St Paul, Minneapolis (Minnesota) | B and C: 0 of 10 |
| C | none | United States, Colorado Springs, Colorado | A and C: 8 of 10 |
| D | `Austin, TX` | Austin, TX (10 of 10) | D and E: 7 of 10 |
| E | `Austin, TX` | Austin, TX (10 of 10) | |

An Actor that passes the location as a separate setting, or leaves it out, returns the jobs of whichever city the proxy exits from, and the results change at every run. This Actor writes your location into the Google query (`<job title> jobs in <location>`), and warns you in the log and in the run summary when a search has no location.

**2. Google shows 10 jobs per request and cannot be paged.** We tested `start=`, `ijn=` and the scroll token through the proxy: none of them returns anything new. The way to get more jobs is to ask Google different questions. With **Complete coverage** on, the Actor adds dates, job types, seniority words, then the companies and job titles it has already found, and keeps every distinct job once:

| Search (measured 31 October 2026) | Google requests | Distinct jobs returned |
|---|---|---|
| `software engineer jobs in Austin, TX`, one request | 1 | 10 |
| same search, Complete coverage | 60 | 297 |
| `nurse jobs in Manchester`, Complete coverage | 60 | 221 |

Those 60 requests read 514 and 417 jobs: the rest were repeats, removed before they reach the dataset and before you pay. The Actor stops a search earlier when its last 10 requests bring fewer than 10 new jobs, or when, after 20 requests, it averages fewer than 2 new jobs per request. A search with no jobs at all stops after 3 requests.

Also worth knowing:

- ⏱️ Schedule it on Apify, run it from the API, send results to Make, Zapier, Google Sheets or a webhook.
- 🔁 **New jobs only** mode remembers what each monitor already delivered, so a daily run returns only fresh jobs.
- 🌍 23 countries, with the matching Google domain and language.
- 💾 Runs in 512 MB of memory: no browser, no JavaScript rendering.

### What data can Google Jobs Scraper extract?

| Field | Type | Description |
|---|---|---|
| `title`, `companyName`, `location` | string | The job as Google shows it |
| `via` | string | The site Google lists the job from |
| `postedAgo`, `postedAt` | string | Age as worded by Google (in the search language) and an estimated date |
| `employmentType`, `employmentTypes` | string, array | Label shown by Google and recognised types (`fulltime`, `parttime`, `contractor`, `internship`) |
| `salaryText`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | string, number | Salary when Google shows one, parsed |
| `remote` | boolean | True when Google marks the job as work from home |
| `benefits`, `badges`, `highlights` | array | Benefit tags, other tags, and the Qualifications, Benefits and Responsibilities lists when Google shows them |
| `descriptionText` | string | The full description as plain text |
| `applyUrl`, `applyOptions` | string, array | Every apply link Google lists (site, URL, whether Google labels it "apply directly"), tracking parameters removed |
| `shareLink`, `companyLogoUrl` | string | The job on Google, and the logo when Google has one |
| `searchQuery`, `searchLocation`, `foundBy`, `rank` | string, integer | Which search, which exact Google query and which position found the job |

Fields Google does not show for a job are `null`. In our runs `highlights` were filled for the United States only; the United Kingdom, Germany and France pages did not carry them.

### How to scrape Google Jobs

1. Open the Actor and go to the **Input** tab.
2. Type one or more **Job titles or keywords**, for example `software engineer`.
3. Type one or more **Locations**, for example `Austin, TX`. Always fill this in.
4. Pick the **Country** (it sets google.com, google.co.uk, google.de and so on) and, if needed, the **Language**.
5. Set **Max jobs** and press **Start**.
6. Download the dataset as JSON, CSV or Excel, or read it through the API.

To follow a market, turn on **New jobs only**, choose **Posted within: Past 24 hours**, name the monitor, and schedule a daily run.

### How much will it cost to scrape Google Jobs?

You pay per event: a small amount when the run starts and an amount for each job returned. The exact prices are in the **Pricing** tab and drop with your Apify plan. Duplicates, jobs already delivered in New jobs only mode, and jobs outside your date, type or remote filter are never charged. The Google proxy is included in the price.

| Apify plan | Price per 1,000 jobs |
|---|---|
| Free | $3.00 |
| Starter (Bronze) | $2.50 |
| Scale (Silver) | $2.20 |
| Business (Gold) and above | $2.00 |

The run start costs $0.002 at the default 512 MB of memory. A Complete coverage search that returns 300 jobs costs about $0.60 on the Gold tier, a single request of 10 jobs about $0.03.

### Input

See the **Input** tab for every option. The ones that need a word:

- **Job titles** and **Locations** are combined: 2 titles and 3 locations make 6 searches.
- **Complete coverage** (on by default) spends up to **Max requests per search** Google requests per search. Off, the Actor makes one request and returns the 10 jobs Google ranks first.
- **Google Jobs URLs** accepts search URLs with `udm=8` or `ibp=htl;jobs`. The query, `hl` and `gl` are read from the URL.
- **Posted within**, **Job types** and **Remote only** are described below.

#### How the filters work

Google has no real filter parameter for Google Jobs that we could use through the proxy. We tested them on 31 October 2026:

| Filter | What Google does with it | What the Actor does |
|---|---|---|
| Posted within | Words such as `since yesterday` and `in the last week` added to the query narrow the results (10 of 10 jobs within the window in English, 3 of 3 in German, 5 of 5 in French). The `tbs=qdr:d` parameter lets 2 and 3 day old jobs through in English and jobs up to 26 days old in German. | Sends the words, never `tbs`, then checks the age shown on each job and drops older ones before charging. |
| Job types | Words such as `contract` and `internship` lean towards the type but do not filter: in our tests 2 of 10 contract results and at least 4 of 10 internship results were full-time jobs. | Sends the word, then checks the type shown on each job and drops the others before charging. A job labelled "Full-time and Contractor" matches Contractor. |
| Remote only | The word `remote` returned 10 of 10 jobs carrying the work from home mark. | Sends the word, then keeps only jobs with the mark. |

The date and type words were checked in English, German and French. In other languages the Actor still checks each job, but the words sent to Google may not narrow the search as well. A job that does not show its age or its type is kept, and counted in the run summary.

### Output

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel. A shortened item:

```json
{
  "jobId": "vOPKYQc9G8yqj5dpAAAAAA",
  "title": "Full Stack/ Angular Software Development Engineer",
  "companyName": "Charles Schwab",
  "location": "Austin, TX",
  "via": "Schwab Jobs",
  "postedAgo": "4 days ago",
  "postedAt": "2026-10-27",
  "employmentType": "Full-time",
  "employmentTypes": ["fulltime"],
  "salaryText": "126K–143K a year",
  "salaryMin": 126000,
  "salaryMax": 143000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "remote": false,
  "applyUrl": "https://hiringcafe.com/job/full-stack-angular-software-development-engineer-charles-schwab-et8d1dxhh8okmrik",
  "applyOptions": [
    { "title": "Schwab Jobs", "url": "https://www.schwabjobs.com/job/southlake/full-stack-angular-software-development-engineer/33727/102568886544", "direct": false },
    { "title": "Hiring Cafe", "url": "https://hiringcafe.com/job/full-stack-angular-software-development-engineer-charles-schwab-et8d1dxhh8okmrik", "direct": true }
  ],
  "searchQuery": "software engineer",
  "searchLocation": "Austin, TX",
  "foundBy": "software engineer jobs in Austin, TX",
  "rank": 1
}
```

Each run also writes a `SUMMARY` record to the key-value store. For every search it gives the Google requests made, empty and failed ones, distinct jobs found, jobs returned, jobs dropped by the date, type and remote checks, whether the location was missing, and why the search stopped (`max-items`, `max-requests`, `low-yield`, `exhausted`, `no-results`, `blocked`).

### Tips

- **Fill in the location.** It is the difference between a stable result and a random city.
- **Raise Max requests per search** (up to 1000) to go past the default 60. In our two Complete coverage runs a request brought 4 to 5 new jobs on average, and the last 10 of 60 requests still brought 47 and 25 new jobs. The `low-yield` stop reason in the summary tells you when a search ran dry: past that point more requests would mostly return jobs you already have.
- **One run, several cities.** Several locations in one run share the **Max jobs** limit.
- **Daily feed.** New jobs only, Posted within past 24 hours, a monitor name and a webhook URL.

### FAQ, disclaimers, and support

#### Is it legal to scrape Google Jobs?

Our Actors are ethical and do not extract any private user data, such as email addresses, gender, or location. They only extract what the user has chosen to share publicly. We therefore believe that our Actors, when used for ethical purposes by Apify users, are safe. However, you should be aware that your results could contain personal data. Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your reason is legitimate, consult your lawyers. Check Google's terms for your use case as well.

#### Why did my run return fewer jobs than Max jobs?

Google had no more distinct jobs for the search, or the budget of requests ran out. Read the `endReason` of each search in the `SUMMARY` record.

#### Why does a job have no salary or no posting age?

Google does not show them for every job. The field is `null`.

#### Why did the run fail?

A run fails when the input is invalid, or when Google refused every request of the run. Nothing is charged in that case. Try again in a few minutes.

#### Can I call it from code?

Yes, see the **API** tab for the endpoints and client libraries. Something wrong or missing? Open a ticket in the **Issues** tab and it will be fixed.

# Actor input Schema

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

One search per line, as you would type it in Google: <code>software engineer</code>, <code>registered nurse</code>, <code>warehouse operative</code>. Each line is searched in each location. The words <code>jobs in</code> and the location are added for you.

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

Cities or regions, one per line: <code>Austin, TX</code>, <code>Manchester</code>, <code>Berlin</code>. <b>Always fill this in.</b> The location is written into the Google query. Without it Google uses the IP address of the proxy and two identical runs return different jobs.

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

Which Google to ask: it sets the domain (google.com, google.co.uk, google.de...) and the country of the results. Match it with your location.

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

Interface language of Google, a two-letter code such as <code>en</code>, <code>de</code>, <code>fr</code>. Empty uses the main language of the country. The date and job type filters were checked in English, German and French; in other languages they are applied to what each job shows.

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

Only jobs posted in this window. Google gets the filter as part of the search, then every job's age is checked again before you pay: a job older than the window is dropped, not charged. <i>Today</i> means up to 24 hours old.

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

Keep only these types. Google's own filter only leans towards a type (a search for internships returns full-time jobs too), so the type shown on each job is checked and a job showing another type is dropped, not charged. A job that shows no type is kept. Empty keeps all.

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

Keep only jobs Google marks as <i>Work from home</i>. Jobs without that mark are dropped, not charged.

## `completeCoverage` (type: `boolean`):

On: ask Google many variations of each search (dates, job types, seniority, then the companies and titles it finds) until nothing new comes back, up to <b>Max requests per search</b>. Off: one request, the 10 jobs Google ranks first.

## `startUrls` (type: `array`):

Optional. Google Jobs search URLs, such as <code>https://www.google.com/search?q=nurse+jobs+in+leeds\&udm=8</code> (or <code>ibp=htl;jobs</code>). The query, <code>hl</code> and <code>gl</code> are read from the URL. Put the place in the query.

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

Total jobs returned by the run, across all searches.

## `maxRequestsPerSearch` (type: `integer`):

Budget of Google requests for each search (each returns up to 10 jobs). The Actor stops earlier when new requests stop finding new jobs.

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

Google requests in flight at once, 1 to 16.

## `newOnly` (type: `boolean`):

Remembers the jobs returned by earlier runs and returns only jobs not delivered before. The first run returns everything that matches. Schedule it daily with <b>Posted within: Past 24 hours</b> for a feed of fresh jobs that never repeats.

## `monitorName` (type: `string`):

Without a name, the memory of delivered jobs belongs to this exact set of searches. With a name, every run using that name shares it, even if you change the searches.

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

If set, the Actor POSTs a small JSON summary to this URL at the end of the run, <b>only when at least one job was returned</b>: <code>actor</code>, <code>runId</code>, <code>datasetId</code>, <code>offers</code>, <code>newOnly</code> and one line per search. Use the final URL: a redirect is not followed, except a 307 or 308. An unreachable webhook is logged and never fails the run. Stored encrypted.

## Actor input object example

```json
{
  "queries": [
    "software engineer"
  ],
  "locations": [
    "Austin, TX"
  ],
  "country": "us",
  "datePosted": "any",
  "remoteOnly": false,
  "completeCoverage": true,
  "maxItems": 50,
  "maxRequestsPerSearch": 60,
  "maxConcurrency": 10,
  "newOnly": false
}
```

# Actor output Schema

## `results` (type: `string`):

All jobs in the default dataset.

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

For each search: Google requests, empty and failed ones, distinct jobs found, jobs returned, jobs dropped by the date, type and remote checks, whether the location was missing, and why the search stopped.

# 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, TX"
    ],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("alexmorain/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, TX"],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("alexmorain/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, TX"
  ],
  "maxItems": 50
}' |
apify call alexmorain/google-jobs-scraper --silent --output-dataset

```

## MCP server setup

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