Caterer.com Scraper – UK Hospitality Jobs, Salaries & Details avatar

Caterer.com Scraper – UK Hospitality Jobs, Salaries & Details

Pricing

from $1.00 / 1,000 job results

Go to Apify Store
Caterer.com Scraper – UK Hospitality Jobs, Salaries & Details

Caterer.com Scraper – UK Hospitality Jobs, Salaries & Details

Scrape UK hospitality jobs from Caterer.com, including chef, hotel, restaurant, bar, and events roles. Search by keyword, location, filters, or URLs. Returns salary, employer, logo, location, skills, and 35+ fields, with optional full description, GPS, and company profile.

Pricing

from $1.00 / 1,000 job results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 days ago

Last modified

Share

Caterer.com Hospitality Jobs Scraper

Scrape UK hospitality jobs from Caterer.com at scale: chef, restaurant, hotel, bar, and events roles. Search by keyword, location, and filters, or paste search URLs to paginate. Every job comes back with 35+ structured fields straight from the listing, and an optional detail pass adds the full description, GPS coordinates, structured address, employment type, valid-through date, and company profile.

Why this scraper

  • 35+ fields per job from the listing page alone, no detail fetch required: title, parsed salary, employer, logo, location, posting source, skills, snippet, and more.
  • Two ways to run: build a search from keyword + location + filters, or paste any Caterer.com search URL and walk it forward.
  • Real server-side filters: job type, advertiser type (direct employer vs agency), minimum salary by period (year / day / hour), posted-within window, and sort order.
  • Parsed salary: the free-text salary string is split into min, max, currency, and period (year / day / hour), including hourly rates common in hospitality.
  • Optional full details: GPS lat/lng, structured address, full description, employment type, valid-through date, and company profile.
  • Cost control built in: a residential request budget cap and a single clear result limit so a run never overspends.
  • Send results straight into Notion, Linear, Airtable, or Apify via optional MCP connectors.
  • Walks the whole catalogue by default. maxPages defaults to 0 (unlimited) — 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 is maxListings.
  • maxListings is the sole cap on volume (default 20, so a first run is small and fast; set 0 for unlimited).
  • Resumable runs. Set resumeFromRunId to 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 & dedup mode for scheduled runs. Turn on incrementalMode and run the same search daily — the actor remembers the previous run itself and returns only NEW, UPDATED, and REAPPEARED jobs by default; unchanged jobs are suppressed (and not billed). See Resume & recurring updates.

Data you get

Sample shape, values are illustrative placeholders, not from a live listing.

FieldExample
jobId100000001
titleHead Chef
jobUrlhttps://www.caterer.com/job/head-chef/sample-agency-job100000001
applyUrlhttps://www.caterer.com/job/head-chef/sample-agency-job100000001
sourceSiteCaterer.com
datePosted2026-01-01T00:00:00.000Z
employer.nameSample Restaurant Group
employer.logoUrlhttps://www.caterer.com/CompanyLogos/00000000000000000000000000000000.png
location.textSoho, Central London (W1)
location.postalCodeW1
location.latitude51.5000
location.longitude-0.1300
salary.rawTextFrom £14 to £18 per hour
salary.min14
salary.max18
salary.currencyGBP
salary.periodhour
skillsMenu planning, Food safety
textSnippetWe are looking for an experienced head chef to lead our kitchen team.
isSponsoredfalse
description (detail)Full job description text appears here when fetchDetails is enabled.
employmentType (detail)FULL_TIME
industry (detail)Catering, Catering-Chef
validThrough (detail)2026-02-01T00:00:00.000Z
location.locality (detail)Soho
location.region (detail)London
location.country (detail)GB

How to use

Basic search in one city:

{
"mode": "search",
"locations": ["London"],
"keywords": "chef",
"maxListings": 20,
"proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}

Search with filters (permanent chef roles, at least £30,000/year, posted in the last 7 days, newest first):

{
"mode": "search",
"locations": ["Manchester", "Leeds"],
"keywords": "chef",
"jobType": "permanent",
"minSalary": 30000,
"salaryType": "annual",
"postedWithin": 7,
"sortBy": "date",
"maxListings": 100,
"proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}

Full details (adds description, GPS, employment type, company profile):

{
"mode": "search",
"locations": ["London"],
"keywords": "restaurant manager",
"fetchDetails": true,
"maxListings": 50,
"proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}

Paginate pasted URLs (filter fields are ignored in URL mode):

{
"mode": "url",
"urls": [
"https://www.caterer.com/jobs/chef/in-london",
"https://www.caterer.com/jobs/in-edinburgh"
],
"maxPages": 5,
"maxListings": 200,
"proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}

Walk the whole catalogue for a location (no page cap, still bounded by maxListings):

{
"mode": "search",
"locations": ["London"],
"maxPages": 0,
"maxListings": 0,
"proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}

Resume a previous run, collecting only jobs it doesn't already have:

{
"mode": "search",
"locations": ["London"],
"maxListings": 0,
"resumeFromRunId": "<a previous run id or dataset id from this account>"
}

Daily monitoring — same input every scheduled run, only changes come back:

{
"mode": "search",
"locations": ["London"],
"keywords": "chef",
"incrementalMode": true,
"maxListings": 0
}

Input parameters

ParameterTypeDefaultDescription
modestringsearchsearch builds URLs from filters; url paginates URLs you paste.
locationsarray["London"]Towns, cities, counties, or regions (search mode). One search per location.
keywordsstring(empty)Role, skill, or employer to search for. Empty lists all hospitality jobs in the location.
jobTypestringanyany / permanent / contract / temporary / part-time / work-from-home.
companyTypestringanyany / employer (direct) / agency.
minSalaryinteger(empty)Lowest salary to include, paired with the salary period.
salaryTypestringannualannual / daily / hourly.
postedWithinstring00 (any) / 1 / 3 / 7 / 14 days.
sortBystringrelevancerelevance / date / salary-desc / salary-asc / distance.
urlsarray(example)Caterer.com search URLs to paginate (url mode).
fetchDetailsbooleanfalseFetch detail pages to add description, GPS, employment type, valid-through, company profile.
maxListingsinteger20The sole cap on total jobs collected across all searches. 0 = unlimited.
maxPagesinteger0Optional 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.
maxResidentialRequestsinteger0Safety cap on residential requests per run. 0 = unlimited.
resumeFromRunIdstringOptional 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). For recurring daily monitoring of the same search, use incrementalMode instead.
incrementalModebooleanfalseTurn on for daily/recurring monitoring. Returns only NEW/UPDATED/REAPPEARED jobs after the first run; unchanged jobs are suppressed and not billed unless emitUnchanged is on.
stateKeystringOptional. Name a monitoring campaign explicitly, or share state across differently-configured runs. Leave empty to derive a key automatically from the search/filter setup.
emitUnchangedbooleanfalseAlso return jobs unchanged since the last run, marked UNCHANGED (extra billed rows).
emitExpiredbooleanfalseAlso return jobs no longer found, marked EXPIRED. Only produced once a run fully scans the tracked search (not capped, not resumed).
proxyobjectResidential GBConnection settings.

Resume & recurring updates

There are two different things here — pick the one that matches what you're doing:

NeedUse
A run stopped and should continueresumeFromRunId / automatic checkpoint recovery
Run the same search every day and receive only changesincrementalMode
Keep separate daily campaigns for similar searchesdistinct stateKey values
Run a normal full snapshotleave both off

Resume (resumeFromRunId) continues one specific interrupted or previous large run: paste a run id or dataset id and this run skips jobs already collected there, returning only the remaining new jobs. An automatic same-run checkpoint also protects against platform migrations/Resurrects without any input needed.

Incremental mode (incrementalMode) is for a schedule (for example, daily): the actor remembers the previous run of the same search by itself, so you never paste a run id. The first run returns everything as NEW. Later runs return only NEW, UPDATED, and REAPPEARED jobs by default — duplicates and unchanged jobs are suppressed (and not charged). Turn on emitUnchanged or emitExpired only when you also want those rows returned (and billed for). State is isolated automatically per mode/locations/keywords/jobType/companyType/minSalary/salaryType/postedWithin/sortBy/urls/fetchDetails setup — maxListings/maxPages/maxResidentialRequests are caps/cost knobs, not filters, and deliberately do not affect the state key, so raising your cap on a later run does not re-bill the whole catalogue. Set stateKey to name or deliberately share a monitoring campaign. EXPIRED is only synthesized once a run fully scans every search/URL target (not capped by Max jobs/Max pages, not a Resume run, not a checkpoint-interrupted run).

When incrementalMode is on, every returned record also carries:

FieldDescription
changeTypeNEW | UPDATED | UNCHANGED | REAPPEARED | EXPIRED
changedFieldsTop-level fields that changed since last seen; non-empty only for UPDATED
firstSeenAtWhen this job was first observed by this monitoring campaign
lastSeenAtWhen this job was last observed

Scanned vs. emitted caps. maxListings counts jobs scanned (looked at and classified), not jobs pushed. Outside incremental mode the two numbers are always equal. Under incremental mode most jobs on a quiet recurring run are UNCHANGED and suppressed — if the cap instead counted pushed rows, the run would keep paging deeper trying to "backfill" the quota with fresh jobs every single day, and cost would never actually drop. Counting scanned jobs means the run stops once it has looked at maxListings worth of the catalogue, exactly like a normal run, whether those jobs turned out to be pushed or suppressed.

What drives change detection: every real field on the job (title, salary, location, employer, skills, description snippet, dates, etc.). The following are excluded from the comparison because they are not job content:

Excluded fieldWhy
scrapedAtStamped fresh on every scrape — would fake an UPDATED every run otherwise.
detailFetchedDescribes this run's enrichment depth, not the job — toggling fetchDetails must not mark every job UPDATED.
crossPostedCountA live count of other employers' duplicate postings of this job (fingerprintCount) — it changes when a third party's listing changes, not this job.
sourceSearchUrlWhich search query/location surfaced this job — a property of the search, not the job. Does not embed a page number.
isSponsored, isHighlighted, isTopJob, isTrafficFromPartnerAd-serving / search-ranking placement flags set by the platform's ad auction, not by the employer editing the job.

These exclusions are reasoned from the data shape (structural review of src/extractor.py), not measured against a live re-scrape over time — a genuine promotion-flag flap could not be observed within this task's request budget. A real change to a real field (title, salary, location, employer, skills, snippet, dates) still triggers UPDATED as usual; the excluded fields are always still returned in the output, just not compared.

Scheduled-run example — same search, run daily:

Day 1 (first run ever for this search):

{ "mode": "search", "locations": ["London"], "incrementalMode": true }

→ every job comes back with "changeType": "NEW".

Day 2 (the schedule fires again, identical input):

{ "mode": "search", "locations": ["London"], "incrementalMode": true }

→ jobs whose title/salary/location/etc. changed come back as "changeType": "UPDATED" with changedFields listing what changed, brand-new jobs come back as "changeType": "NEW" — and jobs that are still there, unchanged, are not returned at all (suppressed, not charged) unless emitUnchanged is on.

Output example

Sample shape, values are illustrative placeholders, not from a live listing.

{
"rowType": "job",
"scrapedAt": "2026-01-01T00:00:00.000Z",
"jobId": 100000001,
"jobUrl": "https://www.caterer.com/job/head-chef/sample-agency-job100000001",
"applyUrl": "https://www.caterer.com/job/head-chef/sample-agency-job100000001",
"sourceSite": "Caterer.com",
"title": "Head Chef",
"datePosted": "2026-01-01T00:00:00.000Z",
"employer": {
"id": 1000000,
"name": "Sample Restaurant Group",
"url": "https://www.caterer.com/jobs/sample-restaurant-group?cmpId=1000000",
"logoUrl": "https://www.caterer.com/CompanyLogos/00000000000000000000000000000000.png",
"isAnonymous": false
},
"location": {
"text": "Soho, Central London (W1)",
"postalCode": "W1",
"locality": "Soho",
"region": "London",
"country": "GB",
"latitude": 51.5000,
"longitude": -0.1300
},
"salary": { "rawText": "From £14 to £18 per hour", "min": 14, "max": 18, "currency": "GBP", "period": "hour" },
"skills": ["Menu planning", "Food safety"],
"textSnippet": "We are looking for an experienced head chef to lead our kitchen team.",
"isSponsored": false,
"description": "Full job description text appears here when fetchDetails is enabled.",
"employmentType": ["FULL_TIME"],
"industry": "Catering, Catering-Chef",
"validThrough": "2026-02-01T00:00:00.000Z",
"directApply": true,
"applyType": "DirectApply",
"detailFetched": true
}

Send results into your apps (MCP connectors)

Optionally pipe results into the apps you already use. Authorize a connector once under Apify, Settings, Integrations, then select it in the input. Set notionParentPageUrl for Notion. Each connector receives a condensed, human-readable summary per item (title plus key fields), not the full JSON; the complete record always stays in the Apify dataset. Supported: Notion, Linear, Airtable, Apify.

Plan requirement

Caterer.com accepts Apify Residential with country GB most reliably, which is the prefilled default. Listing pages occasionally work on Datacenter but are frequently refused, and full job details (fetchDetails) always need Residential GB. Datacenter and non-GB residential are refused on job pages. Apify Residential is available on the Starter plan and above. On the free plan a run may return few or no results; set proxy.apifyProxyGroups to ["RESIDENTIAL"] with apifyProxyCountry "GB" after upgrading.