jobs.ch Scraper avatar

jobs.ch Scraper

Pricing

from $3.00 / 1,000 results

Go to Apify Store
jobs.ch Scraper

jobs.ch Scraper

Scrape job listings from jobs.ch, Switzerland's largest job board. Search by keyword, or browse by region, industry, occupational field or contract type, with filters for workload, seniority, language, company size and publish date. Returns title, company, location, workload and the job URL.

Pricing

from $3.00 / 1,000 results

Rating

0.0

(0)

Developer

Crawler Bros

Crawler Bros

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Scrape jobs.ch — Switzerland's largest job board. Search by keyword, browse by canton/language-region, industry/category, occupational field, or employment (contract) type, and refine with a workload-percentage range, seniority level, required language, company size, and published-since date. Returns title, company, location, contract type, workload, posted date, and the job URL — with an optional deeper mode that adds the full job description, benefits, required skills, and salary when the employer discloses it. HTTP-only, no auth, no cookies required.

What this actor does

  • Five modes: search (keyword), byRegion (canton/language-region browse), byIndustry (category browse), byEmploymentType (contract-type browse), byCategory (occupational field / job function browse) — every filter below is combinable in any mode
  • Filters: city/place, canton/region, industry, occupational field, employment (contract) type, workload % range, seniority level, required language, company size, published-since date
  • Three site languages: English, German, French
  • Optional enrichment: full job description, benefits, required skills, apply URL, and salary (when disclosed)
  • Empty fields are omitted

Output per job

  • jobId, title
  • companyId, companyName, companySlug, companyUrl, companyLogoUrl
  • location — primary place name (e.g. Zürich)
  • locationDetails[] — canton code, city, postal code, country code, latitude/longitude (present on jobs with more than one workplace)
  • workloadMin, workloadMax, workloadText — employment grade in percent (e.g. 80 – 100%)
  • contractType, contractTypes[] — e.g. Permanent position, Freelance, Internship
  • seniorityLevelExecutive position / Specialist / Employee
  • tags[] — badges jobs.ch shows on the listing, e.g. Easy apply, Quick apply (present only when the listing carries one)
  • benefitTags[] — employer-disclosed perks shown on the listing itself (e.g. Working from home, Flexible working hours) — present only on the share of listings that have any; a larger, differently-sourced benefits[] list is also available via includeDescription
  • postedDate, initialPostedDate, postedRelative, initialPostedRelative (present only on reposted/republished listings where it differs from postedRelative, or when jobs.ch echoes the same value)
  • isNew, isBoosted, isPaid, isActive, headhunterApplicationAllowed
  • jobUrl
  • With includeDescription: truedescription, roleSummary (short, normalized job title, e.g. "Consultant Finance" — this is jobs.ch's own internal role-taxonomy label, so it can occasionally read as a broader category than the literal posted title), industry (employer's business sector as text, distinct from the job's category), benefits[], skills[], workHoursText, category, totalOpenings, startDate, jobImmediateStart, directApply, companyWebsite (the employer's own site, distinct from companyUrl), companyOverview, contactName, contactPhone, streetAddress, jobImageUrl (the job posting's own banner/cover image, when the employer supplied one), applicantLocationRequirement (e.g. Switzerland / Abroad — the employer's stated eligibility requirement for applicants), and salaryMin / salaryMax / salaryCurrency / salaryPeriod when the employer discloses a salary range. With includeDescription: true, companyUrl is also refined to the canonical URL the job posting itself declares (jobs.ch occasionally serves a listing-level company slug that only 301-redirects to this canonical one). Use jobUrl as the apply entry point — see Limitations below for why a separate applyUrl isn't offered.
  • recordType: "job", scrapedAt

Input

FieldTypeDefaultDescription
modestringsearchsearch / byRegion / byIndustry / byEmploymentType / byCategory
searchQuerystringFree-text keyword (mode=search); leave blank to browse all jobs
regionstringCanton / language-region (required for mode=byRegion)
locationstringFree-text city/place refinement, e.g. Zürich
industrystringIndustry / employer sector (required for mode=byIndustry)
categorystringOccupational field / job function (required for mode=byCategory) — distinct from industry
employmentTypestringContract type (required for mode=byEmploymentType)
employmentGradeMinint0Workload % lower bound
employmentGradeMaxint100Workload % upper bound
positionstringSeniority level filter
publishedSincestringOnly jobs first published in the last 24h / 3 / 7 / 30 days
languageSkillstringOnly jobs that require this working language: de / en / fr / it
companySegmentstringOnly jobs at large companies (gu) or SMEs (kmu)
includeDescriptionboolfalseFetch each job's detail page for description/benefits/skills/salary
localestringenSite language: en / de / fr
maxItemsint20Hard cap (1–1000)
proxyConfigurationobjectAUTOUsed first for keyword (searchQuery) searches — jobs.ch's own IP reputation narrows relevance for the raw datacenter IP; used only as an automatic fallback for browse/filter modes
{
"mode": "search",
"searchQuery": "data engineer",
"employmentGradeMin": 80,
"employmentGradeMax": 100,
"maxItems": 50
}

Example: browse a canton/region

{
"mode": "byRegion",
"region": "7",
"employmentType": "5",
"maxItems": 100
}

Example: browse an industry with full descriptions

{
"mode": "byIndustry",
"industry": "13",
"includeDescription": true,
"maxItems": 25
}

Example: browse by contract type

{
"mode": "byEmploymentType",
"employmentType": "6",
"location": "Zürich"
}

Example: browse by occupational field, published in the last week

{
"mode": "byCategory",
"category": "106",
"publishedSince": "7",
"languageSkill": "en",
"companySegment": "gu"
}

Use cases

  • Recruiting intelligence — track how many roles a canton or industry is hiring for
  • Labour-market research — analyse workload/contract-type distribution across Switzerland
  • Job aggregators — feed jobs.ch listings into a meta-search product
  • Salary benchmarking — collect the (rare) publicly disclosed salary ranges
  • Company monitoring — watch a specific employer's open roles via searchQuery

FAQ

What's the data source? The public jobs.ch job-search results page, in the same language/data the site itself renders. No login, no API key.

Is this affiliated with jobs.ch? No — this is a third-party actor that reads jobs.ch's publicly accessible pages.

Why is location free text but region a dropdown? jobs.ch's canton/language-regions are a fixed, documented list of ~24 options, so they're a dropdown. City/place names are effectively unbounded, so location stays free text and is combinable with region.

Why is salary often missing? Most Swiss employers do not publish a salary range on their listings. The salaryMin/salaryMax/salaryCurrency fields only appear on the small share of jobs where the employer disclosed one, and only when includeDescription is enabled.

What does includeDescription cost me? It fetches one extra page per job, roughly doubling the number of requests and the run time — leave it off for a fast listing-only scrape.

Can I combine region, industry, category, employmentType, publishedSince, languageSkill, companySegment and searchQuery together? Yes — every filter is honoured in every mode; mode only controls which single field is mandatory.

What's the difference between industry and category? industry is the employer's business sector (e.g. Banking, Retail, Healthcare). category is the job's occupational field / function (e.g. IT, Sales, Finance) — jobs.ch tracks these as two separate taxonomies, so a Banking-industry employer can post an IT-category job. Use whichever axis matches your research question, or combine both.

How fresh is the data? Real-time — every run reads jobs.ch's live search results, not a cache.

Do I need a proxy? No configuration needed — the free AUTO proxy group is on by default. For byRegion/byIndustry/byEmploymentType/byCategory browsing it's only used as an automatic fallback if a run gets temporarily rate-limited. For keyword (searchQuery) searches it's used proactively on every request, because jobs.ch's own search-relevance backend returns a smaller, less on-topic result set to plain datacenter IPs (still HTTP 200 — not a rate limit) unless the request goes through Apify's proxy pool.

Why does an unusual searchQuery sometimes return a handful of unrelated jobs instead of 0? jobs.ch's own search engine is deliberately typo-tolerant/fuzzy rather than a literal substring match — verified by loading the same query directly on jobs.ch in a browser: it shows the identical small result count and titles. This actor faithfully mirrors whatever jobs.ch itself returns; it never fabricates results for an unmatched query.

Data source & limitations

This actor reads jobs.ch's server-rendered search-results HTML (the same Redux state the site itself hydrates from) — not a private or authenticated API. Every filter and output field listed above comes from that public payload.

Axes that exist on jobs.ch but are not exposed by this actor, and why:

  • Company-name filter — jobs.ch resolves a company filter to an internal companyId via an autocomplete lookup rather than a stable text/slug parameter, so it can't be driven reliably from a plain input field. Use searchQuery (jobs.ch's full-text search already matches company names) as a practical substitute.
  • Sort order (relevance / date) — the sort=/order= query parameters accepted by the results page are silently ignored by jobs.ch's backend (verified: identical searchHash/result order regardless of value), so no sortBy input is offered — it would not do anything.
  • Benefit filter (benefit=) — the results page recognizes a benefit= query parameter (both numeric ids and benefit slugs echo back into the embedded search state), but verified live: it makes the underlying search silently fail (fetched: false, 0 results, still HTTP 200) for every value tried, including the exact slugs jobs.ch's own listings emit (training, flexible-working-hours, free-drinks, ...). Unlike the employment-grade-min=0 quirk (which has one clean, verified workaround value), no value makes this filter actually run a search, so it is not exposed as an input. The listing-level benefitTags[] output field is unaffected — that comes from each job's own record, not from filtering.
  • Job "group" filter (group=) — also recognized into the embedded search state and returns fetched: true, but every id from 1–200 tested returns exactly 0 hits with no documented id scheme; excluded as unreliable/undiscoverable rather than exposing a filter that always returns nothing.
  • Listing "label" filter (label=) — accepted into the embedded search state but verified to have zero effect on the result set or count (same totalHits with or without it, for both new and boosted values) — like sort=, it's a no-op parameter.
  • languageSkills, regions, contacts, applicationOptions, videos fields — jobs.ch's search-results payload declares these fields on every listing row, but they were empty ([]/{}) across every sampled query, filter and page during testing, including after filtering by languageSkill. They are intentionally omitted rather than emitted as always-null placeholders.
  • A broader benefits vocabulary — the listing-level benefitTags[] field reformats whatever slug jobs.ch already sent (e.g. working-from-homeWorking from home) rather than mapping against a fixed lookup table, because jobs.ch's benefit-tag vocabulary is open-ended and grows over time; a hardcoded table would silently go stale.
  • Job detail's employmentUnit field — the detail page's ld+json block declares this, but verified live across dozens of listings: its value is always identical to the already-extracted seniorityLevel (Employee / Specialist / Executive position), just sourced from a second place. Omitted as a pure duplicate rather than a second field carrying the same information twice.
  • Job detail's identifier/additionalType/jobLocation.address.addressRegionidentifier.value duplicates jobId; additionalType is a constant boilerplate value (https://schema.org/Product) on every listing; addressRegion is mislabeled by jobs.ch's own markup (it holds the city name, duplicating location, not an actual canton/region). None carry information beyond what's already emitted.
  • Job detail's datePosted/employmentType (schema.org fields) — verified live across sampled listings: datePosted is always identical to the listing's own publicationDate (already emitted as postedDate); employmentType's text value is always identical to the already-extracted, locale-translated contractType. Both are pure duplicates sourced from a second place on the detail page.
  • Apply URL (potentialAction.target.urlTemplate in the detail page's ld+json) — jobs.ch's own structured data declares this as a directly-fetchable "ApplyAction" URL, but verified live (both curl -ILSs with no cookies and a full JS-rendered browser navigation): it 404s. jobs.ch's real "Apply" button is a pure client-side modal — clicking it doesn't even change the browser's URL — so there is no working standalone apply link to expose. jobUrl (the job's own detail page, always reachable) is the correct entry point; a user reaches the same Apply button by opening it.