Upwork Jobs Scraper | 30+ Filters & Duplicate-Free Runs avatar

Upwork Jobs Scraper | 30+ Filters & Duplicate-Free Runs

Pricing

from $2.00 / 1,000 results

Go to Apify Store
Upwork Jobs Scraper | 30+ Filters & Duplicate-Free Runs

Upwork Jobs Scraper | 30+ Filters & Duplicate-Free Runs

Scrape Upwork jobs with title, FULL description, budget, skills and project length. 30+ filters: budget floors across hourly AND fixed, keyword include/exclude, client country, experience level, freshness window. Monitoring mode skips jobs earlier runs returned, so you never pay twice.

Pricing

from $2.00 / 1,000 results

Rating

0.0

(0)

Developer

TQM

TQM

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

18 hours ago

Last modified

Share

Upwork Jobs Scraper extracts freelance job postings from Upwork — title, full description, budget, skills, project length and proposal count — with 30+ filters that let you cut a broad search down to only the postings worth your time.

It searches through Upwork's own JSON endpoint rather than rendering pages in a browser: measured, 50 postings in 30 seconds. It also has a monitoring mode that remembers what previous runs returned, so a scheduled run only ever charges you for postings you have not already seen.

⚠️ Read this first: what this Actor does and does not return

This Actor returns everything Upwork publishes on its public job search: title, the full description, skills, budget, experience level, duration, proposal count and posting time.

It does not return the client's lifetime spend, hire history or payment-verified status. Upwork puts none of that on logged-out search results — it exists only on each posting's own page, behind a Cloudflare challenge, at roughly one browser page-load per row. That is far more expensive than a result is worth, so this Actor does not offer it at all rather than offering it as a switch that quietly multiplies your bill.

The one client filter that does work is clientCountries, because Upwork applies it itself, server-side, before results are returned. It narrows the run and makes it cheaper.

If client spend and hire history are what you are after, this is not the right Actor for you, and this section is here so you find that out before you pay for a run rather than after.

What data can I extract from Upwork?

FieldDescription
titleJob title
urlLink to the original Upwork posting
descriptionJob description
skillsSkills the client tagged the job with
jobTypehourly or fixed
budgetTypeHow the budget is expressed
budgetMinAmount / budgetMaxAmountBudget or hourly-rate bounds
budgetCurrencyBudget currency
budgetMonthlyUsdHourly and fixed budgets projected onto one monthly axis, so they can be compared and sorted together
experienceLevelContractor tier the client asked for
duration / durationWeeksExpected engagement length
workloadStated hours per week
postedAt / createdAt / postedAtRelativeWhen the job was posted
proposalsMinHow many proposals the job already has — your competition. Browser path only (jsonSearch: false); Upwork does not expose it through the fast JSON search
clientTierClient's Upwork tier
contactEmailsAny contact emails present in the posting text
searchQueryWhich of your queries produced this row
jobId / scrapedAtStable id and collection timestamp

How do I use Upwork Jobs Scraper?

  1. Click Try for free.
  2. Set searchQueries — e.g. ["devops", "aws infrastructure"]. Or paste URLs you built in Upwork's own UI into startUrls.
  3. Leave proxyConfiguration on residential (the default). Upwork sits behind Cloudflare and blocks datacenter traffic hard.
  4. Click Save & Start.
  5. Export as JSON, CSV, Excel or XML from the Storage tab.

Input sample

{
"searchQueries": ["devops", "kubernetes", "terraform"],
"maxItems": 100,
"maxPagesPerQuery": 3,
"sortBy": "recency",
"jobType": ["hourly"],
"hourlyRateMin": 40,
"includeKeywords": ["aws", "gcp", "azure"],
"excludeKeywords": ["wordpress", "shopify"],
"postedWithinHours": 48,
"deduplicateAgainstPreviousRuns": true,
"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}

Output sample

{
"jobId": "~021834729471023847",
"title": "DevOps Engineer to harden our AWS environment",
"url": "https://www.upwork.com/jobs/~021834729471023847",
"description": "We need an experienced DevOps engineer to review our Terraform...",
"skills": ["Amazon Web Services", "Terraform", "Kubernetes"],
"jobType": "hourly",
"budgetMinAmount": 45,
"budgetMaxAmount": 80,
"budgetCurrency": "USD",
"budgetMonthlyUsd": 10000,
"experienceLevel": "Expert",
"duration": "3 to 6 months",
"durationWeeks": 18,
"workload": "30+ hrs/week",
"postedAt": "2026-09-04T06:41:00.000Z",
"postedAtRelative": "2 hours ago",
"proposalsMin": null,
"clientTier": "Enterprise",
"searchQuery": "devops",
"scrapedAt": "2026-09-04T08:44:12.310Z"
}

The filters, grouped

There are more than forty. These are the ones that change your results the most.

searchQueries (several per run, deduplicated across all of them) · startUrls (paste a URL built in Upwork's UI, with filters this actor does not expose) · maxItems · maxPagesPerQuery (Upwork shows ~10 jobs per page) · sortBy.

Job shape

jobType (hourly / fixed) · experienceLevel · projectLength · workload.

projectLength applies to hourly jobs only. Upwork itself applies it before returning results, and fixed-price results come back unfiltered. That is Upwork's behaviour, not ours.

Budget

hourlyRateMin / hourlyRateMax · fixedBudgetMin / fixedBudgetMax · minBudgetMonthlyUsd · keepUnknownBudget · hoursPerMonth.

Hourly bounds match overlapping ranges, not contained ones. A $15–40/hr job matches a maximum of 20, because the ranges overlap. This is Upwork's matching behaviour and it surprises people. Fixed-budget bounds are exact — verified 2026-08-14: an upper bound of 200 returned $10, $30, $50 and $100 jobs, and a 1000–5000 window returned $1000 jobs.

minBudgetMonthlyUsd is the filter to reach for when you want one bar across both job types: an hourly rate is projected onto a month using hoursPerMonth, a fixed budget onto its stated duration.

Keywords

includeKeywords (any-of by default; requireAllIncludeKeywords switches it to all-of) · excludeKeywords.

Matched on word boundaries, so ops does not match shops. excludeKeywords is the fastest way to strip the adjacent niches a broad query drags in.

Client country

clientCountries — the only client-side filter Upwork will apply for you.

Give it full country names: ["United States", "Germany"]. "US" returns nothing. Because Upwork applies it before results come back, it makes the run cheaper as well as narrower — and an unrecognised name returns zero rows rather than being silently ignored, so a typo fails loudly.

Competition and freshness

postedWithinHours — only postings from the last N hours. Postings whose age cannot be determined are kept, never dropped.

maxProposals — drop jobs that already have more proposals than you want to compete with. This one needs jsonSearch: false: Upwork does not return proposal counts through its fast JSON endpoint, so on a default run there is nothing to compare against and the filter narrows nothing. The run tells you so in its log rather than leaving you to work it out.

Monitoring mode

deduplicateAgainstPreviousRuns remembers which jobs previous runs returned and skips them, so a scheduled run only charges you for postings you have not seen before. seenExpiryDays (default 30) controls how long a job stays in the ledger; seenStateKey lets two schedules share one ledger or keep separate ones.

This is the setting that makes a daily Upwork watch cheap. Turn it on for any scheduled run.

Proxies: use residential

Upwork sits behind Cloudflare and blocks datacenter traffic hard. proxyConfiguration defaults to residential proxies and should be left there — without them, most runs return nothing. Residential proxy traffic is billed separately by Apify, so keep maxItems sane while you are testing.

A run that returns nothing tells you why

With failOnZeroResults: true (the default), a run that finishes with an empty dataset is marked FAILED with a status message naming the cause. On pay-per-result pricing a zero-row "success" is you paying for nothing and unable to tell a broken actor from a quiet search.

Set it to false for scheduled monitoring runs where an empty result is a legitimate answer.

How much does it cost?

Measured on the platform: 50 rows in 30 seconds at 0.033 compute units. Costs rise with maxItems and maxPagesPerQuery, and residential proxy traffic is billed on top.

There is no expensive tier to accidentally switch on — the JSON search path is the only path, which is why the cost per row stays flat as you scale a run up.

For your own figure, run once with your real input and read the cost from the Runs tab.

Who is this for?

  • Freelancers and agencies who want the day's relevant postings filtered down to the ones with a real budget, a serious client and few proposals — instead of refreshing Upwork.
  • Lead generation — a company posting paid work is a company with budget, right now.
  • Market researchers tracking freelance rates, in-demand skills and budget distributions over time.
  • Recruiters benchmarking contract rates against permanent salaries.

FAQ

Do I need an Upwork account?

No. This actor reads Upwork's public job search. There is no login and no account of yours to be banned.

Will my searches be blocked?

Upwork is behind Cloudflare and is one of the harder sites to scrape reliably. Keep the default residential proxy configuration, and the actor rotates proxy identities when acquiring a session. If a run cannot get through, it tells you rather than returning a silent empty result.

Can I get the client's total spend or hire count?

Not from this Actor. Upwork publishes neither on its public search results — both live on the individual job page, behind a Cloudflare challenge that costs roughly one browser page-load per row. Charging per result while paying per page-load is a bad deal for you and for us, so the option does not exist.

You still get every signal Upwork publishes on the search itself, including clientTier and the full description.

Do I get the full job description, or a truncated one?

The full description, and the complete skill list. Some Upwork scrapers return only what fits on a search card and make you open each job to get the rest; the JSON search path this Actor uses carries both in full on every row.

This actor collects only publicly visible job postings and extracts no private user data. Results can contain personal data — a client's name or an email inside a posting. You should not scrape or store personal data without a legitimate reason, and if you are in the EU, note that GDPR applies. If you are unsure whether your reason qualifies, consult your lawyers. See Apify's background on the legality of web scraping.

Can I export to Excel or Google Sheets?

Yes — JSON, CSV, XML and Excel from the Storage tab, plus a direct Google Sheets integration.

Integrations and API

Trigger runs and collect results via the Apify API, the JavaScript or Python client, or Apify's integrations — Zapier, Make, Slack, Google Drive, GitHub and webhooks. The most useful pattern: an hourly schedule with deduplicateAgainstPreviousRuns and postedWithinHours, plus a Slack webhook, gives you new matching Upwork jobs pushed to you as they appear.

Support

Missing a filter, or seeing something wrong? Open an issue on this actor's Issues tab.