v0.1.0 — first release
First release of wellfound-scraper. Scrapes wellfound.com (formerly AngelList Talent): role landing pages, the /jobs discovery feed, and individual job pages.
Every number below was measured on 2026-09-17.
What you need
A residential exit, and nothing else. Wellfound refuses a datacenter address on every route, robots.txt included — HTTP 403 to plain HTTP, to headless Chromium and to headful Chromium alike. From a 2Captcha residential US exit the same machine got HTTP 200 on /, /jobs, every /role/… shape and every job page, three times out of three.
What it does
- Three modes.
role(a/role/…landing page — full descriptions, company badges, size and tagline, salary and equity; paginates),jobs(the discovery feed, one page),job(one job from its schema.orgJobPosting). - Four fetchers. Playwright (primary, with
--concurrency), Selenium, Puppeteer, and a Scraper API client. All three browser engines were run live against the same listing and returned byte-identical rows — the same 52 skus, 38 columns, and the samepositionon every row. - 38-column
JobPostingschema, JSON and CSV, with a.meta.jsonsidecar per run. - 355 offline checks, fixtures cut from real captures and verified to parse identically to their untrimmed originals.
What the site turned out to be
- Gating is per ROUTE.
/role/…,/jobsand/jobs/{id}-{slug}are served to a residential exit;/company/{slug}is refused to one, to plain HTTP and to a real browser alike, 3 of 3 each. There is no--mode company, because there is nothing this repo could honestly offer for it. - The obvious challenge marker is inverted here.
challenges.cloudflare.comappears once on every page Wellfound serves — the site loads Turnstile as part of its own application, with its sitekey in its own page config — and zero times on either refusal.cf-turnstileappears nowhere at all. The marker set uses_cf_chl_optand its siblings instead, and the suite fails the build if the inverted one comes back. - Walking past the last page does not fail — it repeats.
?page=48of a 47-page listing answers HTTP 200 with page 1's rows again. Runs plan against the site's ownpageCountand read back the page number the server states in its Apollo cache key. - A "page" is twenty COMPANIES, not twenty jobs.
perPage: 20,pageCount: 47,totalStartupCount: 923,totalJobCount: 1881— and 32 to 45 job rows per page. /jobshas no addressable pages.?page=2returns the identical 46 job ids, so that mode reportssingle_page_listing.- Two structured sources, different applications. A landing page carries zero JSON-LD and a Next.js Apollo cache; a job page carries no
__NEXT_DATA__and oneJobPosting. Where both publish a salary they agree. - Pay is one rendered string with nine measured shapes: the bullet splits salary from equity and either side may be absent,
Lis the Indian lakh (1e5), andNo equityis a statement rather than a missing value.