Google Jobs Scraper — Google for Jobs Listings, Apply Links avatar

Google Jobs Scraper — Google for Jobs Listings, Apply Links

Pricing

from $1.00 / 1,000 job listings

Go to Apify Store
Google Jobs Scraper — Google for Jobs Listings, Apply Links

Google Jobs Scraper — Google for Jobs Listings, Apply Links

Google Jobs: about 90–130 job cards per search where Google has that many, not the first ten. Every row: title, company, location, apply links, full description; salary where Google posts one. From $1.00 per 1,000 job listings; a search that returns listings adds a $0.004 fee.

Pricing

from $1.00 / 1,000 job listings

Rating

0.0

(0)

Developer

Steadyfetch Team

Steadyfetch Team

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

1

Monthly active users

2 days ago

Last modified

Share

A Google Jobs scraper that returns the whole Google for Jobs panel, not the first ten results — from $1.00 per 1,000 job listings. One search returns about 90–130 job cards where Google has that many; niche searches return every card Google has. One row per posting: title, company, location, apply links and the full description, with salary where Google posts one.

Never pay for a job we didn't deliver. Set a row cap and a deadline and the run stops exactly there — no runaway bills.

Using an AI agent? Pin this actor in Apify's MCP server with one link: https://mcp.apify.com?tools=steadyfetch/google-jobs-scraper

  • Actor id: steadyfetch/google-jobs-scraper
  • Input: { "queries": ["software engineer jobs in austin"] } — the one field you have to set.
  • Cap the bill: set maxTotalChargeUsd on the run (a run option, not Actor input), e.g. 0.50 — the run stops when it reaches it.

From $1.00 per 1,000 job listings — all-inclusive pay per event, charged only on delivery. Each search that returns at least one listing also carries one search fee of $0.004; a search that returns nothing — empty, blocked, or never fetched because a cap bound first — pays no search fee.

Running the same search again? A listing already delivered to your account is never charged a second time (a run started with a scoped API token needs key-value store permission for that) — see Never pay for the same listing twice.

Just want to see it work? Run it with no input at all ({} from the API or an AI agent) and you get a real 5-listing sample — "software engineer" on Google Jobs US — charged like any run, at most $0.03 on the Apify free plan and less on paid plans. Set only the filters — a country, a location, a row cap — and no job searches, and that same sample runs under them, charged like any run; a limit larger than the sample's own is capped at it, and one extra uncharged row names which settings were yours. In the console, Start with the example searches already in the form and you get those searches for real, up to your row cap.

Google Jobs Scraper input form in the Apify console: job searches, location and country

Unofficial. This actor is not affiliated with, endorsed by, or sponsored by Google. "Google" and all related marks are trademarks of Google LLC. It reads only the public jobs panel an ordinary browser sees.

Every row carries charged and missReason, so you can reconcile the invoice from the dataset itself without opening the console. Only rows with charged: true were billed.


Output — a real row

Five US "software engineer" cards from a real run, in the Apify console's dataset table — the source board, posted date, employment type and the full description, one row per job:

Dataset table of a real Google Jobs Scraper run: five US software engineer job cards with source board, posted date, employment type and description columns

And one delivered row in full:

{
"source": "google-jobs",
"sourceJobId": "Ati0AB1gZvCYY_O-AAAAAA==",
"title": "Part-Time Barista/Counter Person 20+ hours",
"company": "Madison Kitchen",
"location": { "raw": "Seattle, WA", "city": "Seattle", "region": "WA", "postcode": null, "country": "US" },
"via": "Poached Jobs",
"postedRelative": "6 days ago",
"salaryText": "$21.30 an hour",
"salary": { "min": 21.3, "max": null, "currency": "USD", "period": "hour" },
"employmentType": "Part-time",
"qualification": "No degree mentioned",
"applyUrl": "https://poachedjobs.com/jobs/seattle-wa-barista-jobs-madison-kitchen-part-time-baristacounter-person-20-hours",
"applyOptions": [
{ "board": "Poached Jobs", "url": "https://poachedjobs.com/jobs/seattle-wa-barista-jobs-madison-kitchen-part-time-baristacounter-person-20-hours" }
],
"descriptionText": "We're busy and we're hiring! …the job's complete description text…",
"jobUrl": "https://www.google.com/search?ibp=htl;jobs&q=part+time+barista+jobs+near+seattle&htidocid=Ati0AB1gZvCYY_O-AAAAAA%3D%3D",
"query": "part time barista jobs near seattle",
"country": "US",
"dedupeKey": "gjobs:Ati0AB1gZvCYY_O-AAAAAA==",
"charged": true,
"missReason": null
}

Plus one search row per searchquery, country, delivered, outcome — and one summary row with delivered, stoppedBy, resumeCursor, datasetId (paste it into resumeFromDatasetId to continue this run later), carriedOver and chargedEvents.

Browse the full live example dataset → — one real run, unedited: five delivered US listings capped at maxItems: 5, the search row that reconciles them, and the run's own summary row last. Five listings delivered, five charged.

Every row ships the real apply links — the direct URLs on the job boards themselves (LinkedIn, Indeed, ZipRecruiter, the employer's own site…), in the same order Google's "Apply" buttons show them — plus the job's full description text and the via source board. jobUrl opens the exact card on Google Jobs.

Agent / API paste-block

Actor: steadyfetch/google-jobs-scraper
Required: queries (array of job searches, e.g. "software engineer jobs in austin")
Optional: location (string — added to searches that don't already name one)
country (ISO-2, default "US" — which country's Google to ask)
maxItems (integer, default 300 — hard cap for the whole run;
up to 10,000 per run)
maxRunSeconds (integer, default 900 — clean stop, never a timeout kill;
30 to 3,600 seconds)
includeSeen (boolean, default false — OFF: a listing already delivered to
your account is skipped, uncharged. ON: it comes back marked
repeat:true with firstSeenRunId, still uncharged)
resumeFromDatasetId (string — an earlier run's dataset ID; every listing already
in it is skipped and never charged again)
Charges: job-listing once per delivered job listing
search once per search that returns at least one listing ($0.004; a search that returns
nothing pays none)
Note: omit a field — or send it as null — to use its default.
Build spec: https://apify.com/steadyfetch/google-jobs-scraper/api
Token: https://console.apify.com/settings/integrations
curl -X POST "https://api.apify.com/v2/acts/steadyfetch~google-jobs-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"queries":["software engineer jobs in austin"],"maxItems":50}'

How many jobs one search returns

About 90–130 in covered regions, measured across US and UK searches. A niche search returns fewer because Google has fewer — that is the supply, not a cap. The same job reached by two of your searches is delivered and charged once (Google repeats jobs across related searches; we de-duplicate on Google's own job id). More rows come from more searches: vary the phrasing, the location or the country.

Countries

country picks which country's Google results to ask for. US and GB are verified end to end; other codes are passed to Google as-is and the jobs panel works the same way. The rows come back in English (hl=en).

What you are never charged for

situationmissReasonrun status
Google answered with an access checkCHALLENGEDSUCCEEDED — re-runnable, and the row says so
Google rate-limited the requestTHROTTLEDSUCCEEDED — re-runnable
Google returned a page we could not readSOURCE_UNAVAILABLESUCCEEDED — re-runnable
The search ran and its jobs panel was emptyCLEAN_EMPTYSUCCEEDED — a real answer, no result fee and no search fee
A search we could not readUSER_INPUTSUCCEEDED — the row names the fix
Your own maxItems / maxRunSeconds / cost capSTOPPED_AT_LIMITSUCCEEDED — summary row with resumeCursor and the datasetId to continue from
A search never fetched because a cap or deadline bound firstSTOPPED_AT_LIMITSUCCEEDED — no search fee, listed in resumeCursor
A listing your account already has from an earlier runskipped, never charged twice — and a search of only those pays no search fee (needs key-value store permission on a scoped token)

We never guess. An access check is reported as an access check, not as "no jobs found" — a temporary problem is never dressed up as a permanent verdict, and it is never charged. The search fee follows the same rule: it is charged only on a search that returned at least one listing, it sits on that search's own row with charged: true, and the summary row's chargedEvents counts it next to the listings.

A search that comes back blocked, rate-limited or unreadable is fetched again before any of those rows ships. That second attempt is on us: a search only reports a miss when the retry came up short too, and either way there is no result fee.

This actor may fail when Google changes things. Failed searches ship as rows with charged: false, the run still SUCCEEDS, and the row says whether re-running can help.

Never pay for the same listing twice

Every run remembers the listings it delivered for your account, in a key-value store called google-jobs-watch-account in your own Apify account. Run the same searches again and what you already have is skipped before it takes a row slot: no row, no charge, and no bite out of your row cap. The store lives in your own Apify account, so a run started with a scoped API token in restricted-access mode needs key-value store Read, Write and Create permission (or Actor runs set to Full access) — without it the run says so on its status line and on an uncharged first row, and it cannot skip what you already have (Write is what lets the run remember what it delivered; without it every run pays again). The status line says how many (18 already in your account, skipped, not charged) and OUTPUT.skippedSeen holds the number. The search fee follows the same rule: a search that returns only listings you already have returned you nothing new, so it pays no fee.

Want one complete dataset per run instead of only what changed? Turn on Include listings you already have (includeSeen: true) and those rows come back anyway — marked repeat: true with firstSeenAt and firstSeenRunId naming the run that first delivered them, charged: false.

  • The store lives in your account, one record (SEEN). Open it in Storage → Key-value stores to see everything the actor has delivered you, or delete the record to start over. This actor runs with limited permissions and touches nothing else in your account.
  • Entries older than 90 days count as new again, so a listing you collected last quarter is collected (and charged) again if it is still live.
  • Only delivered rows are remembered. A blocked panel, an empty search or a page we could not read leaves nothing behind, so the next run tries it again.
  • If the store cannot be read on some run, the run still runs: it delivers and charges as usual and the status line says the repeat check was unavailable, so you know a repeat may have been charged that once. If the reason is the token's own permissions, the status line says that instead and names the permission to grant — set it under Settings → API & Integrations on the token, or start the run from the console.

Limits are hard limits

maxItems and maxRunSeconds stop the run exactly where you set them. Ask for more than this actor can do and the run still starts. One run delivers up to 10,000 listings and runs 30 to 3,600 seconds. A larger (or smaller) number is not an error: the run continues at the nearest limit and writes one extra uncharged row saying what you asked for and what bound it — you are never left with no run at all. The run still finishes successfully, the summary row names which limit bound, and resumeCursor lists the searches that were not reached. A time limit ends the collecting, never the delivering: a panel already fetched when the clock runs out is still delivered in full, so a run never pays for listings it does not hand you. To collect the rest without paying again for what you already have, re-run the same input with a higher cap and resumeFromDatasetId set to that run's dataset ID (it is on the summary row as datasetId): every listing already in that dataset is skipped — never fetched, never charged — and the new summary's carriedOver says how many. A dataset ID that cannot be read stops the run before it fetches anything, uncharged.

They are limits on the run, not on a container. If Apify moves your run to another server part-way through, or you resurrect a finished run, it picks up from what is already in your dataset: the same job is never delivered twice and never charged twice, and maxItems still means what you typed.

FAQ

Where do the apply links point? At the job boards themselves — the same destinations as Google's own "Apply on …" buttons, with every option listed in applyOptions and the first one in applyUrl.

Why is salary empty on some rows? Because Google did not show one. salaryText is the exact string Google printed and salary is our parse of it; when the card carries no salary both stay null rather than being estimated.

Is the posted date exact? Google publishes a relative age ("6 days ago"), and that is what postedRelative carries. We do not fabricate a timestamp from it.

Does {} charge anything? Yes — like any run, and at most $0.03 on the Apify free plan (less on paid plans). With no input at all the run is a real 5-listing sample ("software engineer" on Google Jobs US) and you pay for the listings it delivers plus the one search fee for the search that returned them. If Google does not answer at that moment you get one uncharged row saying so, never an empty result. Set only filters and no job searches and the same sample runs under them, charged the same way.

What does a search cost? $0.004 per search that returns at least one listing, on top of the per-listing price for what it delivers. A search that returns nothing pays no search fee, and a search the run never fetched — because your cap, your deadline or our own data budget bound first — pays nothing at all. The same search is never charged its fee twice, even across a run that was moved between servers.


Run it from an AI agent (MCP)

Works out of the box from any MCP client (Claude, Cursor, …) via Apify's MCP server — ask for "nurse jobs Google lists in Texas this week" and point it at this actor.

The one-link pin is at the top of this page.

Steadyfetch jobs suite

One actor per source, one job each, the same row shape — so a pipeline built on one source takes the next without a rewrite. All-inclusive pay per event, nothing charged for starting a run, charged only on delivery. Google Jobs adds one search fee of $0.004 per search that returns listings; a search that returns nothing pays nothing.

SourceActor
IndeedIndeed Jobs Scraper — Indeed API Alternative, Salary, Vacancies
A company's own career site (paste a domain)Career Page Scraper — Company Jobs by Domain, 9 ATS Boards
Glassdoor, with the employer ratingGlassdoor Jobs Scraper — Glassdoor API Alternative + Ratings
Several boards at once, de-duplicatedMulti Job Board Scraper — Career Pages, Listings Aggregator
Google Jobs, with the full panel and apply linksthis actor

The rest of the steadyfetch shelf — same contract everywhere: all-inclusive pay per event, nothing charged for starting a run, charged only on delivery (three actors add a small delivery-conditional fee: keyword volume's fresh lookup, profile posts' profile lookup, Google Jobs' search fee — each README states it).

FamilyActors
Ad creative intelligenceFacebook · Google Ads video · TikTok · LinkedIn · Google Ads text & OCR
Trends & keywordsGoogle Trends · Trends Now · Breakout keywords · Autocomplete keywords · Keyword volume & CPC · Social trends
YouTube transcriptsYouTube videos · YouTube channels
InstagramReel transcripts · Profile posts
JobsIndeed · Career sites by domain · Glassdoor · Multi-board · Google Jobs
AmazonProducts · Search · Bestsellers · Sellers
Any media fileSpeech to Text · any link or file

Feedback & support

Found an issue? Open it on the Issues tab — we usually reply within a couple of hours, always within a day. Feature requests welcome.

If it earned its keep, a rating helps other buyers find it, and saving the actor keeps it one click away.