Jobsite UK $1.5๐ฐ Jobs, Details & Company Scraper
Pricing
from $1.00 / 1,000 results
Jobsite UK $1.5๐ฐ Jobs, Details & Company Scraper
From $1/1K. Collect UK job listings from Jobsite.co.uk at scale. Search by keyword, location, filters, or paste search URLs. Returns 35+ clean fields per job from search pages. Optional detail mode adds full description, GPS coordinates, employment type, and company profile.
Pricing
from $1.00 / 1,000 results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
5 days ago
Last modified
Categories
Share
Jobsite.co.uk Jobs Scraper
Collect UK job listings from Jobsite.co.uk at scale. This scraper reads the structured data the site renders into every search page, so it returns 35+ clean fields per job without opening a browser. Search by keyword, location, and filters, or paste search URLs directly. An optional detail mode opens each job page for the full description, GPS coordinates, employment type, and company profile.
Why this scraper
- 35+ fields per job straight from the listing page, including parsed salary (min, max, currency, period), the original posting source, skills, and a text snippet.
- Listing pages run on Datacenter connections, so a basic run is fast and inexpensive and works on the free plan.
- Optional full details add the complete job description, latitude and longitude, employment type, valid-through date, apply type, and company profile (benefits, videos, industries).
- Two modes: build a search from filters, or paginate any Jobsite.co.uk search URL.
- Server-side filters match the website: keyword, location, job type, advertiser type, minimum salary, posted-within, and sort order.
- Built-in connection management: country rotation, automatic failover, retry handling, and a residential request cap so a run can never overspend.
- Walks the whole catalogue by default.
maxPagesdefaults to0(unlimited), so the run walks every result page per search/URL, stopping on its own once the site's reported page count is reached, a page comes back empty, or a page repeats jobs already seen this walk. There is no artificial page-number ceiling; the only cap you need ismaxListings. maxListingsis the sole cap on volume (default20, so a first run is small and fast; set0for unlimited).- Resumable runs. Set
resumeFromRunIdto a previous run or dataset id to collect only the jobs that run doesn't already have (a delta); combine both datasets for the full set. Long unlimited runs also checkpoint their progress to the key-value store, so an Apify platform migration or a Resurrect of a failed run picks up where it left off instead of starting over. - Incremental mode for recurring/scheduled runs. Turn on
incrementalModefor a search you run repeatedly (e.g. daily) to get only NEW and CHANGED jobs each time. UnlikeresumeFromRunId(which finishes one specific interrupted run by id), incremental mode remembers state itself between runs โ no id to paste. See "Incremental & dedup mode" below.
Data you get
Sample shape: values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
jobId | 100000001 |
title | Sample Job Title |
jobUrl | https://www.jobsite.co.uk/job/sample-job-title/sample-company-job100000001 |
sourceSite | Totaljobs.com |
employer.name | Sample Company |
employer.url | https://www.jobsite.co.uk/jobs/sample-company?cmpId=0000000&cmp=1 |
employer.logoUrl | https://www.jobsite.co.uk/CompanyLogos/00000000000000000000000000000000.png |
location.text | Sample Town, Sample County (AA0), AA0 0AA |
location.postalCode | AA0 0AA |
salary.rawText | ยฃ30,000 - ยฃ40,000 per annum |
salary.min | 30000 |
salary.max | 40000 |
salary.currency | GBP |
salary.period | year |
datePosted | 2026-01-01T00:00:00.000Z |
workFromHome | Hybrid |
skills | ["Sample Skill A", "Sample Skill B"] |
textSnippet | Short preview of the job description appears here. |
crossPostedCount | 1 |
isSponsored | false |
When fetchDetails is enabled, each record is also enriched with:
| Field | Example |
|---|---|
description | <p>Full HTML job description...</p> |
descriptionText | Full plain-text job description appears here. |
employmentType | ["FULL_TIME"] |
industry | Sample Industry |
jobLocationType | TELECOMMUTE |
validThrough | 2026-02-01T00:00:00.000Z |
directApply | true |
applyType | DirectApply |
location.latitude | 51.0000 |
location.longitude | 0.0000 |
location.locality | Sample Town |
company.jobsCount | 10 |
company.benefits | [] |
contactPhones | ["+440000000000"] |
contactEmails | ["recruiter@example.com"] |
contactPhones and contactEmails are a best-effort harvest of any contact details a recruiter typed into the job description (or exposed as a tel/mailto link). The platform does not publish structured company contact details, so these are populated only on the minority of posts (mostly recruitment agencies) that include them; most records return empty arrays.
How to use
Basic search by location:
{"mode": "search","locations": ["London"],"maxListings": 20}
Keyword search with filters:
{"mode": "search","locations": ["Manchester"],"keywords": "java developer","jobType": "permanent","minSalary": 50000,"salaryType": "annual","postedWithin": 7,"sortBy": "date","maxListings": 100}
Full details (needs Apify Residential GB):
{"mode": "search","locations": ["London"],"keywords": "nurse","fetchDetails": true,"maxListings": 50,"proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }}
Paginate search URLs directly (multiple supported):
{"mode": "url","urls": ["https://www.jobsite.co.uk/jobs/in-london","https://www.jobsite.co.uk/jobs/contract/in-leeds?postedWithin=3"],"maxListings": 50}
Walk the whole catalogue for a location (no page cap, still bounded by maxListings):
{"mode": "search","locations": ["London"],"maxPages": 0,"maxListings": 0}
Resume a previous run to collect only new jobs since then:
{"mode": "search","locations": ["London"],"maxListings": 0,"resumeFromRunId": "<a previous run id or dataset id from this account>"}
Track a search on a daily schedule, returning only new/changed jobs each run:
{"mode": "search","locations": ["London"],"keywords": "nurse","maxListings": 0,"incrementalMode": true,"emitExpired": true}
Incremental & dedup mode
Set incrementalMode: true for a search you run on a recurring schedule (e.g. once a day) and want only what changed since the last run, instead of the whole list every time. The actor remembers the jobs it saw last time in its own key-value store baseline โ keyed automatically by your search filters, or by stateKey if you set one โ and classifies every job it scans against that baseline:
changeType | Meaning |
|---|---|
NEW | Not seen in any previous run for this state key. |
UPDATED | Seen before, but one or more tracked fields changed (see changedFields). |
UNCHANGED | Identical to the last run. Suppressed by default โ not pushed, not charged โ unless emitUnchanged is on. |
REAPPEARED | Was previously marked EXPIRED, now seen again. |
EXPIRED | Seen in a previous run, not found in this one, only emitted when emitExpired is on and this run reached a natural, uncapped end for every search (no maxListings/maxPages truncation, and not a resumeFromRunId run) โ a capped or partial scan cannot tell "gone" apart from "not reached yet", so it is skipped rather than guessed. |
This is a separate mechanism from resumeFromRunId: Resume finishes one specific interrupted/previous run by pasting its id; Incremental mode tracks the same recurring search automatically, run after run, with no id needed. Combining both is only meaningful to bootstrap a brand-new incremental baseline from an existing resume dataset (first run only) โ once a baseline exists for a state key, a run also setting resumeFromRunId is rejected with an explanation, since resuming would prevent this run from scanning the full search and make later EXPIRED detection unreliable.
Fields excluded from change detection (present in the pushed row as usual, just not compared to decide changeType):
sourceSearchUrl,travelTimeโ describe the search, not the job (which location/URL/distance this run happened to find it under), and can shift across runs with zero change to the job itself.description,descriptionText,validThrough,employmentType,industry,directApply,applyType,jobLocationType,applicantLocationRequirements,contractType,workType,company,contactPhones,contactEmails,detailFetched,externalId, andlocation.latitude/longitude/locality/region/country/streetAddress/postalCodeโ populated or refined only by detail-page enrichment (fetchDetails). A job fetched without detail (or one whose detail fetch happened to fail this run only) would otherwise look "changed" purely because of enrichment availability, not a real site change.datePostedis normalized, not excluded: a pure relative-time render ("Posted 2 days ago") is blanked before comparison since it would drift every run on its own, but an absolute date is kept and still billsUPDATEDon a genuine repost.
Everything else โ title, salary, employer, location text/postcode, skills, sponsorship flags, cross-post count, and so on โ is real listing data and a change bills UPDATED as expected.
Input parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search builds URLs from filters; url paginates pasted URLs. |
locations | array | ["London"] | Towns, cities, counties, or regions. One search per location. |
keywords | string | (empty) | Job title, skill, or company to search for. |
jobType | string | any | permanent, contract, temporary, part-time, work-from-home. |
companyType | string | any | employer (direct) or agency. |
minSalary | integer | (empty) | Minimum salary, paired with salaryType. |
salaryType | string | annual | annual, daily, or hourly. |
postedWithin | integer | 0 | 1, 3, 7, or 14 days. 0 = any time. |
sortBy | string | relevance | date, salary-desc, salary-asc, distance. |
urls | array | (example) | Search URLs to paginate (URL mode). |
fetchDetails | boolean | false | Open each job page for the full detail fields. |
maxListings | integer | 20 | The sole cap on total jobs collected across all searches. 0 = unlimited. |
maxPages | integer | 0 | Optional bound on result pages (25 jobs each) walked per search/URL. 0 = walk the whole catalogue: stops on its own at the site's reported page count, an empty page, or a page that repeats jobs already served this walk, never an artificial page cap. Does not cap job count; use maxListings for that. |
maxResidentialRequests | integer | 0 | Cap on Residential requests per run. 0 = unlimited. |
resumeFromRunId | string | (empty) | Optional id of a previous run (or dataset) of this actor. Jobs already in that dataset are skipped, so this run returns only new jobs (a delta). |
incrementalMode | boolean | false | Track a recurring search across runs; only NEW/UPDATED jobs are returned by default. See "Incremental & dedup mode" above. |
stateKey | string | (empty) | Optional label for the incremental baseline. Leave empty to derive it automatically from the search filters. |
emitUnchanged | boolean | false | Incremental mode only. Also return jobs with no change (changeType: UNCHANGED). |
emitExpired | boolean | false | Incremental mode only. Emit changeType: EXPIRED rows for jobs no longer found, when this run reached a natural, uncapped end. |
proxy | object | Apify proxy | Connection settings. |
Send results into your apps (MCP connectors)
Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape; the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify โ Settings โ Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where items should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Output example
Sample shape: values are illustrative placeholders, not from a live listing.
{"rowType": "job","jobId": 100000001,"harmonisedId": "00000000-0000-0000-0000-000000000000","jobUrl": "https://www.jobsite.co.uk/job/sample-job-title/sample-company-job100000001","sourceSite": "Totaljobs.com","title": "Sample Job Title","datePosted": "2026-01-01T00:00:00.000Z","employer": {"id": 0,"name": "Sample Company","url": "https://www.jobsite.co.uk/jobs/sample-company?cmpId=0&cmp=1","logoUrl": "https://www.jobsite.co.uk/CompanyLogos/0.png","isAnonymous": false},"location": {"text": "Sample Town, Sample County (AA0), AA0 0AA","postalCode": "AA0 0AA","locality": "Sample Town","region": "Sample County","country": "GB","latitude": 51.0000,"longitude": 0.0000},"salary": { "rawText": "ยฃ30,000 - ยฃ40,000 per annum", "min": 30000, "max": 40000, "currency": "GBP", "period": "year" },"skills": ["Sample Skill A", "Sample Skill B"],"textSnippet": "Short preview of the job description appears here.","crossPostedCount": 1,"partnership": { "isBackfilled": true, "isCrossPosted": false, "sourceSiteFriendlyName": "Totaljobs.com" },"employmentType": ["FULL_TIME"],"industry": "Sample Industry","applyType": "DirectApply","descriptionText": "Full plain-text job description appears here.","company": { "jobsCount": 10, "industries": [], "benefits": [] }}
Plan requirement
- Listing mode works on any Apify plan using the default Apify proxy (Datacenter).
- Full details (
fetchDetails) require Apify Residential with country GB. Datacenter and non-GB residential connections are refused on job pages. Residential is available on the Starter plan and above. - Use
maxResidentialRequeststo put a hard cap on residential usage per run.