现代招聘平台普遍采用前后端分离架构,职位数据经由 XHR 异步下发,且核心字段分散于列表接口与详情接口。以 requests 直采静态 HTML 仅能获取渲染骨架,难以保证薪资区间、HR 活跃状态等字段的完整性与保真度。本文基于 Playwright 构建采集流水线,以「接口实时监听」完成字段初采,以「新 Tab 精准加载」完成详情补全,并引入亿牛云(16yun)代理 IP 分散出口,形成一套可复用、抗风控的职位采集方案。
一、问题建模
招聘类站点的采集瓶颈可归纳为三类约束:
- 数据异步化:页面 HTML 为渲染骨架,真实数据来自 XHR 接口,静态直采仅得空壳。
- 字段跨接口:列表接口返回职位名、公司、城市等概要字段,薪资结构与 HR 在线状态通常仅由详情接口下发。
- 上下文依赖:详情接口需在「列表→详情」跳转链路中携带正确参数与
referer,脱离该上下文直接构造请求易触发 403 或返回空载荷。
由此确立两层采集策略:监听层负责发现与初采,加载层负责补全与深采;二者以信号量约束并发,叠加代理层实现出口分散。
二、武器一:接口实时监听(监听层)
通过 page.on("response") 在浏览器网络层挂载响应钩子,接口返回即拦截 JSON 响应体,并按路径特征过滤目标接口。相较于 DOM 抽取,XHR 响应拦截直接获取结构化原始载荷,规避渲染时序依赖与选择器脆弱性,对前端结构变更具备更强鲁棒性。
from playwright.sync_api import sync_playwright
LIST_API_HINT = "/api/i/zl/zlgj"
def on_response(response):
if LIST_API_HINT not in response.url:
return
try:
payload = response.json()
except Exception:
return
jobs = (payload.get("data", {}).get("list")
or payload.get("result", {}).get("list") or [])
for job in jobs:
print(job.get("jobName"), job.get("salary"), job.get("hrStatus"))
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False,
args=["--disable-blink-features=AutomationControlled"])
page = browser.new_page()
page.on("response", on_response)
page.goto("https://www.zhaopin.com/sou?kw=Python&city=765",
wait_until="networkidle")
page.wait_for_timeout(3000)
browser.close()
工程要点:以路径片段而非完整 URL 匹配,可兼容接口版本演进;networkidle 配合超时以捕获懒加载触发的后续请求;禁用自动化特征标志以降低被识别为自动化流量的概率。
三、武器二:新 Tab 精准加载(加载层)
列表层获取 jobId 后,模拟真实用户跳转行为,为每条职位新建 Tab,触发携带正确 referer 与业务参数的详情接口,补全薪资与 HR 状态。
import threading
DETAIL_API_HINT = "/api/i/zl/job/detail"
results, lock = [], threading.Lock()
def collect_detail(response):
if DETAIL_API_HINT not in response.url:
return
try:
d = response.json().get("data", {})
except Exception:
return
with lock:
results.append({
"jobId": d.get("jobId"),
"salary": d.get("salary"),
"hrActiveStatus": d.get("hrInfo", {}).get("activeStatus"),
"hrActiveTime": d.get("hrInfo", {}).get("activeTime"),
})
def open_detail_tab(context, job_id):
tab = context.new_page()
tab.on("response", collect_detail)
tab.goto(f"https://www.zhaopin.com/jobdetail/{job_id}",
wait_until="networkidle")
tab.wait_for_timeout(1500)
tab.close()
新建 Tab 继承上下文的登录态与 referer,可规避 403;单实例用毕即释放,内存占用可控,适配长列表的批量深采。
四、组合流水线
监听层填充 job_index 后,以信号量约束并发详情 Tab 数,逐条深采并合并落库。信号量本质是一种并发令牌机制,在吞吐量与对端压力之间取得平衡。
job_index, detail_buf = {}, []
lock = threading.Lock()
sema = threading.Semaphore(3)
def deep_collect(context, job_id):
with sema:
tab = context.new_page()
tab.on("response", on_detail)
tab.goto(f"https://www.zhaopin.com/jobdetail/{job_id}",
wait_until="networkidle")
tab.wait_for_timeout(1200)
tab.close()
# on_list / on_detail 监听逻辑同前,合并写入 zhilian_final.json
五、数据建模
薪资字段(如 "15-25K")归一为 min/max/unit,便于后续聚合统计;HR 活跃状态(online/offline 配合 activeTime)是判定职位时效性的关键信号——HR 长期离线通常意味着投递转化率低,可作为「活跃职位」过滤阈值。
import re
def parse_salary(raw):
if not raw:
return None
m = re.search(r"(\d+(?:\.\d+)?)\s*[-~]\s*(\d+(?:\.\d+)?)\s*([Kk万]?)", raw)
if not m:
return None
lo, hi, u = float(m.group(1)), float(m.group(2)), m.group(3)
mult = 1000 if u.upper() == "K" else (10000 if u == "万" else 1)
return {"min": lo * mult, "max": hi * mult, "currency": "CNY"}
六、亿牛云代理:出口分散与风控对抗
规模化采集时,单一出口 IP 的请求指纹高度集中,易超出平台风控的频率阈值而触发限流乃至封禁。亿牛云(16yun)提供企业级代理 IP 服务,按形态可分为二类:
- 隧道代理:单一入口地址,后端自动轮转出口节点,客户端无需维护 IP 池,契合高频、无状态采集。
- 动态短效代理:按存活时长(如 1–5 分钟)分配出口 IP,支持短时会话亲和,适合需维持会话状态的场景。
针对本文招聘采集,隧道代理最为适配——开箱即用、出口自动轮转,与信号量限流协同即可在稳定性与成本间取得平衡。Playwright 通过 proxy 参数接入,鉴权链路由代理层承载:
YINIU_PROXY = {
"server": "http://t.16yun.cn:31111", # 隧道代理入口,以亿牛云后台为准
"username": "YOUR_16YUN_USER",
"password": "YOUR_16YUN_PASS",
}
browser = p.chromium.launch(
headless=False, proxy=YINIU_PROXY,
args=["--disable-blink-features=AutomationControlled"])
接入后,监听层与加载层逻辑无需改动。若选用动态短效代理,则需将 IP 列表纳入请求轮询,并在会话超时后重新获取出口。实践上,隧道代理按流量计费,应结合限流与重试控制单位成本;对多城市/多行业任务可拆分至不同隧道入口以进一步分散出口;需明确的是,代理仅解决出口分布问题,不豁免合规义务(见下节)。
七、稳定性与合规
稳定性:以信号量约束并发、引入随机请求间隔、goto 设置超时与重试、已采 jobId 持久化以支持断点续跑;代理层异常时应具备降级与告警能力。
合规:仅采集公开展示的职位信息,不抓取求职者个人数据(PII);遵守 robots.txt 与平台用户协议,控制请求频率;禁止商用转售或用于不正当竞争;企业内用须获授权并做脱敏处理。优先评估平台官方开放 API。
八、小结
监听层解决「数据从何而来」,加载层解决「详情如何补全」,代理层解决「出口如何分散」。三层以信号量约束并发协同运作,可产出结构干净、状态可辨的职位数据集,为薪资分布分析、HR 响应率评估等下游场景提供可靠的数据底座。