Upwork est l'une des plus grandes places de marché pour freelances sur le web ouvert, et ses pages publiques de recherche d'emploi constituent un signal constant de ce que les clients recherchent. Chaque offre porte un titre, un budget ou un taux horaire, une liste de compétences requises et une date de publication, et ce sont ces données que les recruteurs, chercheurs de marché et freelances eux-mêmes utilisent pour suivre la demande : quelles compétences sont en pénurie, quels taux commande une catégorie, et où apparaissent de nouveaux travaux de projet.
Ce guide vous montre comment scraper les offres Upwork avec JavaScript et Node.js en utilisant cheerio. Vous construisez un petit scraper fonctionnel qui récupère une page publique de recherche d'emploi Upwork via la Crawling API, parse le titre, le budget ou taux, les compétences requises, la date de publication et le lien de chaque offre, et exporte le résultat en JSON et CSV. La procédure complète reste limitée aux offres d'emploi publiques. Elle ne touche pas aux profils personnels de freelances, aux coordonnées, ni à quoi que ce soit derrière une connexion, et la section légalité vers la fin n'est pas du remplissage, alors lisez-la avant de pointer ceci sur un volume réel.
Ce que vous allez construire
Un script Node.js qui prend une URL publique de recherche d'emploi Upwork, récupère le HTML rendu via la Crawling API, et extrait un enregistrement structuré pour chaque offre d'emploi sur la page de résultats. Nous utilisons une recherche de « SEO expert » comme exemple fil conducteur et extrayons ces champs par offre :
- Title le titre de l'offre d'emploi, par exemple « SEO Expert for Technical Site Audit ».
- Budget or rate le budget à prix fixe ou la fourchette horaire affichée sur la carte, comme « $1,000 » ou « $25.00-$50.00 ».
- Skills la liste des balises de compétences requises attachées à l'offre.
- Posted time le temps de publication relatif, par exemple « Posted 3 hours ago ».
- Link l'URL vers la page de l'offre d'emploi individuelle.
Pourquoi une simple requête échoue sur Upwork
Si vous demandez une URL de recherche d'emploi Upwork avec un simple client HTTP, vous récupérez rarement les cartes d'emploi. Deux choses jouent contre vous. Premièrement, Upwork rend les résultats de recherche dans le navigateur avec JavaScript, de sorte que le HTML initial est une coquille presque vide jusqu'à ce que les scripts de la page s'exécutent. Deuxièmement, Upwork signale le trafic automatisé de façon agressive : les IP datacenter et les schémas de requêtes qui ne ressemblent pas à un vrai navigateur sont contestés, limités en taux ou bloqués avant d'atteindre les listes rendues.
Un scraper Upwork fonctionnel a donc besoin de deux choses en une seule requête : un navigateur qui rend réellement la page, et une IP que la plateforme lit comme un vrai visiteur. Vous pouvez assembler cela vous-même avec un navigateur sans interface graphique plus un pool de proxies résidentiels rotatifs, mais les assembler et les maintenir en bonne santé représente l'essentiel du travail. La Crawling API regroupe les deux en un seul appel : vous lui envoyez l'URL, elle rend la page derrière une IP de confiance, et retourne le HTML terminé pour que vous le parser avec cheerio. Pour en savoir plus sur la raison pour laquelle le rendu côté client casse les scrapers naïfs, voir comment crawler les sites JavaScript.
La Crawling API vous donne deux tokens : un token normal et un token JavaScript. Les pages de recherche Upwork sont rendues côté client, donc vous avez besoin du token JavaScript (rendu en vrai navigateur) pour que la page revienne remplie. Une requête avec un token normal retournera généralement une coquille vide.
Prérequis
Vous avez besoin de quelques éléments en place avant d'écrire du code. Aucun ne prend longtemps.
JavaScript et Node.js de base. Vous devez être à l'aise pour écrire et exécuter un script Node et installer des packages avec npm. Si vous êtes nouveau sur Node, le guide pour construire un scraper web avec Node.js couvre les bases que ce tutoriel suppose.
Node.js 16 ou ultérieur. Confirmez votre version avec node --version. Si vous ne l'avez pas, installez-le depuis le site Node.js ou via un gestionnaire de versions comme nvm.
Un compte Crawlbase et un token. Inscrivez-vous, ouvrez votre tableau de bord, et copiez votre token JavaScript depuis la page de documentation du compte. Le niveau gratuit vous donne 1 000 requêtes sans carte bancaire. Traitez le token comme un mot de passe : il authentifie vos requêtes, donc gardez-le hors du contrôle de version.
Configurer le projet
Créez un dossier de projet, initialisez-le, et installez les deux bibliothèques dont le scraper a besoin.
node --version mkdir upwork-jobs-scraper && cd upwork-jobs-scraper npm init -y npm install crawlbase cheerio
Deux dépendances font le travail : crawlbase est le client Node officiel pour la Crawling API, et cheerio parse le HTML retourné avec une API de style jQuery afin que vous puissiez extraire des champs individuels par sélecteur CSS. Créez un fichier nommé upwork-scraper.js dans ce dossier et ajoutez le code des étapes ci-dessous.
Étape 1 : Récupérer la page de recherche d'emploi rendue
Commencez par obtenir la page terminée. Importez la classe CrawlingAPI, initialisez-la avec votre token JavaScript, et demandez l'URL de recherche. Comme Upwork rend côté client, passez à l'API une courte attente pour que ses scripts se terminent avant que le HTML soit capturé. Vérifier le code de statut avant de parser rend les échecs bruyants plutôt que silencieux.
const { CrawlingAPI } = require('crawlbase'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); const upworkSearchURL = 'https://www.upwork.com/nx/search/jobs/?q=seo%20expert'; // Render the page in a real browser, then wait for scripts to settle const options = { ajax_wait: 'true', page_wait: 3000 }; api .get(upworkSearchURL, options) .then((response) => { if (response.statusCode === 200) { console.log(response.body.slice(0, 500)); } }) .catch((error) => console.error('API request error:', error));
Exécutez le script avec node upwork-scraper.js et vous devriez voir du vrai balisage d'offres Upwork en haut du corps, pas une coquille allégée. Cela confirme que le rendu fonctionne avant d'écrire un seul sélecteur. Les options ajax_wait et page_wait indiquent à l'API d'exécuter la page dans un vrai navigateur et de laisser le temps à ses scripts de remplir les cartes d'emploi. Si vous préférez que l'API parse les champs courants pour vous plutôt que d'écrire des sélecteurs à la main, passez l'option autoparse et vous récupérez du JSON structuré directement.
// Ask the API to render and parse, returning JSON const options = { ajax_wait: 'true', page_wait: 3000, autoparse: 'true' }; api .get(upworkSearchURL, options) .then((response) => { if (response.statusCode === 200) { console.log(JSON.parse(response.body)); } }) .catch((error) => console.error('API request error:', error));
Cette première requête vient de retourner une page de recherche Upwork entièrement rendue sans navigateur sans interface graphique ni proxy de votre côté. La Crawling API exécute la page dans un vrai navigateur, attend que son JavaScript remplisse les cartes d'emploi, fait tourner les IP résidentielles côté serveur, et gère les défis qu'Upwork lance aux scrapers, de sorte que vous obtenez du HTML terminé (ou du JSON autoparsé) en un seul appel. Pointez-la vers une recherche d'emploi publique sur le niveau gratuit d'abord.
Étape 2 : Analyser chaque offre d'emploi avec cheerio
Avec le HTML rendu en main, chargez-le dans cheerio et parcourez les cartes d'emploi. Upwork présente chaque offre dans un conteneur article répétitif sur la page de résultats de recherche, donc vous sélectionnez chaque carte, puis lisez le titre, le budget ou taux, les compétences, la date de publication et le lien depuis l'intérieur. Lire chaque champ de façon défensive empêche qu'une valeur manquante fasse planter l'exécution.
const cheerio = require('cheerio'); function parseJobs(html) { const $ = cheerio.load(html); const jobs = []; const cards = $('article[data-test="JobTile"]'); cards.each((index, element) => { const card = $(element); const job = {}; // Title and link share one anchor const titleLink = card.find('[data-test="job-tile-title-link"]'); job.title = titleLink.text().trim(); const href = titleLink.attr('href'); job.link = href ? new URL(href, 'https://www.upwork.com').href : ''; // Posted time, e.g. "Posted 3 hours ago" job.posted = card .find('[data-test="job-pubilshed-date"]') .text() .replace(/\s+/g, ' ') .trim(); // Budget (fixed price) or hourly rate range const budget = card .find('[data-test="is-fixed-price"] strong') .last() .text() .trim(); const hourly = card .find('[data-test="job-type-label"]') .text() .trim(); job.budget = budget || hourly || 'Not specified'; // Required skill tags job.skills = card .find('[data-test="token"] span') .map((i, el) => $(el).text().trim()) .get() .filter(Boolean); if (job.title) jobs.push(job); }); return jobs; }
Quelques détails maintiennent cette fidélité à la page. Le titre et le lien proviennent du même ancre job-tile-title-link, donc une seule recherche vous donne les deux, et le href relatif est résolu en URL absolue pour qu'il fonctionne en dehors de la page. La date de publication est lue depuis l'attribut job-pubilshed-date (l'orthographe propre à Upwork, conservée telle quelle pour que le sélecteur corresponde), avec l'espace blanc normalisé. La gestion du budget couvre les deux types d'offres : une offre à prix fixe expose son montant dans le bloc is-fixed-price, tandis qu'une offre horaire porte son label de taux dans job-type-label, de sorte que le parser prend celui qui est présent. Les compétences sont collectées depuis les balises token dans un tableau propre.
Les attributs data-test d'Upwork sont plus stables que les noms de classe générés, mais ils changent quand même sans préavis. Traitez les sélecteurs ci-dessus comme un modèle de départ, pas un contrat. Quand un champ revient vide, réinspectez la page en direct dans les outils de développement de votre navigateur et mettez à jour le sélecteur. La maintenance périodique des sélecteurs est normale pour tout scraper en production, pas le signe que quelque chose est cassé.
Étape 3 : Assembler le script complet avec export JSON et CSV
Assemblez maintenant la récupération et le parsing en un seul script fonctionnel, puis écrivez les enregistrements sur disque en JSON et CSV. La récupération avec autoparse désactivé retourne du HTML rendu brut, ce que le parser cheerio attend.
const fs = require('fs'); const { CrawlingAPI } = require('crawlbase'); const cheerio = require('cheerio'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); async function crawl(pageUrl) { const options = { ajax_wait: 'true', page_wait: 3000 }; const response = await api.get(pageUrl, options); if (response.statusCode === 200) return response.body; console.error(`Request failed: ${response.statusCode}`); return null; } function toCsv(rows) { const headers = ['title', 'budget', 'skills', 'posted', 'link']; const escape = (value) => `"${String(value).replace(/"/g, '""')}"`; const lines = [headers.join(',')]; for (const row of rows) { const flat = { ...row, skills: row.skills.join('; ') }; lines.push(headers.map((h) => escape(flat[h])).join(',')); } return lines.join('\n'); } async function main() { const url = 'https://www.upwork.com/nx/search/jobs/?q=seo%20expert'; const html = await crawl(url); if (!html) return; const jobs = parseJobs(html); fs.writeFileSync('upwork-jobs.json', JSON.stringify(jobs, null, 2)); fs.writeFileSync('upwork-jobs.csv', toCsv(jobs)); console.log(`Saved ${jobs.length} job postings to JSON and CSV`); } main();
Collez la fonction parseJobs de l'étape 2 dans le même fichier pour que main puisse l'appeler. Exécutez-le avec node upwork-scraper.js et vous obtenez deux fichiers : upwork-jobs.json avec les enregistrements structurés complets et upwork-jobs.csv prêt à ouvrir dans un tableur. L'assistant toCsv aplatit le tableau des compétences dans une seule cellule délimitée par des points-virgules, cite chaque champ, et double les guillemets intégrés, ce qui importe car les titres d'emploi contiennent souvent des virgules.
À quoi ressemble le résultat
Le fichier JSON contient un objet par offre dans l'ordre des résultats de recherche, chacun avec le titre, le budget ou taux, les compétences, la date de publication et le lien.
[ { "title": "SEO Expert for Technical Site Audit", "budget": "$1,000", "skills": ["SEO", "Technical SEO", "On-Page SEO"], "posted": "Posted 3 hours ago", "link": "https://www.upwork.com/jobs/SEO-Expert-Technical-Site-Audit_~012abc" }, { "title": "Ongoing SEO Content Strategy", "budget": "Hourly: $25.00-$50.00", "skills": ["SEO", "Content Writing", "Keyword Research"], "posted": "Posted yesterday", "link": "https://www.upwork.com/jobs/Ongoing-SEO-Content-Strategy_~034def" } ]
Le CSV reflète les mêmes lignes avec une ligne d'en-tête, de sorte qu'il s'insère directement dans Excel, Google Sheets, ou tout pipeline de données qui lit les fichiers délimités.
title,budget,skills,posted,link "SEO Expert for Technical Site Audit","$1,000","SEO; Technical SEO; On-Page SEO","Posted 3 hours ago","https://www.upwork.com/jobs/SEO-Expert-Technical-Site-Audit_~012abc" "Ongoing SEO Content Strategy","Hourly: $25.00-$50.00","SEO; Content Writing; Keyword Research","Posted yesterday","https://www.upwork.com/jobs/Ongoing-SEO-Content-Strategy_~034def"
Passer à l'échelle sur les recherches et les pages
Une seule recherche est une démonstration ; un vrai travail parcourt plusieurs requêtes et plusieurs pages de résultats. L'URL de recherche Upwork prend un paramètre de requête q et un paramètre page, donc vous pouvez construire une liste de recherches, parcourir chaque page, parser chaque page avec la même fonction, et étiqueter chaque ligne avec sa requête avant d'exporter. Comme chaque page de résultats de recherche partage la même structure de carte, le parser que vous avez déjà écrit fonctionne sur toutes sans modifications.
const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function scrapeSearch(query, pages) { const all = []; for (let page = 1; page <= pages; page++) { const url = `https://www.upwork.com/nx/search/jobs/?q=${encodeURIComponent( query )}&page=${page}`; const html = await crawl(url); if (!html) continue; const rows = parseJobs(html).map((j) => ({ query, ...j })); all.push(...rows); await sleep(2000); } return all; } scrapeSearch('seo expert', 3).then((rows) => { console.log(`Collected ${rows.length} postings across pages`); });
Ce schéma s'applique directement à la recherche sur le marché de l'emploi et au travail de recrutement. Pour la même approche sur d'autres plateformes d'embauche, voir comment scraper les offres Indeed et comment scraper les offres Monster avec Python, qui couvrent le côté page de résultats de ces sites.
Rester non bloqué
Même avec le rendu géré, Upwork surveille le trafic qui ressemble à un scraper. Quelques habitudes maintiennent une exécution saine, et elles s'appliquent à toute cible commerciale difficile.
-
Cadencez vos requêtes. Introduisez un délai entre les récupérations de pages plutôt que de marteler les pages dans une boucle serrée, comme le fait l'appel
sleepci-dessus. Répartir les requêtes est le facteur unique le plus important pour rester sous les limites de taux d'Upwork. - Misez sur la rotation. Un pool d'IP résidentielles répartit les requêtes sur de nombreuses adresses d'utilisateurs réels de sorte qu'aucune ne déclenche une limite ou un défi. La Crawling API gère cela pour vous ; si vous montez votre propre stack, c'est la partie à bien faire.
- Lisez les codes de statut. Une exécution qui commence à retourner des défis ou des réponses non-200 vous dit que le taux actuel ou le niveau d'IP n'est plus suffisant. Traitez cela comme un signal pour reculer, pas du bruit à ignorer.
Pour le guide complet, voir comment scraper des sites web sans se faire bloquer.
Est-il légal de scraper Upwork ?
Le fait de scraper Upwork est autorisé dépend des conditions d'utilisation d'Upwork, de votre juridiction, et de ce que vous faites avec les données. Les conditions d'Upwork restreignent l'accès automatisé et le scraping, donc collecter des données peut aller à l'encontre de ces conditions quel que soit le soin apporté à votre outillage. Aucun code ici ne change cela ; il fait juste fonctionner la partie technique. Lisez les Conditions d'utilisation d'Upwork et son robots.txt, et traitez les deux comme la limite de ce que vous collectez. Respectez les attentes de taux déclarées d'Upwork et gardez votre volume de requêtes suffisamment bas pour ne pas surcharger ses serveurs.
Ce guide est délibérément limité aux offres d'emploi publiques uniquement : le titre, le budget ou taux, les compétences requises, la date de publication et le lien de l'offre que tout le monde peut voir sur une page de recherche publique sans se connecter. Il ne touche pas aux profils de freelances, noms, photos, coordonnées, historique de travail, gains, ni à aucune autre donnée personnelle, et ne couvre pas ce qui se trouve derrière une connexion. Cette limite compte légalement. Les offres d'emploi sont des annonces professionnelles publiques, mais les profils de freelances sont des données personnelles, et dès que vous collectez des données sur des individus identifiables, vous êtes soumis à la loi sur la vie privée. En vertu du RGPD dans l'UE et du CCPA en Californie, le scraping et le stockage de données personnelles nécessitent une base légale, et les personnes ont des droits d'accès et de suppression. La position sûre et défendable est de rester sur les offres publiques, ne jamais construire de profils d'individus, et ne jamais collecter des coordonnées pour une prospection non sollicitée.
Si votre projet nécessite plus que des offres publiques, ou si vous voulez une structure garantie et des droits commerciaux, cherchez un canal officiel plutôt qu'un scraper plus astucieux. Upwork propose un programme entreprise et API pour un accès aux données sanctionné avec des conditions d'utilisation claires, ce qui est le bon outil quand vous avez besoin de volume ou d'une base contractuelle. Pour la recherche de marché agrégée et non personnelle (quelles compétences sont demandées, quels taux commande une catégorie, comment le volume d'offres évolue), les offres publiques dépouillées de toute identité individuelle sont généralement suffisantes, et c'est exactement ce que ce scraper collecte.
Points clés
- Upwork rend les résultats de recherche côté client et bloque fortement. Une requête simple retourne une coquille vide ou un défi, donc vous devez rendre la page derrière une IP de confiance avant de la parser.
-
La Crawling API fait les deux en un seul appel. Utilisez le token JavaScript avec
ajax_waitetpage_waitpour que les cartes d'emploi se remplissent ; prenez du HTML brut à parser vous-même ou passezautoparse: 'true'pour du JSON. - cheerio extrait les champs. Sélectionnez chaque tuile d'emploi, puis lisez le titre, le budget ou taux, les compétences, la date de publication et le lien, et attendez-vous à ce que les sélecteurs évoluent avec le temps.
- Exportez en JSON et CSV. Écrivez des enregistrements structurés dans les deux formats, en aplatissant le tableau des compétences et en citant les champs CSV pour que les titres chargés de virgules restent intacts.
- Restez sur les offres publiques uniquement. Collectez des données d'emploi publiques, jamais les profils ou détails personnels de freelances, respectez les CGU et le robots.txt d'Upwork, et tenez compte du RGPD et du CCPA dès que des données personnelles sont impliquées.
Foire aux questions
Quelles données puis-je scraper légalement sur Upwork ?
Tenez-vous aux offres d'emploi publiques : le titre, le budget ou taux horaire, les compétences requises, la date de publication et le lien vers chaque offre, tous visibles sur une page de recherche publique sans se connecter. Évitez les profils de freelances, noms, photos, coordonnées, historique de travail et gains, car ce sont des données personnelles soumises à la loi sur la vie privée. Ce guide est limité aux offres publiques uniquement pour cette raison précise.
Pourquoi une simple requête renvoie-t-elle des données incomplètes depuis Upwork ?
Parce qu'Upwork rend ses résultats de recherche côté client avec JavaScript et conteste le trafic automatisé. Une requête HTTP brute depuis une IP datacenter retourne généralement une coquille vide ou une page de blocage plutôt que les cartes d'emploi. Pour obtenir une page complète, vous devez la rendre dans un vrai navigateur derrière une IP de confiance, ce que la Crawling API gère pour vous quand vous utilisez le token JavaScript.
Ai-je besoin du token normal ou du token JavaScript ?
Utilisez le token JavaScript. Les pages de recherche Upwork sont rendues dans le navigateur, donc une requête avec un token normal retourne une coquille non remplie. Le token JavaScript exécute la page dans un vrai navigateur, et passer ajax_wait avec un court page_wait donne aux scripts de la page le temps de remplir les cartes d'emploi avant que le HTML soit capturé.
Mes sélecteurs renvoient des valeurs vides. Qu'est-ce qui a changé ?
Presque certainement le balisage d'Upwork. Même les attributs data-test utilisés ici peuvent changer sans préavis, de sorte que des sélecteurs qui fonctionnaient le mois dernier peuvent se casser. Réinspectez une page en direct dans les outils de développement de votre navigateur et mettez à jour les sélecteurs. La maintenance périodique des sélecteurs est normale pour tout scraper en production.
Puis-je scraper des profils de freelances ou des coordonnées sur Upwork ?
Non, et ce guide ne le couvre pas. Les profils de freelances, noms, coordonnées, historique de travail et gains sont des données personnelles, pas des annonces professionnelles publiques, donc les scraper soulève des obligations RGPD et CCPA et va à l'encontre des conditions d'Upwork. Limitez votre collecte aux offres d'emploi publiques, ne construisez jamais de profils d'individus, et ne collectez jamais de coordonnées pour une prospection non sollicitée. Pour un accès sanctionné à plus que des offres publiques, utilisez l'API officielle ou le programme entreprise d'Upwork.
Comment éviter d'être bloqué en scrapant Upwork ?
Maintenez un faible taux de requêtes par IP, ajoutez des délais entre les récupérations de pages, et routez via des IP résidentielles rotatives pour qu'aucune adresse ne déclenche une limite de taux ou un défi. La Crawling API gère la rotation, un pool d'IP de confiance, et la gestion des défis pour vous ; si vous montez votre propre stack, c'est la partie dans laquelle investir. Surveillez les codes de statut et reculez quand vous commencez à voir des réponses non-200.
Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.
Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.
