Arbeitsagentur Jobs Scraper (Germany) avatar

Arbeitsagentur Jobs Scraper (Germany)

Pricing

from $0.53 / 1,000 jobs

Go to Apify Store
Arbeitsagentur Jobs Scraper (Germany)

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

Hexagone Data

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

2 hours ago

Last modified

Categories

Security

Limited permissions

Share

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äufer around Hamburg; for Koch, 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äufer around Hamburg 1,792 of 1,792, Koch 8,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 for Elektriker, 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 with includePrivateAgencies, and 212 apprenticeships with offerType = 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

  1. Keep the prefilled search term (Steinmetz) and Maximum number of jobs = 20.
  2. Click Start. You pay for 20 jobs: $0.020 (full ads included, start fee included).
  3. 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 pasteExampleWhat you get
A searchhttps://www.arbeitsagentur.de/jobsuche/suche?angebotsart=1&was=Koch&wo=Hamburg&umkreis=25every 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 listhttps://www.arbeitsagentur.de/jobsuche/suche?angebotsart=1&was=Steinmetz&suchbereich=jobs&id=14751-373A144846-Sthe whole search, not only that job
A jobhttps://www.arbeitsagentur.de/jobsuche/jobdetail/14751-373A144846-S, or suche?id=<reference> with no searchthat job with its details
Search with filters...&arbeitszeit=vz;tz&befristung=2&veroeffentlichtseit=28&zeitarbeit=falsesame filters as the site
Every job of the sitehttps://www.arbeitsagentur.de/jobsuche/suche with Maximum number of jobs = 0the 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.

StatusMeaning
doneevery 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_collectionsomething is missing: both numbers and the cause; warning PARTIAL_COLLECTION
stopped_at_limityour maxItems or maximum charge was reached
no_resultsthe site announced 0 jobs: the search is empty, the URL is wrong or the site is down (NO_RESULTS warning)
no_new_jobsevery job of this URL was already delivered by an earlier URL
job_not_availablea pasted job does not exist any more; no job charged
blocked_by_site, rate_limited, network_error, http_error, not_found, site_maintenancethe site refused or did not answer even after new sessions: never reported as an empty search
unrecognized_page, structure_changedthe answer is not a search result the Actor can read; never reported as an empty search
not_processed, interrupted, internal_errorthe 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.

JobsWith full ads, start fee includedWithout 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, Sachsen gives a place in Bavaria (Koch: 32 jobs, against 484 with Sachsen (Bundesland)), Hessen a place in the north, none of its jobs in Hesse (35 against 462), Brandenburg the city (8 against 276). Write Berlin (Bundesland), Brandenburg (Bundesland), Bremen (Bundesland), Hamburg (Bundesland), Hessen (Bundesland) or Sachsen (Bundesland) for the state. For Sachsen, Hessen and Brandenburg, in any case, the run warns (LOCATION_NOT_THE_STATE); Berlin, Hamburg and Bremen alone give the city and its surroundings, without a warning.
  • Always newest first, by first publication date (firstPublishedAt), not by publishedAt, which the site moves when an ad is renewed: with a small maxItems you get the jobs first published most recently, and a job with today's publishedAt can sit far down the list. A sort in your URL is ignored (orderNote says 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_LOW warning 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_collection with the numbers, never done. 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 jobsDistinct can pass totalAtEnd. 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, detailsStatus tells 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.