Apify Actors / Google Jobs listings with salary and apply links

Apify Actor

Google Jobs listings with salary and apply links

Google search has a jobs panel that pulls listings and their apply links into one place, 10 jobs per search. This Actor runs your searches there and saves one row per job with the title, company, location, salary when the listing shows one, the posted date, the full description and Google's job highlights.

Apply links are the posting's own URLs with Google's utm_ tracking tags removed, or Google's redirect link when the page doesn't carry the direct URL. Salaries are parsed into salaryMin, salaryMax, salaryPeriod and salaryCurrency and stay null when the listing shows none, because the Actor never guesses one. Pages that Google blocks, duplicates and jobs outside your filters aren't charged.

Run it on ApifySee input and output

$0.004 per job saved · pay per result, no subscription

Price

EventPriceCharged when
job$0.004Each job saved to the dataset. Blocked or script-only pages, jobs outside your filters, duplicates and failed searches are free
Example runUnitsCost
The input below (up to 30 jobs)up to 30 × $0.004up to $0.12
Empty form (up to 20 "software engineer" jobs)up to 20 × $0.004up to $0.08
1,000 jobs1,000 × $0.004$4.00

What you get

Input

Set the run options in the Apify console, or send the same object through the API.

{
  "queries": [
    "data analyst"
  ],
  "location": "Chicago, IL",
  "datePosted": "3days",
  "maxJobsPerQuery": 30
}

Data analyst jobs near Chicago posted in the last 3 days, up to 30. Up to 50 searches per run and up to 500 jobs per search. The location is added to every search as "near Chicago, IL".

Output

{
  "searchQuery": "software engineer",
  "searchLocation": "Austin, TX",
  "jobId": "eyJqb2IiOiIxIn0=",
  "title": "Senior Software Engineer",
  "company": "Acme Corp",
  "location": "Austin, TX",
  "isRemote": false,
  "via": "Acme Careers",
  "employmentType": "Full-time",
  "salary": "$120K–$150K a year",
  "salaryMin": 120000,
  "salaryMax": 150000,
  "salaryPeriod": "year",
  "salaryCurrency": "$",
  "postedAgo": "3 days ago",
  "postedDaysAgo": 3,
  "postedDateApprox": "2026-11-02",
  "applyUrl": "https://careers.acme.example/jobs/123",
  "applyLinks": [
    { "title": "Acme Careers", "url": "https://careers.acme.example/jobs/123" }
  ],
  "description": "We build things...",
  "descriptionTruncated": false,
  "highlights": [
    { "title": "Qualifications", "items": ["5+ years of experience with Python", "Experience with AWS"] }
  ],
  "position": 1,
  "page": 1,
  "matchedSearch": "software engineer jobs near Austin, TX",
  "source": "google",
  "googleJobsSearchUrl": "https://www.google.com/search?q=software+engineer+jobs+near+Austin%2C+TX&udm=8&hl=en&gl=us",
  "scrapedAt": "2026-11-05T12:00:00.000Z"
}

The output example from the README, trimmed. postedDateApprox is the scrape date minus the age Google shows, a day-level estimate that is null for "30+ days ago". The dataset has a Jobs view and a Salaries view.

Run it from your own code

The Actor takes the same input as a JSON body. This call starts a run, waits for it and returns the dataset items.

curl -X POST "https://api.apify.com/v2/acts/zovo-one~google-jobs-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queries": ["data analyst"], "location": "Chicago, IL", "datePosted": "3days", "maxJobsPerQuery": 30}'

A sync call waits at most 300 seconds. For longer runs, start a run with POST /v2/acts/zovo-one~google-jobs-scraper/runs, wait for it, then read /v2/datasets/<defaultDatasetId>/items.

Limits

Google shows 10 jobs per search, so for more the Actor runs the same search again with the date and job type refinements Google offers and keeps only new jobs. A query stops after 3 refined searches in a row bring nothing new, or after about 2 searches per 10 jobs asked for (25 at most). Expect roughly 100 to 150 jobs at most for a broad search; 500 is a ceiling you set, not a promise. Up to 50 searches per run, 200 characters per search and 100 characters for the location. Descriptions longer than 20,000 characters are cut and descriptionTruncated is true. Salary is read in English, German, French, Spanish, Portuguese, Italian and Dutch; other languages, currencies and formats leave the salary fields null. Each page gets about 150 seconds across its tries.

Questions

How do I scrape Google Jobs for one city?

Put the job title in queries and the city in location, for example "Chicago, IL". The Actor adds plain phrases to the search, like "near Chicago, IL" and "in the last 3 days", the same way Google's own filter buttons do. Each search's googleJobsSearchUrl opens that same search in your browser.

Why are the salary fields empty on some rows?

The listing shows no salary, and the Actor never guesses one. A bound the listing doesn't give stays null too, so "$100K+ a year" has salaryMax null. The currency is the symbol Google shows, so a "$" in Canada stays "$" and isn't turned into USD.

Why did I get fewer jobs than I asked for?

Google shows 10 jobs per search, and paging stops early when the refined searches bring no new jobs. In our tests 20 jobs took 2 searches and 50 jobs took 6. Expect roughly 100 to 150 jobs at most for a broad search.

What happens when Google blocks a page?

Google answers plain connections with a page that asks you to turn on JavaScript. That's why the default proxy is Apify Proxy with the GOOGLE_SERP group, which Apify runs for Google Search pages. If Google sends a JavaScript page, a captcha or HTTP 429, the Actor fetches the page again through the proxy, up to 4 tries per page, and none of these pages are charged. If 30 pages in a row come back script-only, the run stops and fails with a message.

Can I use my own SerpApi key?

Yes. Set source to serpapi and paste your key into serpApiKey. The Actor then calls SerpApi's google_jobs API and returns the same row format. SerpApi bills you for each search on your own plan, and this Actor still charges $0.004 per saved job. Your key is stored as a secret input and is never written to the log or the run report.

Does it remove duplicate jobs?

Yes, with dedupe on (the default). A job already saved by another search in the same run is skipped and not charged, matched on its Google job ID, or on title, company, location and apply URL. Repeats inside one search are always skipped.

Support this work

If this Actor saves you time, you can support the work on GitHub Sponsors. Monthly tiers start at $5, and one-time tiers start at $15. Sponsorship is optional, and the tool works the same without it.

Built and maintained by Zovo. Actor on Apify · All Actors.