恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Playwright动态页面抓取实战:从渲染等待到截图排查

  • 首页
  • 资讯中心
  • /
  • Playwright动态页面抓取实战:从渲染等待到截图排查

相关资讯

GLM-5.3接入Codex保姆级教程:config.toml配置与踩坑实录 2026/9/9 4:13:15
P2P通信与EMAC驱动报错:开发板节点网络排错全记录 2026/9/9 4:13:15
STM32F103环境监测系统:DHT11+SGP30+OLED+蜂鸣器实战 2026/9/9 4:13:15

最新资讯

Modbus RTU底层原理与STM32调试实战指南
7.7 Huge Page 与 Fragment 机制
AI推动编程成通用技能:不会写代码也能用脚本解放双手
嵌入式Linux屏与安卓屏怎么选?开机时间、稳定性、成本实战对比
i.MX6ULL Platform驱动匹配机制详解:从设备树到probe调用
六大场景六款实测下载工具:从视频到固件一网打尽

今日推荐

基于MongoDB的图书管理系统:数据建模与Spring Boot+Vue实战
Claude Code安装配置全攻略:从零开始用上终端AI编程助手
tmux 会话管理与终端复用:AI 编程工作流的调度中枢实战

本周热门

超人会飞不算本事:系统稳定依赖清晰规则与边界设计
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
基于CNN的调制信号识别:MATLAB实现时频图分类实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

Playwright动态页面抓取实战:从渲染等待到截图排查

发布时间:2026/9/9 4:18:15
Playwright动态页面抓取实战:从渲染等待到截图排查 1. 为什么零基础学爬虫到一定程度必须补上 Playwright 这一课想抓的数据在页面源代码里根本找不到打开浏览器却明明能看到。这种“浏览器能看到、代码拿不到”的挫败感几乎每个爬虫初学者都经历过。前几章还在用 requests 一把梭到了某些网站辛辛苦苦写好解析逻辑跑完 print 出来一看空的。页面里除了那几句熟悉的!doctype htmlhtml langzh-cn和一个空壳div idapp/div什么都没有。原因不复杂现在前后端分离的站点太多数据不是服务端塞进 HTML 返回给你的而是浏览器加载页面后再用 JavaScript 异步请求接口、动态创建 DOM 节点。requests 拿到的只是“渲染前的毛坯房”你要的数据在“渲染后的精装房”里而精装的过程由浏览器完成。想拿到完整页面要么逆向分析接口要么用 Playwright 这类浏览器自动化工具让代码去操控一个真实的浏览器内核等页面跑完 JS 再提取内容。这节实战项目教学我不会拿那些已经被写烂的入门 Demo 凑数直接上一套最贴近真实工作场景的组合拳用 Playwright 操控浏览器等待页面完成渲染后把渲染后的 HTML完整拉下来解析同时配合截图去定位“页面什么问题导致数据没抓到”。这套方法论解决了爬虫过程里最让人头疼的一类问题——不是代码写错了而是页面环境变了、元素没加载完、内容在 iframe 里、或者被弹窗遮挡了你纯看代码根本看不出来截图一拉出来就一目了然。先给零基础读者定个心Playwright 没有想象中那么难。它本质上就是一个能替你把浏览器打开、点按钮、填表单、翻页、截图的自动化工具。唯一要适应的是把“面向页面的写法”转换为“面向操作的写法”。整节内容我尽量做到每一句都能听懂、每一步都能照做跟着跑完你对动态页面爬取会有一个质的飞跃。2. 动手前先搞清楚渲染后的 HTML 和你在浏览器“查看源代码”看到的不一样很多新手有个误区用浏览器“查看源代码”看到的 HTML就和 requests 拿到的差不多不对。浏览器里的“查看源代码”看到的是服务端返回的原始 HTML而 DevTools 的 Elements 面板里显示的才是渲染后的 DOM。你需要爬的数据往往只存在于后者中。2.1 一次最简单的“等待渲染完成”思路拆解Playwright 的核心价值不在于它会打开浏览器而在于它替你管理了“渲染等待”这件事。requests 是发送请求立刻拿响应JS 执行不执行它管不着Playwright 则是启动了真正的浏览器页面加载会走完整流程——请求资源、执行脚本、触发事件、更新 DOM。所以用 Playwright 爬数据基本思路就三步打开页面等待关键元素出现提取内容。听起来简单可实操时 90% 的初学者栽在第二步搞不清楚“什么时候算加载完成”。页面里图片还在转圈、数据接口还没返回、某个 JS 文件还在阻塞你急着去解析自然拿到空数据。这时就要靠同步等待机制最常用的是wait_for_selector()。它的意思是“我代码在这里停住直到页面上出现我要的这个元素再继续往下走”。比time.sleep()盲等靠谱得多因为网络快的时候不等白不等网络慢的时候固定等几秒又不够用而wait_for_selector()是精确地等“条件达成”不浪费一秒也不会因为提前执行而扑空。2.2 如何拿到完整渲染后的 HTML用 Playwright 拉取渲染后 HTML 不难使用page.content()就能拿到当前页面的完整 DOM 字符串里面已经包含了 JS 生成的所有标签和数据。但这里有个细节容易踩坑page.content()拿到的是“你调用它的那一刻”的页面状态。如果页面里使用了懒加载下拉才会触发数据请求你没滚动页面就调用它返回的 HTML 里照样没有底部的内容。所以拿 HTML 之前要想清楚页面到底是不是一次性渲染完如果不是需要先模拟滚动或者点击“加载更多”。从信息量来看抓下来的 HTML 会比源码多了很多东西比如打包后的 script 标签里的 JSON 数据、动态拼接的节点、经过 JS 计算后的属性值。想在这种大段 HTML 里定位内容建议不要直接上手正则去搜先用 CSS 选择器或者 XPath 把目标元素夹出来再取文本或者属性效率和稳定性都会好很多。2.3 Playwright 和 Selenium 怎么选为什么教学里推荐前者如果你之前听说过 Selenium可能想问两者差异。Playwright 是后起之秀几个关键优势很突出安装时不需要额外下载驱动它会自动匹配浏览器版本API 设计更现代化等待机制、选择器、截图都内置得很好用而且支持多页面、多标签、iframe 切换这些体验做得比 Selenium 顺手。我不是说 Selenium 一无是处——如果你维护的是一个成熟的老项目团队全在用 Selenium没必要推翻重来。但对零基础入门的新项目我建议直接用 Playwright它更符合“边写边调试”的习惯语言风格也更接近人思考的方式。毕竟学爬虫最容易内耗的是环境配置问题把时间省下来研究页面结构更划算。3. 环境准备从零安装到跑通你的第一个 Playwright 脚本记得带我带过的一个新手弟子上手时光是装 Playwright 就折腾了一小时——先是 pip 装好了库结果忘了装浏览器内核一运行就报错说找不到可执行文件。这类问题其实完全可以避免只要你按顺序走完下面这两个步骤。3.1 Python 环境和 Playwright 库的安装姿势默认你已经装好了 Python 3.8 以上的版本。安装 Playwright 库很简单一条命令pip install playwright这时候只是装了 Python 包还没装浏览器内核必须再跑一条命令playwright install chromium这步会下载 Chromium 内核体积大概一百多兆具体看网速稍微有点耐心。如果你想省事儿只跑这一种浏览器装 Chromium 就够了它可以覆盖绝大多数爬虫场景。Firefox 和 WebKit 看需要再装日常用不到。命令行不好用或者你和我一样喜欢在 VS Code 里直接调也可以试试官方自带的神器录制工具。运行playwright codegen会自动打开一个浏览器窗口和一个操作录制面板。你在浏览器里手动点击、输入、翻页面板里会同步生成对应的 Playwright 代码。这对零基础理解“某种操作该用哪个 API”很有帮助比如你会看到点击按钮生成page.click()填写输入框生成page.fill()。可以把它当一个辅助学习工具不建议生产环境完全依赖录制生成的代码因为录出来的选择器往往很长很脆弱后面维护起来麻烦。3.2 快速验证安装是否成功的冒烟测试安装完第一件事不是写正式代码而是先拿一个最简单的脚本验证环境通不通。新建一个demo.py写入from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(https://example.com, timeout30000) print(page.title()) browser.close()这段代码跑通之后屏幕会打印出Example Domain说明库、浏览器内核、网络全部没问题。如果在这里报错先检查两个点看到Executable doesnt exist的报错是没执行playwright install chromium看到超时是网络问题可能开代理或者换网络解决。这个脚本也可以当作后续所有代码的脚手架在此基础上加内容即可。3.3 有头模式和无头模式跑爬虫该用哪种Playwright 启动浏览器时有个核心参数headless。headlessTrue是无头模式浏览器在后台运行不会弹出窗口适合服务器上跑headlessFalse是有头模式你会看到真实浏览器窗口打开和操作适合调试。零基础阶段我强烈建议先把headless设成False用眼睛盯着浏览器操作看看你的代码做了什么页面出现了什么变化。等确定逻辑没问题再改成True让它安静地在后台跑。不然一旦脚本爬到一半报错你连页面当时的模样都不知道排查效率低一半。4. 实战项目拆解动态渲染页面数据抓取遇到的真实问题这一节我们用一个小项目把整条链路串起来抓取一个动态渲染的排行页面提取列表数据并保存到本地。为了让你能照着复现整个过程我会拆成几个阶段每阶段都写明在做什么、为什么这么做。4.1 先分析页面怎么判断数据是不是动态渲染的拿到一个目标网站不要急着写代码。先在浏览器里打开右键点击“查看页面源代码”用 CtrlF 搜一下你想抓的数据关键词。如果在源代码里能搜到说明数据是服务端渲染的requests 完全可以胜任如果源代码里搜不到但它明明显示在页面上那就是动态渲染的这时候就要上 Playwright。还有一种情况源代码里能看到一部分比如能搜到标题但列表数据搜不到说明是混合渲染模式。这一章学的技术在这种场景下依然适用。我这里用一个排行榜页面做例子。假定页面打开后能看到一个table列表数据分页加载点“下一页”会更新列表内容。我们最终目标是把所有页的记录抓下来包括排名、名称、数值。4.2 明确等待条件怎么用 wait_for_selector 卡准时机页面打开后列表数据不是瞬间出现的。新手最容易犯的错是page.goto()之后马上就去取内容结果页面还停留在空白状态。Playwright 的goto()方法默认等待的是页面load事件它只代表“网络层面加载完了”不代表“JS 逻辑都执行完了”。所以安全做法是在goto()之后加上关键元素的等待。比如这个排行榜页面的列表外层有一个idrank-list的节点那么代码就应该写成这样from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://example.com/rank, timeout60000) # 等待列表加载完成 page.wait_for_selector(#rank-list) html page.content() print(html[:2000]) browser.close()注意我用了timeout60000这是给页面加载设置了一个最大容忍时间。默认超时是 30 秒但某些页面资源多、接口响应慢30 秒不够用提前设长一点能减少很多莫名其妙的超时问题。wait_for_selector返回成功后至少说明你要找的关键节点已经在 DOM 里了这时再去page.content()拿到完整 HTML 的把握会大很多。注意wait_for_selector只负责“元素出现在 DOM 里”不代表“数据一定填充完了”。如果那个元素是空的容器JS 还要再往里填内容你仍然可能拿到空壳。更稳妥的等待方式是循环检查元素的实际文本是否包含预期关键字或者直接等待代表数据行的那一个元素出现比如等待#rank-list tr出现而不是等外层容器。4.3 用 CSS 选择器精准提取目标数据块拿到整个 HTML 后如果直接用正则去搜目标值你会被页面上各种脚本里的同名变量干扰。更稳妥的方式是分两步先用query_selector()把列表区域这个“大块”圈出来再用query_selector_all()提取列表中每一行。看一下核心提取逻辑的示例from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(https://example.com/rank, timeout60000) # 等列表里的每一行渲染完毕 page.wait_for_selector(#rank-list tr) rows page.query_selector_all(#rank-list tr) data_list [] for row in rows: rank row.query_selector(.rank) name row.query_selector(.name) score row.query_selector(.score) if rank and name and score: data_list.append({ rank: rank.inner_text().strip(), name: name.inner_text().strip(), score: score.inner_text().strip() }) for item in data_list: print(item) browser.close()先圈行、再圈格比直接全局选择器一条路走到底要稳得多。某些表格页面结构复杂数据藏在嵌套的div里面甚至不是标准表格结构但策略一致找重复出现的“行容器”提取需要的子节点。这个方法到了任何网站都适用本质是把页面里不断重复的那一坨 HTML 结构当成模板。每个query_selector之后我都做了空值判断这在解析时是保命的习惯。前端代码稍微改一下某个字段没了你的脚本不会整个崩溃最多那一条数据的字段缺失便于排查是哪个结构变了。4.4 翻页抓取时如何应对页面刷新模式和内容替换模式光抓第一页数据当然不够。排行榜通常有几十页翻页会遇到两种截然不同的情况。第一种是传统的多页面跳转点击下一页后整个页面重新加载URL 或页面内容会变化。这种模式下把翻页逻辑放在一个循环里每次点击后重新等待列表元素出现即可。第二种是“无刷新替换”点击下一页后 URL 不变化页面只通过网络请求拉取新数据并替换列表内容。这种模式下最大的坑是点击下一页之后列表元素在 DOM 中其实依然存在wait_for_selector(#rank-list tr)会立刻返回成功因为上一页的数据还在那里这就会导致你抓到重复的上一页内容。这时候正确做法是等待“翻页后才出现”的标志。比如页码高亮变化或者列表第一条的名字改变了。你也可以在点击前记录当前列表第一条文本点击后用page.wait_for_function()去等待第一条内容发生变化这是处理无刷新翻页最实用的办法之一。简单翻页代码骨架可以这样写for page_num in range(2, 6): next_button page.query_selector(#next-page) if not next_button: break # 记录当前列表第一条内容 first_text page.query_selector(#rank-list tr .name).inner_text() next_button.click() # 等待第一条内容变化说明新数据加载完成 page.wait_for_function( (first_text) { const el document.querySelector(#rank-list tr .name); return el el.innerText ! first_text; }, first_text ) # 到这里再解析数据就是新一页的内容用wait_for_function可以在页面上下文中执行 JavaScript 来判断条件是否满足比固定 sleep 或者单纯等元素出现要精准得多。我第一次搞懂这个用法时最大的感受就是写爬虫不是猜而是给程序装一双眼睛让它自己确认“数据到位了没”。5. 截图定位问题页面白屏时最直观的体检单爬虫跑了半天数据不对甚至为空你最需要的是“现场照片”而不是对着报错胡乱猜。Playwright 的截图能力这时候比什么都好用。5.1 截图文件怎么截才能拍出“关键证据”截图分两种截整个页面截某个元素。截整个页面最直观适合看整体布局代码是page.screenshot(pathfull_page.png, full_pageTrue)注意把full_pageTrue带上这会把整个可滚动区域都拍下来而不是只拍视口内的内容。默认的视口高度一般只有 720 像素左右如果内容很长不设置 full_page 得到的截图会“切掉下半身”。截某个元素适合看特定区域有没有渲染成功element page.query_selector(#rank-list) element.screenshot(pathrank_list.png)这两种截图结合使用能快速定位问题在“页面整体没加载出来”还是“局部元素异常”。我在实际开发中习惯在关键节点截图留存比如页面刚打开时截一张、等待条件之后截一张。这样做的好处是回头看日志时能清楚知道每一步页面是什么状态不需要重新跑一遍。5.2 实战排查一整个页面白屏或者只有背景色怎么办页面截图出来是一片白或者只有 body 的背景色问题大概率出在三点第一页面 JS 报错中断了。浏览器里打开控制台能看到红色的错误信息但 Playwright 不会自动帮你打印这些。你可以在代码里监听页面报错事件page.on(console, lambda msg: print(浏览器输出, msg.text)) page.on(pageerror, lambda err: print(页面 JS 出错, err))这样脚本运行时浏览器控制台的错误信息就会同步打印到你的终端里很多疑难杂症一看就穿。第二资源文件没加载成功。如果脚本或样式表来自某个 CDN而 CDN 不稳定页面就渲染不出来。截图往往能看到页面的部分区域有内容但样式错乱或者模模糊糊只显示最基础的文字。第三页面做了浏览器环境检测发现是自动化环境就故意不渲染数据。这种情况常见于一些风控较严的站点。你可以试试在启动浏览器时关闭自动化特征提示或者设置user-agent、viewport与普通浏览器一致代码层面能做的也就是尽量让环境变得“不那么显眼”具体站点差异太大没法一概而论。5.3 实战排查二列表区域空白但页面上能显示其他内容这种问题在我初学动态渲染抓取时遇到得最多。页面打开没问题标题都正常唯独目标列表区域空着。截图出来能清楚看到那个区域确实是个空白但它周围有正常内容这说明动态数据请求那块出了问题。比较常见的原因是列表数据通过额外的 AJAX 接口加载这个接口需要携带特定的 header 或者 cookie而页面初始状态下没有这些信息只有某个事件触发后才带上。也可能是接口返回慢你等的时间不够。遇到这种情况截图的价值是验证“页面确实没渲染出来”接下来需要去 DevTools 的 Network 面板里找到列表数据对应的那个 XHR 请求观察它返回的状态和内容。如果这个接口是公开的也可以考虑直接调接口拿 JSON比渲染页面后从 DOM 里抠快得多。5.4 实战排查三元素明明就在页面里代码却抓不到有一点需要特别留意——元素在页面上“看得见”不等于代码“选得中”。很典型的一个场景是页面里嵌了一层 iframe列表数据在整个 iframe 内部你用主页面page.query_selector()去抓什么都抓不到因为不同 frame 的同名 DOM 之间是隔离的。Playwright 里获取 iframe 内的元素方式如下frame page.frame(urlhttps://example.com/rank-iframe) # 或 frame page.frame_locator(#iframe_id) data frame.query_selector_all(#rank-list tr)先用page.frames看一下页面里到底有哪些 frame再判断目标在哪个 frame 里。这个方法遇到“页面框架复杂”的站点是救命级的。还有一类情况更隐蔽页面中有多个同名的选择器而你选到了第一个隐藏的而不是正在显示的那个。比如需要抓取多个列表项中的价格页面上还有价格筛选的隐藏模板query_selector_all()会把这些隐藏模板也选进来。排查时可以先打印所有选中元素的数量和页面肉眼可见的行数对比数量对不上就说明里面有隐藏元素混进去了。解决办法是定位时加上可见性过滤Playwright 的选择器支持:visible后缀能把不可见元素过滤掉。6. 常见报错与排查技巧实录新手最容易栽的五个坑写这一节之前我翻了一下教学群里大家跑爬虫脚本高频遇到的问题答案高度集中在几个点上。我特意整理成一个速查表外加一些代码层面就能规避的写法。现象根本原因解决方向运行后只显示Process finished with exit code 0什么都没有脚本没有报错但逻辑中没有任何输出或页面没抓到数据检查是否忘了print()检查等待条件是否过早在空元素上通过用page.screenshot看页面状态wait_for_selector超时报错元素没有出现在页面中可能选择器写错、数据渲染失败、元素在 iframe 内先打印完整 title 和 content 的前几百字符确认页面状态检查选择器是否匹配目标元素能打开页面但page.content()还是源码你调用前页面 JS 没执行完或者执行需要点击某个按钮才触发在 content() 之前添加正确的等待条件用wait_for_load_state(networkidle)帮助等待网络空闲中文字符乱码或者打印出来是转义符控制台编码问题或 HTML 实体转义提取文本时用inner_text()而不用get_attribute()或直接切片设置运行环境编码为 UTF-8抓到的内容里混入大量相同结构、明显不是目标内容的数据页面中有隐藏模板、重复 DOM用:visible过滤隐藏元素打印元素数量和页面实际数量对比缩窄选择范围6.1 运行结果显示 exit code 0 但没有内容的特殊处理这个报错很多人遇到过程序没报错、没异常正常退出但你要的结果一个都没有。这类问题的核心在于“脚本没有抛错等于没有检查逻辑是否正确”。如果你的代码里只有page.goto()然后直接解析元素就算元素没加载出来解析结果为空列表程序照样是“成功退出”的没有任何人提醒你出了问题。这提醒我们一件事写爬虫脚本不能只写“成功路径”还要写“数据校验”。比如解析完成后对结果列表做一次长度判断为空就截图并打印告警信息这样才能在跑批时第一时间发现问题。我给自己的脚本加了个习惯动作——核心节点解析完后用 Python 的assert或者if not data: raise Exception(没有抓到数据)强制程序“大声失败”而不是安静地跑完然后白折腾一轮。if not data_list: page.screenshot(patherror_empty.png, full_pageTrue) raise Exception(解析结果为空已截图 error_empty.png打开看页面状况)这样做的价值在于出错时你不需要重跑脚本去复现问题只需要打开截图基本就能定位是页面结构变了、还是反爬拦截了、还是等待条件不够。6.2 Playwright 脚本调试时要不要限制网速或模拟弱网还有一个容易让新手困惑的场景本机跑脚本很快没问题一放到服务器上就各种超时。原因很简单服务器网络环境未必有你本机顺畅某些 CDN 资源在特定网络环境下加载就是慢。这时候调试时可以在本地故意模拟慢网络提前暴露问题。Playwright 的page.route()可以拦截请求并注入延迟import time def slow_down(route, request): time.sleep(3) route.continue_() page.route(**/*, slow_down)这样每个请求都会慢三秒页面的加载逻辑会被整体拉慢你就能看出哪些地方存在隐式的竞态问题。如果连这么慢的加载都能正常抓到数据说明等待条件设计得足够稳。这个方法我自己在调试重要爬虫脚本时经常用能提前排查掉很多偶现 bug。6.3 动态按钮点击失败或无效的原因分析很多页面需要点击“展开全文”或“加载更多”才能显示全部数据。新手照着文档用page.click()结果运气好点成功了运气不好明明按钮在页面上代码却报 element is not attached to the page document。这类报错的本质是你要点击的节点不是一个“稳定的静态节点”而是 JS 临时渲染出来的。你的代码找到这个节点后页面发生了一次重绘原节点被销毁新节点诞生了你的点击目标变成“没有依附到页面文档上的孤儿节点”自然就点不中。遇到这种情况不要想着如何缓存一个节点再用而是每次点击时重新查找节点。Playwright 内部对选择器的处理本来就在点击时重新查找如果你用query_selector先存了节点再用节点去点击反而容易踩这个坑。直接始终用选择器描述目标让 Playwright 自己处理查找时机。按钮不在视口内还会触发另一个问题要确保点击前先滚动到该元素的位置。button page.query_selector(#load-more) if button: button.scroll_into_view_if_needed() button.click()7. 如何把 Playwright 渲染能力融合进上一章学的 Scrapy 框架这个点本来是进阶内容但在实战项目里几乎一定会遇到Scrapy 已经写好了爬虫调度、管道存储和中间件体系你不可能为了某一类需要渲染的页面放弃整个框架重新用 Playwright 写一套。市面上有 scrapy-playwright 这个第三方库专门解决 Scrapy 和 Playwright 结合的问题。7.1 scrapy-playwright 的关键配置思路安装命令很简单pip install scrapy-playwright然后在 Scrapy 项目的settings.py中开启配置DOWNLOAD_HANDLERS { http: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, https: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, } TWISTED_REACTOR twisted.internet.asyncioreactor.AsyncioSelectorReactor这样 Scrapy 的请求就会自动由 Playwright 接管渲染。在编写爬虫时你也可以让特定请求使用 Playwrightdef start_requests(self): url https://example.com/rank yield scrapy.Request( url, meta{ playwright: True, playwright_include_page: True, playwright_page_goto_kwargs: { wait_until: networkidle, timeout: 60000 } }, )关键点在于playwright_include_page设为True这样在parse()里你可以拿到page对象进而调用page.wait_for_selector()这类方法。注意做完这一切之后要主动关闭页面否则浏览器窗口会越积越多资源得不到释放。async def parse(self, response): page response.meta[playwright_page] await page.wait_for_selector(#rank-list) html await page.content() # ... 解析 html ... await page.close()7.2 为什么框架结合时需要处理异步Scrapy 本身是异步框架而 Playwright 的操作也属于异步接口。在 Scrapy 中使用 Playwright 时如果你不熟悉异步编程很容易在等待元素时卡住。写parse方法时用async def中间所有调用 Playwright 方法的步骤都加await。新手阶段的规则就一条凡是文档里说是异步函数的调用时前面别忘await不然后果就是事件循环没等操作完成就直接走到下一步抓回来一堆空数据。这套组合方案实际上打通了一条“普通静态页面用 Scrapy 跑需要渲染的动态页面切换 Playwright 处理”的生产链路。后续你接触的爬虫项目稍微复杂一点八成会落到这种混合架构上现在有这个认知后面衔接不会太吃力。8. 离生产还差一步数据处理、异常兜底和日志记录很多人写的爬虫脚本在本地能跑通就心满意足了但拿到生产环境跑批跑上几个小时就崩仔细一看是没做异常兜底。既然这篇是实战项目教学我就把这一步也写了不藏着掖着。8.1 保存数据时用什么格式更实用入门阶段保存成 CSV 就能满足大多数需求。写入时要注意设置编码为utf-8-sig这个编码会在文件开头加一个 BOMExcel 打开时不会出现中文乱码。用 gbk 编码保存也可以但兼容性上不如utf-8-sig省心。import csv with open(rank_data.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[rank, name, score]) writer.writeheader() writer.writerows(data_list)如果你抓取的数据字段是嵌套结构比如列表项里包含子列表CSV 表达不方便可以改用 JSON 保存层级结构清晰后续用 pandas 或别的工具处理也更方便。8.2 页面级 try/except 异常兜底设计爬虫跑久了什么样的情况都可能遇到页面超时、元素缺失、接口改了、网络断开。如果脚本没有异常捕获任何一个错误都会让整个任务中断。我习惯在“每一页的处理逻辑”外面包一层异常捕获保证单页出错不会影响整体流程for page_num in range(1, 11): try: page.goto(fhttps://example.com/rank?page{page_num}, timeout60000) page.wait_for_selector(#rank-list tr, timeout30000) # 解析并保存 except Exception as e: page.screenshot(pathferror_page{page_num}.png, full_pageTrue) print(f第{page_num}页失败{e}) continue保存日志也是生产环境必不可少的一环。运行脚本的终端一旦关闭所有输出就没有了之后再排查问题会直接断掉线索。加一句简短的日志输出把关键步骤、成功数量、失败信息追加写到一个 log 文件成本极低但价值极高。import logging logging.basicConfig( filenamecrawler.log, levellogging.INFO, format%(asctime)s %(levelname)s %(message)s, ) logging.info(第 %s 页抓取成功共 %s 条, page_num, len(data_list))9. 浏览器自动化运行在云服务器上的资源消耗与取舍最后说一个“脚本放本机能跑放服务器一直崩”的话题。这和页面逻辑没关系纯粹是运行环境的问题。无头浏览器不是完全没有图形资源消耗的。服务器如果配置不高同时跑多个 Playwright 实例容易内存不足导致浏览器进程被系统杀掉。解决办法一是控制并发数量二是在代码里确保用完之后就关闭浏览器三是在启动参数里限制资源browser p.chromium.launch( headlessTrue, args[--disable-dev-shm-usage, --disable-gpu] )--disable-dev-shm-usage在内存较小的 Linux 服务器上特别有用。默认情况下 Chromium 会把共享内存放在/dev/shm而某些云服务器的/dev/shm默认只有几十兆浏览器一跑就爆加上这个参数让它改用普通内存崩溃率会明显下降。如果抓取频率高还可以考虑设置视口大小小一点比如 1280x720 就够了不需要追求 4K减少绘制和截图时的工作量。重要提醒爬虫访问目标网站时应遵守目标网站的 robots 协议及服务条款控制请求频率合理设置抓取间隔不要给对方服务器造成压力。写爬虫的初衷是利用技术解决问题但也要尊重数据源衡量好“技术能不能做”和“该不该做”。我个人在实际操作中的体会是——渲染型页面的爬取成功与否往往不取决于代码多华丽而取决于你能不能准确描述“页面到底什么时候算准备好”。requests 时代我们操心网络请求Playwright 时代我们多了“等待渲染”这一步这一步处理水平直接决定脚本稳定性。刚开始写可以把每个等待条件都写得保守一点多截几张图宁可慢一点也别抓空。跑上几次积累出自己的套路后你会发现动态页面也不过是一件熟悉的工具活儿。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号