需要 Python >= 3.10;LinkedIn、Indeed、BDJobs 使用 socks 代理时需额外执行 pip install "requests[socks]";部分招聘网站的搜索结果不含职位描述,需开启 fetch_description 逐条抓取,速度更慢且更易触发限流;Google Jobs 当前不可用
JobSpy
speedyapplyMIT活跃维护一个 Python 招聘信息抓取库,能从 LinkedIn、Indeed、Glassdoor 等八个招聘网站批量采集职位数据,省去逐个平台爬取的麻烦。
Jobs scraper library for LinkedIn, Indeed, Glassdoor, ZipRecruiter, Bayt, Naukri, BDJobs & HelloWork
pip install -U python-jobspyREADME 未说明,纯 Python 库,无 GPU 或显存要求
一个 Python 库,并发抓取多个招聘网站的职位并汇总成 DataFrame
一个 Python 招聘信息抓取库,能从 LinkedIn、Indeed、Glassdoor 等八个招聘网站批量采集职位数据,省去逐个平台爬取的麻烦。
核心能力
- 并发抓取 LinkedIn、Indeed、Glassdoor 等八个招聘网站
- 把抓取结果聚合为 pandas DataFrame,可直接导出 CSV
- 支持代理轮换,绕过网站封禁
- 提供 jobspy 命令行工具,参数与 scrape_jobs 一致
- 附带 mcp-jobspy MCP 服务器,供 AI 客户端调用
容易踩的坑
- v1.4.1最新正式版2026-10-23 · 2026-10-23 05:11 UTC
- HelloWork returns jobs again after the site changed its result cards (#433)
- ZipRecruiter returns jobs again after the site moved to a new search page layout; the old layout still works (#435, closes #434)
- LinkedIn pay read from a job description keeps its cents, and a range with "k" on one side only, like "$90,000 - $120k", is read (#421)
Thanks @tararuD and @sghanna for the fixes, and @daveroama for the ZipRecruiter report.
- v1.4.02026-10-19 · 2026-10-19 18:20 UTC
查看更新说明
- New
jobspycommand: search from the shell, one JSON line per job;-osaves the full records (#422) - New MCP server for AI agents:
uvx --from "python-jobspy[mcp]" mcp-jobspy, listed in the MCP Registry asio.github.speedyapply/jobspy(#423, #424) jobs.attrs["errors"]andjobs.attrs["warnings"]say which boards failed or changed the search (#419)- A blocked board says so:
response status code 429 (blocked: use a proxy)(#423) - Invalid
scrape_jobsarguments raise a one-lineValueError, unknown ones aTypeError(#415) site_name,job_typeanddescription_formattake the exact lower-case value (#415)- A search with no jobs returns the usual columns with no rows (#418)
- Logging uses the standard
loggingmodule, with loggers namedjobspy.<board>;create_loggerandset_logger_levelare removed (#414) - Type hints ship with the package (
py.typed) (#416) JobPostfields are the output columns (#418, closes #265)min_amountandmax_amountare always floats, and an empty description isNone(#416, #418)- Each board checks what it reads; a job that fails the check is skipped with a warning (#416)
- A proxy passed in
proxiesis used even when the environment sets one (#415) - LinkedIn: pay written in the description fills in when the job page shows none, on US searches (#418)
- HelloWork, Bayt and Naukri parsing fixes (#416, #418)
- Removed the job types
nightsandsummerand ten countries with no Indeed or Glassdoor site (#415) - For code that imports internals:
JobResponse,desired_order,extract_salaryandformat_descriptionare removed; scraper classes take no constructor arguments (#415, #416, #418)
- New
- v1.3.02026-10-08 · 2026-10-08 07:21 UTC
查看更新说明
- New board: HelloWork (France),
site_name="hellowork"(#411) - Unknown
scrape_jobsarguments warn and suggest the right name;verbosedefaults to 1 so warnings show (#402) - Google is out of the default boards;
site_name="ziprecruiter"is accepted (#402) - Proxy lists no longer get challenged on switching proxy;
socks5h://andsocks4://proxies work;ca_certis used on every board (#402) is_remoteno longer marks hybrid jobs remote on Indeed, Glassdoor, Bayt and BDJobs (#403, #405, #407, #408)hours_oldno longer returns older jobs on Indeed and Glassdoor, and takes far fewer requests on Bayt and Naukri (#403, #405, #407, #408)- A job page that fails is logged and the job is kept, on every board (#404, #405, #406, #407, #408)
date_postedis a UTC date on every board;description_format="plain"returns plain text on Indeed, Glassdoor and Bayt (#403, #405, #407)- Indeed: "from", "up to" and single-amount salaries keep their numbers; contract and temporary match jobs outside the US; company rating filled (#403)
- LinkedIn: exact
offset, paging stops at the last page, a throttled page is logged;is_remoteandjob_typewarn, since the logged-out search ignores both (#404) - Glassdoor: descriptions fetched 25 jobs per request, 100 jobs a page with an exact
offset,distancesent, company details filled (#405) - ZipRecruiter: nationwide jobs are returned;
job_type="internship"works (#406) - Bayt:
easy_applyuses the site's filter; part time and temporary supported; company size, industry and experience range filled (#407) - Naukri: part-time and contract jobs are no longer returned as full time;
job_url_directno longer needsfetch_description(#408) - BDJobs: jobs with no location and promoted jobs are returned; apply emails and employer links filled (#408)
user_agentis sent as given on every board (#409)- For code that imports internals:
BaytScraperis nowBayt;CompensationInterval.get_interval,extract_job_typeandget_enum_from_valueare removed (#405, #407, #409)
thanks @Kev744
- New board: HelloWork (France),
- v1.2.02026-10-02 · 2026-10-02 05:27 UTC
查看更新说明
- Installs on Python 3.13 and 3.14; dependency pins removed (#390)
- Glassdoor, ZipRecruiter, Bayt, Naukri and BDJobs return jobs again (#394, #399, #396, #398, #397)
- New
fetch_descriptionflag for boards whose search results don't include the description; replaceslinkedin_fetch_description. Glassdoor descriptions now need it (#394) - ZipRecruiter, Bayt, Naukri and BDJobs honour more filters (
location,hours_old,job_type,is_remote,easy_apply,offset) and fill more fields - Indeed: filters combine instead of only one being sent; salaries outside the US (#392)
- LinkedIn: salary read from the job page; unlisted countries and a missing seniority level no longer crash (#380, #391, #354)
- One board failing no longer discards the others' jobs: boards log errors instead of raising (#400)
- One 15 s request timeout and no automatic retries (#400)
is_remoteon Indeed and LinkedIn no longer guesses from the description (#400)- Salaries keep cents (#380)
tls-clientreplaced bycurl_cffi(#395)- Google returns no jobs: Google Jobs now requires JavaScript (#284)
- Deprecated, to be removed in 2.0:
linkedin_fetch_description, thejobspy.exceptionclasses,Country.US_CANADA(#401)
thanks @ZuoyunZheng, @lixianphys, @thesandrex, @Kaushik4141, @m3ridian-zero, @berkaygemici, @Recursing, @sankalp-b1401, @codechrl, @FullStackist
- v1.1.822026-09-30 · 2026-09-30 08:28 UTC
查看更新说明
Published to PyPI on 2025-07-28; this GitHub Release was added afterwards (#338). Version 1.1.81 was never published.
- BDJobs (Bangladesh) job board (#280)
- SOCKS5 proxy support (#266)
- Glassdoor: rotate user agent / update headers for 403 responses (#274)
thanks @itsShrizon, @Remote-Radical, @letrongtai902
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
JobSpy is a job scraping library that aggregates jobs from popular job boards with one tool:
- Scrapes job postings from LinkedIn, Indeed, Glassdoor, ZipRecruiter, Bayt, Naukri, BDJobs & HelloWork concurrently
- Aggregates the job postings in a dataframe
- Proxy support to bypass blocking
Installation
pip install -U python-jobspy
Python version >= 3.10 required
Usage
from jobspy import scrape_jobs
jobs = scrape_jobs(
site_name=["indeed", "linkedin", "zip_recruiter", "glassdoor"], # also "bayt", "naukri", "bdjobs", "hellowork"
search_term="software engineer",
location="San Francisco, CA",
results_wanted=20,
hours_old=72,
country_indeed="USA",
# fetch_description=True, # read each job's page on boards whose search results have no description (slower)
# proxies=["user:pass@host:port", "localhost"],
)
print(f"Found {len(jobs)} jobs")
print(jobs.head())
jobs.to_csv("jobs.csv", index=False)
Output
SITE TITLE COMPANY LOCATION JOB_TYPE INTERVAL MIN_AMOUNT MAX_AMOUNT JOB_URL DESCRIPTION
indeed Software Engineer AMERICAN SYSTEMS Arlington, VA, USA None yearly 150000 200000 https://www.indeed.com/viewjob?jk=5e409e577046... THIS POSITION COMES WITH A 10K SIGNING BONUS!...
indeed Senior Software Engineer TherapyNotes.com Philadelphia, PA, USA fulltime yearly 110000 135000 https://www.indeed.com/viewjob?jk=da39574a40cb... About Us TherapyNotes is the national leader i...
linkedin Software Engineer - Early Career Lockheed Martin Sunnyvale, CA, USA fulltime yearly None None https://www.linkedin.com/jobs/view/3693012711 Description:By bringing together people that u...
linkedin Full-Stack Software Engineer Rain New York, NY, USA fulltime yearly None None https://www.linkedin.com/jobs/view/3696158877 Rain’s mission is to create the fastest and ea...
zip_recruiter Software Engineer - New Grad ZipRecruiter Santa Monica, CA, USA fulltime yearly 130000 150000 https://www.ziprecruiter.com/jobs/ziprecruiter... We offer a hybrid work environment. Most US-ba...
zip_recruiter Software Developer TEKsystems Phoenix, AZ, USA fulltime hourly 65 75 https://www.ziprecruiter.com/jobs/teksystems-0... Top Skills' Details• 6 years of Java developme...
Command line
The package installs a jobspy command whose options mirror the scrape_jobs() parameters below; jobspy -h lists them, the output fields and exit codes.
uv tool install python-jobspy
jobspy "software engineer" --site indeed,linkedin --location Chicago -n 20
jobspy nurse --site indeed,linkedin --hours-old 24 -o jobs.jsonl
jobspy "data analyst" --remote --fields title,company,job_url
MCP server
mcp-jobspy is an MCP server that runs with uvx mcp-jobspy@latest. See mcp/README.md for setup, tools and proxies.
Standard config works for most clients:
{
"mcpServers": {
"jobspy": {
"command": "uvx",
"args": ["mcp-jobspy@latest"]
}
}
}
Parameters for scrape_jobs()
Optional
├── site_name (list|str):
| linkedin, zip_recruiter, indeed, glassdoor, google, bayt, bdjobs, naukri, hellowork
| (default is all)
│
├── search_term (str)
|
├── google_search_term (str)
| search term for google jobs. This is the only param for filtering google jobs.
│
├── location (str)
│
├── distance (int):
| in miles, default 50
│
├── job_type (str):
| fulltime, parttime, contract, temporary, internship, perdiem, other, volunteer
│
├── proxies (list|str):
| in format ['user:pass@host:port', 'localhost'], or a single proxy as a string
| each job board scraper will round robin through the proxies
| socks proxies on LinkedIn, Indeed and BDJobs need pip install "requests[socks]"
|
├── is_remote (bool)
│
├── results_wanted (int):
| number of job results to retrieve for each site specified in 'site_name'
│
├── easy_apply (bool):
| filters for jobs that are hosted on the job board site
|
├── user_agent (str):
| override the default user agent which may be outdated
│
├── description_format (str):
| markdown, html, plain (Format type of the job descriptions. Default is markdown.)
│
├── offset (int):
| skips that many results (e.g. 25 skips the first 25 and starts from the 26th)
│
├── hours_old (int):
| filters jobs by the number of hours since the job was posted
| (Glassdoor filters by whole days, so it rounds up to the next day.)
│
├── verbose (int) {0, 1, 2}:
| Controls the verbosity of the runtime printouts
| (0 prints only errors, 1 is errors+warnings, 2 is all logs. Default is 1.)
│
├── fetch_description (bool):
| for boards whose search results don't include the job description: fetches each job's
| page for the description and other details (e.g. job type). Without it these are empty
| for those boards. Adds one request per job, so use proxies for larger searches
│
├── linkedin_fetch_description (bool):
| deprecated, use fetch_description (still works; removed in 2.0)
│
├── linkedin_company_ids (list[int]):
| searches for linkedin jobs with specific company ids
|
├── country_indeed (str):
| filters the country on Indeed & Glassdoor (default is usa; see below for the names)
|
├── enforce_annual_salary (bool):
| converts wages to annual salary
|
├── ca_cert (str)
| path to CA Certificate file for proxies
Supported Countries for Job Searching
LinkedIn searches globally & uses only the location parameter.
ZipRecruiter
ZipRecruiter searches for jobs in US/Canada & uses only the location parameter.
Indeed / Glassdoor
Indeed & Glassdoor support most countries, chosen with the country_indeed parameter (default usa). Additionally, use the location
parameter to narrow down the location, e.g. city & state if necessary.
You can specify the following countries when searching on Indeed (use the exact name, * indicates support for Glassdoor):
| Argentina* | Australia* | Austria* | Bahrain |
| Belgium* | Brazil* | Canada* | Chile |
| China | Colombia | Costa Rica | Czech Republic |
| Denmark | Ecuador | Egypt | Finland |
| France* | Germany* | Greece | Hong Kong* |
| Hungary | India* | Indonesia | Ireland* |
| Israel | Italy* | Japan | Kuwait |
| Luxembourg | Malaysia* | Mexico* | Morocco |
| Netherlands* | New Zealand* | Nigeria | Norway |
| Oman | Pakistan | Panama | Peru |
| Philippines | Poland | Portugal | Qatar |
| Romania | Saudi Arabia | Singapore* | South Africa |
| South Korea | Spain* | Sweden | Switzerland* |
| Taiwan | Thailand | Turkey | Ukraine |
| United Arab Emirates | UK* | USA* | Uruguay |
| Venezuela | Vietnam* |
Bayt
Bayt searches all countries unless location names one it covers, e.g. "Dubai, UAE" or "Saudi Arabia".
BDJobs
BDJobs searches Bangladesh. location takes a division or district, e.g. "Dhaka" or "Chattogram Division".
Naukri
Naukri searches India. location takes a city, e.g. "Pune".
HelloWork
HelloWork searches France. location takes a French city, postcode or region, e.g. "Lyon". Most jobs are posted in French, so French search terms find more.
Notes
- Indeed is the best scraper currently with no rate limiting.
- Indeed's
date_postedis the day a job was added to Indeed, the same datehours_oldfilters on. - Most job board endpoints are capped at around 1000 jobs on a given search.
- LinkedIn rate limits bursts of requests with a 429, which clears within about a minute. Use proxies for larger searches.
- Glassdoor rate limits above about 2 requests a second.
Frequently Asked Questions
Q: How do I tell a blocked board from one with no jobs?
A: Failures and ignored options are logged as they happen, and the result's attrs carry the same messages so your code can read them:
jobs.attrs["errors"] # {"linkedin": "response status code 429 (blocked: use a proxy)"}
jobs.attrs["warnings"] # {"bdjobs": ["job_type internship isn't supported, ignoring it"]}
Q: Why is Indeed giving unrelated roles?
A: Indeed searches the description too.
- use - to remove words
- "" for exact match
Example of a good Indeed query
search_term='"engineering intern" software summer (java OR python OR c++) 2025 -tax -marketing'
This searches the description/title and must include software, summer, 2025, one of the languages, engineering intern exactly, no tax, no marketing.
Q: No results when using "google"?
A: Google Jobs is currently unavailable.
Q: Received a response code 429?
A: The board blocked you for sending too many requests. All of the job board sites are aggressive with blocking. We recommend:
- Wait some time between scrapes (site-dependent).
- Try using the proxies param to change your IP address.
JobPost Schema
JobPost
├── id
├── site
├── job_url
├── job_url_direct
├── title
├── company
├── location
├── date_posted
├── job_type: fulltime, parttime, internship, contract, temporary, ...
├── salary_source: direct_data, description (parsed from posting)
├── interval: yearly, monthly, weekly, daily, hourly
├── min_amount
├── max_amount
├── currency
├── is_remote
├── job_level
├── job_function
├── listing_type
├── emails
├── description
├── company_industry
├── company_url
├── company_logo
├── company_url_direct
├── company_addresses
├── company_num_employees
├── company_revenue
├── company_description
├── skills
├── experience_range
├── company_rating
├── company_reviews_count
├── vacancy_count
└── work_from_home_type
A column is empty when the job board doesn't provide it.