Upwork Jobs Scraper — Listings, Budgets, Skills
Pricing
$1.00 / 1,000 per job returneds
Upwork Jobs Scraper — Listings, Budgets, Skills
Scrape Upwork job postings by keyword, category, budget, experience level and client location. Every row carries the FULL job description, hourly or fixed budget, required skills, duration and workload. Clears Upwork's Cloudflare challenge for you. No Upwork account needed.
Pricing
$1.00 / 1,000 per job returneds
Rating
0.0
(0)
Developer
Scrapers Delight
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
💼 Upwork Jobs Scraper — every public job posting, with the full brief
Turn Upwork's job marketplace into a clean, filterable dataset. Give it a keyword — or nothing at all — and it walks Upwork's search results and returns one row per job posting, carrying the complete job description (not the two-line preview), the hourly rate range or fixed budget, the required skills, the experience level Upwork asks for, the project length and the weekly workload.
Upwork sits behind a Cloudflare managed challenge: a plain HTTP request gets a 403 and a 344 KB "Checking your browser" page, and so does a stock headless browser. This actor clears that challenge with a real browser once per run, then does the actual crawling over cheap plain HTTP. No Upwork account, no cookies of your own, no CAPTCHA service.
Read "Known transport limitation" below before you run this on Apify. The clearance step is currently blocked from inside Apify's Linux container for Upwork's job-search rule, and the actor says so loudly rather than returning an empty dataset. The scraper is proven end to end where a desktop browser is available.
🎯 What you get
- 📝 The full job description — median 1,002 characters, up to 10,777 measured. Upwork's search page clamps the text to two lines; this returns the whole brief, so you can run your own keyword, budget or red-flag filters over it.
- 💵 Real budget numbers —
hourlyRateMin/hourlyRateMaxfor hourly contracts,fixedBudgetfor fixed-price projects, as separate numeric fields you can sort and chart. - 🧠 The skill list Upwork attached to the posting — a mean of 6.2 tags per job, plus
matchedSkills, the subset your keyword actually hit. - 🧭 Experience level, project length and hours per week — resolved into human labels ("Expert", "3 to 6 months", "30+ hrs/week"), not Upwork's internal i18n keys.
- ⏱️ Posting timestamps —
postedAt,createdAtandrenewedAtin ISO 8601, so a scheduled run turns straight into a "new since last check" feed. - 🔗 A canonical job URL for every row.
🔍 What you can filter on
Everything below is applied server-side by Upwork, so you only pay for rows you actually want:
| Filter | Values |
|---|---|
| Keywords | any number; Upwork's search syntax (quotes, OR, NOT) works |
| Job type | hourly · fixed price |
| Experience level | Entry Level · Intermediate · Expert |
| Category | all 12 Upwork top-level categories |
| Client location | country, sub-region or continent |
| Client hire history | no hires · 1–9 hires · 10+ hires |
| Hours per week | under 30 · 30+ |
| Project length | <1 month · 1–3 · 3–6 · 6+ months |
| Hourly rate | min / max USD |
| Fixed budget | min / max USD |
| Sort | newest first · most relevant |
You can also paste any Upwork search URL into startUrls and the actor will crawl exactly that
search, filters and all.
📊 Measured field coverage
Not estimates — the numbers below come from parsing 294 unique live job postings across six
different searches (python, shopify developer, video editor, data entry, and the unfiltered
newest-jobs feed) on 2026-09-02:
| Field | Filled | Note |
|---|---|---|
jobId, jobKey, title, url | 100% | |
description, descriptionLength | 100% | mean 1,612 chars · median 1,002 · max 10,777 |
jobType | 100% | 180 hourly / 114 fixed price in the sample |
experienceLevel | 100% | Intermediate 181 · Expert 85 · Entry Level 28 |
duration | 100% | |
postedAt, createdAt | 100% | ISO 8601 |
skills | 98.6% | mean 6.2 skills per job |
currency | 81.3% | present whenever a rate or budget is |
matchedSkills | 71.1% | your keyword's own skill hits |
workload | 61.2% | hourly jobs carry it; fixed-price ones mostly do not |
hourlyRateMin / hourlyRateMax | 42.5% | = 125 of 180 hourly jobs advertised a rate |
fixedBudget | 38.8% | = 114 of 114 fixed-price jobs — every one |
renewedAt | 3.4% | only re-posted jobs have one |
⚠️ Honest limits — read before you buy
No client spend, hire rate or payment-verification data. Upwork shows those only to signed-in
users. On the logged-out search payload they are present as nulls on every single row — 0 of 300
measured. Rather than ship a column that is always empty, this actor does not emit them at all. The
one client signal you can filter on is hire history (clientHires), which Upwork does apply
server-side. If you need spend and hire-rate columns, no logged-out scraper can honestly give them.
Three Upwork filters do nothing for logged-out visitors and are therefore not offered here: "payment verified", "number of proposals" and "contract-to-hire". Each returned the unfiltered 2,444-row baseline when tested. They are omitted rather than shipped as knobs that silently no-op.
Residential proxies are required, not recommended. Cloudflare's challenge was cleared from
residential IPs and never from datacenter or static IPs. Measured 2026-09-02, same browser, same
minute: Apify RESIDENTIAL (US) cleared in 6.2 s / 8.8 s / 9.2 s; Apify auto (datacenter) and a
static-IP group both sat on "Just a moment…" until they were abandoned at 60 s. The input therefore
defaults to RESIDENTIAL + country US.
⚠️ Known transport limitation — read this before running. The clearance step needs a browser
whose fingerprint Cloudflare accepts, and Apify's Linux container currently does not have one for
Upwork's job-search rule specifically. Measured on 2026-09-02 across 12 configurations — real
Google Chrome and bundled Chromium, headed under Xvfb, with and without the sandbox, with software
WebGL on and off, at 2/4/8 GB, on US / unpinned / GB residential IPs and on the platform's own
egress, with 90 s of patience plus mouse movement — the container never obtained cf_clearance for
/nx/search/jobs/. It does clear upwork.com's homepage (HTTP 200 + cf_clearance in 20 s),
and the identical code on a Windows desktop Chrome clears the job search through the same Apify
residential pool in 6–9 s, so the wall is the container's browser fingerprint, not the IP and not
the parser. Two ways round it today:
- Bring your own clearance (
clearanceinput): mintcf_clearancein your own browser and pass it with the exact User-Agent that solved it, plus a proxy that egresses from that same IP. A cf_clearance is bound to the (IP, User-Agent) pair, so all three must line up — and note that Apify's residential sessions do not hold an IP long enough to bridge this reliably (a pinned session's exit IP changed within minutes in testing); this route needs a sticky/ISP proxy. - Run it locally or on your own box, where a normal desktop Chrome is available. The scraper itself is fully proven there: 60 rows in 15 s over Apify residential, one 8.8 s clearance, 2 HTTP requests.
A block is never reported as "no jobs". If Cloudflare blocks everything, the run fails with the challenged-page count and sample URLs in the message. A genuinely empty search exits successfully with zero rows. Those two outcomes are never conflated.
🚀 Example inputs
Fresh leads for a Python freelancer, cheapest possible run
{"searchQueries": ["python", "django", "fastapi"],"jobType": "hourly","hourlyRateMin": 40,"experienceLevel": ["expert"],"sortBy": "recency","maxItems": 200}
Serious buyers only — clients who have hired before, decent budgets
{"searchQueries": ["shopify"],"clientHires": "10-","fixedBudgetMin": 1000,"maxItems": 500}
Market research: the whole marketplace, newest first
{"searchQueries": [],"sortBy": "recency","maxItems": 5000}
Reuse a search you already built on Upwork
{"startUrls": [{ "url": "https://www.upwork.com/nx/search/jobs/?q=react&sort=recency&contractor_tier=3&t=0" }]}
💰 Pricing
$1.00 per 1,000 jobs ($0.001 per job returned) — pay per event, nothing else. No monthly fee,
no charge for starting a run, no charge for pages that came back blocked. Jobs that appear under
more than one of your keywords are deduplicated before billing, so you never pay for the same
posting twice. Rows are delivered and billed together, so a spend cap stops the run cleanly instead
of handing you rows you paid for twice or rows you never paid for.
❓ FAQ
Do I need an Upwork account? No. This reads only what Upwork serves to logged-out visitors.
How many jobs can I get from one keyword? As many as Upwork matches. Deep pagination is verified: on a 2,444-result search, page 49 at 50 rows/page returned offset 2,400 — the tail of the result set, not a truncated first 1,000. The unfiltered marketplace measured 115,014 open jobs.
How fast is it? Upwork serves 50 jobs per request. One browser clearance (~4–5 s measured) is minted at the start of the run; everything after that is plain HTTP.
Can I run it on a schedule to get only new jobs? Yes — sort by recency, keep maxItems
small, and diff on jobId or postedAt between runs.
Why does it use a browser at all if it then uses plain HTTP? Because Cloudflare's managed challenge can only be solved by a real browser, but the resulting clearance cookie can be replayed by a cheap HTTP client. Measured: one clearance served 8 of 8 consecutive search pages with zero re-challenges.
Does it work without a residential proxy? No — see "Honest limits" above.
Why did my Apify run fail with "Cloudflare blocked every request"? That is the known transport limitation above: the clearance step cannot currently be completed from inside Apify's container for Upwork's job-search rule. The run fails loudly on purpose rather than handing you an empty dataset that looks like "Upwork has no jobs".
Is the description the full text? Yes — the whole posting, HTML highlight markup stripped.
What happens if Upwork changes their page? The actor never reports a parse failure as an empty result: unreadable pages are counted and named in the run's status message.
Can it scrape freelancer profiles too? No. One site, one job: this actor does job postings.
What about proposals count / connects required? Not exposed to logged-out visitors.
⚖️ Legal and fair use
This actor collects publicly visible job postings — the same pages any visitor sees without
signing in. It does not log in, does not create accounts, does not solve CAPTCHAs on a human's
behalf, and collects no personal data about freelancers. Upwork's robots.txt disallows
/nx/ for crawlers (Disallow: /nx/), which is a crawler-etiquette directive, not a technical or
legal access control; you are responsible for your own use of the data and for complying with
Upwork's Terms of Service and any applicable law in your jurisdiction. Use the output for market
research, rate benchmarking and finding work — not for spamming clients.