moxlade/guides/upwork freelancer scraper

Upwork freelancer scraper: search talent by rate, JSS, badge and country — with exact earnings and every contract

Ours. This page sells the Moxlade Upwork freelancer scraper on Apify. It answers a search from a census of Upwork profiles we keep and refresh every day, so a query with filters comes back in seconds, and it reads a profile live when you ask for the whole record. Every number below is from our own table or our own runs, dated.

published 2026-09-13 · updated 2026-09-13
what it is
an Apify actor: 65 typed fields per freelancer
where rows come from
a census of 30,042 full profiles, 71,351 indexed, 153 countries — refreshed daily (read 2026-09-13)
a stored search
25 rows in 0.4–1.9 s of run time; 500 rows in one call
a live search
Upwork's current result, its order, 25 rows in under 30 s
a full profile
every contract with charges and feedback both ways, education, employment, languages, certificates
price
$4 / $5 / $20 per 1,000 rows — stored / live search / full profile. No start fee.
code
working examples in Python, JavaScript and curl
use it

Open the actor on Apify, run a stored search with the filters you need, export the dataset.

moxlade/upwork-freelancers on Apify
$4.00 per 1,000 stored rows · $5.00 per 1,000 live search rows · $20.00 per 1,000 full profiles · no start fee, a zero-row run is free

Filters a page scrape cannot do

Upwork's own talent search filters by fixed bands and one order. Ours filters on the numbers a client actually cares about, at the thresholds you choose, because they are columns in our table, not boxes on a page:

  • Rate bandrateMin / rateMax as numbers. Measured on the release runs: 25 of 25 rows inside the band, every time.
  • Job Success ScorejssMin. JSS is on 90.3% of stored rows, as a percentage, not a flag.
  • BadgetopRated, topRatedPlus, risingTalent.
  • Placecountries (any of) and Upwork's regions.
  • Track recordhoursMin, jobsMin, earnedMin, hireAgainMin, memberSinceAfter, lastWorkedWithinDays.
  • Skills (any of), availability, idVerified, excludeAgencies.
  • ChangechangedSince returns only freelancers whose rate, title, JSS, badge, hours, jobs, skills or availability moved since a date or a window (7d), with the changes listed.

Nine sort orders: relevance, rate, hours, JSS, earnings, recency and more. Worked examples: Top Rated US Python developers at $50–100/h and JSS 95+, 1,000+ hours, worked in the last 60 days — run them as they are.

What a profile page hides, in the row

A visitor to an Upwork profile sees an earnings band, a rate and a title. The row we return carries the figures behind them:

  • Exact lifetime earnings where Upwork exposes the figure, with earnings_precision saying exact or band, and earnings_hidden when the freelancer hides it. A figure is on 47.7% of stored rows today and the share grows as the census re-reads profiles.
  • Hours, jobs (hourly and fixed), completed contracts, hire-again rate, average rating, member-since, last-worked date, availability, ID and phone verification — on every profiled row.
  • The contract history. include: ["contracts"] adds every completed contract we hold for the freelancer: title, type, dates, hours, charges, the client's name, the client's feedback with its six detail scores, and what the freelancer wrote about the client. 395,022 contract rows over 30,031 profiles, 156,836 of them with feedback in both directions (read 2026-09-13).
  • The sections a visitor never sees. Education, employment history, languages with proficiency, certificates, other experiences — fetched on 99.2% of stored profiles and on every live read.
  • History. fetched_at, age_days, first_seen_at, last_seen_at, a versions count, and changes when you asked what moved.
  • Private profiles are an answer, not a gap: 5,313 profiles that went private are tracked and come back as status: private with the date and their last public state.

Three ways to ask, three prices

modeinputwhat comes backmeasuredper 1,000 rows
stored searchquery + filtersrows from the census, sorted, with provenance stamps; rows older than maxAgeDays (default 30) left out unless you ask25 rows in 0.4–1.9 s$4.00
live searchliveSearch: true + query + filtersUpwork's current result for the query, in Upwork's order, filters applied server-side; rows join the census25 rows in under 30 s, 25 of 25 on every filter$5.00
full profileprofileUrls + fullProfiles: truethe profile read now, every contract fetched and stored, all the sections3 profiles in 33.7 s cold$20.00
Prices as set on the store. One event per delivered row, charged after the row is in your dataset; maxItems is also your spending cap. A run that returns nothing costs nothing.

The lookup path takes profileUrls (URLs or ~01… ids) and answers from the census at the stored price. Ids the census has not seen are listed in the run's status message, not invented.

Why it is built this way

Every other Upwork talent scraper on the store reads the same search result at run time. That is why they all return the same 25 people in the same order for the same query, differ only in fields and price, and why most of them return nothing on a given day (measured 2026-09-11: 4 of 11 delivered rows). A run-time scrape filters only on what the search result offers, at the steps it offers; it cannot filter on hire-again rate or last-worked date, because those are on the profile, and it cannot say what changed since last week, because it has no last week.

A census can. We read profiles every day, keep every version, and answer searches from the table. The cost of that is honesty about age — every row says when it was read — and the benefit is everything above.

use it

Start with one of the five worked examples, then change the query.

moxlade/upwork-freelancers on Apify
$4.00 per 1,000 stored rows · $5.00 per 1,000 live search rows · $20.00 per 1,000 full profiles · no start fee, a zero-row run is free

Questions

How fresh is a stored row?
Each row carries fetched_at and age_days. A stored search leaves out rows older than maxAgeDays (default 30) unless you set includeStale. The census re-reads the oldest profiles on a rolling basis; liveSearch reads Upwork at run time when you need the current result.
Does it need my Upwork account?
No. Nothing is read through a buyer's account or browser. The census is built from public profiles with anonymous sessions, the same pages a logged-out visitor can open.
What does a run cost?
One event per row that lands in your dataset — $4 per 1,000 stored rows, $5 per 1,000 live search rows, $20 per 1,000 full profiles. No start fee, no minimum, and a zero-row run costs nothing. 500 stored rows are $2.00.
Can I get the data as CSV?
Yes — every run's dataset exports as CSV, JSON, Excel or XML from the Apify console or the API. How to export Upwork freelancers to CSV or JSON.
How does it compare with the other Upwork scrapers on Apify?
We measured the lane on one day with the same query and filters: Best Upwork freelancer scrapers, measured.