做网页爬虫的朋友大概率都遇到过一类难题页面看着内容很多常规请求或者简单爬虫只能抓到首屏数据往下滑动页面才会不断加载新内容也就是大家常说的懒加载和无限滚动。传统requestsBeautifulSoup的组合面对这类JS动态渲染页面基本束手无策因为接口异步请求、DOM动态渲染都依赖浏览器环境。而Playwright作为微软推出的现代化自动化工具原生支持模拟浏览器行为、等待元素加载、监听网络请求是解决这类场景的最优选择之一。本文结合一线爬取实战从原理分析、流程设计、代码实现、异常处理、性能优化五个维度完整讲解如何用Playwright处理懒加载与无限滚动页面同时分享实战中踩过的坑内容兼顾新手入门与进阶优化看完就能直接落地使用。1. 先搞懂原理为什么普通爬虫抓不到滚动加载内容首先区分两个高频概念也是问题的根源图片/资源懒加载页面首屏仅加载可视区域内的图片、视频等资源非可视区域资源会延迟加载通常通过data-src、lazy属性控制滚动到对应位置后才替换为真实资源地址。列表无限滚动页面没有分页按钮用户滚动至底部时前端通过AJAX/fetch向后端请求下一页数据动态拼接DOM节点实现“无限加载”效果。传统HTTP请求工具只会获取页面初始HTML此时异步数据、懒加载资源都还未触发加载自然拿不到完整内容。而Playwright会启动真实浏览器内核复刻人的浏览行为滚动页面、等待请求响应、等待DOM更新从根源上适配动态页面逻辑。2. 整体执行流程设计结合爬取逻辑梳理出标准执行流程下面用流程图直观展示整体链路纯懒加载资源无限滚动列表有新数据无新数据/到达末尾启动Playwright浏览器打开目标网页等待首屏DOM与基础资源加载完成判断页面类型逐区域滚动触发资源加载循环滚动至页面底部等待异步接口请求完成判断是否加载新数据停止滚动解析完整页面DOM提取数据数据存储/导出关闭浏览器释放资源整个流程核心在于模拟滚动行为和合理等待机制这也是和简单自动化脚本最大的区别。很多新手写的脚本要么滚动太快导致数据没加载完要么死循环无限滚动都是这两点没处理好。3. 环境准备本次实战基于Python版本Playwright先完成环境部署命令行依次执行# 安装依赖库pipinstallplaywright# 自动下载对应浏览器内核Chrome/Firefox等playwrightinstall推荐使用无头模式运行浏览器减少资源占用适合服务器后台长期运行。4. 实战一解决图片/资源懒加载爬取这类场景常见于资讯站、图片图库、商品列表页特征是图片地址存放在data-src等自定义属性中首屏src为空或是占位图。4.1 核心思路分段缓慢滚动页面避免一次性滚到底导致部分区域未加载滚动后预留加载等待时间确保浏览器完成资源请求遍历DOM节点提取真实资源链接。4.2 完整可运行代码importtimefromplaywright.sync_apiimportsync_playwrightdefcrawl_lazy_load_image(url):withsync_playwright()asp:# 启动浏览器无头模式关闭图像加载可进一步提速按需开启browserp.chromium.launch(headlessTrue)contextbrowser.new_context()pagecontext.new_page()# 访问页面等待页面基础加载page.goto(url,wait_untildomcontentloaded)time.sleep(2)# 获取页面可视高度、总高度分段滚动viewport_heightpage.viewport_size[height]total_heightpage.evaluate(document.body.scrollHeight)current_height0scroll_stepviewport_height# 每次滚动一屏高度# 循环分段滚动whilecurrent_heighttotal_height:current_heightscroll_step# 执行JS滚动操作page.evaluate(fwindow.scrollTo(0,{current_height}))# 等待懒加载资源渲染time.sleep(1.5)# 实时更新页面总高度部分页面滚动后页面高度会变化total_heightpage.evaluate(document.body.scrollHeight)# 提取所有懒加载图片链接img_listpage.locator(img).evaluate_all( imgs { let res []; imgs.forEach(img { // 优先取懒加载属性再取原生src let src img.dataset.src || img.src; if(src !src.includes(placeholder)){ res.push(src); } }); return res; } )print(f共抓取到图片链接{len(img_list)}条)forlinkinimg_list:print(link)# 释放资源context.close()browser.close()returnimg_listif__name____main__:target_url这里替换为目标测试网址crawl_lazy_load_image(target_url)4.3 关键细节说明分段滚动不使用一次性滚到底window.scrollTo(0, document.body.scrollHeight)部分网站加载速度慢一次性滚动会跳过中间区域导致资源加载失败动态更新页面高度部分页面滚动后会新增内容页面总高度持续变化必须实时重新获取scrollHeight属性兼容不同网站懒加载属性命名不同除了data-src还有data-original、data-lazy等可根据目标站点灵活适配。5. 实战二解决列表无限滚动核心难点无限滚动是爬虫重灾区页面没有分页标识滚动到底部就请求下一页数据极易出现死循环滚动、数据重复抓取、接口请求超时三类问题。5.1 核心思路每次滚动到底部后记录当前页面数据条数/页面高度等待异步接口加载完成对比前后数据状态若数据无新增判定为已到页面末尾终止循环加入最大重试次数防止网络异常导致死循环。5.2 完整可运行代码fromplaywright.sync_apiimportsync_playwrightfromplaywrightimportexpectdefcrawl_infinite_scroll(url):withsync_playwright()asp:browserp.chromium.launch(headlessTrue)contextbrowser.new_context()pagecontext.new_page()page.goto(url,wait_untilnetworkidle)# 配置参数最大重试次数防止死循环max_retry8retry_count0# 记录上一次列表元素数量last_item_count0whileretry_countmax_retry:# 滚动到页面底部page.evaluate(window.scrollTo(0, document.body.scrollHeight))# 等待网络空闲确保异步请求完成page.wait_for_load_state(networkidle)# 统计当前列表条目数根据实际DOM选择器修改item_locatorpage.locator(.list-item)current_countitem_locator.count()ifcurrent_countlast_item_count:# 数据无新增重试计数1retry_count1print(f暂无新数据重试中{retry_count}/{max_retry})else:# 有新数据重置重试计数retry_count0last_item_countcurrent_countprint(f已加载数据条数{current_count})# 全部加载完成后解析数据all_data[]foriteminitem_locator.all():titleitem.locator(h3).inner_text()contentitem.locator(.desc).inner_text()all_data.append({title:title,content:content})print(f爬取完成总计数据{len(all_data)}条)context.close()browser.close()returnall_dataif__name____main__:target_url这里替换为无限滚动测试网址crawl_infinite_scroll(target_url)5.3 核心关键点解析等待策略选择代码中使用networkidle状态等待代表页面网络请求基本结束比固定time.sleep()更智能。固定延时在网络波动、服务器响应慢的场景下极易失效这是专业爬虫和业余脚本的核心区别。防死循环机制通过最大重试次数数据条数对比双重判断连续多次滚动后数据不再增加就判定页面到底彻底避免程序卡死。选择器适配代码中.list-item、h3等选择器需要根据目标网站实际DOM结构修改建议先通过浏览器开发者工具审查元素精准定位节点。6. 实战高频问题与解决方案踩坑总结这部分是长期实战积累的经验也是提升代码稳定性的关键很多人代码逻辑没问题但一运行就报错、漏数据基本都是以下问题问题1滚动后数据加载不完整频繁漏采原因页面接口响应慢、静态延时太短。解决方案混合使用networkidle等待短延时不单一依赖某一种等待方式同时降低滚动步长放慢滚动速度。问题2触发网站反爬出现验证码、访问限制原因滚动频率固定、浏览器指纹特征明显、请求速度过快。解决方案模拟真人操作滚动之间加入随机延时import random; time.sleep(random.uniform(0.8, 2.2))禁用无头模式指纹特征配置浏览器上下文伪装常规参数控制整体爬取速度不要高频连续请求。问题3页面高度不断增长程序无限循环原因部分网站有“预加载”逻辑滚动后会预留空白区域页面高度持续上涨。解决方案放弃单纯判断页面高度改用列表元素数量、文本内容哈希作为终止条件稳定性更强。问题4服务器运行无头浏览器崩溃原因Linux服务器缺少浏览器依赖库、内存占用过高。解决方案服务器环境提前安装系统依赖爬取大页面时分批执行定时回收浏览器上下文释放内存。7. 进阶优化性能与架构升级如果是批量爬取、分布式爬取场景基础代码还可以做三层优化异步版本改造业务量大时同步模式效率偏低可切换为playwright.async_api异步写法并发处理多个页面爬取任务。复用浏览器上下文多个同源页面爬取时复用同一个context保留Cookie和会话减少浏览器反复启动关闭的开销。拦截无用资源爬取文本数据时拦截图片、视频、广告等静态资源请求大幅提升加载速度、节省带宽# 拦截图片、字体等资源page.route(**/*.{png,jpg,jpeg,gif,woff},lambdaroute:route.abort())8. 总结懒加载和无限滚动是动态网页爬虫的两大经典场景Playwright凭借完整的浏览器模拟能力成为解决这类问题的首选工具相比Selenium它启动更快、API更简洁、稳定性更高。整个实现的核心从来不是“写滚动代码”而是合理的等待机制、严谨的终止判断和反爬适配。本文提供的代码可以直接修改选择器后落地使用同时配套的踩坑方案也能解决线上运行的大部分异常。在实际项目中大家可以根据目标网站的加载速度、反爬强度灵活调整滚动步长、等待时长、重试次数。如果后续需要对接接口、做分布式爬虫、数据入库也可以在现有代码基础上继续拓展。