Arbeitsagentur Jobs Scraper (Germany)
Pricing
from $0.53 / 1,000 jobs
Arbeitsagentur Jobs Scraper (Germany)
Scrape the Bundesagentur für Arbeit Jobsuche: whole searches read by publication date, beyond the site's 10,000-row limit with no setup, the same list as the site's page, full ad included. Pay per job.
Pricing
from $0.53 / 1,000 jobs
Rating
0.0
(0)
Developer
Hexagone Data
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
2 hours ago
Last modified
Categories
Security
Limited permissionsShare
Scrape the Jobsuche of the Bundesagentur für Arbeit, Germany's public job board. Type a search term or paste the URL your browser shows, filters included, and get one record per job: title, company, every location, salary, contract, working time, dates, apprentice pay, and the full ad text.
What it does, measured on the site on 2026-11-03:
- It reads every search by first publication date, 500 rows at a time. The site's default order changes from one page to the next, so reading it page after page repeats some jobs and never shows others, while the row count looks
complete: 1,786 distinct jobs out of 1,797 for
Verkäuferaround Hamburg; forKoch, out of 8,507: 8,446 at 50 rows per page, 8,480 to 8,495 at 100, 8,502 at 500. Its runs on Apify, checked against separate reads of the site:Verkäuferaround Hamburg 1,792 of 1,792,Koch8,509 for 8,509 announced (one job replaced between the two reads), while a read at the site's order the same hour found 1,765 and 8,483. If the list moves while a slice is read (a job removed, a job posted), that slice is read again, up to 3 times in all. - Beyond 10,000 results with no setup. The site refuses to go further than 10,000 rows. The Actor splits a larger search by the site's own filters and puts it back together, each job
delivered once: 17,588 of 17,588 for
Büro, 13,698 of 13,698 forElektriker, 121,786 for all jobs in Bavaria (angebotsart=1; 121,779 announced at the end, jobs are removed during a run), 188,700 for every apprenticeship (188,687 announced at the end). - The same list as the site's page, by default. The site's page hides the offers of private placement agencies and lists apprenticeships apart; so does this Actor, unless you ask (for
Steinmetz: 386 jobs on the page and here, plus 17 from private agencies withincludePrivateAgencies, and 212 apprenticeships withofferType = AUSBILDUNG). - The full ad is included. Same price with or without it. The list and the job page disagree on some jobs (17 of 200 had more locations on the job page): the job page wins.
- The run tells you what it got. Distinct jobs against the total announced at the start and read again at the end, with both numbers if anything is missing.
Try it with 20 jobs
- Keep the prefilled search term (
Steinmetz) and Maximum number of jobs = 20. - Click Start. You pay for 20 jobs: $0.020 (full ads included, start fee included).
- Open Jobs overview for the table, Run summary for what was collected and why anything is missing.
What to paste
Type words in Search terms and use the filters of the form, or paste into Start URLs:
| You paste | Example | What you get |
|---|---|---|
| A search | https://www.arbeitsagentur.de/jobsuche/suche?angebotsart=1&was=Koch&wo=Hamburg&umkreis=25 | every job of the search, filters kept as in the URL |
| An apprenticeship search (Ausbildung) | https://www.arbeitsagentur.de/jobsuche/suche?was=Steinmetz&suchbereich=ausbildung (or angebotsart=4) | apprenticeships and dual studies |
| The URL copied after clicking a job on the list | https://www.arbeitsagentur.de/jobsuche/suche?angebotsart=1&was=Steinmetz&suchbereich=jobs&id=14751-373A144846-S | the whole search, not only that job |
| A job | https://www.arbeitsagentur.de/jobsuche/jobdetail/14751-373A144846-S, or suche?id=<reference> with no search | that job with its details |
| Search with filters | ...&arbeitszeit=vz;tz&befristung=2&veroeffentlichtseit=28&zeitarbeit=false | same filters as the site |
| Every job of the site | https://www.arbeitsagentur.de/jobsuche/suche with Maximum number of jobs = 0 | the whole board, only if you ask for it (735,911 jobs when measured): see Known limits |
Supported filters, the site's own list: was, wo, umkreis, suchbereich, angebotsart, pav, arbeitsort, arbeitszeit, befristung, betreuung, veroeffentlichtseit, zeitarbeit, verguetung, ausbildungsverguetung, behinderung,
quereinstieg, externestellenboersen, berufsfeld, beruf, weitereberufe, branche, arbeitgeber, kundennummer, homeoffice, eintrittsdatum, ausbildungsart, schulbildung, koordinaten, wb. They are sent as written; if the site refuses a value (HTTP 400), the run reports it, and names the parameter when the site does.
What the browser adds on its own (id with a search, page, size, sort, aktualisiertVor, as) is removed and named in the run summary (droppedParams), and the whole search is read. Any other parameter (utm_source...) is not a filter of the site and has no effect: it is removed, with a warning.
As on the page, pav=true adds private agencies and a missing pav leaves them out. An address with no real filter left (suche?utm_source=x, suche?page=2, koordinaten without wo) is rejected: it would read the whole site. So is any other address (another site, /ausbildungssuche/, the home
page), with the reason; the other addresses of the run are still read. arbeitsagentur.de without www works. A remote list of URLs (requestsFromUrl) is rejected: paste the URLs themselves.
An empty form is refused: reading the whole board is something you ask for on purpose, by its URL.
Output
One record per job and run (id is the key: a job found by two of your searches is delivered once). Unknown values are null, never "", 0 or false: the site omits many fields. Every field
is listed in the dataset schema. Real record of a run on Apify on 2026-11-03, abridged (ad text shortened, some fields left out):
{"id": "10001-1003955562-S","url": "https://www.arbeitsagentur.de/jobsuche/jobdetail/10001-1003955562-S","title": "Pflege(fach)helfer (m/w/d)","offerType": "ARBEIT","occupation": "Altenpflegehelfer/in","allOccupations": ["Altenpflegehelfer/in"],"companyName": "Bayerisches Rotes Kreuz Kreisverband Rottal-Inn","locations": [{"street": "Pfarrkirchener Str.", "houseNumber": "53", "district": null, "postalCode": "84307", "city": "Eggenfelden", "region": "BAYERN", "country": "DEUTSCHLAND", "latitude": 48.405294, "longitude": 12.767409}],"city": "Eggenfelden","postalCode": "84307","region": "BAYERN","country": "DEUTSCHLAND","fullTime": null,"miniJob": null,"contractDuration": "KEINE_ANGABE","startDate": "2026-11-03","salaryType": "KEINE_ANGABEN","salaryMin": null,"homeOffice": null,"publishedAt": "2026-11-03","firstPublishedAt": "2026-11-03","modifiedAt": "2026-11-03T13:46:59.232","sourceUrl": "https://www.arbeitsagentur.de/jobsuche/suche?angebotsart=1&wo=Bayern&berufsfeld=Altenpflege","scrapedAt": "2026-11-03T12:52:03Z","detailsStatus": "ok","description": "Das Bayerische Rote Kreuz – Körperschaft des öffentlichen Rechtes – ist einer der größten ...","isManagedByEmploymentAgency": false,"partnerName": "arbeitsagentur.de"}
detailsStatus: ok; failed (the job page could not be read, the job stays delivered from the list); skipped (details stopped for this run after repeated refusals); not_requested.
Run summary
Each run writes a readable Run summary (and the same as JSON in OUTPUT), per start URL: status, jobs, jobsDistinct (distinct jobs seen for this URL), totalAnnounced, totalAtEnd (read again after the
last page), missing, slices, slicesOver10000, slicesReread (extra readings because the list moved), slicesStillMoving, passes, droppedParams, orderNote, detail. For the run: detailsRead, detailsFailed, detailsStopped, accessRefusals, anomalies.
| Status | Meaning |
|---|---|
done | every distinct job counted against the total read at the end; at most 1 job or 0.05% missing, shown with its number (warning MISSING_WITHIN_TOLERANCE) |
partial_collection | something is missing: both numbers and the cause; warning PARTIAL_COLLECTION |
stopped_at_limit | your maxItems or maximum charge was reached |
no_results | the site announced 0 jobs: the search is empty, the URL is wrong or the site is down (NO_RESULTS warning) |
no_new_jobs | every job of this URL was already delivered by an earlier URL |
job_not_available | a pasted job does not exist any more; no job charged |
blocked_by_site, rate_limited, network_error, http_error, not_found, site_maintenance | the site refused or did not answer even after new sessions: never reported as an empty search |
unrecognized_page, structure_changed | the answer is not a search result the Actor can read; never reported as an empty search |
not_processed, interrupted, internal_error | the URL was not reached or finished (limit reached before it, run stopped, unexpected error) |
If jobs are missing after the last page, the first page of each slice is read again, and the slices that still lack jobs are read to the end; at most 3 readings per slice in all, passes included. Only what was missing is added, nothing is delivered or charged twice. A job delivered without its full ad is counted in detailsFailed or detailsStopped and in the warning DETAILS_FAILED. A run where no URL delivers anything fails with the exact cause.
Warnings are listed under Checks; "Checks passed" is shown only when there is none. Codes starting with ANO_ come from the shared run checks.
Pricing
Pay per event: $0.75 per 1,000 jobs, the full ad included: the same price with or without includeDetails, and for a pasted job URL. Plus a $0.005 start fee per run (charged by Apify once per GB of memory; the default is 1 GB).
Apify subscribers pay less automatically, on every event: 10% off on the Bronze plan, 20% on Silver, 30% on Gold and above.
| Jobs | With full ads, start fee included | Without full ads, start fee included |
|---|---|---|
| 20 | $0.020 | $0.020 |
| 1,000 | $0.755 | $0.755 |
Only jobs written to your dataset are paid, each once, even if two of your searches find it or the list is read twice. Your maximum charge per run is respected: the run stops cleanly before the job that would exceed it, and no ad is read for a job that could not be paid.
Known limits
- Six German states have a place of the same name. Typed as is,
Sachsengives a place in Bavaria (Koch: 32 jobs, against 484 withSachsen (Bundesland)),Hessena place in the north, none of its jobs in Hesse (35 against 462),Brandenburgthe city (8 against 276). WriteBerlin (Bundesland),Brandenburg (Bundesland),Bremen (Bundesland),Hamburg (Bundesland),Hessen (Bundesland)orSachsen (Bundesland)for the state. For Sachsen, Hessen and Brandenburg, in any case, the run warns (LOCATION_NOT_THE_STATE);Berlin,HamburgandBremenalone give the city and its surroundings, without a warning. - Always newest first, by first publication date (
firstPublishedAt), not bypublishedAt, which the site moves when an ad is renewed: with a smallmaxItemsyou get the jobs first published most recently, and a job with today'spublishedAtcan sit far down the list. Asortin your URL is ignored (orderNotesays so), because the site's own order loses jobs. - A move that leaves no trace. A job removed and a job posted between two pages of the same slice cancel each other out, with no repeated row and no drop in the total: it can hide a job and nothing shows it. Likewise, a job that moves into a slice already read, while another leaves it: the total does not change. Rare, not quantified. A slice still moving after 3 readings is reported (
SLICE_KEPT_MOVING). - No e-mails or phone numbers extracted, on purpose: they are not fields of the site, only text written by the employer. The ad text stays delivered as published. Contacts protected by the site's captcha are not read.
- No incremental mode: each run reads what you ask, from the newest down, and has no memory of earlier runs.
- Reading the whole country is long and needs more memory, and has not been run: largest run measured, 188,700 jobs without ads in 18 minutes using 427 MB. At that rate, the 824,166 jobs with private agencies would take about 1 h 20 without ads and about 1.8 GB (calculated): set 4 GB in the run options. Ads add about 20 per second (1,792 in 88 s), about 11 hours; repeated readings of slices that move add time, not measured. The default run timeout is 24 hours. A search too large for the run's memory gets a
MEMORY_LOWwarning at its start, with the value to set. - Over 10,000 jobs: the site's filters split the search. A slice still above 10,000 with no filter left gives its newest 10,000 rows and
partial_collectionwith the numbers, neverdone. The last resort, by German state, is taken only if the 16 states hold at least 99.5% of the slice (not often the case for apprenticeships: 94%); otherwise, or if some jobs stay out, the numbers are reported. Measured dry on the site: all jobs 824,166 in 853 slices, none above 10,000; apprenticeships 188,659 in 166 slices, one above 10,000 (10,474). - Jobs removed while the list is read still count as seen, so
jobsDistinctcan passtotalAtEnd. New jobs keep coming during a long run: a few may be missing at the end. - A job whose ad could not be read is still delivered and charged: its list data is complete,
detailsStatustells which. After 10 refused ads in a row, the run pauses 60 s and tries one; if it is refused, the pause doubles; after 3 refused tries, ads stop for the run (detailsStopped). - No proxy is used by default: no refusal in any measured run from Apify's servers. If the site refuses, the run retries, then falls back to Apify's datacenter proxy; this fallback has been tested offline only.
Support
Questions, a URL that does not work, a field you need: open an issue on the Actor's Issues tab with the run link.
This Actor is not affiliated with, endorsed or sponsored by the Bundesagentur für Arbeit. The name is used only to describe the website the data comes from. Only publicly visible job ads are collected; ad texts can contain personal data such as a contact name: use the data in line with the site's terms and the privacy laws that apply to you.