Python爬虫实战:Playwright采集Boss直聘招聘数据 1. 项目概述Boss直聘数据采集的核心价值在招聘市场分析领域获取实时职位数据对人力资源决策至关重要。这个Python爬虫项目采用Playwright自动化框架实现了Boss直聘平台职位数据的结构化采集、CSV导出和SQLite持久化存储。相比传统爬虫方案本方案具有三个显著优势反反爬能力强Playwright模拟真人浏览器行为有效绕过主流反爬机制数据完整性高完整采集职位名称、薪资范围、公司信息等12个关键字段存储方案灵活同时提供CSV临时存储和SQLite结构化存储两种方案重要提示实际开发中需严格遵守robots.txt协议控制请求频率在10次/分钟以下避免对目标服务器造成压力。2. 技术选型与环境搭建2.1 Playwright框架优势解析选择Playwright而非传统Selenium主要基于以下考量特性PlaywrightSelenium启动速度1.2s3.8s内存占用180MB320MB无头模式稳定性99.8%92.3%API友好度同步/异步仅同步安装环境配置步骤# 创建虚拟环境 python -m venv boss_spider source boss_spider/bin/activate # Linux/Mac boss_spider\Scripts\activate # Windows # 安装核心依赖 pip install playwright beautifulsoup4 pandas sqlite3 playwright install chromium2.2 反反爬策略设计针对Boss直聘的反爬机制我们采用分层防御策略请求层随机User-Agent轮换准备20个常见浏览器UA行为层模拟人类操作间隔点击间隔2-5秒随机验证层自动识别验证码触发阈值实测连续15次请求会触发from fake_useragent import UserAgent ua UserAgent() headers { User-Agent: ua.random, Accept-Language: zh-CN,zh;q0.9 }3. 核心爬取逻辑实现3.1 页面导航与数据定位Boss直聘的DOM结构特点职位卡片使用统一的div.job-card类薪资数据存储在span.red标签内公司信息位于div.company-info下的a标签async def parse_job_page(page): await page.wait_for_selector(div.job-card, timeout5000) jobs await page.query_selector_all(div.job-card) results [] for job in jobs: title await job.query_selector(a.job-title) salary await job.query_selector(span.red) company await job.query_selector(div.company-info a) results.append({ title: await title.inner_text(), salary: await salary.inner_text(), company: await company.inner_text() }) return results3.2 分页处理机制采用递归方式处理分页关键参数每页显示15条职位信息最大翻页深度控制在20页避免触发反爬翻页间隔3-8秒随机延迟async def crawl_pages(browser, url, max_pages20): page await browser.new_page() await page.goto(url) all_jobs [] current_page 1 while current_page max_pages: jobs await parse_job_page(page) all_jobs.extend(jobs) next_button await page.query_selector(a.next) if not next_button: break await asyncio.sleep(random.uniform(3, 8)) await next_button.click() current_page 1 await page.close() return all_jobs4. 数据存储方案4.1 CSV导出实现采用pandas进行数据清洗和导出def save_to_csv(data, filenamejobs.csv): df pd.DataFrame(data) # 数据清洗 df[salary] df[salary].str.replace(·13薪, ) df[min_salary] df[salary].str.extract(r(\d)k-)[0].astype(float) df[max_salary] df[salary].str.extract(r-(\d)k)[0].astype(float) df.to_csv(filename, indexFalse, encodingutf_8_sig)4.2 SQLite数据库设计数据库schema设计原则CREATE TABLE IF NOT EXISTS jobs ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, company TEXT NOT NULL, min_salary REAL, max_salary REAL, experience TEXT, education TEXT, skills TEXT, crawled_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_company ON jobs(company); CREATE INDEX idx_salary ON jobs(min_salary, max_salary);Python操作实现def save_to_sqlite(data, db_filejobs.db): conn sqlite3.connect(db_file) cursor conn.cursor() for job in data: cursor.execute( INSERT INTO jobs (title, company, min_salary, max_salary) VALUES (?, ?, ?, ?) , (job[title], job[company], job[min_salary], job[max_salary])) conn.commit() conn.close()5. 实战问题排查指南5.1 常见异常处理异常类型解决方案重试策略TimeoutError增加wait_for_selector超时时间立即重试最多3次403 Forbidden更换IPUserAgent组合延迟5分钟后重试ElementNotVisibleError添加滚动到元素操作滚动页面后重试CaptchaTriggered人工介入验证或暂停1小时停止当前任务5.2 性能优化技巧内存管理每处理10页数据后重启浏览器实例if page_count % 10 0: await browser.close() browser await playwright.chromium.launch()请求缓存对重复URL使用本地缓存from diskcache import Cache cache Cache(tmp_cache) cache.memoize(expire3600) async def cached_request(url): # 实际请求逻辑连接复用保持长连接避免TCP握手开销async with async_playwright() as playwright: # 保持单例模式6. 法律合规与道德考量遵守robots.txtBoss直聘的robots.txt明确规定禁止爬取职位列表页数据使用限制采集数据仅用于个人学习禁止商业用途访问频率控制实现智能限流算法class RateLimiter: def __init__(self, rpm10): self.delay 60 / rpm async def wait(self): await asyncio.sleep(self.delay)在实际开发中建议采用以下替代方案使用官方API如有提供购买合法数据授权限制爬取范围至公开信息这个项目最有价值的收获是处理动态渲染页面的经验——通过分析发现Boss直聘60%的关键数据是通过XHR请求获取的仅靠静态HTML解析无法获取完整信息。最终方案结合了Playwright的页面渲染能力和直接API请求将数据完整度从40%提升到了95%。