尧图精选

Python后端爬虫专题19:浏览器看到职位,HTTPX却只拿到“正在加载”——Playwright处理动态页面

🕒 发布时间:2026/10/2 2:19:37 📁 来源:尧图网络
Python后端爬虫专题19浏览器看到职位HTTPX却只拿到“正在加载”——Playwright处理动态页面上一篇练习完整答案这组统计说明“运行生命周期正常结束但业务不完整”300 个 429 中有 120 个重试耗尽800 个 item 不能视为成功全量。处置顺序是暂停新的大批任务、确认来源限额与 Retry-After、降低单域并发和目标频率、保留少量错误 URL/响应快照、核对是否发生封禁或容量故障修正后用小流量恢复不能通过代理轮换规避限制。运行 Stats 的关键项通常在Dumping Scrapy statsdownloader/request_count、downloader/response_status_count/200、item_scraped_count、elapsed_time_seconds、finish_reason。Downloader Middleware 适合传输层的请求/响应、重试、通用头和缓存Item Pipeline 适合经过回调后的业务 item 校验、去重与持久化。把职位 CSS 选择器放进 Downloader Middleware 会导致传输层依赖每个站点 DOM也绕开 Spider 回调错误语义。先证明数据真的不在 HTML 中访问 TargetLab/dynamic时浏览器页面最终可能展示职位但 HTTPX 初始响应只有main idjobs正在加载……/main和一段fetch(/api/dynamic/jobs)。BeautifulSoup 不执行 JavaScript所以搜索职位标题必然为空。这不是选择器写错而是数据在后续请求或渲染结果里。诊断顺序应是查看页面源代码打开开发者工具 Network筛选 fetch/XHR检查返回 JSON 是否是公开且允许调用的接口只有数据由脚本计算、必须执行浏览器 API 或没有可用接口时才考虑 Playwright。Playwright 适配器为什么只有四步render_dynamic_job(page, url, timeout_ms)接收一个已创建的 Page而不是在函数里启动浏览器。它执行 goto等待article.job-detail[data-job-id]这个业务节点读取渲染后的完整 HTML再交给已有parse_detail。输出仍是 JobItem。调用者拥有 browser/context/page 生命周期因此可以为一个任务复用浏览器并在 finally 中可靠关闭。若函数内部每个 URL 都启动 Chromium几十个详情就会消耗大量内存若模块保存全局 page多任务 Cookie 和并发状态会相互污染。cd project.\.venv\Scripts\python.exe-m pytest tests\test_framework_adapters.py::test_playwright_adapter_waits_for_business_element_then_reuses_parser-q测试提供一个实现goto/wait_for_selector/content的受控 Page使用真实详情 Fixture验证等待的是业务节点而不是任意时间最终解析器仍输出同一 external_id。它不下载 Chromium因此 CI 可稳定运行手工观察时执行下面两条命令第二条会访问课程运行中的真实/dynamic-job输出完整 JobItem.\.venv\Scripts\python.exe-m playwright install chromium.\.venv\Scripts\python.exe-m scripts.verify_dynamic_browser为什么不要 sleep(3)固定睡 3 秒在快机器上浪费时间在慢网络上又可能不够。等待业务 selector 表达“我们需要的内容已经出现”。还可等待特定 response 或页面自定义状态但networkidle对持续上报和长连接页面可能永远不安静不能当通用完成信号。timeout 是失败边界不是提高成功率的魔法。超时后应保存截图、当前 HTML、URL 和 task_id 供调查同时关闭 page不要无限延长到 Worker 被占满。若页面显示登录或验证码应停止并回到授权流程。浏览器环境会带来哪些新成本镜像需要浏览器二进制和系统库体积更大一个 Context 占用的内存远高于 HTTP 连接渲染有 CPU 成本页面脚本可能发起大量图片、字体和统计请求崩溃恢复也更复杂。可以在明确不影响页面业务的前提下拦截图片等资源但必须测试不要盲目屏蔽所有脚本或 XHR。会话隔离用 BrowserContext。授权用户的 Cookie 应只存在对应租户/来源 context任务结束清理持久化 storage_state 属于敏感凭据不能提交到仓库。浏览器不是安全策略的替代品goto 前仍要 allowlist URL页面中的重定向和弹窗也要限制。普通页面与动态页面共用一份解析器最容易出现的长期问题是http_parser.py与playwright_parser.py分别维护字段半年后一个识别 13 薪、另一个不识别。JobRadar 让 Playwright 只负责把页面变成最终 HTML之后复用parse_detail和 Pydantic字段合同只有一份。若 JSON API 的字段与 DOM 完全不同可单独做parse_api_job但输出仍必须是 JobItem并用同一组业务模型测试。不要为了“统一”先把 JSON 拼成假 HTML那会丢失类型并增加转义问题。本篇完整浏览器适配模块这个函数短却有真实职责和调用者浏览器调度器创建 Page函数等待业务就绪并返回已校验职位。它不负责进程启动、权限、数据库或重试边界因此可以被精确测试。仅在页面必须执行 JavaScript 时使用的 Playwright 薄适配层。fromtypingimportProtocolfrom.modelsimportJobItemfrom.parsingimportparse_detailclassBrowserPage(Protocol):asyncdefgoto(self,url:str,*,wait_until:str)-object:...asyncdefwait_for_selector(self,selector:str,*,timeout:float)-object:...asyncdefcontent(self)-str:...asyncdefrender_dynamic_job(page:BrowserPage,url:str,*,timeout_ms:float5000)-JobItem:等待职位根节点出现再把渲染后 HTML 交回共享详情解析器。iftimeout_ms0:raiseValueError(timeout_ms must be positive)awaitpage.goto(url,wait_untildomcontentloaded)awaitpage.wait_for_selector(article.job-detail[data-job-id],timeouttimeout_ms)returnparse_detail(awaitpage.content(),url)本篇课后练习比较wait_for_timeout(3000)、wait_for_selector(...)、wait_for_load_state(networkidle)的成功条件与误判风险。写出一个完整async with async_playwright()调用示例负责创建 browser/context/page、调用render_dynamic_job并关闭资源。对 TargetLab/dynamic做诊断指出初始 HTML、JavaScript 请求和真正数据接口分别在哪里。下一篇要决定这个场景到底该用浏览器还是直接调用公开接口。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →