1. 项目概述与核心挑战最近在做一个电商数据分析的项目需要获取拼多多百亿补贴板块的商品数据。一开始想着用传统的requests库配合BeautifulSoup应该就能搞定毕竟爬虫老手了。但实际操作下来发现拼多多百亿补贴页面完全是另一回事——商品列表、价格、销量这些关键信息在初次加载的HTML源码里根本找不到全是后续通过JavaScript动态渲染出来的。直接用requests去拿只能拿到一个空壳页面。这就是典型的动态网页爬虫难题。过去我们可能会用Selenium但它启动慢、资源占用高在需要稳定、高效地批量抓取数据时显得有点力不从心。这时候微软开源的Playwright进入了我的视野。它号称是为现代Web应用而生的自动化测试工具但在我看来它在处理动态内容、反爬策略方面简直是爬虫工程师的“瑞士军刀”。它支持无头模式、能自动等待元素加载、甚至可以模拟真实用户行为来绕过一些简单的检测这正是攻克拼多多这类复杂站点的利器。这个实战项目就是记录我如何利用Playwright一步步构建一个能够稳定、自动化提取拼多多百亿补贴商品数据的爬虫。整个过程不仅涉及工具的使用更关键的是对目标网站行为的分析和应对策略。无论你是刚接触Playwright还是正在为某个动态网站的数据抓取头疼希望这篇详尽的记录都能给你带来直接的参考价值。2. 环境准备与Playwright基础工欲善其事必先利其器。在开始编写爬虫之前我们需要搭建一个稳定且高效的开发环境。这个环境的核心就是Python和Playwright。2.1 Python环境与依赖安装首先确保你的电脑上安装了Python建议版本在3.8及以上。我习惯使用venv创建独立的虚拟环境避免项目间的包冲突。# 创建项目目录并进入 mkdir pdd_crawler cd pdd_crawler # 创建虚拟环境 python -m venv venv # 激活虚拟环境 (Windows) venv\Scripts\activate # 激活虚拟环境 (MacOS/Linux) source venv/bin/activate激活虚拟环境后命令行提示符前通常会显示(venv)表示你正在这个独立的环境中操作。接下来安装核心库pip install playwright安装playwright库的同时它会提供一个命令行工具。但仅仅安装Python库还不够Playwright需要对应的浏览器驱动才能工作。我们需要安装至少一种浏览器推荐Chromium因为它最轻量且与Chrome兼容。# 安装Playwright所需的浏览器Chromium, Firefox, WebKit playwright install chromium这个命令会下载Chromium浏览器及其配套的Playwright驱动。如果你需要测试其他浏览器也可以安装firefox或webkit但针对爬虫场景Chromium通常足够了。注意playwright install命令可能会因为网络问题下载缓慢或失败。如果遇到这种情况可以尝试设置环境变量来使用国内镜像源例如设置PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwright/。具体方法可以参考Playwright官方文档的故障排查部分。除了playwright我们可能还需要一些辅助库来处理数据pip install pandas openpyxlpandas用于将爬取的数据整理成结构化的DataFrame并导出为Excel或CSVopenpyxl是pandas写入Excel文件所需的引擎。2.2 Playwright核心概念快速上手对于用过Selenium的朋友Playwright的学习曲线会非常平缓。它的API设计更现代、更一致。这里快速过几个最核心的概念方便后续理解代码。1. 同步 vs. 异步 APIPlaywright提供了同步sync_api和异步async_api两套API。对于爬虫这种I/O密集型任务异步API能极大提升效率尤其是在处理多个页面或等待网络请求时。但为了降低初学者的理解门槛本实战项目将使用同步API它的代码逻辑是线性的更直观。如果你熟悉asyncio完全可以迁移到异步模式以获得更好性能。2. 浏览器、上下文和页面这是Playwright的三个核心对象层级Browser 代表一个浏览器实例比如我们启动的Chromium。Context 浏览器上下文。你可以把它想象成一个独立的“隐身会话”。每个上下文拥有独立的cookie、本地存储和缓存互不干扰。这在爬虫中非常有用可以隔离不同任务或模拟不同用户。Page 标签页。我们绝大部分操作导航、点击、提取数据都在Page对象上进行。一个典型的启动流程是启动浏览器 - 创建上下文 - 打开页面。3. 自动等待这是Playwright相比Selenium的一个巨大优势。在Selenium中我们经常需要写显式等待WebDriverWait来等待元素出现否则容易抛出NoSuchElementException。Playwright的绝大多数操作如click,fill,text_content都内置了智能等待。它会自动等待元素变得可操作可见、可点击、稳定等后才执行动作超时时间默认为30秒。这大大简化了代码减少了因页面加载速度导致的随机失败。4. 选择器Playwright支持多种选择元素的方式最常用的是CSS选择器和XPath。它的语法和document.querySelector一致非常友好。例如page.locator(‘.product-name’)会定位到所有class为product-name的元素。理解了这些基础我们就可以开始分析目标网站并设计爬虫方案了。3. 目标网站分析与爬虫策略设计在写任何一行爬虫代码之前花时间仔细分析目标网站的结构和行为模式是成功的关键。这能帮你避开很多坑设计出更鲁棒、更高效的爬取策略。3.1 拼多多百亿补贴页面特点分析打开拼多多官网或App找到“百亿补贴”入口。通过浏览器开发者工具F12进行观察你会发现以下几个显著特点动态渲染 页面初始HTML非常简洁商品列表区域通常是一个空的容器比如一个div idproduct-list。真正的商品数据是通过页面加载后发起的XHRAjax请求获取的JSON数据再由前端JavaScript动态插入到DOM中。这意味着直接解析初始HTML无效。无限滚动 商品列表采用“无限滚动”加载更多。当你滚动到页面底部时会触发一个新的请求来获取下一页的商品数据。这需要爬虫模拟滚动行为。反爬措施 像拼多多这样的大型电商平台肯定有反爬机制。虽然百亿补贴是公开页面反爬不会像登录后页面那么严格但仍可能包括请求头校验 检查User-Agent,Referer等。行为检测 过于快速的点击、滚动或非人类的行为模式如无间隔的规律性操作可能触发验证或封禁IP。参数加密 某些API请求的查询参数或载荷Payload可能被加密或包含动态生成的令牌token直接复制难以复用。数据结构化程度高 虽然页面是动态的但通过浏览器网络工具抓包通常能找到返回结构化JSON数据的API接口。这些数据非常干净包含了商品ID、名称、价格、销量、图片链接等所有我们需要的信息比解析HTML更可靠。3.2 爬虫策略模拟浏览器 vs. 直接调用API基于以上分析我们有两种主要策略策略A模拟浏览器解析最终DOM这就是Playwright的典型用法。我们用它启动一个真实的浏览器加载页面执行滚动操作让所有商品加载完毕然后直接从渲染后的页面DOM树中提取商品信息。优点 最接近真实用户能应对任何前端渲染技术通用性强。对于找不到或难以逆向的API接口这是唯一选择。缺点 资源消耗大内存、CPU速度相对较慢。如果页面元素结构复杂选择器可能不稳定。策略B拦截网络请求直接获取API数据利用Playwright可以监听和拦截网络请求的能力。我们让浏览器加载页面但不关心它渲染成什么样只关注它发起了哪些网络请求。当我们发现那个返回商品列表JSON数据的请求时直接获取其响应内容。优点 效率极高数据纯净且结构化无需解析HTML节省大量计算资源。缺点 需要精准识别出目标API请求并且其请求参数可能动态变化或有有效期限制需要一定的逆向分析能力。本次实战的策略选择对于拼多多百亿补贴经过抓包分析我找到了返回商品数据的API接口且其参数相对固定没有复杂的动态加密。因此我们将采用策略B为主策略A为辅的混合模式使用Playwright打开页面模拟必要的浏览行为如下拉刷新、滚动以触发API请求并获取正确的Cookie和上下文。通过Playwright的page.on(‘request’)或page.on(‘response’)事件监听器拦截目标API的响应直接提取JSON数据。如果API拦截失败或发生变化则回退到策略A从渲染好的DOM中提取数据作为保底方案。这种设计既追求了最高效率也保证了爬虫的健壮性。3.3 关键步骤拆解我们的爬虫将按以下步骤运行启动与配置 启动Playwright创建一个浏览器上下文并设置合理的请求头特别是User-Agent以模拟普通浏览器。导航与监听 打开百亿补贴页面URL同时设置网络请求监听器准备捕获目标API响应。触发数据加载 通过模拟滚动操作触发页面加载更多商品从而让目标API被调用。数据提取与解析 从拦截到的API响应中解析JSON提取商品字段。翻页/循环 判断是否还有更多数据通过模拟点击“下一页”按钮或继续滚动重复步骤3-4直到抓取足够数据或没有新数据为止。数据存储 将提取的数据清洗、整理后保存到pandas DataFrame并最终导出为Excel或CSV文件。资源清理 关闭页面、上下文和浏览器释放资源。接下来我们就进入具体的代码实现环节。4. 爬虫核心代码实现与详解现在让我们把策略转化为代码。我会分模块详细解释每一部分的作用和注意事项。4.1 初始化Playwright与浏览器配置首先我们编写一个函数来初始化Playwright并创建一个配置好的浏览器上下文。上下文Context的配置至关重要它决定了我们爬虫的“身份”和行为特征。from playwright.sync_api import sync_playwright import time def create_browser_context(): 创建并配置一个浏览器上下文。 返回: (browser, context) 元组 # 启动Playwright p sync_playwright().start() # 启动Chromium浏览器headlessFalse表示显示浏览器界面便于调试。正式运行可设为True。 browser p.chromium.launch(headlessFalse, slow_mo100) # slow_mo让动作慢一点方便观察生产环境去掉 # 创建一个新的浏览器上下文可以设置视窗大小、User-Agent等 context browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ) # 可以额外设置默认超时时间 context.set_default_timeout(30000) # 30秒 return p, browser, context代码详解与避坑指南sync_playwright().start(): 这是使用同步API的标准启动方式。chromium.launch(headlessFalse, slow_mo100):headlessFalse: 非无头模式浏览器窗口会显示出来。在开发调试阶段这非常有用你可以亲眼看到页面加载、滚动是否正常。在生产环境部署时务必将其改为headlessTrue这样浏览器在后台运行不显示界面节省资源且适合服务器环境。slow_mo100: 将每个Playwright操作延迟100毫秒。这也是纯调试工具让你能看清发生了什么。生产环境一定要删除或设为0否则爬取速度会极慢。browser.new_context(): 创建上下文。在这里我们设置了两个重要参数viewport: 设置浏览器窗口大小。有些网站的响应式布局会根据窗口大小返回不同的内容或布局设置一个常见的桌面端分辨率可以避免意外。user_agent: 设置用户代理字符串。使用一个最新的、常见的桌面版Chrome的UA是最基本的反反爬措施。永远不要使用Playwright的默认UA那会直接暴露你是自动化脚本。set_default_timeout(30000): 将上下文的默认操作超时时间设为30秒。对于网络较慢或元素加载慢的页面可以适当调高。4.2 导航至目标页面并设置请求拦截接下来我们打开目标页面并设置一个监听器来拦截我们感兴趣的API响应。def intercept_api_response(page, target_url_pattern): 设置一个监听器用于拦截匹配特定模式的API响应并存储其响应体。 这是一个闭包函数利用外部变量存储数据。 intercepted_data [] # 用于存储拦截到的响应数据 def on_response(response): # 检查响应URL是否匹配我们的目标模式 if target_url_pattern in response.url: try: # 尝试将响应体解析为JSON json_data response.json() intercepted_data.append(json_data) print(f[拦截成功] URL: {response.url}) # 你可以在这里打印一些关键信息例如商品数量 if goods_list in json_data: print(f 本次拦截到 {len(json_data[goods_list])} 个商品) except Exception as e: # 如果解析JSON失败可能是接口格式不对或不是目标接口 print(f[拦截但非JSON] URL: {response.url}, 错误: {e}) # 可以添加更多匹配模式例如检查响应头 content-type 是否为 application/json # 将监听器绑定到页面的‘response’事件 page.on(response, on_response) return intercepted_data # 返回这个列表后续主函数可以从中读取数据 def main(): p, browser, context create_browser_context() page context.new_page() # 在上下文中打开一个新页面 # 目标页面URL (以拼多多百亿补贴频道为例实际URL请以浏览器地址栏为准) target_page_url https://youhui.pinduoduo.com/ # 示例URL可能已变更 # 目标API URL模式 (需要通过浏览器开发者工具-网络标签页分析得到) # 这个模式需要你实际访问页面观察XHR请求找到一个返回商品列表JSON的请求URL api_url_pattern api.pinduoduo.com/api/xxx/goods_list # 示例模式需要替换 print(正在导航至目标页面...) page.goto(target_page_url, wait_untilnetworkidle) # 等待到网络空闲状态 print(页面加载完成。) # 设置API响应拦截器 print(正在设置API拦截监听器...) goods_data_list intercept_api_response(page, api_url_pattern) # ... 后续步骤模拟滚动以触发API请求关键点分析如何找到target_url_pattern这是本策略最核心的一步。你需要手动用浏览器打开拼多多百亿补贴页面按F12打开开发者工具切换到“网络”(Network)标签页。然后滚动页面让更多商品加载。在网络请求列表中筛选XHR或Fetch请求仔细查看每个请求的URL和预览(Preview)选项卡。寻找一个返回内容为JSON格式且里面包含商品数组如goodsList,items等的请求。将其URL中的一部分具有唯一性的部分作为我们的target_url_pattern。例如你可能发现一个URL是https://api.pinduoduo.com/api/proxy/api/xxx/queryGoodsList?page1size50...那么api_url_pattern可以设为queryGoodsList或api/proxy/api/xxx/queryGoodsList。这个模式需要足够独特以避免拦截到不相关的请求。page.goto(... wait_untilnetworkidle):wait_until参数控制导航何时被认为完成。‘networkidle’表示等待到至少500毫秒内没有新的网络请求发出。这对于等待页面初始资源JS, CSS, 图片加载完毕非常有用比默认的‘load’仅等待HTML加载和onload事件触发更可靠。intercept_api_response函数设计:它利用了Python的闭包特性。内部函数on_response可以访问外部函数的变量intercepted_data。我们将这个内部函数注册为页面response事件的监听器。每当页面收到一个响应无论是HTML、JS、CSS还是XHR这个函数都会被调用。在函数内部我们检查响应的URL是否包含目标模式。如果是则尝试将其解析为JSON并存入列表。这里用try...except包裹是因为可能拦截到非JSON响应比如图片避免程序崩溃。4.3 模拟用户行为以触发数据加载设置了拦截器但页面初始加载可能只请求了第一页数据。我们需要模拟滚动来触发加载更多商品的API请求。# 接上面的 main 函数 print(开始模拟滚动以加载更多商品...) scroll_pause_time 2 # 每次滚动后等待的时间用于等待新内容加载和网络请求 max_scroll_attempts 10 # 最大滚动尝试次数防止无限滚动 last_height page.evaluate(document.body.scrollHeight) for i in range(max_scroll_attempts): # 模拟滚动到页面底部 page.evaluate(window.scrollTo(0, document.body.scrollHeight)) print(f 第{i1}次滚动到底部...) time.sleep(scroll_pause_time) # 等待新内容加载和可能的API请求 # 计算新的滚动高度并与上一次对比 new_height page.evaluate(document.body.scrollHeight) if new_height last_height: print( 滚动高度未变化可能已加载所有商品或遇到加载结束标识。) # 可以添加更精确的结束判断例如检查页面是否有“没有更多了”的提示元素 # if page.locator(‘.no-more’).is_visible(): # print(“检测到‘没有更多’提示停止滚动。”) break last_height new_height # 可选在滚动几次后检查是否已经拦截到数据 if goods_data_list and len(goods_data_list) 0: # 假设每次API返回一页数据我们可以根据需求决定是否继续滚动 # 例如如果我们只想抓5页数据 if len(goods_data_list) 5: print(f已拦截到{len(goods_data_list)}批数据达到目标停止滚动。) break print(滚动模拟结束。)模拟行为的要点page.evaluate(): 这是在浏览器页面上下文中执行JavaScript代码的方法。‘document.body.scrollHeight’获取整个页面的高度‘window.scrollTo(0, document.body.scrollHeight)’则滚动到页面底部。等待策略 滚动后必须等待一段时间time.sleep因为新内容的加载和对应的API请求是异步的。scroll_pause_time的设置需要权衡太短可能导致请求还没发出我们就认为结束了太长则降低效率。2-3秒是一个比较安全的起点。更高级的做法是等待某个特定元素出现如新的商品卡片但Playwright的自动等待主要针对元素操作对网络请求的等待简单的sleep在初期更可靠。滚动终止条件高度不变 连续两次滚动后页面总高度不再增加通常意味着没有新内容加载了。这是最通用的判断。检测结束元素 很多无限滚动页面在底部会有一个“加载中...”或“没有更多了”的提示元素。你可以用page.locator(‘.loading-tip’).is_visible()或page.locator(‘.no-more-data’).is_visible()来更精确地判断。这需要你事先观察页面结构。达到目标 如果你知道每页返回的商品数量并且只想抓取固定页数可以在拦截到足够批次的响应后主动停止。反爬考量 过于规律和快速的滚动是机器人行为的特征。可以在scroll_pause_time中加入随机延迟比如time.sleep(scroll_pause_time random.uniform(0, 1.5))让行为更拟人化。4.4 解析拦截数据与保底DOM方案滚动结束后goods_data_list列表中应该已经存储了我们拦截到的所有API响应数据。现在来解析它。import pandas as pd all_goods [] if goods_data_list: print(f成功从API拦截到{len(goods_data_list)}批数据开始解析...) for batch in goods_data_list: # 这里的解析路径完全取决于API返回的JSON结构 # 你需要仔细分析拦截到的JSON找到商品数组所在的路径 # 例如可能是 batch[result][goodsList] 或 batch[data][items] goods_list batch.get(result, {}).get(goodsList, []) if not goods_list: # 尝试其他可能的路径 goods_list batch.get(data, {}).get(items, []) if not goods_list: print(警告未在本次拦截数据中找到商品列表跳过此批次。) print(batch) # 打印出来看看结构方便调整 continue for goods in goods_list: item { goods_id: goods.get(goodsId), goods_name: goods.get(goodsName), price: goods.get(price), # 单位可能是分需要转换 sales: goods.get(sales), image_url: goods.get(imageUrl), promotion_price: goods.get(promotionPrice), # 百亿补贴价 brand: goods.get(brandName), # ... 根据你的需求添加更多字段 } # 价格单位转换如果API返回的是分 if item[price] and isinstance(item[price], int): item[price] item[price] / 100.0 if item[promotion_price] and isinstance(item[promotion_price], int): item[promotion_price] item[promotion_price] / 100.0 all_goods.append(item) print(f从API共解析出{len(all_goods)}个商品。) else: print(未从API拦截到数据将尝试从DOM中提取保底方案。) # 保底方案从渲染后的页面DOM中提取 all_goods extract_from_dom(page) # 将数据转换为DataFrame df pd.DataFrame(all_goods) print(df.head()) # 预览前几行数据 # 数据清洗示例 # 去重 df.drop_duplicates(subset[goods_id], inplaceTrue, keepfirst) # 处理空值 df[sales].fillna(0, inplaceTrue) # 排序 df.sort_values(by[sales], ascendingFalse, inplaceTrue) # 保存到文件 output_file pdd_baiyi_butie_goods.xlsx df.to_excel(output_file, indexFalse) print(f数据已保存至: {output_file})保底DOM提取函数示例当API拦截失败时比如接口地址变了或者参数无法模拟我们需要一个从页面直接抓取的方案。def extract_from_dom(page): 从已渲染的页面DOM中提取商品信息。 goods_items [] # 使用Playwright选择器定位商品卡片元素。这需要你手动分析页面HTML结构。 # 例如每个商品卡片可能有一个类名 ‘.goods-item’ item_locators page.locator(.goods-item).all() # .all() 获取所有匹配的元素 print(f从DOM中找到 {len(item_locators)} 个商品卡片元素。) for item_locator in item_locators: try: # 在每个卡片元素内部进一步定位具体信息 # 这些选择器需要你根据实际页面调整非常容易因网站改版而失效 name item_locator.locator(.goods-name).text_content().strip() price_elem item_locator.locator(.price) price price_elem.text_content().strip() if price_elem.count() 0 else N/A # ... 类似地提取其他字段 goods_items.append({ goods_name: name, price: price, # ... }) except Exception as e: print(f提取单个商品信息时出错: {e}) continue # 跳过这个出错的商品 return goods_itemsAPI解析与DOM提取的对比API解析 数据精准、结构化、稳定只要接口不变。是首选方案。DOM提取 作为保底通用性强但脆弱。页面HTML结构微调就可能导致选择器失效需要频繁维护。且提取到的文本可能需要复杂的清洗如去除多余空格、单位等。4.5 完整流程整合与资源清理最后我们将所有步骤整合到main函数中并确保在任何情况下都能正确关闭资源避免内存泄漏。def main(): p, browser, context, page None, None, None, None try: p, browser, context create_browser_context() page context.new_page() target_page_url https://youhui.pinduoduo.com/ api_url_pattern queryGoodsList # 请替换为实际观察到的模式 print(导航与监听...) page.goto(target_page_url, wait_untilnetworkidle) goods_data_list intercept_api_response(page, api_url_pattern) print(模拟滚动触发加载...) # ... (滚动代码如前所述) print(解析与存储数据...) # ... (数据解析和保存代码如前所述) print(爬取任务完成) except Exception as e: print(f爬虫运行过程中出现错误: {e}) import traceback traceback.print_exc() # 打印详细的错误堆栈便于调试 finally: # 确保无论如何都关闭资源 print(正在清理资源...) if page: page.close() if context: context.close() if browser: browser.close() if p: p.stop() print(资源清理完毕。) if __name__ __main__: main()使用try...except...finally结构是编写健壮爬虫的好习惯。即使在爬取过程中发生异常finally块中的代码也会执行确保浏览器进程被关闭不会在后台残留。5. 高级技巧与反反爬策略基本的爬虫跑起来后我们还需要考虑如何让它更稳定、更隐蔽、更能应对网站的变化。5.1 请求头管理与会话保持除了User-Agent其他请求头也很重要。我们可以为整个上下文设置额外的请求头或者为单个页面设置。context browser.new_context( viewport{width: 1920, height: 1080}, user_agent..., # 设置额外的HTTP头 extra_http_headers{ Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, Upgrade-Insecure-Requests: 1, Sec-Fetch-Dest: document, Sec-Fetch-Mode: navigate, Sec-Fetch-Site: none, Sec-Fetch-User: ?1, Cache-Control: max-age0, } )设置这些头信息可以让你的请求看起来更像来自一个真实的浏览器。Sec-Fetch-*系列头在现代浏览器中很常见设置它们能通过一些简单的指纹检查。5.2 代理IP与指纹伪装对于大规模或高频抓取使用代理IP池是避免IP被封的基本操作。Playwright可以在启动浏览器或创建上下文时设置代理。browser p.chromium.launch( headlessTrue, proxy{ server: http://your-proxy-server:port, username: your-username, # 如果需要认证 password: your-password } )更高级的反爬会检测浏览器指纹如WebGL, Canvas, 字体列表等。Playwright提供了一些方法来修改或随机化指纹但这属于更复杂的对抗领域。对于拼多多百亿补贴这类公开数据做到设置合理的UA、请求头、使用代理并控制访问频率通常就足够了。5.3 处理验证码与登录态如果爬虫触发了验证码如滑块、点选自动化解决的难度和成本会急剧上升。我们的策略是尽量避免触发控制频率 在滚动、点击操作间加入随机延迟。模拟人类行为 除了滚动可以随机地移动鼠标page.mouse.move(x, y)或者在页面非交互区域随机点击。使用持久化上下文 如果网站需要登录才能访问更多数据你可以手动登录一次然后将浏览器上下文的存储状态cookies, local storage保存下来下次爬虫启动时加载这个状态就恢复了登录会话。# 保存上下文状态 storage_state context.storage_state(pathauth_state.json) # 下次启动时加载 context browser.new_context(storage_stateauth_state.json)5.4 错误重试与日志记录网络不稳定、元素加载超时都可能导致单次请求失败。为关键操作如page.goto,page.locator().click()添加重试机制能提升稳定性。import time from tenacity import retry, stop_after_attempt, wait_exponential retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10)) def safe_goto(page, url): 带重试的页面跳转 return page.goto(url, wait_untilnetworkidle) # 使用 try: safe_goto(page, target_url) except Exception as e: print(f多次重试后仍无法加载页面: {e})同时为你的爬虫配置日志系统如Python内置的logging模块将运行信息、错误、拦截到的数据量等记录到文件方便后期监控和排查问题。6. 常见问题排查与优化建议在实际运行中你可能会遇到各种各样的问题。这里记录了一些典型情况及其解决思路。6.1 问题排查清单问题现象可能原因排查步骤与解决方案拦截不到API数据1. API URL模式不匹配。2. 数据不是通过XHR请求可能是WebSocket或Fetch且Playwright默认监听不全。3. 页面加载逻辑改变需要先执行某些JS或点击才能触发API。1.核对URL模式在开发者工具中仔细检查目标请求的完整URL确保拦截模式是其子串。尝试更通用或更具体的模式。2.监听所有响应修改拦截函数暂时打印所有响应的URL (print(response.url))看看目标请求是否出现以及它的确切类型。3.检查触发条件在页面手动操作如点击分类、排序观察API请求何时出现。可能需要先模拟这些点击操作。页面无法正常加载/空白1. 网站检测到自动化工具并屏蔽。2. 请求头或Cookie有问题。3. 目标URL需要特定Referer。1.检查headless模式尝试headlessFalse看页面是否正常显示。有些网站对无头模式有检测。2.增强伪装使用更完整的请求头如前文所述特别是User-Agent和Sec-Fetch-*头。3.设置Referer在context或page的extra_http_headers中设置正确的Referer通常是网站的首页或上一级页面URL。滚动后没有新数据加载1. 滚动后等待时间不足AJAX请求未完成。2. 滚动触发了其他加载逻辑如点击“加载更多”按钮。3. 已到达数据末尾。1.增加等待时间调大scroll_pause_time或使用page.wait_for_timeout()代替time.sleep()。2.观察网络活动在headlessFalse模式下打开开发者工具网络标签手动滚动看触发了什么请求。可能需要模拟点击而不是滚动。3.检测结束元素实现前文提到的通过判断特定提示元素是否存在来终止滚动。提取的数据为空或乱码1. JSON解析路径错误。2. 响应内容被压缩或编码。3. DOM选择器失效保底方案。1.打印原始响应在拦截函数中先不解析JSON直接打印response.text()或response.body()的前几百个字符确认数据结构。2.检查编码确保正确解码Playwright的response.text()通常会处理好。3.更新选择器如果使用DOM方案定期检查页面元素结构是否变化更新CSS选择器或XPath。运行速度慢1.headlessFalse和slow_mo未关闭。2. 等待时间设置过长。3. 未使用异步API。1.生产环境配置确保launch参数为headlessTrue, slow_moNone。2.优化等待将固定等待改为条件等待如page.wait_for_selector(‘.new-item’)或减少固定等待时间。3.考虑异步对于大规模抓取将核心逻辑改写为异步(async/await)版本可以并行处理多个页面。6.2 性能与可维护性优化分离配置 将目标URL、API模式、请求头、选择器等易变的内容提取到配置文件如config.yaml或config.py中修改配置无需动代码。模块化设计 将浏览器管理、页面操作、数据解析、存储等逻辑拆分成独立的函数或类提高代码可读性和复用性。增量抓取 如果数据量很大可以考虑记录已抓取的商品ID下次运行时跳过它们实现增量更新。使用Playwright的expect断言 在关键步骤后使用expect(locator).to_be_visible()等断言可以让代码更健壮并在失败时提供更清晰的错误信息。监控与告警 对于长期运行的爬虫可以添加简单的监控如定期检查输出文件是否更新、日志中是否有大量错误并集成邮件或消息通知。这个基于Playwright的拼多多百亿补贴爬虫项目从环境搭建、策略分析到代码实现和问题排查覆盖了动态网页爬虫的核心流程。记住爬虫是与目标网站持续“对话”的过程网站改版、反爬策略升级都要求爬虫随之调整。保持代码的灵活性和可维护性定期测试才能让数据管道长久稳定地运行下去。