Scraping careers pages at the Big Four

Six hiring systems — Workday, SuccessFactors, Avature, Talentsoft, SmartRecruiters, Greenhouse — flowing into one normalized JSON feed

We built a job scraper for the careers sites companies run themselves. Big Four Careers Scraper reads the hiring systems of Deloitte, PwC, EY and KPMG, the professional-services firms around them (BDO, Forvis Mazars, Grant Thornton, RSM), and the European banks and insurers on the same systems.

2,500live roles per run
18firms
24countries
~90sper full run

It skips job boards entirely. Every role comes straight from the employer's own hiring system, so it is confirmed open at the moment you get it, with the real location, the real posting language, and the employer's own requisition id:

{
    "employer": "PwC",
    "employerAtsPlatform": "workday",
    "reqId": "552577WD",
    "jobTitle": "Senior Associate Indirect Tax",
    "locationCity": "Brussels",
    "locationCountry": "BE",
    "language": "en",
    "employmentType": "Full time",
    "postedAt": "2026-08-21",
    "verifiedLiveAt": "2026-08-24T06:12:03.000Z",
    "url": "https://pwc.wd3.myworkdayjobs.com/..."
}

Two of those fields do most of the work. verifiedLiveAt says the employer's own system reported the role open at that timestamp, so you never spend time on a posting that was filled last week. reqId is the id the employer's hiring actually runs on, which makes deduplication exact instead of fuzzy title-matching. Boards can give you neither.

Why careers pages and not job boards

A job board re-indexes what employers post, on its own schedule, with its own taxonomy. It keeps roles the employer already filled, and it lists the same requisition three times under three titles. The employer's own careers site only carries what is actually open.

We got to measure the difference. A job-search pipeline tracked both channels side by side through August, opening every posting to check whether it was still live and whether it fit. The employer channel won by a factor of four:

Comparison of job boards and employer careers pages: 98 board records with 5 worth applying to, against 244 careers-page records with 47 worth applying to
RunJob boardsEmployer careers pages
16 August12% worth applying to26% worth applying to
19 August17%36%
24 August5%  (5 of 98 records)19%  (47 of 244 records)

Why nobody scrapes careers pages

Careers pages are hard to scrape as a category, because there is no category. Every employer runs an applicant tracking system, an ATS, and every ATS behaves differently. Among the Big Four in Belgium alone: KPMG runs Talentsoft, with 114 vacancies to page through. PwC runs Workday. EY runs SuccessFactors. Deloitte runs Avature, whose search form ignores URL parameters, so a browser has to fill it in. Several of these return a 403 on the HTML and only answer as a JSON API, and it is a different JSON API per platform.

That same pipeline first worked this channel the hard way: fourteen AI agents reading careers pages one by one. Here is that approach against one actor run, on the same firms:

AI agents reading by handBig Four Careers Scraper
Coverage8 firms in 2 countries18 firms in 24 countries
Time23 minutes43 seconds
Records51343 (filtered run), ~2,500 full
Methoda different workaround per firmthe ATS's own data interface

The hand-reading was not badly built. It hit the structural problem: per-firm ATS knowledge doesn't transfer, so every added employer costs the same effort as the first one. That knowledge is exactly what the actor packages.

How the actor reads each ATS

The actor fingerprints which ATS an employer runs from structural evidence on the careers page (script tags, link targets, hydration payloads), then pulls the live roster through that platform's own data path:

PlatformHow the actor reads it
Workdayofficial CXS JSON API
SuccessFactorsserver-rendered HTML, paginated
Avatureserver-rendered HTML, standard and white-label tenants
RadancyAJAX results endpoint
SmartRecruiterspublic JSON API
Greenhousepublic JSON API

Reading the ATS directly also buys reach a per-country scraper can't have. Several firms run one shared tenant for many markets: PwC serves ten European countries from a single Workday board, EY runs one global SuccessFactors tenant, Deloitte Central Europe covers five countries on one Avature board. One fetch, many markets. The same Workday network is why a run also returns roles at Belfius, ING and Vontobel.

The location fields are parsed from the ATS record, not from a board's regional feed, and language is the posting's real language, which for cross-border European roles is half the screening.

Diff mode

Diff mode: a scheduled run compared against stored per-employer state, returning only appeared and disappeared roles

Most users of hiring data don't want the full list every day. They want to know what changed. mode: "diff" keeps per-employer state between runs and returns only the roles that appeared or disappeared since the last one. On a daily schedule that turns the actor into a hiring-signal feed per employer, and repeat runs bill only the changes.

Custom employers

The built-in roster is professional services, but the detection pipeline is generic. customEmployers takes any employer with a name, a country and a careers-page URL and runs the same ATS detection against it. A watchlist of employers that used to sit in a document becomes a scheduled feed:

{
    "mode": "diff",
    "countries": ["BE", "LU"],
    "customEmployers": [
        { "name": "Alter Domus", "country": "LU", "careersUrl": "https://careers.alterdomus.com" }
    ]
}

Limits

  • Rows carry no description body. Shortlisting still means fetching the posting itself to read requirements and responsibilities. The row hands you a live URL and a confirmed-live timestamp, so the fetch is cheap and never lands on a dead page, but it is a fetch. A detail mode for shortlists is on our roadmap.
  • Coverage is EU/EEA, UK and Switzerland. Roles an ATS resolves to other regions are dropped by design.
  • An employer can change ATS. Detection is re-run automatically when a cached detection stops working, which costs one slower run.

Cost

The actor charges per result. Narrow a run with countries or employerNames to pay for only the slice you need, and use diff mode on a schedule so repeat runs bill only what changed.

The actor is on the Apify Store: apify.com/studio-amba/big-four-careers-scraper