<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Devil Scrapes</title>
    <description>The latest articles on DEV Community by Devil Scrapes (@devil_scrapes).</description>
    <link>https://dev.to/devil_scrapes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3960872%2Ffa930ad0-5ebc-4ca7-b894-6bc6fb3e2b40.png</url>
      <title>DEV Community: Devil Scrapes</title>
      <link>https://dev.to/devil_scrapes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devil_scrapes"/>
    <language>en</language>
    <item>
      <title>Breezy HR Jobs Scraper: Pull Every Posting From Any Breezy HR Career Board</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sun, 26 Jul 2026 17:42:39 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/breezy-hr-jobs-scraper-pull-every-posting-from-any-breezy-hr-career-board-45j9</link>
      <guid>https://dev.to/devil_scrapes/breezy-hr-jobs-scraper-pull-every-posting-from-any-breezy-hr-career-board-45j9</guid>
      <description>&lt;h2&gt;
  
  
  Breezy HR shows up more than you'd expect
&lt;/h2&gt;

&lt;p&gt;Breezy HR is a lighter-weight ATS that a lot of SMB and mid-market employers reach for — I've seen it running healthcare networks with 600+ open reqs and three-person equipment dealers with a single listing, on the exact same &lt;code&gt;{company}.breezy.hr&lt;/code&gt; URL pattern. Every one of those boards is served from one JSON endpoint (&lt;code&gt;https://{company}.breezy.hr/json&lt;/code&gt;), no pagination, the whole board in one response.&lt;/p&gt;

&lt;p&gt;That simplicity on Breezy's end doesn't mean scraping it at scale is simple on yours — which is why I built a dedicated Breezy HR jobs scraper under Devil Scrapes, tested end to end against boards ranging from a handful of postings to several hundred, so the same code path holds regardless of company size.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data looks like
&lt;/h2&gt;

&lt;p&gt;A real row from the dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"posting_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5a3b663b95ce"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company_slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rhynocare"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RhynoCare"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apply_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://rhynocare.breezy.hr/p/5a3b663b95ce-cook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"published_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-25T00:00:00.673Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Full-Time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$25 – $27 / hour"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"St. Catharines"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_remote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-26T15:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;salary&lt;/code&gt; is deliberately kept as freeform text exactly as published (&lt;code&gt;"$25 – $27 / hour"&lt;/code&gt;) rather than force-parsed into numeric fields — Breezy employers write pay ranges in enough different formats that guessing at parsing would introduce more noise than it removes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive approach and why it's trickier than it looks
&lt;/h2&gt;

&lt;p&gt;Breezy's &lt;code&gt;/json&lt;/code&gt; endpoint is genuinely convenient on paper — no auth, whole board in one call. The gotcha that trips up a quick script is what happens when a company slug is wrong or unused: Breezy doesn't return a clean 404. It &lt;strong&gt;redirects to its own marketing site&lt;/strong&gt; instead, which means a naive scraper will either choke trying to parse marketing-site HTML as job data, or — worse — silently report zero postings for a company that's actually fine, because it never noticed the redirect at all.&lt;/p&gt;

&lt;p&gt;Add to that the usual scale problems — a client that doesn't look like a browser at the TLS layer, no backoff when a board briefly rate-limits, one bad slug taking down an entire multi-company batch — and "quick script" turns into an afternoon of edge-case chasing pretty fast.&lt;/p&gt;

&lt;p&gt;There's also a proxy wrinkle worth being honest about: Breezy's feed didn't show any blocking in our own testing from a typical residential-style exit IP, but a run through a plain datacenter IP got a flat 403 in our own cloud validation. We ship this Actor defaulting to a residential proxy through Apify Proxy for exactly that reason — we'd rather absorb that cost up front than have your run intermittently fail depending on which exit IP you happened to draw that day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/DevilScrapes/breezy-hr-jobs-scraper" rel="noopener noreferrer"&gt;Breezy HR Jobs Scraper&lt;/a&gt; detects that marketing-site redirect precisely and skips the company instead of choking on it, rotates Chrome/Firefox/Safari TLS fingerprints via &lt;code&gt;curl-cffi&lt;/code&gt; on every request, retries transient &lt;code&gt;429&lt;/code&gt;/&lt;code&gt;5xx&lt;/code&gt; responses with backoff up to 5 attempts, and routes every request through a rotating residential proxy session — the same infrastructure we lean on harder the day this target tightens up further.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/breezy-hr-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companySlugs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rhynocare&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;barloworldequipment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxResultsPerCompany&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location_city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or paste the same JSON straight into the Apify Console input form for a one-off pull.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'd actually use this for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recruiting &amp;amp; talent intelligence&lt;/strong&gt; — track what a Breezy-hosted employer is hiring for, where, and at what pay band, when published.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hiring-intent signals for BD/SDR teams&lt;/strong&gt; — a sudden spike in open reqs at a target account is a decent lead qualifier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job-board aggregation&lt;/strong&gt; — Breezy coverage slots next to our &lt;a href="https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper" rel="noopener noreferrer"&gt;SmartRecruiters&lt;/a&gt;, &lt;a href="https://apify.com/DevilScrapes/workday-jobs-scraper" rel="noopener noreferrer"&gt;Workday&lt;/a&gt;, &lt;a href="https://apify.com/DevilScrapes/workable-jobs-scraper" rel="noopener noreferrer"&gt;Workable&lt;/a&gt;, and &lt;a href="https://apify.com/DevilScrapes/teamtailor-jobs-scraper" rel="noopener noreferrer"&gt;Teamtailor&lt;/a&gt; scrapers in one pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labor-market research&lt;/strong&gt; — sample hiring demand by industry or region using structured, machine-readable data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HR-tech pipelines&lt;/strong&gt; — wire rows straight into a CRM, dashboard, or n8n/Make workflow on a schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing, honestly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;$0.005 per run&lt;/strong&gt; flat, plus &lt;strong&gt;$0.0015 per posting&lt;/strong&gt; written. A 1,000-posting pull costs &lt;strong&gt;$1.51&lt;/strong&gt; total. A company with an invalid slug or zero open reqs is never billed for rows it didn't produce. Apify's $5 free credit for new accounts covers roughly 3,300 postings before you'd spend anything yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is more interesting than it looks
&lt;/h2&gt;

&lt;p&gt;The redirect-based bad-slug detection is the single most important piece of engineering in this Actor, and it's invisible if you've never hit it: get it wrong and you either crash on marketing-site HTML or — worse — report a false "zero postings" for a company that's actually fine. Getting that distinction right, plus routing consistently through a residential exit IP so a future tightening on Breezy's side doesn't turn into an intermittently-broken run, is where the real reliability work went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;This covers public postings only, not authenticated internal views. Breezy's list endpoint doesn't publish the full job-description body — only the &lt;code&gt;apply_url&lt;/code&gt;, which links to the full text on the HTML page that this Actor never fetches. You supply company slugs, not display names, since a brand like "RhynoCare" may not match its actual subdomain. And it's a point-in-time snapshot — schedule recurring runs to track how a board changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/DevilScrapes/breezy-hr-jobs-scraper" rel="noopener noreferrer"&gt;Breezy HR Jobs Scraper&lt;/a&gt; is live on the Apify Store at $1.50 per 1,000 results, with Apify's standard $5 free trial credit and no card required to start. It's part of a growing ATS-coverage fleet under &lt;a href="https://apify.com/DevilScrapes" rel="noopener noreferrer"&gt;Devil Scrapes&lt;/a&gt; — BambooHR, Recruitee, Ashby, and Personio are all covered too.&lt;/p&gt;

&lt;p&gt;Have you hit a Breezy board that behaves differently from what's described here? Tell me in the comments — that's how the redirect-detection logic in this Actor got as solid as it is.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Personio Jobs Scraper: Pull Every Posting From Any Personio Careers Page</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sun, 26 Jul 2026 17:42:03 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/personio-jobs-scraper-pull-every-posting-from-any-personio-careers-page-2lac</link>
      <guid>https://dev.to/devil_scrapes/personio-jobs-scraper-pull-every-posting-from-any-personio-careers-page-2lac</guid>
      <description>&lt;h2&gt;
  
  
  Personio is the default ATS across DACH
&lt;/h2&gt;

&lt;p&gt;If you're doing anything with hiring data in Germany, Austria, or Switzerland, Personio comes up constantly — it's the leading HR/recruiting SaaS across the DACH region, and every company using it to publish open roles gets a careers page at &lt;code&gt;{company}.jobs.personio.de&lt;/code&gt;. That page is backed by a public, unauthenticated XML feed (still carrying its legacy &lt;code&gt;workzag-jobs&lt;/code&gt; branding under the hood) listing every currently-open position: title, office, department, employment type, seniority, years of experience, and a full job description split into named sections.&lt;/p&gt;

&lt;p&gt;There was no dedicated Personio Actor on the Apify Store as of when I built this, despite Personio's footprint being large enough to matter for anyone doing DACH-region recruiting or labor-market research. So I built one under Devil Scrapes: point it at a list of company subdomains and get one clean row per open posting, across as many companies as you want in a single run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data looks like
&lt;/h2&gt;

&lt;p&gt;A real row from the dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chrono24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"position_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1234567&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior Backend Engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subcompany"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"office"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Karlsruhe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additional_offices"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Engineering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recruiting_category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tech"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employment_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Full-time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seniority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"years_of_experience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3-5 years"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"backend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"occupation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Software Development"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"occupation_category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-01T09:00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://chrono24.jobs.personio.de/job/1234567"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description_html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"We are looking for a Senior Backend Engineer to join our team ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-26T12:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;description_text&lt;/code&gt; is always populated regardless of settings; &lt;code&gt;description_html&lt;/code&gt; only fills in when you opt into the raw HTML sections — useful if you're rendering postings somewhere downstream rather than just reading them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive approach and why it's more work than it looks
&lt;/h2&gt;

&lt;p&gt;Personio's XML feed is genuinely public and well-structured, which makes it tempting to think a quick script will do. It mostly will — until you try running it across more than a handful of companies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feed availability varies per subdomain.&lt;/strong&gt; Some companies have deactivated their board, some never had one; your loop needs to treat "zero postings" as a normal outcome and keep going, not as a reason to stop the batch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Company input normalization.&lt;/strong&gt; People will hand you a bare subdomain, a full URL, a URL with or without the trailing path — resolving all of those to the same identifier cleanly is fiddly enough that it's easy to get wrong on the first pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fingerprinting and pacing.&lt;/strong&gt; A bare Python client doesn't look like a browser at the TLS layer, and firing requests across dozens of companies in a tight loop is exactly the pattern that starts drawing attention over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Actor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/DevilScrapes/personio-jobs-scraper" rel="noopener noreferrer"&gt;Personio Jobs Scraper&lt;/a&gt; resolves any of those input formats to the right subdomain automatically, walks the XML feed for every company you list, and isolates failures per company so one bad subdomain never derails the rest of the run. We rotate Chrome/Firefox TLS fingerprints via &lt;code&gt;curl-cffi&lt;/code&gt; and retry &lt;code&gt;408/429/5xx&lt;/code&gt; with exponential backoff up to 5 attempts, honoring &lt;code&gt;Retry-After&lt;/code&gt; when the target sends one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/personio-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chrono24&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urbansportsclub&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxJobsPerCompany&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeHtml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;office&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seniority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or paste the same JSON into the Apify Console input form for a quick one-off pull without writing any code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'd actually use this for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DACH-region recruiter pipelines&lt;/strong&gt; — pull every open role across a portfolio of German, Austrian, and Swiss employers into one normalized feed, alongside our &lt;a href="https://apify.com/DevilScrapes/teamtailor-jobs-scraper" rel="noopener noreferrer"&gt;Teamtailor&lt;/a&gt; and &lt;a href="https://apify.com/DevilScrapes/workday-jobs-scraper" rel="noopener noreferrer"&gt;Workday&lt;/a&gt; coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hiring-intent signal for BD/sales teams&lt;/strong&gt; — watch which offices and departments a target account is scaling before it's public news.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job-board aggregation&lt;/strong&gt; — fold Personio postings into a multi-ATS feed alongside &lt;a href="https://apify.com/DevilScrapes/multi-ats-jobs-scraper" rel="noopener noreferrer"&gt;Multi-ATS&lt;/a&gt; and &lt;a href="https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper" rel="noopener noreferrer"&gt;SmartRecruiters&lt;/a&gt; coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labor-market research&lt;/strong&gt; — a structured, machine-readable sample of DACH-region postings for wage- or skills-demand studies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive intelligence&lt;/strong&gt; — track a rival's open roles over time to infer which office or team is actually scaling.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing, honestly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;$0.005 per run&lt;/strong&gt; flat, plus &lt;strong&gt;$0.0015 per posting&lt;/strong&gt; written to the dataset. A 1,000-posting pull costs &lt;strong&gt;$1.505&lt;/strong&gt; total. A company with zero current openings costs nothing beyond the warm-up fee — you don't pay for rows that never existed. Apify's $5 free trial credit for new accounts covers roughly 3,300 postings before you'd spend anything yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is more interesting than it looks
&lt;/h2&gt;

&lt;p&gt;The part that took actual iteration wasn't the XML parsing itself — it was normalizing Personio's inconsistently-populated optional fields (&lt;code&gt;subcompany&lt;/code&gt;, &lt;code&gt;schedule&lt;/code&gt;, &lt;code&gt;additional_offices&lt;/code&gt;) across companies that each configure Personio slightly differently, without inventing a value the source feed never actually provided. Getting a stable schema that holds across an arbitrary set of DACH employers, most of whom you'll never have tested against directly, is most of the real engineering here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;This Actor covers Personio only — Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Teamtailor, and BambooHR live in sibling Actors. It only resolves the bare &lt;code&gt;{company}.jobs.personio.de&lt;/code&gt; subdomain form — a company fronting Personio behind its own &lt;code&gt;careers.example.com&lt;/code&gt; domain isn't resolvable this way. There's no expiry field either; Personio's feed only ever shows currently-open postings, with no way to tell how long a listing has been live beyond its &lt;code&gt;created_at&lt;/code&gt; timestamp. And it's a live-only snapshot — schedule recurring runs if you want to track change over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/DevilScrapes/personio-jobs-scraper" rel="noopener noreferrer"&gt;Personio Jobs Scraper&lt;/a&gt; is live on the Apify Store at $1.50 per 1,000 results, with Apify's standard $5 free trial credit and no card required to start. The devil's in the data — and it's one of several ATS-coverage Actors we run under &lt;a href="https://apify.com/DevilScrapes" rel="noopener noreferrer"&gt;Devil Scrapes&lt;/a&gt;, alongside BambooHR, Ashby, Recruitee, and Breezy HR.&lt;/p&gt;

&lt;p&gt;If you're tracking hiring across DACH and hitting a Personio board this doesn't handle right, tell me in the comments — that's exactly the feedback that ends up in next week's fix.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Ashby Jobs Scraper: Pull Every Posting From Any Ashby Job Board</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sun, 26 Jul 2026 17:41:27 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/ashby-jobs-scraper-pull-every-posting-from-any-ashby-job-board-3d1g</link>
      <guid>https://dev.to/devil_scrapes/ashby-jobs-scraper-pull-every-posting-from-any-ashby-job-board-3d1g</guid>
      <description>&lt;h2&gt;
  
  
  Ashby is where venture-backed hiring lives
&lt;/h2&gt;

&lt;p&gt;If you're tracking hiring at startups and scale-ups, Ashby shows up constantly. It's become one of the default ATS choices for venture-backed companies, and its careers pages (&lt;code&gt;jobs.ashbyhq.com/{orgSlug}&lt;/code&gt;) tend to publish more than most — real team names, workplace type (remote/hybrid/on-site), and often a compensation band, since a chunk of Ashby's customer base operates in markets with pay-transparency requirements.&lt;/p&gt;

&lt;p&gt;That combination — startup hiring signal plus actual comp data — is exactly why I built a dedicated Ashby jobs scraper under the Devil Scrapes banner, separate from the Ashby coverage baked into our broader &lt;code&gt;multi-ats-jobs-scraper&lt;/code&gt;. Ashby's board endpoint is different enough (its own GraphQL host, its own field set) to justify a purpose-built Actor rather than folding it into a generic multi-ATS tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data looks like
&lt;/h2&gt;

&lt;p&gt;A single row from a real run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"posting_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7af121a1-d29a-4745-84c1-ef1b58a3b840"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3P Silicon Architect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"org_slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"team_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"San Francisco"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workplace_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hybrid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employment_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FullTime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"secondary_locations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Seattle"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compensation_summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$342K – $555K • Offers Equity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apply_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://jobs.ashbyhq.com/openai/7af121a1-d29a-4745-84c1-ef1b58a3b840"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-26T15:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;compensation_summary&lt;/code&gt; is the field most scrapers skip because it requires trusting the org actually published one — we surface it exactly as given, &lt;code&gt;null&lt;/code&gt; when it wasn't, never a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive approach and why it stalls
&lt;/h2&gt;

&lt;p&gt;Ashby's board data lives behind an internal GraphQL endpoint (&lt;code&gt;jobs.ashbyhq.com/api/non-user-graphql?op=ApiJobBoardWithTeams&lt;/code&gt;) rather than a documented public API, which means the honest starting point for most people is opening devtools, watching the careers page load, and copying the request. That gets you a proof of concept in twenty minutes. It gets messier once you try to run it against more than one org reliably:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GraphQL isn't a stable public contract.&lt;/strong&gt; There's no versioned docs page — you're reverse-engineering the operation name and payload shape from network traffic, and you need to handle the response's nested &lt;code&gt;teams&lt;/code&gt; hierarchy correctly to get real department-style groupings instead of guessing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team names require a join, not a lookup.&lt;/strong&gt; &lt;code&gt;team_name&lt;/code&gt; isn't sitting flat on each posting — it's referenced by ID against a separate &lt;code&gt;teams&lt;/code&gt; array in the same response, and getting that join wrong silently produces blank team fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A batch across many orgs needs resilience.&lt;/strong&gt; One org having zero open roles, or a slug that doesn't exist at all, shouldn't take down the rest of a 20-org run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Actor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/DevilScrapes/ashby-jobs-scraper" rel="noopener noreferrer"&gt;Ashby Jobs Scraper&lt;/a&gt; fetches an org's entire board in one GraphQL call — no pagination — joins the team hierarchy into a clean &lt;code&gt;team_name&lt;/code&gt; field, and constructs a guaranteed-live &lt;code&gt;apply_url&lt;/code&gt; without a second HTTP request. We rotate browser TLS fingerprints and retry transient &lt;code&gt;429&lt;/code&gt;/&lt;code&gt;5xx&lt;/code&gt; responses with backoff so a multi-org batch finishes even when one org's board hiccups mid-run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/ashby-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orgSlugs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ramp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxResultsPerOrg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;locationFilter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;team_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;compensation_summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or drop the same JSON straight into the Apify Console input form for a one-off pull.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'd actually use this for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recruiting &amp;amp; sourcing against startups&lt;/strong&gt; — track what a target company is hiring for, on which team, at what comp band, all normalized.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hiring-intent signal for GTM/SDR teams&lt;/strong&gt; — a burst of open reqs at a funded startup is a leading indicator worth watching before a headcount announcement goes public.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job-board aggregation&lt;/strong&gt; — Ashby coverage slots next to our &lt;a href="https://apify.com/DevilScrapes/workday-jobs-scraper" rel="noopener noreferrer"&gt;Workday&lt;/a&gt;, &lt;a href="https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper" rel="noopener noreferrer"&gt;SmartRecruiters&lt;/a&gt;, and &lt;a href="https://apify.com/DevilScrapes/multi-ats-jobs-scraper" rel="noopener noreferrer"&gt;Multi-ATS&lt;/a&gt; scrapers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compensation benchmarking&lt;/strong&gt; — a structured feed of published comp bands across companies that use Ashby, useful for comp-research and market-rate studies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HR-tech pipelines&lt;/strong&gt; — wire rows straight into a CRM, dashboard, or n8n/Make workflow on a schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing, honestly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;$0.005 per run&lt;/strong&gt; flat, plus &lt;strong&gt;$0.0015 per posting&lt;/strong&gt; written. A 1,000-posting run costs &lt;strong&gt;$1.505&lt;/strong&gt; total. An org with zero open roles costs nothing beyond the warm-up fee. Apify's $5 free credit for new accounts covers roughly 3,300 postings before you spend anything yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is more interesting than it looks
&lt;/h2&gt;

&lt;p&gt;The genuinely tricky part isn't the fetch — Ashby returns a full board in one response, which is a gift compared to paginated APIs. It's building the &lt;code&gt;apply_url&lt;/code&gt; reliably without a second request. Ashby's schema doesn't hand you a ready-made canonical link on every posting; constructing one that's guaranteed to resolve, across arbitrary orgs you've never scraped before, without an extra round-trip per posting, is where the real work went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;This Actor covers Ashby only — Greenhouse, Lever, Workday, and SmartRecruiters live in sibling Actors. There's no first-class &lt;code&gt;department&lt;/code&gt; field in Ashby's schema; &lt;code&gt;team_name&lt;/code&gt; is the closest real grouping signal it exposes. There's no org-name enrichment either — &lt;code&gt;org_slug&lt;/code&gt; is exactly the input slug you gave us, since the endpoint itself doesn't expose a display name. And it reflects the board's current, live state only — no historical or removed postings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/DevilScrapes/ashby-jobs-scraper" rel="noopener noreferrer"&gt;Ashby Jobs Scraper&lt;/a&gt; is live on the Apify Store at $1.50 per 1,000 results, with Apify's standard $5 free trial credit and no card needed to start. It's part of a growing ATS-coverage fleet under &lt;a href="https://apify.com/DevilScrapes" rel="noopener noreferrer"&gt;Devil Scrapes&lt;/a&gt; — BambooHR, Recruitee, Personio, and Breezy HR are all covered too.&lt;/p&gt;

&lt;p&gt;What's the trickiest Ashby board you've tried to scrape? Drop it in the comments — genuinely curious what edge cases are out there.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Recruitee Jobs Scraper: Pull Every Posting From Any Recruitee Career Site</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sun, 26 Jul 2026 17:40:51 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/recruitee-jobs-scraper-pull-every-posting-from-any-recruitee-career-site-3l6a</link>
      <guid>https://dev.to/devil_scrapes/recruitee-jobs-scraper-pull-every-posting-from-any-recruitee-career-site-3l6a</guid>
      <description>&lt;h2&gt;
  
  
  Why Recruitee boards are worth scraping
&lt;/h2&gt;

&lt;p&gt;Recruitee is a popular ATS for mid-market companies across Europe, and every employer running their careers page on it — &lt;code&gt;{company}.recruitee.com&lt;/code&gt; — is quietly serving the entire job board from one public JSON endpoint. That's genuinely useful: unlike ATS platforms that paginate or gate descriptions behind a second call, Recruitee's list endpoint already inlines the full HTML description, salary fields, and location data in a single response.&lt;/p&gt;

&lt;p&gt;The catch, as with every ATS scraping job, is doing this reliably across more than one or two companies. I run a small fleet of ATS-specific Actors under Devil Scrapes, and Recruitee was a clean gap: solid data density, salary fields when published, and no dedicated coverage on the Apify Store worth the price it charged.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data looks like
&lt;/h2&gt;

&lt;p&gt;Here's one real row from the dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2680730&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"guid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dl9ua"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data-architect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Data Architect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auditdata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Auditdata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"R&amp;amp;D"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"information_technology"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employment_type_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fulltime_permanent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"experience_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"experienced"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"education_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bachelor_degree"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Poland"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hybrid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"on_site"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary_max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary_currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description_html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;h4&amp;gt;...&amp;lt;/h4&amp;gt;&amp;lt;p&amp;gt;... HTML string ...&amp;lt;/p&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"careers_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://auditdata.recruitee.com/o/data-architect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"careers_apply_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://auditdata.recruitee.com/o/data-architect/c/new"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"published_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-21T07:48:42+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-21T07:48:42+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-26T15:00:00+00:00"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Salary fields are &lt;code&gt;null&lt;/code&gt; here because this employer didn't publish a band — when they do, &lt;code&gt;salary_min&lt;/code&gt;/&lt;code&gt;salary_max&lt;/code&gt; come back as clean floats instead of Recruitee's raw numeric strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive approach and why it gets messy fast
&lt;/h2&gt;

&lt;p&gt;Recruitee's list endpoint is genuinely convenient — one request per company gets you everything, no second call for descriptions. That said, going from "I found the endpoint in devtools" to "I have a reliable pipeline" still runs into real friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recruitee's own schema is inconsistently populated.&lt;/strong&gt; &lt;code&gt;salary&lt;/code&gt; sometimes arrives as a nested object with &lt;code&gt;null&lt;/code&gt; fields, &lt;code&gt;department&lt;/code&gt; is sometimes missing entirely — you need to normalize these into stable nullable fields rather than crashing on a &lt;code&gt;KeyError&lt;/code&gt; the third time you hit a company that omitted a field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 404 on a bad company ID isn't a real failure.&lt;/strong&gt; If you type a company subdomain wrong or an employer's board is currently empty, that's zero postings — not a crash. Getting that distinction right across a large batch matters more than it sounds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fingerprinting.&lt;/strong&gt; A bare Python HTTP client doesn't replicate the TLS handshake a real browser sends, and once you're firing requests across many companies in a loop, that starts to matter.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Actor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/DevilScrapes/recruitee-jobs-scraper" rel="noopener noreferrer"&gt;Recruitee Jobs Scraper&lt;/a&gt; does the one-call-per-company fetch for you, handles Recruitee's inconsistent nested objects, and treats a per-company zero-result response as a normal outcome rather than a run-ending error. We rotate browser TLS fingerprints on every request and retry transient failures with backoff, so a batch of fifty companies doesn't die because one hiccuped.&lt;/p&gt;

&lt;p&gt;Python SDK example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/recruitee-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companyIds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auditdata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxResultsPerCompany&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeDescription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salary_min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or paste the same JSON straight into the Apify Console input form if you just want a quick pull without writing a script.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'd actually use this for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recruiting &amp;amp; talent intelligence&lt;/strong&gt; — track what a target Recruitee-hosted employer is hiring for, by department and location.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hiring-intent signal for BD/SDR teams&lt;/strong&gt; — open reqs, especially a sudden cluster in one department, are a decent proxy for growth before it's public news.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job-board aggregation&lt;/strong&gt; — Recruitee coverage slots next to our &lt;a href="https://apify.com/DevilScrapes/ashby-jobs-scraper" rel="noopener noreferrer"&gt;Ashby&lt;/a&gt;, &lt;a href="https://apify.com/DevilScrapes/workday-jobs-scraper" rel="noopener noreferrer"&gt;Workday&lt;/a&gt;, &lt;a href="https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper" rel="noopener noreferrer"&gt;SmartRecruiters&lt;/a&gt;, and &lt;a href="https://apify.com/DevilScrapes/multi-ats-jobs-scraper" rel="noopener noreferrer"&gt;Multi-ATS&lt;/a&gt; scrapers into one hiring-intel pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compensation research&lt;/strong&gt; — where employers publish salary bands, this is one of the few ATS platforms that gives you clean numeric fields for it instead of freeform text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ATS data pipelines&lt;/strong&gt; — wire structured rows straight into a CRM, dashboard, or n8n/Make workflow on a schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing, honestly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;$0.005 per run&lt;/strong&gt; as a flat warm-up fee, plus &lt;strong&gt;$0.0015 per posting&lt;/strong&gt; written to your dataset. A 1,000-posting pull runs &lt;strong&gt;$1.51&lt;/strong&gt; total. Nothing is charged for a company that returns zero postings beyond the warm-up. Apify's $5 free credit for new accounts covers roughly 3,300 postings before you'd spend anything out of pocket.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting part
&lt;/h2&gt;

&lt;p&gt;The fiddly engineering here isn't the HTTP call, it's the schema cleanup. Recruitee's API returns &lt;code&gt;null&lt;/code&gt;-riddled nested objects inconsistently — a &lt;code&gt;salary&lt;/code&gt; object with all-null children, a &lt;code&gt;department&lt;/code&gt; that's sometimes a string and sometimes absent entirely. Turning that into stable, always-present nullable fields (rather than crashing on the third company that structures things slightly differently) is most of what makes this reliable across an arbitrary batch of employers you've never scraped before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;This only covers publicly published boards, not authenticated internal views. You supply company IDs (the subdomain from the careers URL), not display names — "Auditdata" the brand and &lt;code&gt;auditdata&lt;/code&gt; the subdomain aren't guaranteed to match. And every sampled company we've tested returns its full board in one response with no pagination — an unusually large board comes back as-is, whatever that single response contains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/DevilScrapes/recruitee-jobs-scraper" rel="noopener noreferrer"&gt;Recruitee Jobs Scraper&lt;/a&gt; is live on the Apify Store at $1.50 per 1,000 results, with Apify's standard $5 free trial credit and no card required to start. It's one Actor in a growing ATS-coverage fleet under &lt;a href="https://apify.com/DevilScrapes" rel="noopener noreferrer"&gt;Devil Scrapes&lt;/a&gt; — BambooHR, Ashby, Personio, and Breezy HR are covered too.&lt;/p&gt;

&lt;p&gt;Got a Recruitee board that behaves differently from what's documented here? Drop it in the comments — that's exactly the kind of edge case we fix fast.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>BambooHR Jobs Scraper: Pull Every Open Role From Any BambooHR Careers Site</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sun, 26 Jul 2026 17:40:16 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/bamboohr-jobs-scraper-pull-every-open-role-from-any-bamboohr-careers-site-1oja</link>
      <guid>https://dev.to/devil_scrapes/bamboohr-jobs-scraper-pull-every-open-role-from-any-bamboohr-careers-site-1oja</guid>
      <description>&lt;h2&gt;
  
  
  The hiring-signal hiding in plain sight
&lt;/h2&gt;

&lt;p&gt;BambooHR runs the careers page for a huge slice of the SMB and mid-market world — everything from regional healthcare networks to ballet companies. If you've ever needed to answer "who is this company hiring right now, and for what," BambooHR's public &lt;code&gt;{company}.bamboohr.com/careers&lt;/code&gt; page is usually where the answer lives. The catch is that there's no dashboard for pulling that data across dozens of companies at once, no bulk export, and no official API key you can just request.&lt;/p&gt;

&lt;p&gt;I build small, focused scrapers for a living (under the Devil Scrapes banner), and BambooHR kept coming up as a gap: recruiters, SDRs, and labor-market researchers all wanted "every open req at this employer" without hand-copying a careers page. So I built a BambooHR jobs scraper that talks to BambooHR's own careers JSON endpoints directly and normalizes the output into one schema across every tenant.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data looks like
&lt;/h2&gt;

&lt;p&gt;Here's a single row from a real run — this is what lands in your dataset per posting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adobe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IT Security Engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://adobe.bamboohr.com/careers/15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employment_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Full-Time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mayfair"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"London, City of"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"United Kingdom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_remote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date_posted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-29"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description_html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;About Us&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"About Us Our mission is simple..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-26T12:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note &lt;code&gt;is_remote&lt;/code&gt; is a tri-state boolean, not a forced &lt;code&gt;true&lt;/code&gt;/&lt;code&gt;false&lt;/code&gt; — when the employer never said, we leave it &lt;code&gt;null&lt;/code&gt; instead of guessing "on-site." That distinction matters if you're building any kind of remote-hiring filter downstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive approach, and where it falls apart
&lt;/h2&gt;

&lt;p&gt;If you open devtools on any BambooHR careers page, you'll find the &lt;code&gt;/careers/list&lt;/code&gt; endpoint returning JSON almost immediately — it looks trivial at first glance. The friction shows up once you try to do this at any real scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Company-by-company inconsistency.&lt;/strong&gt; Some subdomains have zero open jobs, some 404 because the company deactivated its careers page, some reject a custom-domain front-end. Your script needs to treat all three as normal outcomes, not exceptions that kill the whole batch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full descriptions are a second endpoint.&lt;/strong&gt; The list feed gives you titles and locations; the actual HTML job description body lives behind a separate &lt;code&gt;/careers/{id}/detail&lt;/code&gt; call per posting, which means orchestrating N+1 requests cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client fingerprinting.&lt;/strong&gt; A bare &lt;code&gt;requests.get()&lt;/code&gt; from a script looks nothing like a browser at the TLS/HTTP2 layer, and once you're doing this against dozens of company subdomains in a loop, that difference starts mattering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is exotic, but it's exactly the kind of "boring at small scale, annoying at real scale" work that eats an afternoon you didn't plan to spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actor
&lt;/h2&gt;

&lt;p&gt;This is where &lt;a href="https://apify.com/DevilScrapes/bamboohr-jobs-scraper" rel="noopener noreferrer"&gt;BambooHR Jobs Scraper&lt;/a&gt; comes in. We rotate Chrome/Firefox TLS fingerprints via &lt;code&gt;curl-cffi&lt;/code&gt; on every request, retry &lt;code&gt;408/429/5xx&lt;/code&gt; with exponential backoff up to 5 attempts, and isolate failures per company so one bad subdomain never sinks the rest of your batch. You get back clean, Pydantic-validated rows — no half-parsed HTML, no silently-empty datasets.&lt;/p&gt;

&lt;p&gt;Run it via the Apify Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/bamboohr-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adobe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nycballet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxJobsPerCompany&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fetchFullDescription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location_city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or fire it directly from the Apify Console with the same JSON as input — no code required if you just want a one-off pull.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'd actually use this for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recruiting pipelines&lt;/strong&gt; — build sourcing lists against every BambooHR-hosted employer in a target industry, normalized into one schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hiring-intent signal for sales/BD&lt;/strong&gt; — a sudden burst of open reqs at a target account is a decent proxy for headcount growth or a new-market push, before it shows up anywhere else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job-board aggregation&lt;/strong&gt; — BambooHR coverage slots next to our &lt;a href="https://apify.com/DevilScrapes/teamtailor-jobs-scraper" rel="noopener noreferrer"&gt;Teamtailor&lt;/a&gt;, &lt;a href="https://apify.com/DevilScrapes/workday-jobs-scraper" rel="noopener noreferrer"&gt;Workday&lt;/a&gt;, &lt;a href="https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper" rel="noopener noreferrer"&gt;SmartRecruiters&lt;/a&gt;, &lt;a href="https://apify.com/DevilScrapes/workable-jobs-scraper" rel="noopener noreferrer"&gt;Workable&lt;/a&gt;, and &lt;a href="https://apify.com/DevilScrapes/multi-ats-jobs-scraper" rel="noopener noreferrer"&gt;Multi-ATS&lt;/a&gt; scrapers to build one hiring-intel feed across the whole ATS landscape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labor-market research&lt;/strong&gt; — sample hiring demand by industry, region, or company size band with a structured, machine-readable dataset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive intelligence&lt;/strong&gt; — watch a specific competitor's open reqs over time to infer where they're actually growing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing, honestly
&lt;/h2&gt;

&lt;p&gt;The Actor charges &lt;strong&gt;$0.005 per run&lt;/strong&gt; (a flat warm-up fee) plus &lt;strong&gt;$0.0015 per job posting&lt;/strong&gt; written to your dataset. A 1,000-posting pull costs &lt;strong&gt;$1.51&lt;/strong&gt; total. A company with zero current openings costs nothing beyond the warm-up fee — you're not billed for rows that never existed. Apify gives every new account $5 of free credit, no card required, which covers roughly 3,300 postings before you'd spend a cent of your own money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is more interesting than it looks
&lt;/h2&gt;

&lt;p&gt;The genuinely fiddly part wasn't the HTTP calls — it was normalizing BambooHR's own inconsistencies across tenants. Some companies fill in &lt;code&gt;department&lt;/code&gt;, some leave it null; some populate &lt;code&gt;location_state&lt;/code&gt; with a full region name, others with an abbreviation; &lt;code&gt;is_remote&lt;/code&gt; is sometimes explicit and usually isn't. Building one stable schema that holds up across hundreds of independently-configured BambooHR instances, without silently inventing values the source data doesn't actually contain, is most of the real engineering here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;This Actor only resolves &lt;code&gt;{subdomain}.bamboohr.com&lt;/code&gt; addresses — if a company fronts its BambooHR careers page with a custom domain, you'll need the underlying BambooHR subdomain instead. There's no compensation field either, because BambooHR's public feed doesn't expose structured salary data on any company we've checked — we don't invent one. And it's a point-in-time snapshot: schedule recurring runs if you want to track how a board changes over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/DevilScrapes/bamboohr-jobs-scraper" rel="noopener noreferrer"&gt;BambooHR Jobs Scraper&lt;/a&gt; is live on the Apify Store now, priced at $1.50 per 1,000 results with Apify's standard $5 free trial credit (no credit card needed to start). It's one of several ATS-coverage Actors we run under &lt;a href="https://apify.com/DevilScrapes" rel="noopener noreferrer"&gt;Devil Scrapes&lt;/a&gt; — if you need Recruitee, Ashby, Personio, or Breezy HR coverage too, those are live as well.&lt;/p&gt;

&lt;p&gt;What ATS is your target company running that isn't covered yet? Drop it in the comments — that's usually how the next Actor in this fleet gets picked.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Multi-ATS Jobs Scraper: One Normalized Feed for Greenhouse, Lever &amp; Ashby</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sat, 25 Jul 2026 09:36:16 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/multi-ats-jobs-scraper-one-normalized-feed-for-greenhouse-lever-ashby-23d1</link>
      <guid>https://dev.to/devil_scrapes/multi-ats-jobs-scraper-one-normalized-feed-for-greenhouse-lever-ashby-23d1</guid>
      <description>&lt;h1&gt;
  
  
  Multi-ATS Jobs Scraper: One Normalized Feed for Greenhouse, Lever &amp;amp; Ashby
&lt;/h1&gt;

&lt;p&gt;Greenhouse, Lever, and Ashby together power the hiring pipelines of a huge share of tech companies — but they're three different APIs with three different field names, and you don't always know upfront which one a given company runs on. If you're building a hiring-intel dataset, a job aggregator, or a "who's hiring" signal for sales, that ambiguity is the actual bottleneck, not the scraping itself. This post covers how the &lt;strong&gt;Multi-ATS Jobs Scraper&lt;/strong&gt; collapses all three into one schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why cross-ATS job data is worth pulling
&lt;/h2&gt;

&lt;p&gt;Each platform publishes structured, public postings data — no login needed to see them, since they're built for candidates to browse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Greenhouse&lt;/strong&gt;: &lt;code&gt;boards-api.greenhouse.io/v1/boards/{slug}/jobs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lever&lt;/strong&gt;: &lt;code&gt;api.lever.co/v0/postings/{slug}?mode=json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ashby&lt;/strong&gt;: &lt;code&gt;api.ashbyhq.com/posting-api/job-board/{slug}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HR-tech builders&lt;/strong&gt; assembling cross-company hiring datasets without hand-picking which API to call per employer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDR / sales-intel teams&lt;/strong&gt; using "who is hiring for X" as a buying-intent signal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job-aggregator and ATS-analytics platforms&lt;/strong&gt; that need one schema instead of three&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The naive approach, and where it falls apart
&lt;/h2&gt;

&lt;p&gt;Say you want Stripe, Palantir, and Ramp's open roles. Stripe runs Greenhouse, Palantir runs Lever, Ramp runs Ashby — and nothing about a bare company name tells you that upfront. You'd need to either maintain a lookup table of company-to-ATS mappings (which goes stale) or probe each API in turn and see what returns a 200. Then, even once you know which platform, the field names don't line up: Greenhouse has no &lt;code&gt;team&lt;/code&gt; field distinct from department; Lever and Ashby do. Only Ashby exposes structured salary. Only Greenhouse reliably reports &lt;code&gt;updated_at&lt;/code&gt;. Writing three separate parsers and reconciling them into one row shape is the actual work — not any single API call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built instead
&lt;/h2&gt;

&lt;p&gt;The Multi-ATS Jobs Scraper takes a bare company slug (&lt;code&gt;"stripe"&lt;/code&gt;), a full careers URL (&lt;code&gt;"https://jobs.lever.co/palantir"&lt;/code&gt;), or an explicit &lt;code&gt;{atsType, companySlug}&lt;/code&gt; object if you already know the platform — and auto-detects which of the three it's dealing with when you don't tell it. Every row comes back in one consistent schema regardless of source platform, with &lt;code&gt;curl-cffi&lt;/code&gt; fingerprint impersonation, retry-with-backoff on &lt;code&gt;429&lt;/code&gt;/&lt;code&gt;5xx&lt;/code&gt;, and Apify Proxy session rotation absorbing the parts that make a large multi-company sync fragile.&lt;/p&gt;

&lt;p&gt;Run it via the Apify Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/multi-ats-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;atsType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;greenhouse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companySlug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stripe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jobs.lever.co/palantir&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jobs.ashbyhq.com/ramp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxItemsPerCompany&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeDescription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ats&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salary_min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or paste the same array into the &lt;a href="https://apify.com/DevilScrapes/multi-ats-jobs-scraper" rel="noopener noreferrer"&gt;Store page&lt;/a&gt; input form directly.&lt;/p&gt;

&lt;p&gt;Sample output row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ramp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ashby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3b1c2e9a-0000-0000-0000-000000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior Software Engineer, Payments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Engineering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"team"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Payments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New York City"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;211400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary_max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;290600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary_currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://jobs.ashbyhq.com/ramp/3b1c2e9a-0000-0000-0000-000000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"posted_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-02T18:11:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description_html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;p&amp;gt;About the role...&amp;lt;/p&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The technically interesting bit
&lt;/h2&gt;

&lt;p&gt;Auto-detection sounds like a simple try-each-API-in-turn loop, and mostly it is — but the edge case is a slug that happens to resolve on more than one platform. It's rare (we haven't hit it in practice), but when it happens we keep the platform with more open postings and log a warning rather than silently picking one arbitrarily. If you need a guarantee rather than a best-effort tie-break, the explicit &lt;code&gt;{atsType, companySlug}&lt;/code&gt; form skips detection entirely and runs slightly faster. We'd rather document that edge case than pretend detection is infallible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing — no fine print
&lt;/h2&gt;

&lt;p&gt;Pay-Per-Event: &lt;strong&gt;$0.005&lt;/strong&gt; per run plus &lt;strong&gt;$0.0015&lt;/strong&gt; per job posting written to the dataset — &lt;strong&gt;$1.50 / 1,000 results&lt;/strong&gt;. A run collecting 500 postings across three companies costs roughly $0.75 in result charges plus the $0.005 start fee. Empty runs cost only the start fee. New Apify accounts get &lt;strong&gt;$5 of free credit&lt;/strong&gt;, no card required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;Salary is Ashby-only — Greenhouse and Lever don't publish a structured comp field, and we won't regex-guess one out of free-text descriptions, so &lt;code&gt;salary_min&lt;/code&gt;/&lt;code&gt;salary_max&lt;/code&gt;/&lt;code&gt;salary_currency&lt;/code&gt; are honestly &lt;code&gt;null&lt;/code&gt; for those platforms. &lt;code&gt;team&lt;/code&gt; is Lever/Ashby only since Greenhouse doesn't split team from department. And this only reaches public, unauthenticated boards — internal/private listings need employer credentials this Actor doesn't support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, and the rest of the fleet
&lt;/h2&gt;

&lt;p&gt;Greenhouse, Lever, and Ashby are three platforms in a bigger landscape. We also run the &lt;a href="https://apify.com/DevilScrapes/workday-jobs-scraper" rel="noopener noreferrer"&gt;Workday Jobs Scraper&lt;/a&gt;, the &lt;a href="https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper" rel="noopener noreferrer"&gt;SmartRecruiters Jobs Scraper&lt;/a&gt;, and the &lt;a href="https://apify.com/DevilScrapes/workable-jobs-scraper" rel="noopener noreferrer"&gt;Workable Jobs Scraper&lt;/a&gt; — same keyless, pay-per-result approach, covering the rest of the major ATS market.&lt;/p&gt;

&lt;p&gt;Grab the &lt;a href="https://apify.com/DevilScrapes/multi-ats-jobs-scraper" rel="noopener noreferrer"&gt;Multi-ATS Jobs Scraper&lt;/a&gt; on the Apify Store and try it with the free $5 credit. Found a company slug that returns wrong results, or hit an ATS quirk we don't handle? Open a support ticket or leave a review — we read every one. Browse the rest of the fleet at &lt;a href="https://apify.com/DevilScrapes" rel="noopener noreferrer"&gt;apify.com/DevilScrapes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Which three companies would you point this at first?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Workable Jobs Scraper: Pull Every Posting From Any Workable Careers Board</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sat, 25 Jul 2026 09:35:41 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/workable-jobs-scraper-pull-every-posting-from-any-workable-careers-board-31lg</link>
      <guid>https://dev.to/devil_scrapes/workable-jobs-scraper-pull-every-posting-from-any-workable-careers-board-31lg</guid>
      <description>&lt;h1&gt;
  
  
  Workable Jobs Scraper: Pull Every Posting From Any Workable Careers Board
&lt;/h1&gt;

&lt;p&gt;Workable is one of the most common ATS choices for startups and mid-size companies — if you've ever seen a careers page at &lt;code&gt;apply.workable.com/{subdomain}&lt;/code&gt; or &lt;code&gt;{subdomain}.workable.com&lt;/code&gt;, that's it. Thousands of employers run on it, and every one of those boards is served by the same public widget API. This post covers what that data looks like, why hand-rolling a scraper against it gets annoying past one company, and how the &lt;strong&gt;Workable Jobs Scraper&lt;/strong&gt; handles it for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Workable job data is worth pulling
&lt;/h2&gt;

&lt;p&gt;Workable exposes open postings — title, department, location, remote flag, and description — through a public widget endpoint that any careers page already calls client-side. No login, no API key. That makes it a solid signal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recruiters and sourcers&lt;/strong&gt; pulling every open req from a target employer's board in one pass&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job-board aggregator operators&lt;/strong&gt; who want Workable coverage alongside Workday, SmartRecruiters, Greenhouse, Lever, and Ashby&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDR / BD teams&lt;/strong&gt; using active hiring as a buying-intent signal — a company scaling its engineering team is a company that might need your product&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HR-tech pipeline builders&lt;/strong&gt; wiring structured rows into a CRM, dashboard, or n8n/Make workflow on a schedule&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The naive approach, and where it gets annoying
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;apply.workable.com/api/v1/widget/accounts/{subdomain}&lt;/code&gt; returns a full board in one call, which sounds like the easy path — and for one company, it basically is. The friction shows up in the details: Workable accepts both &lt;code&gt;{subdomain}.workable.com&lt;/code&gt; and &lt;code&gt;apply.workable.com/{subdomain}&lt;/code&gt; as valid public URL forms and you have to normalize both to the same subdomain before you can hit the widget endpoint. The server-side facet filters that look like they should narrow results (&lt;code&gt;department&lt;/code&gt;, &lt;code&gt;location&lt;/code&gt;) don't reliably work against this endpoint, so filtering has to happen after the fetch, not before. And subdomains that don't exist need to fail gracefully rather than take down a whole multi-company batch. Multiply that across dozens of subdomains and it's a script you're now maintaining, not writing once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built instead
&lt;/h2&gt;

&lt;p&gt;The Workable Jobs Scraper hits the same public widget API and absorbs the annoying parts: &lt;code&gt;curl-cffi&lt;/code&gt; browser-fingerprint impersonation on every request, retries with backoff on transient &lt;code&gt;429&lt;/code&gt;/&lt;code&gt;5xx&lt;/code&gt;, and normalization across whichever mix of subdomains you feed it. It also documents the client-side-filter constraint honestly instead of pretending server-side filters work — &lt;code&gt;searchQuery&lt;/code&gt;, &lt;code&gt;department&lt;/code&gt;, and &lt;code&gt;location&lt;/code&gt; all run after the single fetch per company, at no extra request cost, so you're never billed twice for the same board.&lt;/p&gt;

&lt;p&gt;Run it via the Apify Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/workable-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;remotebase&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxResultsPerCompany&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeDescription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location_country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_remote&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or paste the same JSON into the &lt;a href="https://apify.com/DevilScrapes/workable-jobs-scraper" rel="noopener noreferrer"&gt;Store page&lt;/a&gt; input form directly.&lt;/p&gt;

&lt;p&gt;Sample output row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6E7795E82F"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI Engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"remotebase"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Remotebase"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pakistan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_remote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://apply.workable.com/j/6E7795E82F"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"posted_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description_html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-23T15:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The technically interesting bit
&lt;/h2&gt;

&lt;p&gt;Workable accepts a bare subdomain, &lt;code&gt;apply.workable.com/{subdomain}&lt;/code&gt;, or &lt;code&gt;{subdomain}.workable.com&lt;/code&gt; as equally valid public entry points, but they don't all resolve identically against the widget API — you have to extract the actual account subdomain regardless of which form someone pastes in. And the facet filters that look like a free server-side narrowing option (&lt;code&gt;department=&lt;/code&gt;, &lt;code&gt;location=&lt;/code&gt;) are a trap: they don't reliably filter this specific endpoint's results, so a scraper that trusts them silently returns incomplete data. We treat that as a documented constraint, not a bug to route around with retries — filtering happens client-side, after the one fetch, so you always get the full board first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing — no fine print
&lt;/h2&gt;

&lt;p&gt;Pay-Per-Event: &lt;strong&gt;$0.005&lt;/strong&gt; per run plus &lt;strong&gt;$0.0015&lt;/strong&gt; per job posting written to the dataset — &lt;strong&gt;$1.50 / 1,000 results&lt;/strong&gt;. No data lands, no charge beyond the warm-up fee. New Apify accounts get &lt;strong&gt;$5 of free credit&lt;/strong&gt;, no card required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;A posting listed at multiple locations emits only its first location as one row. Only currently-live postings are returned — there's no historical archive of removed postings. And as noted above, filtering runs client-side after the fetch, since Workable's own facet params don't reliably narrow this endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, and the rest of the fleet
&lt;/h2&gt;

&lt;p&gt;Workable is one piece of the ATS puzzle. We also run the &lt;a href="https://apify.com/DevilScrapes/workday-jobs-scraper" rel="noopener noreferrer"&gt;Workday Jobs Scraper&lt;/a&gt;, the &lt;a href="https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper" rel="noopener noreferrer"&gt;SmartRecruiters Jobs Scraper&lt;/a&gt;, and the &lt;a href="https://apify.com/DevilScrapes/multi-ats-jobs-scraper" rel="noopener noreferrer"&gt;Multi-ATS Jobs Scraper&lt;/a&gt; covering Greenhouse, Lever, and Ashby in one normalized schema — same keyless, pay-per-result model throughout.&lt;/p&gt;

&lt;p&gt;Grab the &lt;a href="https://apify.com/DevilScrapes/workable-jobs-scraper" rel="noopener noreferrer"&gt;Workable Jobs Scraper&lt;/a&gt; on the Apify Store and try it with the free $5 credit. Hit a board that behaves differently, or need a field we don't expose? Reach out via &lt;a href="https://apify.com/DevilScrapes" rel="noopener noreferrer"&gt;DevilScrapes on Apify&lt;/a&gt; — we ship fixes fast. Browse the rest of the fleet there too.&lt;/p&gt;

&lt;p&gt;Which Workable board would you pull first — and what would you do with the "actively hiring" signal?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SmartRecruiters Jobs Scraper: Pull Any Employer's Postings From One Keyless API</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sat, 25 Jul 2026 09:34:30 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/smartrecruiters-jobs-scraper-pull-any-employers-postings-from-one-keyless-api-4hfd</link>
      <guid>https://dev.to/devil_scrapes/smartrecruiters-jobs-scraper-pull-any-employers-postings-from-one-keyless-api-4hfd</guid>
      <description>&lt;h1&gt;
  
  
  SmartRecruiters Jobs Scraper: Pull Any Employer's Postings From One Keyless API
&lt;/h1&gt;

&lt;p&gt;Thousands of mid-size and enterprise employers run their careers page on SmartRecruiters — Visa, Bosch, and a long tail of companies you'd recognize. If you're building recruiting pipelines, a job-board aggregator, or a competitive hiring-intel feed, SmartRecruiters coverage is table stakes. This post walks through what the data looks like, why the naive scraping approach breaks down at scale, and how the &lt;strong&gt;SmartRecruiters Jobs Scraper&lt;/strong&gt; gets you clean rows instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SmartRecruiters data is worth pulling
&lt;/h2&gt;

&lt;p&gt;Every SmartRecruiters careers board (&lt;code&gt;jobs.smartrecruiters.com/{companyId}&lt;/code&gt;) exposes structured posting data — title, location, department, function, employment type, and a canonical apply URL — through the same public JSON API the page itself calls. That's useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recruiters and sourcers&lt;/strong&gt; tracking open reqs at target employers by function and location&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job-board aggregator operators&lt;/strong&gt; who need SmartRecruiters alongside Workday, Greenhouse, Lever, and Ashby&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive-intel analysts&lt;/strong&gt; inferring a rival's growth areas from where they're actively hiring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ATS/data-pipeline builders&lt;/strong&gt; wiring structured job rows into a CRM or workflow tool&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The naive approach, and where it falls apart
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;api.smartrecruiters.com/v1/companies/{companyId}/postings&lt;/code&gt; is a real, documented-ish public endpoint, and a single &lt;code&gt;curl&lt;/code&gt; against it will return JSON. The friction shows up once you're doing this for more than one company: you need to paginate correctly, apply the server-side filters (&lt;code&gt;q&lt;/code&gt;, &lt;code&gt;department&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;, &lt;code&gt;country&lt;/code&gt;) without silently dropping rows, handle the detail endpoint for full descriptions without doubling your request count unnecessarily, and survive transient &lt;code&gt;429&lt;/code&gt;s across a batch of dozens of employers without the whole run dying on employer #12. None of that is hard in isolation — it's the compounding maintenance burden that gets expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built instead
&lt;/h2&gt;

&lt;p&gt;The SmartRecruiters Jobs Scraper hits that same public postings API directly and absorbs the parts that make multi-employer batches fragile: &lt;code&gt;curl-cffi&lt;/code&gt; browser-fingerprint impersonation so requests present a real handshake, retries with exponential backoff on &lt;code&gt;429&lt;/code&gt;/&lt;code&gt;5xx&lt;/code&gt;, and normalized output regardless of how many company IDs you pass in one run. Server-side filters (&lt;code&gt;searchQuery&lt;/code&gt;, &lt;code&gt;department&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;, &lt;code&gt;country&lt;/code&gt;) narrow the result set &lt;em&gt;before&lt;/em&gt; you're billed for a row.&lt;/p&gt;

&lt;p&gt;Run it via the Apify Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/smartrecruiters-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companyIds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Visa&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxResultsPerCompany&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeDescription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location_city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;department&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or drop the same JSON straight into the input form on the &lt;a href="https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper" rel="noopener noreferrer"&gt;Store page&lt;/a&gt; — no code needed.&lt;/p&gt;

&lt;p&gt;Sample output row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"posting_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"743999...abc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sr. Manager, Data Platform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Visa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Visa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Austin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Texas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_remote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Technology"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employment_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Full-time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apply_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://jobs.smartrecruiters.com/Visa/743999...abc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description_html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Visa is looking for a Sr. Manager..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-21T14:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The technically interesting bit
&lt;/h2&gt;

&lt;p&gt;SmartRecruiters' &lt;code&gt;companyId&lt;/code&gt; isn't always the display name you'd guess — it's a slug pulled straight from the careers URL, and it's case-sensitive with its own quirks per tenant. Most "generic" scrapers treat this as a solved problem and just string-match against a display name, which silently returns zero results for a lot of real companies. We take the company ID as-is from the URL structure the site itself uses, so &lt;code&gt;jobs.smartrecruiters.com/Visa&lt;/code&gt; maps directly to &lt;code&gt;companyIds: ["Visa"]&lt;/code&gt; without any guessing layer that can quietly fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing — no fine print
&lt;/h2&gt;

&lt;p&gt;Pay-Per-Event: &lt;strong&gt;$0.005&lt;/strong&gt; per run plus &lt;strong&gt;$0.0015&lt;/strong&gt; per job posting written to the dataset. A 1,000-posting pull costs &lt;strong&gt;$1.51&lt;/strong&gt;. No matching postings, no charge beyond the small warm-up fee. New Apify accounts get &lt;strong&gt;$5 of free credit&lt;/strong&gt;, no card required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;We only reach the public, unauthenticated postings boards — not internal employee-only views. &lt;code&gt;includeDescription&lt;/code&gt; adds one detail request per posting, so leave it off if you just need metadata fast. And you supply the SmartRecruiters &lt;code&gt;companyId&lt;/code&gt;, not a freeform company name — a display name like "Bosch" can differ from its actual slug, so pull the ID straight from the careers URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, and the rest of the fleet
&lt;/h2&gt;

&lt;p&gt;SmartRecruiters is one platform in a bigger landscape. We also run the &lt;a href="https://apify.com/DevilScrapes/workday-jobs-scraper" rel="noopener noreferrer"&gt;Workday Jobs Scraper&lt;/a&gt;, the &lt;a href="https://apify.com/DevilScrapes/workable-jobs-scraper" rel="noopener noreferrer"&gt;Workable Jobs Scraper&lt;/a&gt;, and the &lt;a href="https://apify.com/DevilScrapes/multi-ats-jobs-scraper" rel="noopener noreferrer"&gt;Multi-ATS Jobs Scraper&lt;/a&gt; for Greenhouse, Lever, and Ashby in one normalized schema — same keyless, pay-per-result approach across the board.&lt;/p&gt;

&lt;p&gt;Grab the &lt;a href="https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper" rel="noopener noreferrer"&gt;SmartRecruiters Jobs Scraper&lt;/a&gt; on the Apify Store and try it with the free $5 credit. Found a company that behaves differently, or need a field we don't expose? Open an issue on the Actor's Issues tab — we ship fixes weekly. Browse the rest of the fleet at &lt;a href="https://apify.com/DevilScrapes" rel="noopener noreferrer"&gt;apify.com/DevilScrapes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Which employer's SmartRecruiters board would you pull first?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Workday Jobs Scraper: How to Pull Every Posting From Any Workday Career Site</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sat, 25 Jul 2026 09:34:25 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/workday-jobs-scraper-how-to-pull-every-posting-from-any-workday-career-site-5gm9</link>
      <guid>https://dev.to/devil_scrapes/workday-jobs-scraper-how-to-pull-every-posting-from-any-workday-career-site-5gm9</guid>
      <description>&lt;h1&gt;
  
  
  Workday Jobs Scraper: How to Pull Every Posting From Any Workday Career Site
&lt;/h1&gt;

&lt;p&gt;If you've ever tried to build a recruiting pipeline, a labor-market dataset, or a "who's hiring" competitive-intel feed, you've hit Workday. It powers the career sites of a huge share of the Fortune 500 — NVIDIA, Salesforce, Adobe, half of enterprise healthcare and finance. Every one of those &lt;code&gt;*.myworkdayjobs.com&lt;/code&gt; boards looks different on the surface, but underneath they all run on the same internal JSON API. That's the detail this post is about, and it's also the reason we built the &lt;strong&gt;Workday Jobs Scraper&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Workday job data is worth pulling
&lt;/h2&gt;

&lt;p&gt;Workday postings are structured, current, and — unlike a lot of enterprise HR data — genuinely public. No login wall, no gated candidate portal. Every req has a title, location, requisition ID, and (usually) a full description sitting behind the site's own search box. That's a clean signal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recruiters and sourcers&lt;/strong&gt; mapping which enterprise employers have open reqs in a given function&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job-board aggregators&lt;/strong&gt; who need Workday coverage next to Greenhouse, Lever, and Ashby&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive-intel teams&lt;/strong&gt; watching a rival's hiring velocity as a proxy for headcount growth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labor-market researchers&lt;/strong&gt; sampling demand by role, location, or industry at scale&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The naive approach, and why it gets annoying fast
&lt;/h2&gt;

&lt;p&gt;Open devtools on any Workday career site, filter to XHR, and you'll spot a call to &lt;code&gt;/wday/cxs/{tenant}/{site}/jobs&lt;/code&gt;. Looks trivial — just a POST with a search payload. The catch is everything downstream of that first successful call. Workday sits behind Cloudflare, so a bare &lt;code&gt;requests.post()&lt;/code&gt; from a script gets a fingerprint mismatch and a block long before you've paginated through a real board. You also need to work out the tenant/data-center/site triple from the URL structure (&lt;code&gt;{tenant}.{dc}.myworkdayjobs.com/{site}&lt;/code&gt;), handle pagination correctly, and — if you want full descriptions — fire a second request per posting without tripping rate limits. None of that is exotic, but multiply it by dozens of tenants and it's a maintenance job, not a script.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built instead
&lt;/h2&gt;

&lt;p&gt;The Workday Jobs Scraper talks to that same &lt;code&gt;cxs&lt;/code&gt; JSON endpoint directly, but does the parts that make it fragile for you: we present a real Chrome TLS/H2 handshake via &lt;code&gt;curl-cffi&lt;/code&gt; browser impersonation, retry &lt;code&gt;408/429/5xx&lt;/code&gt; responses with exponential backoff (&lt;code&gt;Retry-After&lt;/code&gt; honoured), and rotate proxy sessions through Apify Proxy when a run is pulling many tenants back to back. You give it career-site URLs — or explicit &lt;code&gt;{tenant, dc, site}&lt;/code&gt; objects if you already know them — and get back one normalized row per posting, regardless of which of the hundreds of Workday tenants you're pointed at.&lt;/p&gt;

&lt;p&gt;Run it via the Apify Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/workday-jobs-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;careerSites&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxResultsPerSite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeDescription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_req_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or paste the same input directly into the &lt;a href="https://apify.com/DevilScrapes/workday-jobs-scraper" rel="noopener noreferrer"&gt;Store page&lt;/a&gt; and hit Start — no code required.&lt;/p&gt;

&lt;p&gt;One output row looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/job/US-CA-Santa-Clara/Senior-Factory-Support-Firmware-Engineer_JR1998421"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior Factory Support Firmware Engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US, CA, Santa Clara"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"posted_on_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Posted 5 Days Ago"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Full time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_req_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JR1998421"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite/job/US-CA-Santa-Clara/Senior-Factory-Support-Firmware-Engineer_JR1998421"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nvidia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wd5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"site"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NVIDIAExternalCareerSite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description_html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NVIDIA is seeking a Senior Factory Support Firmware Engineer..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-21T11:40:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The technically interesting bit
&lt;/h2&gt;

&lt;p&gt;Workday's public URL (&lt;code&gt;{tenant}.{dc}.myworkdayjobs.com/{site}/job/...&lt;/code&gt;) and its internal detail payload aren't the same thing — the detail data actually lives behind the &lt;code&gt;cxs&lt;/code&gt; path, keyed by an &lt;code&gt;externalPath&lt;/code&gt; that only shows up once you've already paginated the listing endpoint. A lot of "generic" Workday scrapers on the market skip this and just re-request the public HTML page for descriptions, which is slower and more fragile. We fetch the detail payload the way the site's own JS does, which is also why &lt;code&gt;includeDescription&lt;/code&gt; costs one extra request per posting rather than a full page render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing — no fine print
&lt;/h2&gt;

&lt;p&gt;Pay-Per-Event: &lt;strong&gt;$0.005&lt;/strong&gt; per run (one-off warm-up) plus &lt;strong&gt;$0.0015&lt;/strong&gt; per job posting written to the dataset. A 1,000-posting pull costs &lt;strong&gt;$1.51&lt;/strong&gt;. No data lands, no charge beyond the warm-up fee. Apify gives every new account &lt;strong&gt;$5 of free credit&lt;/strong&gt;, no card required, which covers a couple thousand rows before you spend a cent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;We only reach public career sites — not SSO-gated internal portals — and Workday doesn't expose a structured salary field, so we don't invent one. &lt;code&gt;maxResultsPerSite&lt;/code&gt; caps volume but doesn't change price, because you're billed per row written, not per HTTP call. And like any scrape, this is a point-in-time snapshot — schedule recurring runs if you want to track how a board changes week over week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, and the rest of the fleet
&lt;/h2&gt;

&lt;p&gt;Workday is one piece of the ATS landscape. If you need coverage beyond it, we also run the &lt;a href="https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper" rel="noopener noreferrer"&gt;SmartRecruiters Jobs Scraper&lt;/a&gt;, the &lt;a href="https://apify.com/DevilScrapes/workable-jobs-scraper" rel="noopener noreferrer"&gt;Workable Jobs Scraper&lt;/a&gt;, and the &lt;a href="https://apify.com/DevilScrapes/multi-ats-jobs-scraper" rel="noopener noreferrer"&gt;Multi-ATS Jobs Scraper&lt;/a&gt; covering Greenhouse, Lever, and Ashby in one normalized schema — all keyless, all pay-per-result.&lt;/p&gt;

&lt;p&gt;Grab the &lt;a href="https://apify.com/DevilScrapes/workday-jobs-scraper" rel="noopener noreferrer"&gt;Workday Jobs Scraper&lt;/a&gt; on the Apify Store and try it with the free $5 credit — no card needed. If you hit a tenant that behaves differently or need a field we don't expose yet, open an issue on the Actor's Issues tab; we read every one. Browse the rest of the fleet at &lt;a href="https://apify.com/DevilScrapes" rel="noopener noreferrer"&gt;apify.com/DevilScrapes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What Workday tenant would you point this at first?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Scrape Mobile.bg Car Listings (Bulgaria) to JSON/CSV</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sat, 13 Jun 2026 10:33:11 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/how-to-scrape-mobilebg-car-listings-bulgaria-to-jsoncsv-2aj1</link>
      <guid>https://dev.to/devil_scrapes/how-to-scrape-mobilebg-car-listings-bulgaria-to-jsoncsv-2aj1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; Use the &lt;a href="https://apify.com/DevilScrapes/mobile-bg-bulgaria-cars" rel="noopener noreferrer"&gt;Mobile.bg Bulgaria Car Scraper&lt;/a&gt; on Apify. Paste a filtered Mobile.bg search URL (or leave it empty for the default cars-and-jeeps feed), set &lt;code&gt;maxResults&lt;/code&gt;, and the Actor returns structured rows — price in EUR and BGN, make, model, year, mileage, fuel, gearbox, engine power, body type, colour, and seller — as a JSON or CSV dataset. No API key needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why scrape Mobile.bg?
&lt;/h2&gt;

&lt;p&gt;Mobile.bg is Bulgaria's largest used-car marketplace and the primary venue for Bulgarian used-car transactions. It carries tens of thousands of listings from private sellers and dealers, priced in both EUR and BGN (Bulgarian lev). Bulgaria is adopting the euro, so the site exposes dual-currency pricing natively — the &lt;code&gt;price&lt;/code&gt; field is in EUR and &lt;code&gt;price_bgn&lt;/code&gt; carries the lev equivalent.&lt;/p&gt;

&lt;p&gt;Mobile.bg publishes no public API. The site is served in Windows-1251 encoding — the Cyrillic character set that predates UTF-8 adoption in Eastern Europe. Getting clean, correctly decoded data out of it requires handling that encoding correctly, matching the browser's TLS fingerprint so the site does not block automated requests, and mapping Bulgarian-language spec labels to standard field names. The Actor handles all of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What fields does the scraper return? 🔥
&lt;/h2&gt;

&lt;p&gt;Every row maps to the &lt;code&gt;ResultRow&lt;/code&gt; Pydantic model in the Actor source. Here is a realistic sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"listing_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"11772628462288076"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"listing_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.mobile.bg/obiava-11772628462288076-audi-a3-s-line-s3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Audi A3 S-line S3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"make"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Audi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_bgn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14864&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mileage_km"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;255000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fuel_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Дизелов"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transmission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ръчна"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine_power_hp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine_size_cc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Хечбек"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Бял"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"first_registration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"октомври 2010"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"гр. Варна"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seller_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seller_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"photo_urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"//mobistatic1.focus.bg/mobile/photosorg/076/1/11772628462288076_t4.webp"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Audi A3 2.0 TDI ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"posted_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"НОВА ОБЯВА"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-02T10:00:00+00:00"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete field set is: &lt;code&gt;listing_id&lt;/code&gt;, &lt;code&gt;listing_url&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt; (always &lt;code&gt;EUR&lt;/code&gt;), &lt;code&gt;price_bgn&lt;/code&gt;, &lt;code&gt;mileage_km&lt;/code&gt;, &lt;code&gt;fuel_type&lt;/code&gt;, &lt;code&gt;transmission&lt;/code&gt;, &lt;code&gt;engine_power_hp&lt;/code&gt;, &lt;code&gt;engine_size_cc&lt;/code&gt;, &lt;code&gt;body_type&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;first_registration&lt;/code&gt;, &lt;code&gt;location&lt;/code&gt;, &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;seller_type&lt;/code&gt;, &lt;code&gt;seller_name&lt;/code&gt;, &lt;code&gt;photo_urls&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;posted_date&lt;/code&gt;, and &lt;code&gt;scraped_at&lt;/code&gt;. Fields tagged enrichment-only (&lt;code&gt;location&lt;/code&gt;, &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;seller_type&lt;/code&gt;, &lt;code&gt;seller_name&lt;/code&gt;) are populated when &lt;code&gt;enrichDetails&lt;/code&gt; is true.&lt;/p&gt;

&lt;p&gt;A note on Cyrillic values: fuel type, gearbox, body type, and colour are returned in their original Bulgarian text (e.g. &lt;code&gt;Дизелов&lt;/code&gt;, &lt;code&gt;Автоматична&lt;/code&gt;, &lt;code&gt;Хечбек&lt;/code&gt;). The column names are standard English, so your pipeline can process them without knowing Bulgarian — just map the Cyrillic values if you need English labels in your output.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does it cost to scrape Mobile.bg listings? 💰
&lt;/h2&gt;

&lt;p&gt;Pricing is Pay-Per-Event.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;actor-start&lt;/code&gt; (one-off per run)&lt;/td&gt;
&lt;td&gt;$0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;result-row&lt;/code&gt; (per car listing)&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;1,000 results in a single run costs $2.05&lt;/strong&gt; ($0.05 start + 1,000 × $0.002). Every new Apify account gets $5 of free credit — enough for two full runs of 1,000 listings before you spend anything. No credit card required to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does the anti-blocking work?
&lt;/h2&gt;

&lt;p&gt;Mobile.bg is a Windows-1251 encoded site with anti-bot protections on its listing pages. We handle the blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We rotate Chrome, Firefox, and Safari TLS fingerprints using &lt;code&gt;curl-cffi&lt;/code&gt; browser impersonation — the site sees a real-browser handshake, not a Python HTTP client.&lt;/li&gt;
&lt;li&gt;We decode the Windows-1251 Cyrillic encoding correctly on every response before parsing, so you never get garbled text in &lt;code&gt;fuel_type&lt;/code&gt;, &lt;code&gt;body_type&lt;/code&gt;, or &lt;code&gt;description&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We rotate residential proxy sessions through Apify Proxy on every block — a fresh session ID and a fresh Bulgarian exit IP, automatically.&lt;/li&gt;
&lt;li&gt;We retry on 408, 429, and 5xx responses with exponential backoff (starting at two seconds, capping at thirty, up to five attempts per page), honouring &lt;code&gt;Retry-After&lt;/code&gt; headers.&lt;/li&gt;
&lt;li&gt;Partial runs surface with a clear status message; we never return empty data with a green status.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to run the scraper from Python
&lt;/h2&gt;

&lt;p&gt;Install &lt;a href="https://docs.apify.com/api/client/python" rel="noopener noreferrer"&gt;apify-client&lt;/a&gt; and call the Actor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;run_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;searchUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.mobile.bg/obiavi/avtomobili-dzhipove/bmw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enrichDetails&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apifyProxyGroups&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RESIDENTIAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/mobile-bg-bulgaria-cars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;make&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EUR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_bgn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BGN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mileage_km&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;km&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To target a specific make or model, apply filters on mobile.bg and paste the resulting URL into &lt;code&gt;searchUrl&lt;/code&gt;. Leave &lt;code&gt;searchUrl&lt;/code&gt; empty to scrape the default cars-and-jeeps feed. Turn &lt;code&gt;enrichDetails&lt;/code&gt; off to halve the request count — you still get make, model, year, price (both EUR and BGN), mileage, fuel, gearbox, engine power and size, body type, colour, and photos from the listing cards.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the main use cases?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cross-border arbitrage: Bulgaria vs Western Europe 💡
&lt;/h3&gt;

&lt;p&gt;Bulgaria's used-car prices are among the lowest in the EU. A dataset of Mobile.bg listings filtered by &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, and &lt;code&gt;mileage_km&lt;/code&gt;, compared against equivalent data from coches.net, leboncoin, or Marktplaats, reveals the price delta for specific models. Importers use this to source vehicles for resale in higher-price markets.&lt;/p&gt;

&lt;h3&gt;
  
  
  EUR/BGN dual-currency price tracking
&lt;/h3&gt;

&lt;p&gt;Bulgaria is in the process of euro adoption. Mobile.bg already shows both prices. The &lt;code&gt;price&lt;/code&gt; (EUR) and &lt;code&gt;price_bgn&lt;/code&gt; (BGN) fields let you track market pricing through the currency transition — useful for financial modelling and policy research.&lt;/p&gt;

&lt;h3&gt;
  
  
  Depreciation modelling: price vs mileage vs age
&lt;/h3&gt;

&lt;p&gt;Pull a broad dataset for a specific model (e.g. all Volkswagen Passat listings), export to CSV, and fit a regression. The &lt;code&gt;year&lt;/code&gt;, &lt;code&gt;mileage_km&lt;/code&gt;, &lt;code&gt;engine_power_hp&lt;/code&gt;, and &lt;code&gt;transmission&lt;/code&gt; fields give you the standard independent variables. Bulgarian private-seller pricing tends to be less filtered by dealer margin, which produces more signal for depreciation studies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dealer intelligence
&lt;/h3&gt;

&lt;p&gt;Filter &lt;code&gt;seller_type&lt;/code&gt; to &lt;code&gt;dealer&lt;/code&gt; (requires &lt;code&gt;enrichDetails: true&lt;/code&gt;), segment by &lt;code&gt;location&lt;/code&gt;, and you have a directory of active Bulgarian car dealers with their listed inventory. Track price changes across successive runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fresh listings monitoring
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;posted_date&lt;/code&gt; field carries Mobile.bg's own relative posting marker (e.g. &lt;code&gt;НОВА ОБЯВА&lt;/code&gt; — "new listing"). Run the Actor daily and filter to new listings to catch fresh private-seller posts before they attract attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is it legal to scrape Mobile.bg?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Actor accesses publicly visible listing data — the same data any browser user sees without logging in. Mobile.bg's terms of service govern automated access; review them for your jurisdiction and use case before running at scale. We recommend responsible pacing and legitimate analytical use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fuel type and body type values are in Bulgarian — can I translate them?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The column names (&lt;code&gt;fuel_type&lt;/code&gt;, &lt;code&gt;body_type&lt;/code&gt;, &lt;code&gt;transmission&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;) are standard English. The values are preserved in the original Bulgarian so nothing is lost. Common mappings: &lt;code&gt;Дизелов&lt;/code&gt; → Diesel, &lt;code&gt;Бензинов&lt;/code&gt; → Petrol, &lt;code&gt;Електрически&lt;/code&gt; → Electric, &lt;code&gt;Ръчна&lt;/code&gt; → Manual, &lt;code&gt;Автоматична&lt;/code&gt; → Automatic, &lt;code&gt;Хечбек&lt;/code&gt; → Hatchback, &lt;code&gt;Седан&lt;/code&gt; → Sedan, &lt;code&gt;Комби&lt;/code&gt; → Estate/Wagon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What currency are prices in?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;price&lt;/code&gt; is always in EUR and &lt;code&gt;currency&lt;/code&gt; is always &lt;code&gt;EUR&lt;/code&gt;. &lt;code&gt;price_bgn&lt;/code&gt; carries the Bulgarian-lev equivalent shown on the site. If &lt;code&gt;price_bgn&lt;/code&gt; is null, the listing did not show a lev amount.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does &lt;code&gt;enrichDetails&lt;/code&gt; add?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;enrichDetails: true&lt;/code&gt; fetches each listing's detail page for the full technical table, exact engine displacement, the seller's city (&lt;code&gt;location&lt;/code&gt;), and &lt;code&gt;seller_type&lt;/code&gt;. Without enrichment you still get make, model, year, price (EUR + BGN), mileage, fuel, gearbox, engine power and size, body type, colour, and photos from the listing card.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does a run of 2,000 listings cost?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;$0.05 (start) + 2,000 × $0.002 = &lt;strong&gt;$4.05&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start collecting Bulgarian car listing data
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/DevilScrapes/mobile-bg-bulgaria-cars" rel="noopener noreferrer"&gt;Mobile.bg Bulgaria Car Scraper&lt;/a&gt; is live on the Apify Store. Click &lt;strong&gt;Try for free&lt;/strong&gt; — $5 of credit included, no card required.&lt;/p&gt;

&lt;p&gt;Your first dataset exports to JSON or CSV in minutes. If you hit an edge case or need a field added, open an issue on the Actor's Issues tab and we'll address it in the next weekly release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.apify.com/api/client/python" rel="noopener noreferrer"&gt;Apify Python client documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.apify.com/actor/run/pay-per-event" rel="noopener noreferrer"&gt;Apify Pay-Per-Event pricing guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mobile.bg/obiavi/avtomobili-dzhipove" rel="noopener noreferrer"&gt;Mobile.bg cars search&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>data</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to Scrape Marktplaats Car Listings (Netherlands) to JSON/CSV</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sat, 13 Jun 2026 10:27:55 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/how-to-scrape-marktplaats-car-listings-netherlands-to-jsoncsv-173n</link>
      <guid>https://dev.to/devil_scrapes/how-to-scrape-marktplaats-car-listings-netherlands-to-jsoncsv-173n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; Use the &lt;a href="https://apify.com/DevilScrapes/marktplaats-netherlands-cars" rel="noopener noreferrer"&gt;Marktplaats Netherlands Car Scraper&lt;/a&gt; on Apify. Paste a filtered Marktplaats auto's search URL, set &lt;code&gt;maxResults&lt;/code&gt;, and the Actor walks the result pages and delivers structured rows — price in EUR, make, model, year, mileage, fuel, transmission, body type, drivetrain, energy label, seller, and photos — into a JSON or CSV dataset. No API key, no local scraping stack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why scrape Marktplaats for car listings?
&lt;/h2&gt;

&lt;p&gt;Marktplaats.nl is the Netherlands' dominant classifieds marketplace — the Dutch equivalent of Craigslist and eBay Classifieds rolled into one. The auto's category carries listings from both private sellers and professional dealers (handelaren), making it the primary source for Dutch used-car pricing data. Marktplaats publishes no public API for its listings.&lt;/p&gt;

&lt;p&gt;The site has a few characteristics that make extraction non-trivial: listing data is served through Marktplaats' own internal search service (not a straightforward HTML scrape), the platform uses Dutch-language spec labels (bouwjaar, kilometerstand, Handgeschakeld, Voorwielaandrijving), and it employs residential proxy detection on its search endpoints. The Actor handles all of that, including Dutch-specific quirks like bid-based listings (&lt;code&gt;Bieden&lt;/code&gt;) and the Dutch energy label system (A through G).&lt;/p&gt;

&lt;h2&gt;
  
  
  What fields does the scraper return? 💡
&lt;/h2&gt;

&lt;p&gt;Every row maps directly to the &lt;code&gt;ResultRow&lt;/code&gt; Pydantic model in the Actor's source. Here is a realistic sample row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"listing_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"m2406042278"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"listing_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.marktplaats.nl/v/auto-s/skoda/m2406042278-skoda-octavia-1-6-comfort"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Skoda Octavia 1.6 Comfort"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"make"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Skoda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Octavia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Te koop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mileage_km"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;315844&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fuel_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Benzine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transmission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Handgeschakeld"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hatchback"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"drive_train"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Voorwielaandrijving"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"energy_label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Grijs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lienden"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nederland"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seller_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dealer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seller_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MBSmart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seller_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"56392587"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"photo_urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://images.marktplaats.com/api/v1/hz-mp-pro-listing/images/4933080c-8de2-40e5-95e9-e9cc46631ef3?rule=ecg_mp_eps$_85.jpg"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Algemene informatie Aantal deuren: 5 Kleur: Grijs ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"posted_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vandaag"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-02T00:00:00+00:00"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete field set is: &lt;code&gt;listing_id&lt;/code&gt;, &lt;code&gt;listing_url&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt; (always &lt;code&gt;EUR&lt;/code&gt;), &lt;code&gt;price_type&lt;/code&gt;, &lt;code&gt;mileage_km&lt;/code&gt;, &lt;code&gt;fuel_type&lt;/code&gt;, &lt;code&gt;transmission&lt;/code&gt;, &lt;code&gt;body_type&lt;/code&gt;, &lt;code&gt;drive_train&lt;/code&gt;, &lt;code&gt;energy_label&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;location&lt;/code&gt;, &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;seller_type&lt;/code&gt;, &lt;code&gt;seller_name&lt;/code&gt;, &lt;code&gt;seller_id&lt;/code&gt;, &lt;code&gt;photo_urls&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;posted_date&lt;/code&gt;, and &lt;code&gt;scraped_at&lt;/code&gt;. The &lt;code&gt;color&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; fields are populated via detail-page enrichment.&lt;/p&gt;

&lt;p&gt;Note &lt;code&gt;drive_train&lt;/code&gt; — this is the exact field name in the &lt;code&gt;ResultRow&lt;/code&gt; model (not &lt;code&gt;drivetrain&lt;/code&gt;). Use it as-is when processing the dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does it cost to scrape Marktplaats listings?
&lt;/h2&gt;

&lt;p&gt;Pricing is Pay-Per-Event.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;actor-start&lt;/code&gt; (one-off per run)&lt;/td&gt;
&lt;td&gt;$0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;result-row&lt;/code&gt; (per car listing)&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;1,000 results in a single run costs $2.05&lt;/strong&gt; ($0.05 start + 1,000 × $0.002). Every new Apify account gets $5 of free credit — enough to pull your first two thousand listings before spending anything. No credit card required to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does the anti-blocking work? 🔥
&lt;/h2&gt;

&lt;p&gt;Marktplaats.nl protects its search endpoints from automated scraping. Here is how we handle it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We rotate Chrome, Firefox, and Safari TLS fingerprints on every request using &lt;code&gt;curl-cffi&lt;/code&gt; browser impersonation. The platform sees a real-browser TLS handshake, not a Python HTTP client.&lt;/li&gt;
&lt;li&gt;We rotate residential proxy sessions through Apify Proxy on any block — a new session ID and a new Dutch exit IP, automatically.&lt;/li&gt;
&lt;li&gt;We retry on 408, 429, and 5xx responses with exponential backoff (starting at two seconds, capping at thirty, up to five attempts per page), honouring &lt;code&gt;Retry-After&lt;/code&gt; headers.&lt;/li&gt;
&lt;li&gt;Sponsored Admarkt ads (item IDs beginning with &lt;code&gt;a&lt;/code&gt;) are detected and skipped — they contaminate price analysis datasets.&lt;/li&gt;
&lt;li&gt;Partial successes surface with a clear status message. We never quietly return an empty dataset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default proxy configuration already requests Dutch &lt;code&gt;RESIDENTIAL&lt;/code&gt; exits. Nothing to configure.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run the scraper from Python
&lt;/h2&gt;

&lt;p&gt;Install &lt;a href="https://docs.apify.com/api/client/python" rel="noopener noreferrer"&gt;apify-client&lt;/a&gt; and call the Actor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;run_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;searchUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.marktplaats.nl/l/auto-s/volkswagen/golf/#Language:all-languages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enrichDetails&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apifyProxyGroups&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RESIDENTIAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/marktplaats-netherlands-cars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;make&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mileage_km&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;energy_label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To target a specific search, apply your filters on marktplaats.nl, copy the URL, and paste it into &lt;code&gt;searchUrl&lt;/code&gt;. The Actor translates it to the internal search query. Set &lt;code&gt;enrichDetails&lt;/code&gt; to &lt;code&gt;False&lt;/code&gt; if you only need the search-payload fields — price, make, model, year, mileage, fuel, transmission, body type, drivetrain, energy label, seller, and photos — without fetching each detail page.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the main use cases?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Used-car price analytics for the Dutch market
&lt;/h3&gt;

&lt;p&gt;Marktplaats combines private sellers and dealers in a single feed, giving a complete picture of Dutch retail and C2C pricing. Pull listings for a model range, export to CSV, and build price-vs-age and price-vs-mileage charts. The &lt;code&gt;energy_label&lt;/code&gt; column adds a sustainability dimension you will not find on most car data APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Energy label composition research
&lt;/h3&gt;

&lt;p&gt;The Netherlands has aggressive emission regulations and a strong push toward electrification. The &lt;code&gt;energy_label&lt;/code&gt; field (A through G) lets you aggregate the label distribution across any search filter — useful for fleet compliance reporting, real-estate proximity analysis, or EV market research.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dealer inventory monitoring
&lt;/h3&gt;

&lt;p&gt;Filter &lt;code&gt;seller_type&lt;/code&gt; to &lt;code&gt;dealer&lt;/code&gt;, run on a cron schedule, and diff by &lt;code&gt;listing_id&lt;/code&gt; to track new stock and price changes. The &lt;code&gt;seller_id&lt;/code&gt; field lets you group all listings from the same dealership even when the &lt;code&gt;seller_name&lt;/code&gt; display name changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bid-based listing research
&lt;/h3&gt;

&lt;p&gt;Listings with &lt;code&gt;price_type: "Bieden"&lt;/code&gt; are minimum-bid auctions. Filter to &lt;code&gt;Bieden&lt;/code&gt; listings and you have a view of the distressed-sale segment of the Dutch market — a common source of under-market deals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-border sourcing
&lt;/h3&gt;

&lt;p&gt;Dutch used-car prices are competitive against some neighbouring markets, especially on specific segments. A Marktplaats dataset filtered by &lt;code&gt;fuel_type&lt;/code&gt;, &lt;code&gt;mileage_km&lt;/code&gt;, and &lt;code&gt;year&lt;/code&gt; gives European importers a current Dutch benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is it legal to scrape Marktplaats.nl?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Actor accesses publicly visible listing data — the same data any browser user sees without logging in. Marktplaats's terms of service govern automated access; review them for your use case before running at scale. We recommend responsible pacing and legitimate analytical use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is &lt;code&gt;price_type&lt;/code&gt; and why is it sometimes &lt;code&gt;Bieden&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Marktplaats supports fixed-price listings (&lt;code&gt;Te koop&lt;/code&gt;), bid-based listings (&lt;code&gt;Bieden&lt;/code&gt;), and reserved listings (&lt;code&gt;Gereserveerd&lt;/code&gt;). The &lt;code&gt;price&lt;/code&gt; field is still populated for &lt;code&gt;Bieden&lt;/code&gt; listings (it shows the minimum bid), and &lt;code&gt;price_type&lt;/code&gt; tells you which mode applies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why are &lt;code&gt;color&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; sometimes null?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both come from the listing detail page, not the search payload. Set &lt;code&gt;enrichDetails: true&lt;/code&gt; (the default) to fill them in. Turn it off to halve the request count — price, specs, seller, and photos all come from the search results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does a run of 5,000 listings cost?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;$0.05 (start) + 5,000 × $0.002 = &lt;strong&gt;$10.05&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are sponsored Admarkt ads included?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Admarkt ads (item IDs starting with &lt;code&gt;a&lt;/code&gt;) are detected and skipped automatically. They distort price comparisons and typically point to dealer landing pages rather than individual listings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start collecting Dutch car listing data
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/DevilScrapes/marktplaats-netherlands-cars" rel="noopener noreferrer"&gt;Marktplaats Netherlands Car Scraper&lt;/a&gt; is live on the Apify Store. Click &lt;strong&gt;Try for free&lt;/strong&gt; — $5 of credit included, no card required.&lt;/p&gt;

&lt;p&gt;Your first dataset exports to JSON or CSV in minutes. Questions or edge cases? Open an issue on the Actor's Issues tab and we'll address it in the next weekly release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.apify.com/api/client/python" rel="noopener noreferrer"&gt;Apify Python client documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.apify.com/actor/run/pay-per-event" rel="noopener noreferrer"&gt;Apify Pay-Per-Event pricing guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.marktplaats.nl/l/auto-s/" rel="noopener noreferrer"&gt;Marktplaats auto's search&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>data</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to Scrape leboncoin Car Listings (France) to JSON/CSV</title>
      <dc:creator>Devil Scrapes</dc:creator>
      <pubDate>Sat, 13 Jun 2026 10:22:38 +0000</pubDate>
      <link>https://dev.to/devil_scrapes/how-to-scrape-leboncoin-car-listings-france-to-jsoncsv-40ib</link>
      <guid>https://dev.to/devil_scrapes/how-to-scrape-leboncoin-car-listings-france-to-jsoncsv-40ib</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; Use the &lt;a href="https://apify.com/DevilScrapes/leboncoin-france-cars" rel="noopener noreferrer"&gt;leboncoin France Car Scraper&lt;/a&gt; on Apify. Paste a filtered leboncoin.fr Voitures search URL, set &lt;code&gt;maxResults&lt;/code&gt;, and the Actor walks the result pages and returns structured rows — price in EUR, brand, model, year, mileage, fuel, gearbox, Crit'Air sticker, seller, location, and photos — as a JSON or CSV dataset. No API key, no local scraping stack to configure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why scrape leboncoin for car listings?
&lt;/h2&gt;

&lt;p&gt;leboncoin.fr is France's largest classifieds marketplace and the default venue for millions of French used-car transactions each year. The Voitures section carries hundreds of thousands of live listings from private sellers and professional dealers alike. There is no public API — structured data access means extracting it from the search pages.&lt;/p&gt;

&lt;p&gt;That extraction is not straightforward. leboncoin is fronted by bot protection. Datacenter IP ranges get challenged or blocked. The listing data is embedded in JSON inside server-rendered HTML, paginated at 35 results per page. The French automotive classification has its own vocabulary: Crit'Air air-quality stickers (a regulatory requirement for driving in low-emission zones), horsepower measured in chevaux, fuel types like Essence and Hybride. The Actor handles all of it and delivers clean, typed rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  What fields does the scraper return?
&lt;/h2&gt;

&lt;p&gt;Every row maps exactly to the &lt;code&gt;ResultRow&lt;/code&gt; Pydantic class in the Actor source. Here is a realistic sample row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"listing_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3117822207"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"listing_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.leboncoin.fr/ad/voitures/3117822207"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Morris Mini COOPER 1275 S"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"make"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MORRIS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Autre"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1968&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;34900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mileage_km"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;101890&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fuel_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Essence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transmission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Manuelle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine_power_hp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine_size_cc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Citadine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vert"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"first_registration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"07/1968"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Saint-Jean-du-Cardonnay"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Seine-Maritime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"postcode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"76150"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seller_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dealer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seller_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MECA SPORT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"critair"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Non classé"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"photo_urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://img.leboncoin.fr/api/v1/lbcpb1/images/12/99/eb/1299eb9f07aa41b5f0430ce684d22b4e689dc47c.jpg?rule=ad-image"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Magnifique Morris Mini Cooper 1275 S restaurée."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"posted_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-12-26 14:04:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-02T00:00:00+00:00"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full field set is: &lt;code&gt;listing_id&lt;/code&gt;, &lt;code&gt;listing_url&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt; (always &lt;code&gt;EUR&lt;/code&gt;), &lt;code&gt;mileage_km&lt;/code&gt;, &lt;code&gt;fuel_type&lt;/code&gt;, &lt;code&gt;transmission&lt;/code&gt;, &lt;code&gt;engine_power_hp&lt;/code&gt;, &lt;code&gt;engine_size_cc&lt;/code&gt;, &lt;code&gt;body_type&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;first_registration&lt;/code&gt;, &lt;code&gt;location&lt;/code&gt;, &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;postcode&lt;/code&gt;, &lt;code&gt;seller_type&lt;/code&gt;, &lt;code&gt;seller_name&lt;/code&gt;, &lt;code&gt;critair&lt;/code&gt;, &lt;code&gt;photo_urls&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;posted_date&lt;/code&gt;, and &lt;code&gt;scraped_at&lt;/code&gt;. The &lt;code&gt;description&lt;/code&gt; field comes from the search payload; when &lt;code&gt;enrichDetails&lt;/code&gt; is on, it is re-fetched from the detail page for the freshest copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does it cost to scrape leboncoin listings? 💰
&lt;/h2&gt;

&lt;p&gt;Pricing is Pay-Per-Event — you pay only when rows land in the dataset.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;actor-start&lt;/code&gt; (one-off per run)&lt;/td&gt;
&lt;td&gt;$0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;result-row&lt;/code&gt; (per car listing)&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;1,000 results in a single run costs $2.05&lt;/strong&gt; ($0.05 start + 1,000 × $0.002). Every new Apify account receives $5 of free credit — enough to pull your first couple of thousand listings before you spend anything. No credit card required to begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does the anti-blocking work?
&lt;/h2&gt;

&lt;p&gt;leboncoin.fr is fronted by bot protection. Residential proxy sessions are not optional — they are required for reliable access. Here is what we do so you do not have to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We rotate Chrome, Firefox, and Safari TLS fingerprints on every request using &lt;code&gt;curl-cffi&lt;/code&gt; browser impersonation. The site's protection stack sees a real browser handshake, not a Python HTTP client.&lt;/li&gt;
&lt;li&gt;We rotate residential proxy sessions through Apify Proxy on every block — a fresh session ID and a fresh French exit IP, automatically.&lt;/li&gt;
&lt;li&gt;We retry on 408, 429, and 5xx responses with exponential backoff (starting at two seconds, capped at thirty, up to five attempts per page), honouring &lt;code&gt;Retry-After&lt;/code&gt; headers.&lt;/li&gt;
&lt;li&gt;When the site pushes back, we slow down rather than triggering a harder block. Partial runs surface with a clear status message; we never return empty data with a green status.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default proxy config already requests &lt;code&gt;RESIDENTIAL&lt;/code&gt; exits. You do not need to configure anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run the scraper from Python 🔥
&lt;/h2&gt;

&lt;p&gt;Install the &lt;a href="https://docs.apify.com/api/client/python" rel="noopener noreferrer"&gt;apify-client&lt;/a&gt; library and call the Actor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;run_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;searchUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.leboncoin.fr/recherche?category=2&amp;amp;brand=Peugeot&amp;amp;vehicle_type=4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enrichDetails&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useApifyProxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apifyProxyGroups&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RESIDENTIAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevilScrapes/leboncoin-france-cars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;make&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mileage_km&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;critair&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To target a specific search, apply your filters on leboncoin.fr, copy the URL, and paste it into &lt;code&gt;searchUrl&lt;/code&gt;. The Actor walks result pages from there. Set &lt;code&gt;enrichDetails&lt;/code&gt; to &lt;code&gt;False&lt;/code&gt; if you only need the search-payload fields — price, brand, model, year, mileage, fuel, gearbox, colour, seller, location, and photos — and want to halve the request count.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the main use cases?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Used-car price analytics for the French market
&lt;/h3&gt;

&lt;p&gt;Pull listings for a specific make and model, export to CSV, and load into your analysis tool of choice. leboncoin's depth in the private-seller segment gives a view of French retail pricing that dealer-only datasets miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Crit'Air label composition research
&lt;/h3&gt;

&lt;p&gt;France's Crit'Air system classifies vehicles from label 0 (fully electric) through to Non classé for the oldest polluters. Low-emission zones in Paris, Lyon, and other major cities restrict entry by Crit'Air label. The &lt;code&gt;critair&lt;/code&gt; field lets you aggregate the sticker distribution across any filtered search — useful for policy research, fleet management planning, or market forecasting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dealer inventory monitoring
&lt;/h3&gt;

&lt;p&gt;Filter &lt;code&gt;seller_type&lt;/code&gt; to &lt;code&gt;dealer&lt;/code&gt;, set a daily schedule, and diff successive datasets by &lt;code&gt;listing_id&lt;/code&gt; to catch new arrivals and price cuts. &lt;code&gt;seller_name&lt;/code&gt;, &lt;code&gt;location&lt;/code&gt;, and &lt;code&gt;postcode&lt;/code&gt; let you segment by dealer and geography.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-border arbitrage from France
&lt;/h3&gt;

&lt;p&gt;French private-seller prices, especially on diesel estates and older luxury cars, can diverge meaningfully from equivalent listings in neighbouring markets. A dataset filtered by &lt;code&gt;fuel_type&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, and &lt;code&gt;mileage_km&lt;/code&gt; gives importers a current benchmark.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lead generation: French car dealers
&lt;/h3&gt;

&lt;p&gt;Dealer listings carry &lt;code&gt;seller_name&lt;/code&gt; and the dealer's &lt;code&gt;location&lt;/code&gt; and &lt;code&gt;postcode&lt;/code&gt;. Run a broad sweep across the Voitures section and you have a structured directory of active French car dealers, filterable by region.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is it legal to scrape leboncoin.fr?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Actor accesses publicly visible listing data — the same data any browser user sees without logging in. leboncoin's terms of service govern automated access; review them for your jurisdiction and use case before running at scale. We recommend responsible pacing and legitimate analytical use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does this Actor require a residential proxy?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;leboncoin.fr's bot protection challenges or blocks datacenter IP ranges. Residential exit IPs from Apify Proxy ride past this reliably. The default proxy configuration already requests &lt;code&gt;RESIDENTIAL&lt;/code&gt; — you do not need to change anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the Crit'Air field?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Crit'Air (Certificat Qualité de l'Air) is a French government air-quality sticker required for driving in designated low-emission zones (Zones à Faibles Émissions). It is published on listings where the seller has declared it. Values include &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt;, &lt;code&gt;4&lt;/code&gt;, &lt;code&gt;5&lt;/code&gt;, and &lt;code&gt;Non classé&lt;/code&gt;. Not all listings include it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does 5,000 listings cost?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;$0.05 (start) + 5,000 × $0.002 = &lt;strong&gt;$10.05&lt;/strong&gt; for one run of 5,000 results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How often can I run it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As often as you need. Schedule in the Apify Console with a cron expression. The Apify API lets you access and diff successive datasets programmatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start collecting French car listing data
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/DevilScrapes/leboncoin-france-cars" rel="noopener noreferrer"&gt;leboncoin France Car Scraper&lt;/a&gt; is live on the Apify Store. Click &lt;strong&gt;Try for free&lt;/strong&gt; — $5 of credit included, no card required.&lt;/p&gt;

&lt;p&gt;Your first dataset is minutes away. If you hit an edge case or need an additional field, open an issue on the Actor's Issues tab and we'll address it in the next weekly release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.apify.com/api/client/python" rel="noopener noreferrer"&gt;Apify Python client documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.apify.com/actor/run/pay-per-event" rel="noopener noreferrer"&gt;Apify Pay-Per-Event pricing guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.leboncoin.fr/recherche?category=2" rel="noopener noreferrer"&gt;leboncoin Voitures search&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>data</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
