Google Jobs scraper: export job listings with salary and apply links to CSV
Recruiters, job boards and labour-market analysts use Google Jobs because it gathers postings from job boards and company career sites in one place. It has no export, and one search page shows 10 jobs.
arcadium/google-jobs takes search queries and places and returns one JSON row per job: title, company, location, posted date, salary as shown and parsed into min, max, currency and period, job type, the full description and every apply link. When Google lists the company’s own applicant tracking system posting, that link is picked as applyUrl. No login; recruiter names, emails and phone numbers are never output.
Cost of this example on the Apify Free plan: 60 × $0.0081 = $0.486 ($0.189 on the Apify Gold plan and higher), plus $0.001 per GB of memory when the run starts.
What you need
- An Apify account and API token.
- The roles and places to search, for example
data analystinAustin, TX.
Step 1: Run it in the Apify Store (no code)
- Open arcadium/google-jobs on the Apify Store and sign in (a free Apify account works).
- Type the roles into Search queries, one per line, the way you would type them into Google.
- Add the places under Locations, for example
Austin, TXorBerlin. Each query runs once per location. - Set Date posted to
weekand Max jobs per search to 30. Above 10, the actor also runs the filter variants Google offers on the page and merges them without duplicates. - Optional: set a maximum cost per run in the run options as a hard cap.
- Click Start. Rows appear in the Output tab; export them as JSON, CSV or Excel.
The same input as JSON (paste it in the JSON tab of the input form):
{
"queries": [
"data analyst",
"business analyst"
],
"locations": [
"Austin, TX"
],
"datePosted": "week",
"maxResultsPerQuery": 30
}
Step 2: Call it over the Apify API
One HTTP call runs the actor and returns its rows. It suits runs up to about five minutes; for longer runs start the run asynchronously and read the dataset when it finishes.
curl -X POST "https://api.apify.com/v2/acts/arcadium~google-jobs/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"queries":["data analyst","business analyst"],"locations":["Austin, TX"],"datePosted":"week","maxResultsPerQuery":30}'
Step 3: Run it from Python
With apify-client 3.x, call() returns a Run object: read the dataset id as run.default_dataset_id (older examples use run["defaultDatasetId"], which no longer works).
# pip install "apify-client>=3"
import csv, os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("arcadium/google-jobs").call(run_input={
"queries": ["data analyst", "business analyst"],
"locations": ["Austin, TX"],
"datePosted": "week",
"maxResultsPerQuery": 30,
})
rows = list(client.dataset(run.default_dataset_id).iterate_items()) # apify-client 3.x
keys = ["jobId", "title", "companyName", "location", "postedAt", "salaryText",
"salaryMin", "salaryMax", "salaryPeriod", "employmentTypes", "via", "applyUrl"]
with open("google-jobs.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=keys, extrasaction="ignore")
w.writeheader()
for r in rows:
if r.get("rowType") != "job":
continue
s = r.get("salary") or {}
w.writerow({**r, "salaryMin": s.get("min"), "salaryMax": s.get("max"), "salaryPeriod": s.get("period"),
"employmentTypes": ", ".join(r.get("employmentTypes") or [])})
for e in (r for r in rows if r.get("rowType") == "error"):
print("no jobs:", e.get("input"), e.get("code"), e.get("message"))
Step 4: Run it from JavaScript
// npm install apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('arcadium/google-jobs').call({
"queries": [
"data analyst",
"business analyst"
],
"locations": [
"Austin, TX"
],
"datePosted": "week",
"maxResultsPerQuery": 30
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
Step 5: Ask an AI assistant (MCP)
Apify’s hosted MCP server turns the actor into one tool. Add it to Claude, Cursor, VS Code or any MCP client:
{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com/?tools=arcadium/google-jobs",
"headers": {
"Authorization": "Bearer <APIFY_TOKEN>"
}
}
}
}Then ask, for example:
Use arcadium/google-jobs to find data analyst jobs in Austin, TX posted this week. Group them by company, show the salary range where one is listed and give me the direct apply link of each job.
More clients and the local setup on the MCP page.
New jobs every day
Save the input as a Task in Apify Console, set datePosted to today and add a daily Schedule. jobId is Google’s id of the posting and stays the same across searches and runs: keep the ids you have seen and the new ones are the new jobs.
Other countries and languages
Set country to the Google market and language to the interface language. Labels such as "3 days ago" follow the language; postedAt, employmentTypes and salary.period are normalised for en, de, fr, es, it, pt and nl.
{
"queries": [
"Pflegefachkraft"
],
"locations": [
"Berlin",
"Hamburg"
],
"country": "DE",
"language": "de"
}
Which company ATS a job links to
With "jobsDbExport": true each row gets a jobsDb object: the ATS (Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Personio, Teamtailor or Workday), the company’s board on it, the ATS posting id and every apply URL without tracking parameters. It suits recruiting and sales teams that use hiring as a buying signal.
Output fields
Every row has a rowType; filter on it first. Failed inputs come back as free rows with a code and message. The fields this tutorial uses, from the actor’s dataset schema:
| Field | What it holds |
|---|---|
rowType | Kind of row. Filter on this first. Values: 'job': one job posting from Google Jobs (charged). 'error': a search found nothing or could not be read; see code and message (free). |
jobId | Google's document id of the posting (htidocid), e.g. d6Zuw_TVelot7iRuAAAAAA==. The same posting keeps it across queries and runs: use it to deduplicate. |
title | Job title as published, e.g. Software Development Engineer II. |
companyName | Hiring organisation as Google lists it, e.g. Amazon Web Services, Inc. |
location | Job location as Google shows it, e.g. Seattle, WA or Berlin. Remote jobs may say Anywhere. |
via | Site Google read the posting from, e.g. LinkedIn, Indeed, Acme Careers (the 'via ...' line). |
postedAt | Approximate posting date (yyyy-mm-dd) computed from postedAgo on the run day; '30+ days ago' gives a lower bound. Null when Google gave no age. |
salaryText | Salary as Google shows it, e.g. $144K–$194K a year, 30 € pro Stunde. Null when not stated. |
salary | Salary parsed from salaryText: min, max (equal for a single value), currency (ISO code), period (hour, day, week, month, year). |
employmentTypes | Normalised employment types: FULLTIME, PARTTIME, CONTRACTOR, INTERN, TEMPORARY. Empty when unknown. |
description | Job description text as Google shows it (includeDescription). Emails and phone numbers replaced with [email] and [phone]. |
descriptionComplete | True when description is the full text; false when Google showed only a preview (some markets show about 400 characters). |
applyUrl | Best apply link: the company's own applicant tracking system posting when Google lists one, else Google's first apply option. |
applyOptions | Every apply option Google lists: source (site name), label (button text), url, domain and ats (greenhouse, lever, workday, ... when the URL is a company job board). |
jobsDb | jobsDbExport only: atsJobId (<ats>___<posting id>, as career-site-jobs names it), ats, boardToken, boardKey, atsUrl, applyUrls without tracking parameters, fingerprint (sha1 of company|title|location). |
All fields: Google Jobs Scraper guide.
Cost per run
Pay per event: you pay for the rows delivered, at the actor’s live Apify price on 2026-11-03. Error, coverage and summary rows are free.
| What | Rows | Price per row (Free plan) | Subtotal |
|---|---|---|---|
| Job: 2 queries x 1 location x up to 30 jobs | 60 | $0.0081 | $0.486 |
Cost of this example on the Apify Free plan: 60 × $0.0081 = $0.486 ($0.189 on the Apify Gold plan and higher), plus $0.001 per GB of memory when the run starts. Higher Apify plans pay less per row. The Store page always has the current price.
Limitations
maxResultsPerQueryis a cap: a narrow search returns fewer jobs, and you pay only for the rows you get. The same job found by two searches comes once and is charged once.- The cost example assumes both searches reach 30 jobs.
- Google shows 10 jobs per search. Past that, coverage comes from Google's own filter variants, so the total per search is what those variants list (typically 60-150 for a broad search), not every job Google has.
- Results, ranking and the location area are Google's. A location is sent the way you would type it ("in Seattle, WA"); a very small place may return jobs from the surrounding area.
- Some markets show only a preview of the description (about 400 characters,
descriptionComplete: false); the full text is on the apply links. - Salaries are what Google shows; many postings have none.
- Labels such as "3 days ago" follow
language;postedAt,employmentTypesandsalary.periodare normalised for en, de, fr, es, it, pt and nl.
Links
- Google Jobs Scraper: fields, price table and sample output
- How to use Google Jobs Scraper: every input field, n8n, LangChain
- arcadium/google-jobs on the Apify Store
- All tutorials