为什么选 DrissionPage
BOSS直聘的职位数据接口 zpgeek/search/joblist.json 有完善的设备指纹和行为检测,直接用 requests/httpx 模拟很容易被封。DrissionPage 驱动真实 Chrome 内核,天然通过所有浏览器指纹检测,同时提供 Python API 直接监听网络请求,一举两得。
环境安装
pip install DrissionPage
# 需要本地已安装 Chrome 浏览器
核心代码
from DrissionPage import ChromiumPage, ChromiumOptions
import csv, time, hashlib
opts = ChromiumOptions()
# opts.headless() # 调试时不建议开 headless
page = ChromiumPage(addr_or_opts=opts)
def collect_jobs(keyword: str, city_code: str = "101010100", max_page: int = 10):
# 采集职位列表
url = f"https://www.zhipin.com/web/geek/job?query={keyword}&city={city_code}"
page.get(url)
time.sleep(2)
seen = set()
results = []
for p in range(1, max_page + 1):
print(f"[*] 采集第 {p} 页...")
# 监听 XHR 接口
page.listen.start("zpgeek/search/joblist.json")
# 触发翻页(点击下一页按钮或模拟 URL 参数变更)
if p > 1:
next_btn = page.ele("css:.options-pages .next")
if next_btn:
next_btn.click()
else:
break
packet = page.listen.wait(timeout=10)
if not packet:
print("[-] 未收到响应,可能已到最后一页")
break
data = packet.response.body
jobs = data.get("zpData", {}).get("jobList", [])
for job in jobs:
uid = hashlib.md5(f"{job['jobName']}{job['brandName']}".encode()).hexdigest()
if uid in seen:
continue
seen.add(uid)
results.append({
"职位": job.get("jobName"),
"公司": job.get("brandName"),
"薪资": job.get("salaryDesc"),
"城市": job.get("cityName"),
"经验": job.get("jobExperience"),
"学历": job.get("jobDegree"),
"技能": ",".join(job.get("skills", [])),
"链接": f"https://www.zhipin.com/job_detail/{job.get('encryptJobId')}.html",
})
page.listen.stop()
time.sleep(1.5)
return results
def save_csv(jobs: list, filename: str = "jobs.csv"):
if not jobs:
print("[-] 无数据可保存")
return
with open(filename, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=jobs[0].keys())
writer.writeheader()
writer.writerows(jobs)
print(f"[+] 已保存 {len(jobs)} 条数据到 {filename}")
if __name__ == "__main__":
jobs = collect_jobs("Python开发", city_code="101010100", max_page=5)
save_csv(jobs, "python_jobs.csv")
page.quit()
处理反爬细节
1. 滚动加载触发
部分页面需要先滚动到底部触发加载:
page.scroll.to_bottom()
time.sleep(1) # 等待懒加载
2. 登录状态保持
第一次运行手动登录,之后保存用户数据目录:
opts = ChromiumOptions()
opts.set_user_data_path(r"C:\Users\你的用户名\AppData\Local\Google\Chrome\User Data")
opts.set_argument("--profile-directory=boss_profile") # 独立 Profile
3. IP 被限频处理
出现 CAPTCHA 或请求被拦截时,增加随机等待:
import random
time.sleep(random.uniform(2, 5))
数据清洗
import pandas as pd
df = pd.read_csv("python_jobs.csv")
# 薪资拆分为最小/最大
df[["min_sal", "max_sal"]] = df["薪资"].str.extract(r"(\d+)-(\d+)K")
df["min_sal"] = pd.to_numeric(df["min_sal"])
df["max_sal"] = pd.to_numeric(df["max_sal"])
# 过滤低薪
df = df[df["min_sal"] >= 15]
df.to_csv("filtered_jobs.csv", index=False, encoding="utf-8-sig")
print(df[["职位", "公司", "薪资"]].head(20))
效果
| 关键词 | 采集数量 | 耗时 |
|---|---|---|
| Python开发 | 480条 | 约2分钟 |
| 数据分析 | 320条 | 约1.5分钟 |
| 前端工程师 | 510条 | 约2.5分钟 |
DrissionPage 方案稳定性高,维护成本低,适合中小规模定期数据采集。