Send a LinkedIn or Indeed search, a job page or a career page. Web Unlocker returns it as a visitor sees it: 60 LinkedIn jobs or 50 on Indeed, billed as one page. No login, no job board API. A failed page costs nothing.
1,000 free requests · Pay only on success · 195+ countries · Claude ready
Job boards take 3 to 30 seconds. It is still working, no need to click again.
Ready for your own searches? Get an API key or book a demo.
Tell us a little about your team and get:
A LinkedIn job search page carries 60 job cards with title, company, location and posted date, and Web Unlocker bills it as one page. LinkedIn job scrapers on Apify and Bright Data bill every job as a record of its own, and ScraperAPI charges 30 credits for a LinkedIn page.
One page, 60 jobs.
167 pages, 10,000 jobs.
Need the full description? Each job's own page is one more request, so 1,000 descriptions cost $0.98. An Indeed search page holds 50 jobs at the same price, and so does a company's career page.
What 10,000 LinkedIn jobs from search pages cost
167 search pages of 60 jobs, at published prices
Ready to run it on your own searches? Get your API key and free requests.
One hundred job pages asked for one after another, no retries: LinkedIn searches in 25 cities, the next pages of a search, job pages with the full description, Indeed searches and job pages, and company career pages on Lever, Ashby, Greenhouse and Workday.
A page that does not come back costs $0.
You pay for pages, not for tries.
Keep a LinkedIn search to the last 24 hours and every job on the page is a day old or newer, marked Be an early applicant while it is fresh. Your script asks for that page as often as you like: a check every hour is 24 pages a day, $0.71 a month.
Job datasets refresh weekly or monthly.
Your page is this hour's.
Job board scraping without a browser fleet of your own: Web Unlocker returns the page, and the job cards are plain HTML you can read with a few lines of code. The same request works on LinkedIn, Indeed and career pages.
Fill in the form above. The key arrives by email within one business day.
A LinkedIn or Indeed search, the next pages of a search, a job's own page for its description, or a company's career page.
Cut the job cards out of the page and keep the fields you need: title, company, location, date and link.
pip install requestsGOLOGIN_SCRAPING_API_KEY=your_key python linkedin_jobs.pyhttps://parsing.webunlocker.gologin.com/v1/scrapeimport html
import json
import os
import re
import time
from urllib.parse import urlencode
import requests
query = "data analyst"
where = "Chicago, IL"
def scrape(url):
for attempt in range(3):
response = requests.get(
"https://parsing.webunlocker.gologin.com/v1/scrape",
params={"url": url, "geo": "us", "format": "json"},
headers={"apikey": os.environ["GOLOGIN_SCRAPING_API_KEY"]},
timeout=90,
)
page = response.json().get("data", {}).get("html", "") if response.ok else ""
if "urn:li:jobPosting:" in page:
return page
time.sleep(5)
raise RuntimeError("No jobs came back, try again later")
def text(pattern, card):
found = re.search(pattern, card, re.S)
return " ".join(html.unescape(re.sub(r"<[^>]+>", " ", found.group(1))).split()) if found else None
search = "https://www.linkedin.com/jobs/search?" + urlencode({"keywords": query, "location": where})
page = scrape(search)
jobs = []
for card in page.split("<li")[1:]:
job_id = re.search(r"urn:li:jobPosting:(\d+)", card)
if not job_id:
continue
jobs.append({
"title": text(r'base-search-card__title[^>]*>(.*?)</h3>', card),
"company": text(r'base-search-card__subtitle[^>]*>(.*?)</h4>', card),
"location": text(r'job-search-card__location[^>]*>(.*?)</span>', card),
"posted": text(r"<time[^>]*>(.*?)</time>", card),
"date": text(r'datetime="([^"]+)"', card),
"url": "https://www.linkedin.com/jobs/view/" + job_id.group(1),
})
print(json.dumps({"query": query, "where": where, "jobs_on_page": len(jobs), "jobs": jobs}, indent=2, ensure_ascii=False))GOLOGIN_SCRAPING_API_KEY=your_key node linkedin-every-page.mjshttps://parsing.webunlocker.gologin.com/v1/scrapeimport { writeFileSync } from 'node:fs';
const query = 'registered nurse';
const where = 'Texas, United States';
async function scrape(url) {
for (let attempt = 1; attempt <= 3; attempt++) {
const params = new URLSearchParams({ url, geo: 'us', format: 'json' });
const response = await fetch(`https://parsing.webunlocker.gologin.com/v1/scrape?${params}`, {
headers: { apikey: process.env.GOLOGIN_SCRAPING_API_KEY },
});
const body = response.ok ? await response.json() : null;
if (body?.success) return body.data?.html ?? '';
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error(`Nothing came back for ${url}, try again later`);
}
const text = (card, pattern) =>
(card.match(pattern)?.[1] ?? '').replace(/<[^>]+>/g, ' ').replace(/&/g, '&').replace(/'/g, "'").replace(/\s+/g, ' ').trim();
const cell = (value) => `"${value.replace(/"/g, '""')}"`;
const jobs = new Map();
let pages = 0;
for (let start = 0; start < 1000; start += 10) {
const params = new URLSearchParams({ keywords: query, location: where, start });
const page = await scrape(`https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?${params}`);
pages += 1;
const cards = page.split('<li').slice(1).filter((card) => card.includes('urn:li:jobPosting:'));
if (cards.length === 0) break;
for (const card of cards) {
const id = card.match(/urn:li:jobPosting:(\d+)/)[1];
jobs.set(id, [
text(card, /base-search-card__title[^>]*>([\s\S]*?)<\/h3>/),
text(card, /base-search-card__subtitle[^>]*>([\s\S]*?)<\/h4>/),
text(card, /job-search-card__location[^>]*>([\s\S]*?)<\/span>/),
card.match(/datetime="([^"]+)"/)?.[1] ?? '',
`https://www.linkedin.com/jobs/view/${id}`,
]);
}
}
const rows = [['title', 'company', 'location', 'date', 'url'], ...jobs.values()];
writeFileSync('linkedin-jobs.csv', rows.map((row) => row.map(cell).join(',')).join('\n'));
console.log(`${jobs.size} jobs from ${pages} pages saved to linkedin-jobs.csv`);
console.log(rows.slice(1, 4).map((row) => row.slice(0, 3).join(' | ')).join('\n'));GOLOGIN_SCRAPING_API_KEY=your_key node two-boards.mjshttps://parsing.webunlocker.gologin.com/v1/scrapeconst query = 'software engineer';
const city = 'Austin, TX';
async function scrape(url, marker) {
for (let attempt = 1; attempt <= 3; attempt++) {
const params = new URLSearchParams({ url, geo: 'us', format: 'json' });
const response = await fetch(`https://parsing.webunlocker.gologin.com/v1/scrape?${params}`, {
headers: { apikey: process.env.GOLOGIN_SCRAPING_API_KEY },
});
const page = response.ok ? (await response.json()).data?.html : '';
if (page?.includes(marker)) return page;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error(`Nothing came back for ${url}, try again later`);
}
const text = (markup = '') => markup.replace(/<[^>]+>/g, ' ').replace(/&/g, '&').replace(/'/g, "'").replace(/\s+/g, ' ').trim();
const key = (job) => `${job.company} ${job.title}`.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
const linkedin = await scrape(`https://www.linkedin.com/jobs/search?${new URLSearchParams({ keywords: query, location: city })}`, 'urn:li:jobPosting:');
const fromLinkedin = linkedin
.split('<li')
.filter((card) => card.includes('urn:li:jobPosting:'))
.map((card) => ({
board: 'LinkedIn',
title: text(card.match(/base-search-card__title[^>]*>([\s\S]*?)<\/h3>/)?.[1]),
company: text(card.match(/base-search-card__subtitle[^>]*>([\s\S]*?)<\/h4>/)?.[1]),
location: text(card.match(/job-search-card__location[^>]*>([\s\S]*?)<\/span>/)?.[1]),
url: `https://www.linkedin.com/jobs/view/${card.match(/urn:li:jobPosting:(\d+)/)[1]}`,
}));
const indeed = await scrape(`https://www.indeed.com/jobs?${new URLSearchParams({ q: query, l: city, limit: 50 })}`, 'mosaic-provider-jobcards"]=');
const start = indeed.indexOf('=', indeed.indexOf('mosaic-provider-jobcards"]=')) + 1;
const cards = JSON.parse(indeed.slice(start, indeed.indexOf(';\n', start)));
const results = cards.metaData.mosaicProviderJobCardsModel.results;
const fromIndeed = results.filter((job, i) => results.findIndex((other) => other.jobkey === job.jobkey) === i).map((job) => ({
board: 'Indeed',
title: job.title,
company: job.company,
location: job.formattedLocation,
url: `https://www.indeed.com/viewjob?jk=${job.jobkey}`,
}));
const jobs = new Map();
for (const job of [...fromLinkedin, ...fromIndeed]) {
const seen = jobs.get(key(job));
if (seen) seen.boards.add(job.board);
else jobs.set(key(job), { ...job, boards: new Set([job.board]) });
}
const onBoth = [...jobs.values()].filter((job) => job.boards.size === 2);
console.log(`LinkedIn ${fromLinkedin.length} + Indeed ${fromIndeed.length} = ${jobs.size} different jobs, ${onBoth.length} on both boards, 2 pages billed`);
for (const job of onBoth.slice(0, 5)) console.log(`${job.title} | ${job.company} | ${job.location}`);Ready to run it on your own searches? Get your API key and free requests.
LinkedIn has no job search API for your app, ready job scrapers bill every job, and a free job scraper library leaves the proxies to you. Here is what each route takes for the same jobs.
| What it takes | JobSpy | Apify | Bright Data | |
|---|---|---|---|---|
| You pay for | A page | Your proxies | Each job | Each record |
| 10,000 LinkedIn jobs from searches | $0.16 | Free + proxies | $10 | $15 |
| Jobs per billed unit | 60 | None | 1 | 1 |
| The whole page in HTML or Markdown | Yes | No | No | No |
| Runs without proxies of your own | Yes | No | Yes | Yes |
| Free to start | 1,000 pages | Free | $5 a month | 5,000 records |
Scraping one job board in depth? See the Indeed scraper, or any other site with Web Unlocker at the same price.
One account, one proxy network, one ecosystem. Pick the product the job needs.
Scraping other sites too? See the Indeed, Google Maps and Reddit scrapers, or every web scraping tool.
A program that loads job board pages and pulls out the postings as data: title, company, location, salary, posted date, description and link. With Web Unlocker the loading part is one API call per page, on LinkedIn, Indeed and company career pages alike, and you keep the fields you need.
Your script asks for a job board's search page, reads the job cards on it, and follows each card to the job's own page when it needs the full description. Web Unlocker loads every page in a real browser from the country you pick and returns the HTML, so your code only parses.
Web scraping job postings starts with the search a visitor would run, a job title and a place. Send its address to Web Unlocker. A LinkedIn search page returns 60 jobs and an Indeed page 50, each with title, company, location and date. The code above does exactly that in Python and Node.js.
Job boards and aggregators collect postings from many sites into one search. Recruiting agencies and sales teams watch who is hiring and for what. Analysts track salaries and demand by role and city, and AI job agents match people to fresh openings. Each of them needs the same thing: the job pages, today.
Yes. LinkedIn job scraping works on public pages: the job search and job pages open without a login, the first search page lists 60 jobs, the next pages 10 each, and a job's page carries the full description, seniority, employment type and the number of applicants. Web Unlocker returns them the way a visitor sees them.
There is no LinkedIn jobs API or LinkedIn job search API for reading job search results. LinkedIn's Job Posting API is for partners that post jobs to LinkedIn, and it is not taking new partners. To read public job postings, a job scraper loads the same pages a visitor opens.
To scrape jobs from LinkedIn, send the search address to the job scraper API with the requests library, split the page into job cards and read the title, company, location and date from each. The Python tab above is a complete LinkedIn job scraper in Python, tested on the live API: 60 jobs in one request.
LinkedIn answers a burst of requests from one address with errors, often after about ten pages, so a script on one server stops there. Web Unlocker loads each page in a real browser on a residential IP in the country you pick. The Node.js tab walked one search to its end: 67 pages, 660 jobs.
LinkedIn, Indeed, Dice, Wellfound, Built In and SimplyHired search pages came back in our tests, and so did career pages on Lever, Ashby, Greenhouse and Workday. Glassdoor and ZipRecruiter did not come back reliably from a US address, so we do not list them.
Open the job's own page through Web Unlocker: linkedin.com/jobs/view/ followed by the job id, or Indeed's job page. It returns the description with the title, company, location and posted date. One page per job, 1,000 descriptions for $0.98.
Yes. Ask for Markdown and Web Unlocker returns the job page as text, ready for an agent or Claude. The Gologin scraping skill lets Claude call it for you, and MCP & AI Skills turns a plain request into a working job scraper.
Job postings are published to be read, and a job scraper reads the public pages a visitor sees, without a login. The rules differ by country and by what you do with the data, so check your own use against them. Web Unlocker returns the page; what you collect is your call.
Under $1 per 1,000 pages on the job scraper API, and the same on every job board. A LinkedIn search page holds 60 jobs, so 10,000 jobs from search pages cost 16 cents. A page that fails to load costs nothing. Plans start at $49 a month for 50,000 requests.
Yes, to start: Web Unlocker comes with 1,000 free requests, enough for 60,000 LinkedIn jobs from search pages or 1,000 full job descriptions. Open source job scraper libraries are free too, and you bring the proxies and keep them working.
It depends on what you need. Job scraping services and job data APIs like Apify and Bright Data sell ready rows and bill every job as a record; free job scraping software needs proxies of your own. Web Unlocker is a job board scraper and jobs scraping API that returns the whole page at one price, on any job board, so you decide which fields you keep.
JobSpy is a free open-source Python library for LinkedIn, Indeed, Glassdoor and ZipRecruiter. It runs on your own machine, so the proxies are yours to find and keep working, and its own notes say LinkedIn answers bursts of requests with errors. Web Unlocker brings the network and returns the page, and you pay only for pages that come back.
The LinkedIn jobs scrapers on Apify return ready rows and bill every job, from $1 per 1,000 jobs. Web Unlocker bills the page: a LinkedIn search page holds 60 jobs, so 10,000 jobs cost 16 cents, and a few lines of code, like the tabs above, cut out the fields.
Tell us a little about your team and get: