去年帮一位HR朋友优化招聘流程时,我意外发现市面上85%的求职网站都在用相似的HTML结构展示岗位信息。这个发现促使我系统性地尝试用数据抓取技术实现求职流程自动化。不同于简单的爬虫demo,这个项目需要解决反爬机制、动态渲染、验证码识别等实际问题,最终实现从岗位搜索到简历投递的全流程自动化。
在Python生态中,Scrapy和BeautifulSoup是常见组合,但面对现代Web应用时存在明显局限。经过实测对比,我最终采用Playwright+Pyppeteer方案,原因有三:
关键配置示例:
python复制async with async_playwright() as p:
browser = await p.chromium.launch(headless=False)
context = await browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36'
)
page = await context.new_page()
求职网站通常部署了多层次防护:
我的应对方案:
重要提示:严格遵守robots.txt规则,单个域名请求频率控制在30次/分钟以下
主流求职网站的页面结构虽有差异,但核心字段具有共性。通过XPath和CSS选择器组合提取:
python复制job_title = await page.query_selector('//h1[@class="job-title"]')
company_name = await page.query_selector('.company-info > h2')
salary_range = await page.eval_on_selector('.salary', 'el => el.innerText')
建立字段映射表应对不同平台:
| 平台名称 | 标题选择器 | 公司选择器 |
|---|---|---|
| 平台A | .position-title | .employer-name |
| 平台B | #jobTitle | div.company > a |
| 平台C | h1[itemprop=title] | span[itemprop=hiringOrganization] |
现代求职网站普遍采用无限滚动加载,解决方案:
python复制async def auto_scroll(page):
await page.evaluate('''async () => {
await new Promise((resolve) => {
let totalHeight = 0;
const distance = 100;
const timer = setInterval(() => {
const scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if(totalHeight >= scrollHeight){
clearInterval(timer);
resolve();
}
}, 200);
});
}''')
基于TF-IDF和余弦相似度的简易匹配方案:
python复制from sklearn.feature_extraction.text import TfidfVectorizer
def calculate_match(job_desc, resume_text):
vectorizer = TfidfVectorizer()
tfidf = vectorizer.fit_transform([job_desc, resume_text])
return (tfidf * tfidf.T).A[0,1]
处理文件上传字段的特殊方法:
python复制await page.set_input_files('input[type=file]', './resume.pdf')
await page.fill('#years-of-exp', '5')
await page.select_option('#education-level', 'master')
| 错误类型 | 现象描述 | 解决方案 |
|---|---|---|
| 元素定位失败 | TimeoutError | 增加等待时间 + 备用选择器 |
| 验证码触发 | CAPTCHA弹出 | 降低操作频率 + 人工干预标记 |
| 账号封禁 | 403状态码 | 更换IP + 清除浏览器指纹 |
| 动态加载失败 | 数据未完整加载 | 强制等待DOMContentLoaded事件 |
python复制context = await browser.new_context(
storage_state="auth.json",
bypass_csp=True
)
python复制import asyncio
semaphore = asyncio.Semaphore(3) # 控制并发数
async def worker(url):
async with semaphore:
# 抓取逻辑
项目实施过程中必须注意:
我曾通过修改User-Agent和添加Referer头部成功将封禁率从40%降至7%,但关键还是保持适度原则。建议在凌晨1-5点执行批量操作,此时服务器负载较低且风控策略相对宽松。