TotalJobs UK [only $2/1k] Scraper ·  Jobs + Salary + Geo avatar

TotalJobs UK [only $2/1k] Scraper · Jobs + Salary + Geo

Pricing

from $2.00 / 1,000 results

Go to Apify Store
TotalJobs UK [only $2/1k] Scraper ·  Jobs + Salary + Geo

TotalJobs UK [only $2/1k] Scraper · Jobs + Salary + Geo

[only $2] Map the UK job market from TotalJobs.com — 44,490+ postings across 1,780+ listing pages. Each row carries title, employer + logo, location + lat/lng, parsed salary band (min/max/currency/period), dates, and employment type. One outputrecord per job. Apify Residential GB required

Pricing

from $2.00 / 1,000 results

Rating

0.0

(0)

Developer

Muhamed Didovic

Muhamed Didovic

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

TotalJobs UK Scraper

Scrape job postings from TotalJobs.com (UK) — title, employer + logo, location with lat/lng, parsed salary band (min / max / currency / period), datePosted, validThrough, employment type, industry, and directApply flag. One flat row per job from rich JobPosting JSON-LD.

How TotalJobs UK Scraper works

Why this actor

TotalJobs is one of the largest UK job boards (~44,490 postings, ~1,780 listing pages). It runs behind Akamai Bot Manager — direct connections and US/non-GB residential proxies return 403 "Access Denied" from Akamai's edge. The Kong API gateway (gateway.totaljobs.com) is VPC-internal and never exposed publicly, so the iOS app is not a scrape shortcut.

The actor solves all that:

  • Apify Residential GB with per-request session rotation (only pool that returns clean 200 OK)
  • JobPosting JSON-LD extraction — every detail page ships 14 fields server-side; no DOM walking needed for the core data
  • Parsed salary band — TotalJobs doesn't put salary in JSON-LD baseSalary, so we regex the visible HTML (£70,967 to £83,926 per annum{min: 70967, max: 83926, currency: "GBP", period: "annum"})
  • Auto-pagination — follows rel="next" or ?page=N until maxItems rows are emitted for that listing URL (each listing gets its own budget)
  • Mixed input — listing URLs auto-paginate + emit one row per detail; direct detail URLs scrape one row each

Use cases

  • Recruitment market intelligence — salary benchmarking by region/role, employer activity tracking
  • Sales prospecting — find companies hiring in your target verticals + locations
  • HR competitive analysis — compare your salary bands against TotalJobs market signal
  • ATS / job-aggregator integration — clean structured input for downstream pipelines
  • Geospatial analytics — every row carries location.lat + location.lng from JSON-LD PostalAddress.geo

Input

FieldTypeRequiredNotes
startUrlsstring[]yesMix of listing URLs (https://www.totaljobs.com/jobs/in-london, /jobs/{keyword}/in-{location}) and direct detail URLs (/job/{title-slug}/{org-slug}-job{id}).
maxItemsintegernoMaximum job rows emitted per listing URL. 3 listings × maxItems: 100 → up to 300 total rows. Direct detail URLs always emit 1 row each. Each row = one paid outputrecord. Default 1000. Free-tier users have a hidden global ceiling of 100 rows.
maxConcurrencyintegernoParallel HTTP requests for detail-page fetches. Sweet spot 3–5 via Apify Residential GB. Default 4.
maxRequestRetriesintegernoPer-URL retry budget on Akamai 403, proxy CONNECT failures, HTTP/2 stream resets, and network errors. Each retry rotates the proxy session with mild exponential backoff. Default 6.
proxyobjectnoApify Residential GB required. Default is wired correctly — don't override unless you know what you're doing.

Example input

{
"startUrls": [
"https://www.totaljobs.com/jobs/software-engineer/in-london",
"https://www.totaljobs.com/jobs/in-manchester"
],
"maxItems": 200,
"maxConcurrency": 4,
"proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}

Output schema

Every row has rowType: "job". 14 fields from JSON-LD + parsed salary band + structured location.

{
"rowType": "job",
"listingUrl": "https://www.totaljobs.com/jobs/in-london",
"jobId": "107245246", // numeric — stable identifier
"jobUrl": "https://www.totaljobs.com/job/regional-optimization-lead/bp-energy-job107245246",
"title": "Regional Optimization Lead",
// ── JobPosting JSON-LD ──
"description": "<p>Entity: Supply, Trading & Shipping…</p>", // HTML
"datePosted": "2026-05-07T02:51:32.477Z", // ISO 8601
"validThrough": "2026-06-18T02:51:32.477Z",
"employmentType": "FULL_TIME", // or null
"industry": "Management, Management-Area Management",
"directApply": true,
"jobLocationType": null, // "TELECOMMUTE" for remote
"applicantLocationRequirements": [], // populated when remote-friendly
// ── Employer (from JSON-LD hiringOrganization) ──
"employer": {
"name": "BP Energy",
"url": "https://www.totaljobs.com/jobs/bp-energy?cmpId=1428985&cmp=1",
"logoUrl": "https://www.totaljobs.com/CompanyLogos/2e66e5e85ad5408380e06078af3eb663.png"
},
// ── Location (from JSON-LD jobLocation.address + geo) ──
"location": {
"text": "St James, London, WC2N 5DU, GB",
"locality": "St James",
"region": "London",
"postalCode": "WC2N 5DU",
"country": "GB",
"lat": 51.50445,
"lng": -0.13601
},
// ── Salary (parsed from body text via regex — not in JSON-LD) ──
"salary": {
"rawText": "£70,967 to £83,926 per annum",
"min": 70967,
"max": 83926,
"currency": "GBP", // ISO code (GBP/USD/EUR)
"period": "annum" // "annum" / "hour" / "day" / "week" / "month"
},
// ── Apply flow ──
"applyUrl": null, // JS-resolved at click — see Notes below
"applyType": "internal", // "internal" / "external" / "unknown" (from directApply)
"scrapedAt": "2026-05-15T06:25:31.012Z"
}

How it works

  1. Classify input — listings (/jobs/...) vs. details (/job/{slug}-job{id}). Listings auto-paginate; details scrape one row each.
  2. Fetch via Apify Residential GBimpit with Firefox TLS fingerprint. Direct + Evomi residential get 403 Akamai blocks; Apify GB returns clean 200 OK.
  3. For listings: collect detail-URL anchors per page, follow rel="next" until empty or cap. Then concurrent detail fetches via sliding window.
  4. For each detail: parse JobPosting JSON-LD for 14 fields. Run salary regex on body text. Emit one flat row.

⚠️ Apply URL limitation

TotalJobs renders the apply button server-side as a disabled placeholder (<button aria-label="apply-button-placeholder" disabled>). The real apply URL is loaded async via XHR after click, triggered by JS event listeners we can't execute without a browser.

We tested 15 standard REST-style apply-API endpoints (e.g. /api/applicationredirect/{id}, /api/v1/listings/{id}/apply) — all returned 404. The apply data lives in __PRELOADED_STATE__.applyNowSection but contains only listingId + listingGlobalId, not the resolved external URL.

v1 ships applyUrl: null and applyType derived from JSON-LD's directApply flag:

  • applyType: "internal" → TotalJobs hosts the application (directApply: true)
  • applyType: "external" → External recruiter hosts (directApply: false)
  • applyType: "unknown" → JSON-LD didn't specify

Buyers needing the actual recruiter URL should open the jobUrl in a browser and click apply — or wait for v1.1 which will reverse-engineer the JS XHR call. No competing TotalJobs scraper on Apify Store currently provides applyUrl either — this is a TotalJobs-platform limitation, not a scraper gap.

Notes & limitations

  • Apify Residential GB is mandatory. Direct connections from non-UK IPs get 403 Akamai blocks. Evomi residential (any country) also gets blocked. Apify Residential GB returns clean 200 OK on every probe we tested.
  • Per-listing-page result count varies. Some listing pages return 10 detail anchors, some 7, occasionally 0 (TotalJobs hides duplicate-job rows). The pagination loop handles empty pages gracefully and continues to page N+1.
  • employmentType fill ≈ 70%. Not every JobPosting JSON-LD declares it. We don't synthesize when missing.
  • jobLocationType only set for remote/hybrid roles. Non-remote jobs leave it null — matches JSON-LD semantics.
  • Salary fill ≈ 100% when surfaced. When a job has a published salary it parses cleanly; when it doesn't (small fraction of jobs) the salary field is null.
  • /jobs/{company-slug}-jobs URLs work for company-specific listings (vs the /jobs/in-{location} keyword listings).

FAQ

Which TotalJobs URLs work? Two types: listing URLs (/jobs/in-london, /jobs/software-engineer/in-manchester, /jobs/{company-slug}-jobs) which auto-paginate and emit one row per linked job, and direct detail URLs (/job/{title-slug}/{org-slug}-job{id}) which scrape one row each. You can mix both in the same startUrls array.

Why do I need Apify Residential GB? TotalJobs sits behind Akamai Bot Manager with strict country rules. Direct connections, datacenter proxies, and non-GB residential (we tested Evomi IN/US/EU) all return 403 "Access Denied" from Akamai's edge. Apify Residential GB is the only pool we found that returns clean 200 OK on every probe.

Why is applyUrl always null? TotalJobs renders the apply button server-side as a disabled placeholder — the real URL loads async via XHR after click. We can't execute that JS without a browser. The applyType field (derived from JSON-LD's directApply flag) tells you whether the application is hosted internally (directApply: true) or by an external recruiter (directApply: false). See the ⚠️ Apply URL section above for the full breakdown.

What does each outputrecord charge cover? One job row with all 14 JSON-LD fields plus the parsed salary band (min/max/currency/period) and structured location (lat/lng). maxItems is per listing URL, so a maxItems: 100 run with 2 listings = up to 200 charges.

Can the parsed salary handle annual / hourly / ranges? Yes. The regex catches £70,967 to £83,926 per annum, £50k - 70k per year, £15 per hour, and single values. The period field normalizes to annum/hour/day/week/month. When a job has no published salary, salary is null — we don't synthesize.

Why does one listing page sometimes return 7 jobs and another 10? TotalJobs hides duplicate-job rows on listing pages. The pagination loop handles empty / short pages gracefully and continues to page N+1 until either empty or maxItems rows have been emitted for that listing.

Support

  • Bugs / feature requests — open an issue on the GitHub repo
  • Custom exports / tailored fields — drop a note via the Apify Store contact form
  • Other actors — see my Apify Store profile for the rest of the catalog

⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by TotalJobs.com, StepStone Group, or any of their subsidiaries. All trademarks mentioned are the property of their respective owners.

The scraper extracts only publicly visible job postings rendered server-side by TotalJobs — no login, no CAPTCHA solving, no API-key forgery, no gateway.totaljobs.com (VPC-internal) probing. The actor honours robots.txt and rate-limits via concurrency cap (default 4) to avoid burdening TotalJobs's infrastructure.

Users are responsible for:

  • Complying with TotalJobs.com's Terms of Service
  • Following UK GDPR + your jurisdiction's data-protection laws when storing or processing scraped postings
  • Not contacting candidates listed by employers in scraped postings
  • Not republishing scraped data in a way that competes commercially with TotalJobs

SEO Keywords

totaljobs scraper, scrape totaljobs, totaljobs uk scraper, totaljobs.com scraper, totaljobs api, Apify totaljobs, uk jobs scraper, uk job board scraping, jobpostings api, jobposting json-ld scraper, uk recruitment api, recruitment scraper uk, uk salary data, salary band extraction, uk job market data, hiring intelligence uk, employer hiring data, b2b sales prospecting uk, london jobs scraping, manchester jobs scraper, edinburgh jobs scraper, akamai bot manager bypass, apify residential gb