Google Jobs Scraper
Give it job searches ("software engineer", "registered nurse") and places ("Seattle, WA", "Berlin"), and it returns one JSON row per Google Jobs result: title, company, location, posted date, salary (as shown and parsed), job type, full description and every apply link Google lists. $8.10 per 1,000 jobs on the Free plan, $3.15 on Business plans and above; you pay only for rows you get.
$8.10 per 1,000 jobs on the Free plan, down to $3.15 on higher plans
Why this actor
- The company's own ATS posting is picked as the best apply link, with the ATS and board detected.
- Goes past Google's 10 jobs per page to 60-150 jobs per search, with Google's own filters.
- SerpApi parameter names (
q,date_posted,employment_type,gl,hl,num_pages) and these field names are also accepted:query,location,jobType,maxResults,maxPagesPerQuery,remote. - No login; recruiter names, emails and phones are never output. Empty searches are free rows.
What you get
| Field | What it holds |
|---|---|
jobId | Google's id of the posting, stable across searches and runs (use it to dedupe). |
title, companyName, location | Job title, hiring organisation and place as Google shows them. |
via | Site Google read the posting from (LinkedIn, Indeed, the company's career site, ...). |
postedAgo, postedAt | Age as shown ("3 days ago") and the approximate date it gives (2026-10-19). |
salaryText, salary | Salary as shown and parsed: min, max, currency, period (hour, year, ...). |
employmentType, employmentTypes | Job type as shown and normalised (FULLTIME, PARTTIME, CONTRACTOR, INTERN, TEMPORARY). |
qualification, workFromHome | Google's qualification chip ("No degree mentioned") and its work-from-home flag. |
description, descriptionComplete | The job description, and whether it is the full text or Google's preview. |
applyUrl, applyOptions | Best apply link (the company's own ATS posting when listed) and every apply option with site, URL and detected ATS. |
googleJobsUrl | Link that opens the posting in Google Jobs. |
searchText, query, searchLocation, position | The exact text sent to Google, your query and location, and the rank. |
jobsDb | With jobsDbExport: ATS, board, ATS posting id, clean apply URLs, fingerprint (see below). |
Sample row (from the README example; long text shortened, lists cut to two items):
{
"rowType": "job",
"platform": "google-jobs",
"jobId": "d6Zuw_TVelot7iRuAAAAAA==",
"title": "Software Development Engineer, Network Monitoring and Remediation (NMR)",
"companyName": "Amazon Web Services, Inc.",
"location": "Seattle, WA",
"via": "Indeed",
"postedAgo": "5 days ago",
"postedAt": "2026-10-17",
"salaryText": "$144K–$194K a year",
"salary": {
"min": 144000,
"max": 194000,
"currency": "USD",
"period": "year",
"source": "structured"
},
"employmentType": "Full-time",
"employmentTypes": [
"FULLTIME"
],
"qualification": null,
"workFromHome": false,
"description": "DESCRIPTION\n\nAmazon's network carries traffic for hundreds of millions of customers ...",
"descriptionComplete": true,
"applyUrl": "https://www.indeed.com/viewjob?jk=917ad7a1f42a4176&utm_campaign=google_jobs_apply&utm_source=google_jobs_apply&utm_medium=organic",
"applyOptions": [
{
"source": "Indeed",
"label": "Apply on Indeed",
"url": "https://www.indeed.com/viewjob?jk=917ad7a1f42a4176&utm_campaign=google_jobs_apply&utm_source=google_jobs_apply&utm_medium=organic",
"domain": "indeed.com",
"ats": null
},
{
"source": "LinkedIn",
"label": "Apply on LinkedIn",
"url": "https://www.linkedin.com/jobs/view/software-development-engineer-network-monitoring-and-remediation-nmr-at-amazon-web-services-aws-4480587462?utm_campaign=go...",
"domain": "linkedin.com",
"ats": null
}
],
"googleJobsUrl": "https://www.google.com/search?ibp=htl;jobs&q=software+engineer+jobs+in+seattle&htidocid=d6Zuw_TVelot7iRuAAAAAA%3D%3D",
"searchText": "software engineer in Seattle, WA",
"query": "software engineer",
"searchLocation": "Seattle, WA",
"position": 2,
"language": "en",
"country": "US",
"scrapedAt": "2026-10-22T08:00:00.000Z"
}
Price
Live Apify price on 2026-11-03, per 1,000 rows, by Apify plan. You pay only for rows delivered; error and summary rows marked free in the README cost nothing. Plus $0.001 per GB of memory when a run starts.
| Per 1,000 | Free | Bronze | Silver | Gold | Platinum | Diamond |
|---|---|---|---|---|---|---|
| Job (main) | $8.10 | $4.05 | $3.60 | $3.15 | $3.15 | $3.15 |
Use cases
- Job market and salary research: who is hiring for a role, where, at what pay, by job type.
- Recruiting, sourcing and sales: companies hiring for X (a buying signal), with links to their career sites.
- Job boards and aggregators: fresh postings with full text and every apply link, deduplicated by
jobId. - Joining with ATS data:
jobsDbExportgives the company's Greenhouse, Lever, Workday ... board and posting id. - AI agents: "open jobs for X in Y this week" as clean rows.
Run it from code or an AI agent
Actor ID arcadium/google-jobs. Set APIFY_TOKEN to your Apify API token. The input below is the actor’s prefilled example; see the guide for every field.
curl
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":["software engineer"],"locations":["Seattle, WA"],"datePosted":"any","remoteOnly":false,"maxResultsPerQuery":10,"country":"US","language":"en","includeDescription":true}'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": [
"software engineer"
],
"locations": [
"Seattle, WA"
],
"datePosted": "any",
"remoteOnly": false,
"maxResultsPerQuery": 10,
"country": "US",
"language": "en",
"includeDescription": true
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);Python
# pip install apify-client
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("arcadium/google-jobs").call(run_input={
"queries": ["software engineer"],
"locations": ["Seattle, WA"],
"datePosted": "any",
"remoteOnly": False,
"maxResultsPerQuery": 10,
"country": "US",
"language": "en",
"includeDescription": True,
})
items = client.dataset(run.default_dataset_id).list_items().items # apify-client 3.x returns a Run object
print(items)MCP
{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com/?tools=arcadium/google-jobs",
"headers": {
"Authorization": "Bearer <APIFY_TOKEN>"
}
}
}
}LangChain
# pip install langchain-apify (reads APIFY_API_TOKEN from the environment)
from langchain_apify import ApifyActorsTool
tool = ApifyActorsTool("arcadium/google-jobs")
rows = tool.invoke({"run_input": {
"queries": ["software engineer"],
"locations": ["Seattle, WA"],
"datePosted": "any",
"remoteOnly": False,
"maxResultsPerQuery": 10,
"country": "US",
"language": "en",
"includeDescription": True,
}})
# or give `tool` to an agent: create_react_agent(llm, [tool])LlamaIndex
# pip install llama-index-readers-apify
import os
from llama_index.core import Document, VectorStoreIndex
from llama_index.readers.apify import ApifyActor
reader = ApifyActor(os.environ["APIFY_TOKEN"])
documents = reader.load_data(
actor_id="arcadium/google-jobs",
run_input={
"queries": ["software engineer"],
"locations": ["Seattle, WA"],
"datePosted": "any",
"remoteOnly": False,
"maxResultsPerQuery": 10,
"country": "US",
"language": "en",
"includeDescription": True,
},
dataset_mapping_function=lambda item: Document(text=str(item.get("description") or ""), metadata={"url": item.get("url")}),
)
index = VectorStoreIndex.from_documents(documents)CrewAI
# pip install crewai crewai-tools langchain-apify (reads APIFY_API_TOKEN from the environment)
from crewai import Agent
from crewai_tools import ApifyActorsTool
tool = ApifyActorsTool(actor_name="arcadium/google-jobs")
researcher = Agent(
role="Research analyst",
goal="Answer with data collected by the tool",
backstory="Uses public web data.",
tools=[tool],
)n8n
{
"nodes": [
{
"name": "Run arcadium/google-jobs",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
0,
0
],
"parameters": {
"method": "POST",
"url": "https://api.apify.com/v2/acts/arcadium~google-jobs/run-sync-get-dataset-items",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"queries\":[\"software engineer\"],\"locations\":[\"Seattle, WA\"],\"datePosted\":\"any\",\"remoteOnly\":false,\"maxResultsPerQuery\":10,\"country\":\"US\",\"language\":\"en\",\"includeDescription\":true}",
"options": {
"timeout": 300000
}
}
}
],
"connections": {}
}Google Sheets
// Extensions > Apps Script. Store your token once: Project Settings > Script properties > APIFY_TOKEN.
function importLastRun() {
const token = PropertiesService.getScriptProperties().getProperty('APIFY_TOKEN');
const url = 'https://api.apify.com/v2/acts/arcadium~google-jobs/runs/last/dataset/items?status=SUCCEEDED&clean=true&format=json';
const res = UrlFetchApp.fetch(url, { headers: { Authorization: 'Bearer ' + token } });
const rows = JSON.parse(res.getContentText());
if (!rows.length) return;
const keys = Object.keys(rows[0]);
const values = rows.map((r) => keys.map((k) => (typeof r[k] === 'object' ? JSON.stringify(r[k]) : r[k])));
const sheet = SpreadsheetApp.getActiveSheet();
sheet.clearContents();
sheet.getRange(1, 1, 1, keys.length).setValues([keys]);
sheet.getRange(2, 1, values.length, keys.length).setValues(values);
}Tutorials
Limitations
- 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.
FAQ
Why do I get fewer jobs than maxResultsPerQuery? Google had no more for that search: its page and filter variants stopped adding new jobs. Broaden the query or the location, or add more locations.
Why is a search an error row with code no_match? Google Jobs listed nothing for it ("It looks like there aren't any Jobs matches"). Check the spelling or broaden it. It is free.
Can I search any country? Yes: set country (and language for local labels), e.g. "country": "IN" or "country": "DE", "language": "de".
Something looks wrong in the output. Who do I tell? Open an issue on the actor page with the run link. We read them all.
Related actors
ATS Lookup
Find the ATS behind a company domain (Greenhouse, Lever, Ashby, Workday and 5 more), its job board URL and open jobs; $4 per 1,000 companies found.
$4.00 per 1,000 companies with a board on the Free plan, down to $3.00 on higher plans
Career Site Jobs Scraper
Open jobs from a company's own career site (Greenhouse, Lever, Workday, Personio and 5 more ATS) for a company domain; $1.80 per 1,000 jobs.
$1.80 per 1,000 jobs on the Free plan, down to $1.08 on higher plans
Seek Jobs Scraper
Seek.com.au and Seek.co.nz jobs (title, company, suburb, pay, work type, full description, closing date) by keyword and place; $1.80 per 1,000 jobs.
$1.80 per 1,000 jobs on the Free plan, down to $1.35 on higher plans
Wellfound Jobs Scraper
Wellfound (AngelList Talent) startup jobs by role, location or URL with salary, equity, description and company stage; $2.70 per 1,000 jobs.
$2.70 per 1,000 jobs