jobs.ch Scraper
Pricing
from $3.00 / 1,000 results
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
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,titlecompanyId,companyName,companySlug,companyUrl,companyLogoUrllocation— 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,InternshipseniorityLevel—Executive position/Specialist/Employeetags[]— 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-sourcedbenefits[]list is also available viaincludeDescriptionpostedDate,initialPostedDate,postedRelative,initialPostedRelative(present only on reposted/republished listings where it differs frompostedRelative, or when jobs.ch echoes the same value)isNew,isBoosted,isPaid,isActive,headhunterApplicationAllowedjobUrl- With
includeDescription: true—description,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'scategory),benefits[],skills[],workHoursText,category,totalOpenings,startDate,jobImmediateStart,directApply,companyWebsite(the employer's own site, distinct fromcompanyUrl),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), andsalaryMin/salaryMax/salaryCurrency/salaryPeriodwhen the employer discloses a salary range. WithincludeDescription: true,companyUrlis 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). UsejobUrlas the apply entry point — see Limitations below for why a separateapplyUrlisn't offered. recordType: "job",scrapedAt
Input
| Field | Type | Default | Description |
|---|---|---|---|
mode | string | search | search / byRegion / byIndustry / byEmploymentType / byCategory |
searchQuery | string | – | Free-text keyword (mode=search); leave blank to browse all jobs |
region | string | – | Canton / language-region (required for mode=byRegion) |
location | string | – | Free-text city/place refinement, e.g. Zürich |
industry | string | – | Industry / employer sector (required for mode=byIndustry) |
category | string | – | Occupational field / job function (required for mode=byCategory) — distinct from industry |
employmentType | string | – | Contract type (required for mode=byEmploymentType) |
employmentGradeMin | int | 0 | Workload % lower bound |
employmentGradeMax | int | 100 | Workload % upper bound |
position | string | – | Seniority level filter |
publishedSince | string | – | Only jobs first published in the last 24h / 3 / 7 / 30 days |
languageSkill | string | – | Only jobs that require this working language: de / en / fr / it |
companySegment | string | – | Only jobs at large companies (gu) or SMEs (kmu) |
includeDescription | bool | false | Fetch each job's detail page for description/benefits/skills/salary |
locale | string | en | Site language: en / de / fr |
maxItems | int | 20 | Hard cap (1–1000) |
proxyConfiguration | object | AUTO | Used 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 |
Example: keyword search
{"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
companyIdvia an autocomplete lookup rather than a stable text/slug parameter, so it can't be driven reliably from a plain input field. UsesearchQuery(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: identicalsearchHash/result order regardless of value), so nosortByinput is offered — it would not do anything. - Benefit filter (
benefit=) — the results page recognizes abenefit=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 theemployment-grade-min=0quirk (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-levelbenefitTags[]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 returnsfetched: 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 (sametotalHitswith or without it, for bothnewandboostedvalues) — likesort=, it's a no-op parameter. languageSkills,regions,contacts,applicationOptions,videosfields — 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 bylanguageSkill. They are intentionally omitted rather than emitted as always-null placeholders.- A broader
benefitsvocabulary — the listing-levelbenefitTags[]field reformats whatever slug jobs.ch already sent (e.g.working-from-home→Working 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
employmentUnitfield — the detail page's ld+json block declares this, but verified live across dozens of listings: its value is always identical to the already-extractedseniorityLevel(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.addressRegion—identifier.valueduplicatesjobId;additionalTypeis a constant boilerplate value (https://schema.org/Product) on every listing;addressRegionis mislabeled by jobs.ch's own markup (it holds the city name, duplicatinglocation, 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:datePostedis always identical to the listing's ownpublicationDate(already emitted aspostedDate);employmentType's text value is always identical to the already-extracted, locale-translatedcontractType. Both are pure duplicates sourced from a second place on the detail page. - Apply URL (
potentialAction.target.urlTemplatein the detail page's ld+json) — jobs.ch's own structured data declares this as a directly-fetchable "ApplyAction" URL, but verified live (bothcurl -ILSswith 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.