1. 为什么选择Selenium和XPath来爬取招聘数据如果你正在找工作或者想了解某个行业的薪资水平、技能要求手动去招聘网站一页页翻看效率实在太低了。我试过翻个十几页就头昏眼花而且数据还不好整理。这时候用Python写个小程序让它自动帮你收集信息就非常省事了。在众多爬虫工具里我特别推荐Selenium配合XPath这个组合尤其是对付像BOSS直聘这样动态加载内容比较多的网站实测下来很稳。你可能听说过requests库它简单快捷但对于很多现代网站尤其是大量使用JavaScript渲染的页面requests直接拿到的HTML源码往往是空的或者只有个骨架关键数据都看不到。这是因为数据是后来通过JS脚本动态加载进来的。Selenium的厉害之处就在于它能模拟一个真实的浏览器把网页完整地加载、渲染出来包括所有通过JS生成的内容。这样你就能拿到最终呈现在用户眼前的完整页面数据了。那拿到页面后怎么提取具体信息呢比如职位名称、公司、薪资。这里就要用到XPath了。你可以把XPath理解为网页的“导航地址”。一个网页就像一份结构复杂的XML或HTML文档XPath就是一种能在这个文档树中精准定位到某个节点的查询语言。比如你想找到所有职位名称它们可能都在某个特定的span标签里并且有一个独特的class属性。用XPath你就能写一条像//span[classjob-name]/text()这样的表达式直接把所有职位名称文本“揪”出来非常直观和强大。所以这个组合的路线图就很清晰了用Selenium这个“自动化机器人”打开浏览器访问网站执行搜索翻页拿到渲染好的完整页面然后用XPath这个“精准提取器”从页面里把我们需要的数据字段一个个定位并抓取下来。整个过程几乎模拟了人的所有操作但更快、更准、不知疲倦。接下来我就带你一步步实现它从环境搭建到代码编写最后成功把数据存到本地CSV文件里。2. 动手前的准备工作环境与工具安装工欲善其事必先利其器。在开始写代码之前我们需要把“厨房”收拾好把必要的“厨具”备齐。整个过程不复杂跟着我做就行。2.1 安装Python和必备库首先确保你的电脑上已经安装了Python。我推荐使用Python 3.8或更高的版本太老的版本可能对某些新库支持不好。打开你的命令行Windows上是CMD或PowerShellMac或Linux上是Terminal输入python --version检查一下。如果显示了版本号比如“Python 3.8.10”那就没问题。接下来安装我们需要的Python库。核心就是selenium用来驱动浏览器。我们还会用到lxml它是一个高效的HTML/XML解析器用来配合XPath解析网页内容。另外虽然Python自带csv模块处理CSV文件但为了编码省心我们也会注意一个小细节。在命令行里输入以下命令来安装pip install selenium lxml通常这样就可以了。如果安装速度慢可以考虑使用国内的镜像源比如清华源命令是pip install selenium lxml -i https://pypi.tuna.tsinghua.edu.cn/simple。2.2 下载浏览器驱动Selenium需要调用一个真实的浏览器来工作它自己不能凭空渲染网页。你可以选择Chrome、Firefox、Edge等。我这里以**Firefox火狐浏览器**为例因为它对Selenium的支持一直很稳定而且它的驱动geckodriver配置起来也比较直观。确保安装了Firefox浏览器去火狐官网下载并安装最新版的Firefox。下载geckodriver这是连接Selenium和Firefox的桥梁。你需要去它的GitHub发布页面下载。一个比较方便的国内镜像地址是https://registry.npmmirror.com/binary.html?pathgeckodriver/。打开后找到最新版本比如v0.33.0根据你的操作系统下载对应的文件Windows选geckodriver-v0.33.0-win64.zipmacOS选geckodriver-v0.33.0-macos.tar.gz。放置驱动并配置路径下载后解压你会得到一个名为geckodriverWindows下是geckodriver.exe的可执行文件。你需要把它放在一个Python能找到的地方。有三种常见方法方法一最简单直接把这个文件扔到你的Python脚本所在的同一个文件夹里。我们接下来的代码示例就采用这种方法。方法二把它放到系统环境变量PATH包含的任意目录下比如Windows的C:\Windows。方法三在代码里指定这个驱动文件的完整路径。注意驱动版本最好和你的浏览器大版本号匹配。不过一般来说较新版本的geckodriver都兼容近期多个版本的Firefox。如果运行时报错可以检查一下版本对应关系。2.3 选择你的代码编辑器写Python代码一个好用的编辑器能事半功倍。新手我强烈推荐PyCharm它有社区免费版对代码提示、调试、管理项目都非常友好。当然你用VS Code、Sublime Text甚至系统自带的记事本都可以只要能把代码保存成.py文件就行。好了工具齐备我们的“厨房”已经就绪。接下来我们就要开始点火炒菜正式进入编码环节了。3. 核心实战编写爬虫主逻辑现在进入最核心的部分我会把代码拆解成几个功能模块并配上详细的解释。你不用担心即使你是新手跟着我的注释一步步来也能看懂。3.1 导入库与初始化设置首先我们创建一个新的Python文件比如叫boss_spider.py。然后在文件开头导入所有需要的“工具”。import csv from time import sleep from selenium import webdriver from selenium.webdriver.firefox.service import Service from selenium.webdriver.common.by import By from lxml import etreecsv用来把爬取的数据写入CSV表格文件。sleep让程序暂停一会儿模拟人的操作间隔也给网页加载留出时间这对应对反爬很重要。selenium.webdriver核心用来启动和控制浏览器。By它提供了定位网页元素的各种方法比如通过ID、CLASS_NAME、XPATH等。lxml.etree我们将用它来解析网页源代码并使用XPath提取数据。接下来我们初始化浏览器。这里我演示两种模式有界面模式和无界面模式。开发调试时用有界面模式可以看到浏览器每一步在做什么非常直观。真正部署运行时可以用无界面模式不弹出浏览器窗口节省资源。# 指定驱动路径如果你把geckodriver.exe放在了脚本同目录下 driver_path ./geckodriver.exe service Service(driver_path) # 有界面模式调试用 # browser webdriver.Firefox(serviceservice) # 无界面模式运行用 options webdriver.FirefoxOptions() options.add_argument(-headless) # 开启无头模式 browser webdriver.Firefox(serviceservice, optionsoptions) # 设置隐式等待。这是一个智能等待浏览器会在一定时间内这里设20秒持续尝试查找元素找到了就立即继续没找到直到超时才报错。 browser.implicitly_wait(20)3.2 模拟登录与搜索操作我们不是去爬取需要账号登录后的个人数据而是爬取公开的招聘列表所以通常不需要处理复杂的登录。但是网站可能会有一些初始的Cookie验证。一个简单有效的方法是直接用Selenium打开网站首页让浏览器自然接收这些Cookie。# 目标网站首页 home_url https://www.zhipin.com browser.get(home_url) print(已访问首页等待页面加载...) sleep(5) # 显式等待5秒确保首页完全加载包括可能的弹窗或初始化脚本 # 接下来我们要找到搜索框输入职位关键词比如“Python” try: # 通过CSS选择器定位搜索框。F12打开开发者工具查看元素发现搜索框有个类名‘.ipt-search’ search_box browser.find_element(By.CSS_SELECTOR, .ipt-search) search_box.clear() # 清空可能存在的默认文本 search_box.send_keys(Python) # 输入关键词 print(已输入搜索关键词‘Python’) except Exception as e: print(f定位搜索框失败: {e}) browser.quit() exit() # 定位并点击搜索按钮 try: search_button browser.find_element(By.CSS_SELECTOR, .btn-search) search_button.click() print(已点击搜索按钮) sleep(8) # 等待搜索结果页加载 except Exception as e: print(f定位或点击搜索按钮失败: {e}) browser.quit() exit()这里用到了find_element方法和By.CSS_SELECTOR。为什么用CSS选择器因为在这个例子里搜索框和按钮的class名称比较简洁唯一。在实际操作中你需要按F12打开浏览器的开发者工具使用左上角的箭头工具点选页面上的元素查看它的HTML结构找到最稳定、唯一的属性来定位。XPath也是一种强大的定位方式我们稍后解析数据时会重点使用。3.3 解析页面数据XPath大显身手点击搜索后我们就进入了招聘列表页。现在页面已经包含了我们想要的职位信息。我们需要从这堆HTML里把有用的数据“挖”出来。首先获取当前页面的完整HTML源码page_html browser.page_source接下来就是lxml和XPath的舞台了。我们把HTML字符串转换成一个可以被查询的“树”结构。# 将页面源码加载到etree中 tree etree.HTML(page_html)现在我们来分析网页结构。打开开发者工具查看一个职位卡片的HTML。你会发现通常所有职位列表项li都包裹在一个ul里而这个ul又在一个具有特定class比如search-job-result的div里。每个li就对应一个职位。我们的目标是提取每个职位卡片里的职位名、薪资、工作地点、公司名、经验要求、福利标签等。我们需要为每个字段编写XPath表达式。XPath小课堂//表示从根节点开始查找所有后代节点。[classxxx]是属性过滤表示查找class属性等于‘xxx’的节点。/text()是获取该节点的文本内容。.在表达式开头表示从当前节点即i开始查找。# 首先定位到所有职位列表项 job_items tree.xpath(//div[classsearch-job-result]/ul/li) print(f本页共找到 {len(job_items)} 个职位) # 准备一个列表用来存放这一页所有职位的信息 current_page_jobs [] for item in job_items: # 提取各个字段使用try...except避免某个字段缺失导致程序崩溃 try: # 职位名称通常在 span classjob-name 里 job_name item.xpath(.//span[classjob-name]/text())[0].strip() except IndexError: job_name N/A try: # 薪资在 span classsalary 里 salary item.xpath(.//span[classsalary]/text())[0].strip() except IndexError: salary N/A try: # 工作地点在 span classjob-area 里 location item.xpath(.//span[classjob-area]/text())[0].strip() except IndexError: location N/A try: # 公司名称在 h3 classcompany-name 下的 a 标签里 company item.xpath(.//h3[classcompany-name]/a/text())[0].strip() except IndexError: company N/A # 经验、学历等标签通常在一个 ul classtag-list 里可能有多个我们用空格拼接 tags_list item.xpath(.//ul[classtag-list]/li/text()) tags .join([tag.strip() for tag in tags_list]) if tags_list else N/A # 职位详情页的链接很重要方便后续深入查看 try: # 注意这里获取的是相对路径需要拼接上网站域名 detail_path item.xpath(.//h3[classcompany-name]/a/href)[0] detail_url fhttps://www.zhipin.com{detail_path} except IndexError: detail_url N/A # 把提取到的信息组成一个字典存入列表 job_info { 职位: job_name, 薪资: salary, 地点: location, 公司: company, 标签: tags, 详情链接: detail_url } current_page_jobs.append(job_info) # 也可以在控制台打印一下看看 print(f已提取: {job_name} - {salary} - {company})这段代码就是一个完整的单页数据解析流程。核心思想就是先定位到包裹所有条目的容器然后循环遍历每个条目在条目内部使用相对路径以.开头定位各个子元素提取文本或属性。3.4 数据存储与翻页循环一页的数据解析完了我们需要把它存起来然后去抓取下一页。存储到CSV文件CSV逗号分隔值文件非常适合存储表格数据可以用Excel直接打开。我们在程序开始时创建文件并写入表头在每解析完一页后追加数据。# 在程序开头打开创建一个CSV文件写入表头 filename boss_python_jobs.csv with open(filename, w, newline, encodingutf-8-sig) as f: # 注意编码用utf-8-sig解决Excel乱码 writer csv.writer(f) writer.writerow([职位, 薪资, 地点, 公司, 标签, 详情链接]) print(CSV文件表头已写入) # 在解析完一页数据后即得到current_page_jobs列表后将数据追加到CSV文件 with open(filename, a, newline, encodingutf-8-sig) as f: writer csv.writer(f) for job in current_page_jobs: writer.writerow([job[职位], job[薪资], job[地点], job[公司], job[标签], job[详情链接]]) print(f第X页数据已写入CSV)实现自动翻页翻页是爬虫自动化的关键。我们需要找到“下一页”按钮并点击它。但这里有个坑很多网站的“下一页”按钮在最后一页会失效变成灰色或消失或者它的HTML结构会变。更稳健的方法是分析URL规律。观察BOSS直聘翻页时的URL变化你会发现页码是作为查询参数page体现在URL里的。例如 第一页https://www.zhipin.com/web/geek/job?queryPythonpage1第二页https://www.zhipin.com/web/geek/job?queryPythonpage2这样我们就不需要去点击那个可能不稳定的“下一页”按钮了直接构造每一页的URL让浏览器访问即可。base_url https://www.zhipin.com/web/geek/job?query{}page{} keyword Python max_pages 10 # 设定你想爬取的最大页数避免无限循环 for page in range(1, max_pages 1): print(f\n开始爬取第 {page} 页...) # 构造当前页的URL current_url base_url.format(keyword, page) browser.get(current_url) # 浏览器访问该URL sleep(10) # 等待新页面加载时间可根据网络情况调整 # 这里插入我们上面写的页面解析代码解析当前页的HTML # ... # 解析完成后将数据写入CSV # ... print(f第 {page} 页爬取完成。) # 短暂停顿模拟人工操作降低请求频率 sleep(random.uniform(2, 5)) # 随机等待2-5秒 print(所有页面爬取完毕)通过分析URL规律来翻页比点击按钮更可靠是爬虫中常用的技巧。至此一个能够自动搜索、翻页、解析、存储的爬虫主干逻辑就完成了。4. 高级技巧与常见问题避坑指南把基础流程跑通只是第一步。在实际操作中你会遇到各种问题网站也会有反爬措施。下面分享几个我踩过坑后总结出来的高级技巧和应对策略。4.1 应对反爬机制让你的爬虫更“像人”网站不喜欢被爬会设置各种障碍。我们的目标是让爬虫行为尽量模拟真人降低被屏蔽的风险。随机延迟与间隔不要在极短时间内连续请求。像上面代码里用的sleep(random.uniform(2, 5))就是很好的实践。你还可以在翻页、点击等操作前后都加入随机等待。使用无头模式时的额外参数在无头模式下一些网站能检测到你是自动化脚本。可以添加一些参数来“伪装”options webdriver.FirefoxOptions() options.add_argument(-headless) options.add_argument(--disable-gpu) # 禁用自动化控制标志这是关键的一步 options.set_preference(dom.webdriver.enabled, False) options.set_preference(useAutomationExtension, False) # 可以设置一个普通的User-Agent options.set_preference(general.useragent.override, Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0)处理登录验证与弹窗有时访问首页会弹出登录框。我们的策略是不登录直接关闭它。你可以尝试在页面加载后检查是否有弹窗元素然后点击关闭按钮。try: close_btn browser.find_element(By.CSS_SELECTOR, 弹窗关闭按钮的选择器) close_btn.click() sleep(2) except: pass # 没找到弹窗就继续IP被封问题如果爬取速度过快、频率过高最严重的后果是IP被暂时封禁。对于个人学习和小规模爬取严格遵守延迟策略通常可以避免。如果需要进行大规模爬取则需要考虑使用代理IP池等技术这超出了本文入门范围但你需要有这个意识。4.2 XPath定位失败怎么办写XPath最头疼的就是定位不到元素或者定位到一堆空值。除了检查class名是否写对还有这些技巧使用浏览器开发者工具直接复制XPath在元素上右键 - 复制 - 复制XPath。但注意浏览器生成的XPath往往是绝对路径非常冗长且脆弱页面结构一变就失效不推荐直接使用。你可以把它作为参考改写成更简洁的相对路径或属性定位。使用contains()函数进行模糊匹配有时class名是动态的包含一部分固定字符。例如//div[contains(class, job-item)]可以匹配class属性中包含job-item的所有div。使用逻辑运算符可以组合多个条件。//span[classsalary and text()]表示定位class为salary并且有文本内容的span标签。文本内容定位如果你想直接通过页面上的文字定位可以用//*[text()具体文字]但要小心页面其他地方有相同文字。多手准备对于关键元素可以准备两到三种不同的XPath策略一个失败了尝试另一个增加代码的健壮性。4.3 数据清洗与异常处理爬下来的数据经常是“脏”的包含多余的空格、换行符或者有些字段缺失。在存储前进行清洗很重要。# 在提取文本后使用 .strip() 去除首尾空白字符 job_name item.xpath(.//span[classjob-name]/text()) job_name job_name[0].strip() if job_name else N/A # 处理空列表情况 # 对于可能由多个文本节点组成的字段先提取列表再合并清理 tags_list item.xpath(.//ul[classtag-list]/li//text()) # 列表推导式对每个文本先strip清理再过滤掉空字符串最后用空格连接 tags .join([tag.strip() for tag in tags_list if tag.strip()])异常处理是保证爬虫长时间稳定运行的关键。网络可能断开页面结构可能微调元素可能找不到。务必用try...except把可能出错的代码块包起来记录错误日志而不是让程序直接崩溃。try: company item.xpath(.//h3[classcompany-name]/a/text())[0].strip() except (IndexError, AttributeError) as e: print(f提取公司名时出错: {e} 当前item HTML片段: {etree.tostring(item)[:200]}) # 打印部分HTML帮助调试 company N/A4.4 将代码模块化与函数化当你的爬虫代码越来越长最好把它拆分成函数这样逻辑更清晰也便于维护和复用。例如你可以把解析单页的函数、保存数据的函数、控制翻页的主循环分开。def parse_job_page(browser): 解析当前浏览器页面中的职位信息 page_html browser.page_source tree etree.HTML(page_html) job_items tree.xpath(//div[classsearch-job-result]/ul/li) job_list [] # ... 解析逻辑 ... return job_list def save_to_csv(job_list, filename, modea): 将职位列表保存到CSV文件 with open(filename, mode, newline, encodingutf-8-sig) as f: writer csv.writer(f) for job in job_list: writer.writerow([job[职位], job[薪资], ...]) print(f已保存 {len(job_list)} 条数据到 {filename}) def main(): # 初始化浏览器 browser init_browser(headlessTrue) # 访问首页等初始化操作 # ... # 主循环 for page in range(1, 11): go_to_page(browser, page) # 跳转到指定页的函数 jobs parse_job_page(browser) save_to_csv(jobs, jobs.csv) sleep(random.uniform(3, 7)) browser.quit() if __name__ __main__: main()这样组织代码看起来就清爽多了哪个部分出了问题也容易定位。写完代码后先用有界面模式跑一遍看着浏览器自动操作确认每一步都如你所愿。调试无误后再切换到无界面模式去批量运行。最后打开生成的CSV文件检查一下数据是否完整、整洁一份属于你自己的招聘市场分析数据集就诞生了。这个过程虽然会遇到一些小麻烦但当你看到程序自动为你收集好几百条数据时那种成就感是非常棒的。