ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

使用 Crawlee(Python)构建 LinkedIn 职位抓取器:从 PlaywrightCrawler 到 Streamlit 应用与 CSV 导出

使用 Crawlee(Python)构建 LinkedIn 职位抓取器:从 PlaywrightCrawler 到 Streamlit 应用与 CSV 导出 使用 CrawleePython构建 LinkedIn 职位抓取器从 PlaywrightCrawler 到 Streamlit 应用与 CSV 导出【免费下载链接】crawleeCrawlee—A web scraping and browser automation library for Node.js to build reliable crawlers. In JavaScript and TypeScript. Extract data for AI, LLMs, RAG, or GPTs. Download HTML, PDF, JPG, PNG, and other files from websites. Works with Puppeteer, Playwright, Cheerio, JSDOM, and raw HTTP. Both headful and headless mode. With proxy rotation.项目地址: https://gitcode.com/GitHub_Trending/cr/crawlee这是一篇基于 Crawlee 官方博客教程website/blog/2024/10-14-linkedin-job-scraper-python/index.md整理的实战指南。本文将以 Crawlee for Python 的PlaywrightCrawler为核心完整演示如何抓取 LinkedIn 职位搜索结果页中的职位标题、公司名称、发布时间与职位链接并通过 Streamlit 构建一个接收动态用户输入、最终输出 CSV 文件的 Web 应用。读完本文你将掌握 Crawlee 的请求路由Router机制、Playwright 定位器提取元素、数据入库push_data与数据集导出export_data的完整链路并能直接动手复刻一个可运行的 LinkedIn 职位抓取器。前置准备用 Crawlee 脚手架创建项目Crawlee 提供了官方脚手架命令可以直接生成一个带PlaywrightCrawler模板的项目。打开终端执行pipx run crawlee create linkedin-scraper当终端询问选择哪种 Crawler 时选择PlaywrightCrawler。选择它的原因很直接LinkedIn 的职位搜索结果页依赖 JavaScript 动态渲染只有通过无头浏览器才能真正拿到完整的 DOM 结构而PlaywrightCrawler正是 Crawlee 中面向需要执行 JavaScript 的网站的内置方案。脚手架生成完毕后进入项目目录并安装依赖cd linkedin-scraper poetry install项目使用 Poetry 管理依赖。安装完成后脚手架会生成main.py、routes.py、__main__.py等样板文件接下来我们逐一改造它们把它变成 LinkedIn 职位抓取器。:::note 关于本文所基于的仓库当前开源仓库 Crawlee 是 Crawlee 的 TypeScript/JavaScript 实现packages/下每个子包对应一个模块而本教程使用的 Crawlee for Python 是同一设计理念的 Python 实现。两者在PlaywrightCrawler、Router、push_data、export_data等核心概念上保持一致仓库源码可以作为理解这些概念的权威佐证。 :::第一步分析 LinkedIn 职位搜索页的 URL 结构在动手写代码之前先用浏览器分析目标页面。打开 LinkedIn 并退出登录如果已登录账号进入职位Jobs板块按自己关心的职位关键词与地点搜索复制搜索结果的 URL。你会得到类似这样的地址https://www.linkedin.com/jobs/search?keywordsBackend%20DeveloperlocationCanadageoId101174742trkpublic_jobs_jobs-search-bar_search-submitposition1pageNum0?之后的部分就是搜索参数query string对我们而言最重要的是两个参数keywords职位关键词对应页面搜索框里的职位名称location地点对应页面搜索框里的位置信息。其余参数中trk、position、pageNum保持固定即可而geoId地理区域 ID可以移除——它只是一个可选的地理限定符去掉后不会影响按关键词与地点搜索的基本行为。因此我们的抓取器只需要把用户输入的职位名称填入keywords、把用户输入的地点填入location并固定其余参数就能动态构造出任意职位的搜索结果 URL。第二步改造main.py动态构造 URL 并运行爬虫打开项目中的main.py用下面的代码替换原有内容from crawlee.playwright_crawler import PlaywrightCrawler from .routes import router import urllib.parse async def main(title: str, location: str, data_name: str) - None: base_url https://www.linkedin.com/jobs/search # URL encode the parameters params { keywords: title, location: location, trk: public_jobs_jobs-search-bar_search-submit, position: 1, pageNum: 0 } encoded_params urllib.parse.urlencode(params) # Encode parameters into a query string query_string ? encoded_params # Combine base URL with the encoded query string encoded_url urllib.parse.urljoin(base_url, ) query_string # Initialize the crawler crawler PlaywrightCrawler( request_handlerrouter, ) # Run the crawler with the initial list of URLs await crawler.run([encoded_url]) # Save the data in a CSV file output_file f{data_name}.csv await crawler.export_data(output_file)这段代码做四件事构造参数把用户传入的title、location与固定的trk、position、pageNum组装成字典URL 编码用urllib.parse.urlencode把字典编码为查询字符串。这很关键——用户输入中可能包含空格、中文等字符例如 Backend Developer 会被编码成Backend%20Developer不编码会导致请求 URL 非法或参数解析错误拼接完整 URLbase_url ? query_string即最终要抓取的地址运行爬虫并导出数据PlaywrightCrawler接收request_handlerrouter作为请求处理器crawler.run([encoded_url])以该 URL 为起始请求启动抓取最后crawler.export_data(output_file)把抓到的数据以 CSV 格式写入{data_name}.csv。:::tip 原教程代码中urlencode与urljoin直接以裸函数形式调用这里显式写为urllib.parse.urlencode与urllib.parse.urljoin避免遗漏from urllib.parse import urlencode, urljoin的导入导致NameError。两种写法等价按你的习惯选择即可。 :::关于export_data与push_data这对组合在仓库的 Python 用法示例 website/src/pages/home_page_example.py 中可以看到标准模式请求处理器内用context.push_data(data)逐条写入数据爬虫跑完后用crawler.export_data(results.csv)一次性导出整个数据集也可以直接用await crawler.get_data()在内存中读取数据。在 Crawlee 的 TS 实现中PlaywrightCrawler与PlaywrightCrawlingContext的类型定义位于 packages/playwright-crawler/src/internals/playwright-crawler.ts它继承了浏览器爬虫的上下文能力因此push_data、add_requests、enqueue_links等数据与请求操作都直接挂在context上用法与上面的 Python 示例一一对应。第三步路由设计——默认处理器收集职位链接URL 构造完成接下来改造脚手架生成的路由文件routes.py。Crawlee 的 Router 机制让同一个爬虫可以为不同标签label的请求注册不同的处理函数。在本项目中我们使用两个处理器默认处理器default_handler处理起始 URL负责收集搜索结果页上所有职位详情的链接职位列表处理器job_listing处理每条职位详情页提取具体字段。Crawlee 的 Router 在仓库源码中定义于 packages/core/src/router.ts其设计就是把 URL 分发给对应的 handler——Router[PlaywrightCrawlingContext]()创建与 Playwright 上下文类型绑定的路由器router.default_handler与router.handler(label)分别注册默认与具名路由context.add_requests添加带label的新请求时爬虫就会把该请求派发给对应 handler。先看默认处理器。当 Playwright 爬虫访问职位搜索结果页后我们需要提取页面上所有职位帖子的链接。打开浏览器开发者工具观察页面结构你会发现职位列表位于一个 class 为jobs-search__results-list的有序列表ul/ol中每个职位条目内的a标签即职位详情页链接对应代码router Router[PlaywrightCrawlingContext]() router.default_handler async def default_handler(context: PlaywrightCrawlingContext) - None: Default request handler. # select all the links for the job posting on the page hrefs await context.page.locator(ul.jobs-search__results-list a).evaluate_all(links links.map(link link.href)) # add all the links to the job listing route await context.add_requests( [Request.from_url(rec, labeljob_listing) for rec in hrefs] )核心是这一行选择器与求值context.page.locator(ul.jobs-search__results-list a).evaluate_all(links links.map(link link.href))locator是 Playwright 的定位器对象ul.jobs-search__results-list a匹配列表容器内的全部链接evaluate_all在浏览器上下文中执行回调把所有href取出来返回给 Python 侧。随后用Request.from_url(rec, labeljob_listing)把每个链接包装成带job_listing标签的请求通过context.add_requests(...)批量加入队列——这正是 Crawlee 路由机制发挥作用的地方这些新请求会绕过默认处理器直接进入job_listing处理器。第四步路由设计——job_listing处理器提取职位详情现在实现job_listing处理器提取每个职位页面的标题、公司、发布时间并记录职位 URL。先回到浏览器开发者工具用检查元素Inspect逐个确认字段对应的 CSS 选择器比如标题位于h1.top-card-layout__title对应代码router.handler(job_listing) async def listing_handler(context: PlaywrightCrawlingContext) - None: Handler for job listings. await context.page.wait_for_load_state(load) job_title await context.page.locator(div.top-card-layout__entity-info h1.top-card-layout__title).text_content() company_name await context.page.locator(span.topcard__flavor a).text_content() time_of_posting await context.page.locator(div.topcard__flavor-row span.posted-time-ago__text).text_content() await context.push_data( { # we are making use of regex to remove special characters for the extracted texts title: re.sub(r[\s\n], , job_title), Company name: re.sub(r[\s\n], , company_name), Time of posting: re.sub(r[\s\n], , time_of_posting), url: context.request.loaded_url, } )几个值得注意的细节等待页面加载完成await context.page.wait_for_load_state(load)确保页面主资源加载完毕再提取内容避免拿到空值。逐个字段提取三个字段分别用text_content()获取文本职位标题div.top-card-layout__entity-info h1.top-card-layout__title公司名称span.topcard__flavor a发布时间div.topcard__flavor-row span.posted-time-ago__text正则清洗text_content()返回的文本往往夹杂换行与空白字符用re.sub(r[\s\n], , text)移除多余空白让数据更干净。注意使用re模块前需要在routes.py顶部import re。记录来源 URLcontext.request.loaded_url是当前请求实际加载的 URL把它一并存入数据便于事后回溯数据来源。数据入库await context.push_data({...})把字典写入 Crawlee 的默认数据集Dataset。push 的数据会自动持久化爬虫结束后即可导出。第五步用 Streamlit 构建 Web 应用抓取核心完成现在给它套一层用户界面。本项目使用 Streamlit 作为 Web 框架。在项目目录下新建app.py并确保全局 Python 环境已安装 Streamlitpip install streamlit。import streamlit as st import subprocess # Streamlit form for inputs st.title(LinkedIn Job Scraper) with st.form(scraper_form): title st.text_input(Job Title, valuebackend developer) location st.text_input(Job Location, valuenewyork) data_name st.text_input(Output File Name, valuebackend_jobs) submit_button st.form_submit_button(Run Scraper) if submit_button: # Run the scraping script with the form inputs command fpoetry run python -m linkedin-scraper --title {title} --location {location} --data_name {data_name} with st.spinner(Crawling in progress...): # Execute the command and display the results result subprocess.run(command, shellTrue, capture_outputTrue, textTrue) st.write(Script Output:) st.text(result.stdout) if result.returncode 0: st.success(fData successfully saved in {data_name}.csv) else: st.error(fError: {result.stderr})工作流程如下st.form创建一个表单包含三个输入框职位名称默认backend developer、职位地点默认newyork、输出文件名默认backend_jobs以及一个Run Scraper提交按钮提交后用 Python 标准库subprocess拼装并执行命令poetry run python -m linkedin-scraper --title ... --location ... --data_name ...——即以命令行参数方式把表单输入传给抓取脚本st.spinner显示正在抓取的等待状态命令输出通过result.stdout展示在页面上若返回码为 0result.returncode 0则提示数据已保存到{data_name}.csv否则用st.error展示stderr中的错误信息。这个设计把爬虫与界面解耦界面只负责收集参数、启动子进程、回显结果抓取逻辑完全由 Crawlee 脚本承担。第六步改造__main__支持命令行参数为了让app.py中那条poetry run python -m linkedin-scraper --title ...命令生效需要让包的可执行入口能够解析这三个命令行参数。修改项目中的__main__.pyimport asyncio import argparse from .main import main def get_args(): # ArgumentParser object to capture command-line arguments parser argparse.ArgumentParser(descriptionCrawl LinkedIn job listings) # Define the arguments parser.add_argument(--title, typestr, requiredTrue, helpJob title) parser.add_argument(--location, typestr, requiredTrue, helpJob location) parser.add_argument(--data_name, typestr, requiredTrue, helpName for the output CSV file) # Parse the arguments return parser.parse_args() if __name__ __main__: args get_args() # Run the main function with the parsed command-line arguments asyncio.run(main(args.title, args.location, args.data_name))这里使用标准库argparse定义了三个必填参数--title、--location、--data_name解析后通过asyncio.run(main(...))异步运行抓取主函数。这样既可以直接命令行运行poetry run python -m linkedin-scraper --title data engineer --location Berlin --data_name de_jobs也可以被 Streamlit 的子进程调用。第七步运行与验证一切就绪在终端启动 Streamlit 应用streamlit run app.py浏览器会自动打开 Streamlit 界面输入职位、地点与输出文件名后点击 Run Scraper页面会显示抓取进度完成后提示数据已保存回到项目目录打开生成的 CSV 文件即可看到结构化的职位数据——每一行包含职位标题、公司名称、发布时间与职位链接原理纵深从仓库源码理解PlaywrightCrawler与 Router教程至此已经可以完整跑通。为了让你对这套机制有更深的把握这里补充几个可以从当前仓库源码直接验证的底层事实PlaywrightCrawler是浏览器爬虫的一个具体实现。在仓库中packages/playwright-crawler/src/internals/playwright-crawler.ts 定义了PlaywrightCrawlerOptions与PlaywrightCrawlingContext后者继承自BrowserCrawlingContext并混入了 Playwright 专用的上下文工具gotoExtended、infinite_scroll、快照、Cloudflare 挑战处理等见 packages/playwright-crawler/src/internals/utils/playwright-utils.ts。也就是说你在 Python 教程里用到的context.page、context.add_requests、context.push_data在 TS 实现中都有对应的结构化定义二者 API 语义一致。Router 是按标签分发的路由器。仓库源码 packages/core/src/router.ts 实现了Router类default_handler兜底所有未匹配请求handler(label)匹配带特定label的请求。本教程中默认处理器负责起始页、job_listing处理器负责详情页正是该机制的标准用法。数据流是逐条写入 统一导出。抓取过程中每条数据经context.push_data进入数据集爬虫结束后crawler.export_data(file)一次性导出 CSV/JSON。仓库的 Python 示例 website/src/pages/home_page_example.py 同时展示了export_data(results.json)与get_data()两种取数方式可作为扩展参考。扩展方向与注意事项翻页与无限滚动本教程只抓取搜索结果第一页。如果需要多页数据可在默认处理器中解析pageNum递增构造新 URL或参考 Crawlee 博客中关于无限滚动页面的处理思路context.infinite_scroll()配合networkidle等待见 website/blog/2024/08-27-how-to-scrape-infinite-scrolling-pages/index.md。登录墙与反爬未登录状态下 LinkedIn 对搜索结果与详情页的可见字段有限如果出现登录要求或验证需要考虑会话管理Crawlee 的 SessionPool与代理轮换但超出本教程范围。选择器稳定性top-card-layout__title等选择器依赖 LinkedIn 的页面结构LinkedIn 改版后需要重新用开发者工具核对 CSS 选择器。遵守平台条款任何抓取行为都应遵守目标网站的服务条款与 robots 协议控制抓取频率仅抓取公开可见的数据。结语通过这个项目我们完成了一次完整的 Crawlee 实战用脚手架创建PlaywrightCrawler项目、动态构造并编码搜索 URL、用 Router 的默认处理器与具名处理器分阶段抓取、用push_data结构化存储数据、用export_data导出 CSV最后用 Streamlit 封装成可交互的 Web 应用。这套浏览器爬虫 请求路由 数据集导出 轻量前端的组合可以轻松迁移到其他招聘网站、电商列表页等同类场景是 Crawlee 最典型的应用模式之一。【免费下载链接】crawleeCrawlee—A web scraping and browser automation library for Node.js to build reliable crawlers. In JavaScript and TypeScript. Extract data for AI, LLMs, RAG, or GPTs. Download HTML, PDF, JPG, PNG, and other files from websites. Works with Puppeteer, Playwright, Cheerio, JSDOM, and raw HTTP. Both headful and headless mode. With proxy rotation.项目地址: https://gitcode.com/GitHub_Trending/cr/crawlee创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表