# Google Jobs Scraper - Job Search & Apply Links (`chronometrica/google-jobs-scraper`) Actor

Search Google Jobs by keyword and location. Export unique jobs with titles, companies, full descriptions, salary text, posting dates, and direct ATS or job-board apply links. No Google login or external API key required.

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

## Pricing

from $3.50 / 1,000 google job results

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/platform/actors/running/actors-in-store#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 - Job Search & Apply Links

### 🔎 What does Google Jobs Scraper do?

Google Jobs Scraper searches public Google Jobs results by keyword and
location. It saves one clean row for each unique job.

Select a Google market, add one or more searches, choose an optional shared
location, and set a saved-row limit. Each row can include the job title,
company, location, full description, salary text, schedule, posting age,
Google job URL, and direct application options.

Use it to:

- 🔔 Build scheduled job alerts.
- 🧑‍💼 Monitor hiring by company, role, or location.
- 📊 Study public job demand and salary text.
- 🧱 Add job listings to a database, dashboard, or job board.
- 🚀 Find direct employer, career-site, and job-board application links.
- 📦 Export data as JSON, JSONL, CSV, Excel, XML, RSS, or HTML.

The Actor reads public, logged-out Google Jobs results. It does not need a
Google account, browser cookies, or an external API key.

### 📦 What Google job data can I extract?

Each default-dataset row represents one unique Google job found during one
Actor run.

| Data group           | Example fields                                           |
| -------------------- | -------------------------------------------------------- |
| 🔍 Search context    | `query`, `requestedLocation`, `country`, `language`      |
| 🆔 Job identity      | `jobId`, `jobUrl`                                        |
| 💼 Job details       | `title`, `companyName`, `location`, `via`, `description` |
| 💰 Job facts         | `postedAtRaw`, `scheduleType`, `salaryRaw`, `tags`       |
| 🚀 Application links | `applyOptions[].title`, `applyOptions[].link`            |
| 🚦 Row state         | `status`, `statusReason`                                 |
| 🕐 Run context       | `observedAt`, `runId`, `schemaVersion`                   |

Missing public values stay `null` or use an empty list. The Actor does not
guess missing values or replace them with zero.

#### 🚀 Direct application links

The `applyOptions` list contains the application pages that Google supplies.
Each item has a provider `title` and a direct `link`. The list can include an
employer applicant tracking system (ATS), a company career page, or a job
board. An ATS is software that an employer uses to receive job applications.

One job can have several application options. The raw dataset row contains the
full list. The default Results table does not show this nested field.

### ⚙️ Can I use this Actor through an API?

Yes. Run the Actor in Apify Console or connect it to the Apify API, Python SDK,
JavaScript SDK, webhooks, schedules, or Apify integrations.

Keep your Apify token in an environment variable or secret store. Do not place
it in source code.

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/chronometrica~google-jobs-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "market": "CA-en",
    "queries": ["data analyst", "business intelligence analyst"],
    "location": "Toronto, Ontario, Canada",
    "maxItems": 25
  }'
```

Use the returned run ID to check the run, read the default dataset, or attach a
webhook. The same JSON input works with the Apify Python and JavaScript clients.

### 🎯 Why scrape Google Jobs?

Google Jobs brings listings from employers, career sites, and job boards into
one search view. This can give you broader public job coverage than one job
board.

| Use case                 | How the data helps                                          |
| ------------------------ | ----------------------------------------------------------- |
| 🔔 Job alerts            | Find new jobs for a role and location on a schedule.        |
| 🧑‍💼 Recruiting research   | Track companies, roles, locations, and hiring language.     |
| 📊 Labor-market analysis | Compare visible demand, salary text, and posting activity.  |
| 🧱 Job-board feeds       | Load unique job rows and direct application choices.        |
| 🕵️ Employer monitoring   | Watch hiring for selected companies or role groups.         |
| 🤖 Automated workflows   | Send jobs to a database, spreadsheet, CRM, or alert system. |

The Actor collects source data. It does not rank candidates, assess people, or
make hiring decisions.

### 💵 Pricing

Google Jobs Scraper uses pay-per-result pricing. One result means one unique
job row saved to the default dataset. The price depends on your Apify plan.

| Apify plan | Price per 1,000 saved jobs |
| ---------- | -------------------------- |
| Free       | $5.00                      |
| Starter    | $4.50                      |
| Scale      | $4.00                      |
| Business   | $3.50                      |

Duplicate and invalid candidates do not create a result charge. Use `maxItems`
to set a firm saved-row limit. Check the Store **Pricing** tab for the current
rate before a production run.

Start with `maxItems: 10` when you test a new search.

### 🚀 How do I use Google Jobs Scraper?

1. Open **Google Jobs Scraper** in Apify Console.
2. Select the Google market and result language.
3. Enter one or more job titles or keywords.
4. Enter one optional city or region for all searches.
5. Set the maximum number of jobs to save.
6. Click **Start**.
7. Open **Output** to inspect the dataset and run summary.
8. Download the data or connect the run to another tool.

### ⬇️ Input

```json
{
  "market": "CA-en",
  "queries": ["data analyst", "business intelligence analyst"],
  "location": "Toronto, Ontario, Canada",
  "maxItems": 25
}
```

| Setting    | What it controls                                                                       |
| ---------- | -------------------------------------------------------------------------------------- |
| `market`   | Tested Google country site and result language. It is not a strict job-country filter. |
| `queries`  | One to 100 job-title or keyword searches. Each query can have up to 200 characters.    |
| `location` | One optional city, region, or country for all searches. A city gives precise results.  |
| `maxItems` | Maximum unique rows saved across the run. The allowed range is 1 to 1,000.             |

The form offers 95 tested country and language pairs. Countries with more than
one tested language have one option for each language. The Actor still accepts
the earlier `country` and `language` API fields when they form one supported
market pair.

The Actor removes duplicate queries without treating letter case as different.
It gives active searches balanced row turns in query order. It requests only
the search pages that can still contribute to the shared saved-row limit. This
prevents one broad search from using the full limit and avoids paying for pages
that cannot add a saved row.

The Actor follows Google's continuation tokens until it reaches `maxItems`,
the source ends, or the 100-page per-query safety cap.

### ⬆️ Output sample

Results are stored in the default dataset. Field order is stable for JSON,
CSV, spreadsheet, API, and database work.

```json
{
  "query": "data analyst",
  "requestedLocation": "Toronto, Ontario, Canada",
  "country": "CA",
  "language": "en",
  "jobId": "example-job-id",
  "jobUrl": "https://www.google.com/search?ibp=htl;jobs&htidocid=example-job-id",
  "title": "Data Analyst",
  "companyName": "Example Company",
  "location": "Toronto, ON",
  "via": "Example Careers",
  "description": "Analyze business data and prepare reports.",
  "postedAtRaw": "Posted 2 days ago",
  "scheduleType": "Full-time",
  "salaryRaw": "$70,000-$85,000 a year",
  "tags": ["2 days ago", "Full-time", "No degree mentioned"],
  "applyOptions": [
    {
      "title": "Apply on Example Careers",
      "link": "https://jobs.example.com/data-analyst"
    }
  ],
  "observedAt": "2026-08-08T23:40:50.000Z",
  "runId": "example-run-id",
  "schemaVersion": "4.0",
  "status": "succeeded",
  "statusReason": "Job listing returned by Google Jobs."
}
```

#### 📌 Run summary

The `OUTPUT` record is stored in the default key-value store. It reports:

- `queriesReceived`: number of unique searches received.
- `sourceStates`: result state for each search.
- `pagesRequested`: source batches requested.
- `candidatesSeen`: jobs found before validation and duplicate removal.
- `rowsSaved`: unique job rows saved and charged.
- `duplicatesSkipped`: repeated job IDs not saved or charged.
- `invalidCandidatesSkipped`: candidates without the required identity fields.
- `stopReason`: `saved_row_cap_reached` or `sources_exhausted`.
- `startedAt` and `finishedAt`: run times in ISO format.

Use this record to tell the difference between a small result set, duplicate
jobs, a source error, and a completed saved-row limit.

A `partial` source state means that Google returned useful jobs before a later
continuation request failed. The Actor keeps and charges only those useful
rows.

### 🚦 Status, identity, and data quality

- Every saved row has a valid `jobId`, `title`, and `companyName`.
- Every saved row has `status: "succeeded"`.
- The Actor uses `jobId` to remove duplicates across all queries in one run.
- `observedAt` is the collection time. `postedAtRaw` is source text, not a
  normalized posting time.
- `tags` contains the exact job tags that Google supplies. The Actor does not
  infer or classify these values.
- `description`, salary, schedule, location, source, job URL, and application
  options can be missing.
- If every search produces no useful rows, the Actor writes `OUTPUT` and then
  marks the run as failed.
- Rows from a later run are new observations. The Actor does not update rows in
  an earlier dataset.

The Actor matches each detail section with Google's job ID. If Google omits
that ID and several jobs have the same title, the Actor leaves uncertain
`description`, `tags`, and `applyOptions` values empty.

#### ✅ Tested behavior

The current release passed a fixed 100-row cloud check. It saved 100 rows with
100 unique Google job IDs and 100 result billing events. The check also
confirmed the public field order, nested application-link order, and absence of
secret keys in the dataset and run summary.

Live job results can change between runs. This check confirms the release
process. It does not promise that each query will return 10 jobs.

### 🔒 Public-source boundary

The Actor reads public, logged-out Google Jobs results. It does not:

- Ask for a Google login, browser cookies, or account credentials.
- Access private or account-only data.
- Ask for an external search API key.
- Solve access challenges or bypass access controls.
- Open application pages or check their contents.
- Add candidate scores or make hiring decisions.

### ⚠️ Limits and interpretation

- Google controls which jobs and fields are visible for each search.
- Google can stop pagination before the requested row limit.
- Duplicate jobs across queries can reduce the final row count.
- Descriptions, salary text, schedules, and application links can be missing.
- Google can change, remove, localize, or limit results at any time.
- Country, language, query wording, and location can change the results.
- The Google market localizes results. It does not guarantee that every job is
  in that country.
- Application URLs are links supplied by Google. Check each destination before
  you use it in an automated process.
- `maxItems` is a saved-row limit. It is not a promise that the source contains
  that many jobs.

### ❓ FAQ

#### Does this Actor need a Google login, cookies, or an external API key?

No.

#### Why did I get fewer jobs than requested?

Google can return fewer jobs, repeat the same job across searches, omit a Jobs
result page, or limit the visible results. Check `sourceStates`,
`candidatesSeen`, `duplicatesSkipped`, `invalidCandidatesSkipped`, `rowsSaved`,
and `stopReason` in `OUTPUT`.

#### How do I collect more than 10 jobs?

Use several focused queries. Search related job titles separately. The Actor
removes repeated Google job IDs across those searches.

#### Where are the direct application links?

Open the raw dataset row and read `applyOptions`. The default Results table does
not show this nested list.

#### Can I run the same searches on a schedule?

Yes. Use an Apify schedule. Use `jobId` to compare jobs across runs and
`observedAt` to record when each row was collected.

### ⚖️ Responsible use

This Actor is an independent tool. It is not affiliated with or endorsed by
Google. You are responsible for your use of the data and for compliance with
applicable laws and source terms.

### 🛟 Support

Include the Apify run ID, a small input sample, what you expected, and what you
received. State whether the issue affects one row or the full run. Do not send
passwords, cookies, or account credentials.

### 🔗 Related job scrapers

- [LinkedIn Jobs Scraper](https://apify.com/chronometrica/linkedin-jobs-scraper)
  — collect public jobs with descriptions, criteria, salary signals, and Easy
  Apply status.
- [Indeed Jobs Scraper](https://apify.com/chronometrica/indeed-jobs-scraper) —
  collect public jobs with salaries, descriptions, benefits, skills, and apply
  links.
- [Upwork Job Scraper](https://apify.com/chronometrica/upwork-job-scraper) — find
  public freelance jobs with skills, budgets, proposal counts, and activity
  signals.

# Actor input Schema

## `market` (type: `string`):

Select a tested Google country site and result language. This setting does not guarantee that every job is in this country.

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

Enter one job title, skill, employer, or phrase per line. Do not add the location here when you use the location field below.

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

Google adds this location to every search. Leave it blank for broad results in the selected market. Use one location per run.

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

The Actor stops after it saves this many unique jobs across all searches. You pay only for saved jobs. Google can return fewer jobs.

## Actor input object example

```json
{
  "market": "US-en",
  "queries": [
    "data analyst"
  ],
  "location": "",
  "maxItems": 25
}
```

# Actor output Schema

## `dataset` (type: `string`):

Unique Google job rows saved by this run.

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

Counts, source states, and the stop reason for this run.

# 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": [
        "data analyst"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("chronometrica/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": ["data analyst"] }

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

```

## MCP server setup

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