使用Playwright实战爬取京东图书新书榜:动态价格与分页处理

发布时间:2026/6/23 21:52:08

使用Playwright实战爬取京东图书新书榜:动态价格与分页处理 1. 项目概述与核心价值最近在帮一个做图书数据分析的朋友抓取京东图书新书榜的数据他不仅需要书名、作者这些基础信息还特别强调要拿到实时的动态价格并且榜单是分页的。这听起来就是个典型的动态网页爬虫需求页面数据很可能是通过JavaScript异步加载的。如果用传统的requests库配合BeautifulSoup大概率会扑空因为初始HTML里根本没有价格和后续分页的数据。我第一时间就想到了Playwright这个现代浏览器自动化工具。它不像Selenium那样需要额外安装浏览器驱动而且对动态内容的处理能力堪称一流尤其擅长应对像京东这样的大型电商网站复杂的交互逻辑。这个项目本质上就是利用Playwright模拟真实用户浏览行为攻克动态渲染、价格获取和分页遍历这三个核心难点最终稳定、高效地抓取到结构化的榜单数据。对于刚接触爬虫或者被动态网站困扰的朋友来说这个实战案例非常有价值。它跳过了简单的静态页面抓取直接切入当前爬虫领域最主流的挑战如何与一个由JavaScript驱动的现代Web应用进行交互并提取数据。通过这个项目你不仅能学会Playwright的基本操作更能掌握一套处理动态内容、解析复杂页面结构、设计稳健爬取逻辑的完整方法论。无论你是想监控商品价格、分析市场趋势还是学习高级爬虫技术这都是一个绝佳的练手项目。整个过程我会拆解得非常细从环境搭建、页面分析到代码编写和错误处理确保即使你是Python新手跟着步骤也能跑通。2. 环境准备与Playwright基础工欲善其事必先利其器。我们首先得把Playwright这个“利器”准备好。它和Selenium最大的不同在于其“一体化”的设计理念。Playwright由微软开发它为Chromium、Firefox和WebKit三大浏览器引擎提供了统一的高层API并且自带浏览器二进制文件无需你手动管理驱动开箱即用。2.1 安装Playwright安装过程非常简单。打开你的命令行终端CMD、PowerShell或终端使用pip进行安装。我强烈建议在一个独立的虚拟环境中进行以避免包依赖冲突。如果你使用conda可以先创建一个新环境。# 使用pip安装playwright库 pip install playwright # 安装Playwright所需的浏览器二进制文件Chromium, Firefox, WebKit playwright install chromium这里我选择安装chromium因为它最常用性能也足够。playwright install命令会自动下载对应操作系统的、与库版本匹配的浏览器省去了很多麻烦。安装完成后你可以通过一个简单的脚本来测试是否成功。import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 启动一个Chromium浏览器实例headlessFalse表示有界面方便调试 browser await p.chromium.launch(headlessFalse) page await browser.new_page() await page.goto(https://www.jd.com) print(await page.title()) await browser.close() asyncio.run(main())如果运行后能打印出“京东(JD.COM)-正品低价、品质保障、配送及时、轻松购物”这样的标题说明环境一切正常。注意Playwright支持同步和异步两种API。在这个项目中为了代码的清晰和现代性我将使用异步APIasync/await。异步操作在处理网络请求等待时效率更高更符合爬虫这类I/O密集型任务的特点。2.2 理解Playwright的核心概念在开始写爬虫之前有必要快速过一下Playwright的几个核心对象这能帮你更好地理解后续的代码Browser 代表一个浏览器实例。你可以把它想象成一个完整的、可以打开多个窗口的浏览器程序。我们通过launch()方法启动它。Context 浏览器上下文。它类似于一个独立的隐身会话拥有独立的cookie、本地存储和缓存。在一个Browser实例下可以创建多个互不干扰的Context这在需要模拟多个用户或者隔离会话时非常有用。在这个项目中我们暂时用不到太复杂的上下文管理。Page 页面。这是我们将要操作的主要对象对应浏览器中的一个标签页。绝大部分操作如跳转网址、点击元素、输入文本、提取数据都是在Page对象上完成的。Locator 定位器。这是Playwright中用于查找和操作页面元素的强大工具。它比直接使用page.query_selector()更现代、更强大支持链式调用并且具有自动等待元素可用的智能特性。我们将主要使用page.locator()来定位目标元素。理解了这些我们就可以开始分析目标页面设计我们的爬取策略了。3. 目标页面分析与爬取策略设计我们的目标是京东图书的“新书榜”。首先我们需要手动打开浏览器访问这个页面观察它的结构和行为。你可以直接访问https://book.jd.com/booktop/0-0-0.html?category1713-0-0-0-10001-1这个链接。注意京东的URL结构可能会变化如果这个链接失效你可以直接在京东图书频道找到“新书榜”的入口。3.1 页面结构观察与数据定位打开页面后按下F12打开开发者工具进入“元素”Elements面板。我们需要找到榜单列表的HTML结构。列表容器 滚动页面观察榜单区域。通常这类列表会被包裹在一个具有特定id或class的div中。经过检查我发现榜单列表位于一个idplist的ul元素内。这是一个非常清晰的信号。单条商品信息 在#plist下面每一个li标签就对应一本图书。我们需要从每个li中提取书名、作者/出版社、价格、以及可能的折扣信息。动态价格 这是关键点。你会发现页面初始加载时价格区域可能显示为“加载中...”或者是一个占位符。当你把鼠标悬停在某个商品上或者滚动页面时价格才会显示出来。这说明价格是通过JavaScript异步请求接口获取并动态填充的。我们不能直接从初始HTML中提取必须等待价格加载完成。分页机制 翻到页面底部可以看到分页栏。京东的分页通常是点击“下一页”按钮或者直接点击页码。我们需要让Playwright模拟这个点击动作并等待下一页内容加载完成。注意 在分析页面时务必遵守网站的robots.txt协议通常位于网站根目录如https://www.jd.com/robots.txt。虽然robots.txt是君子协议不具有强制约束力但作为负责任的爬虫开发者我们应该尊重网站的爬取规则避免对服务器造成过大压力。我们的爬虫应设置合理的请求间隔如每次操作后等待1-3秒模拟人类浏览速度并且最好在访问量较低的时段例如凌晨运行。3.2 爬取策略设计基于以上分析我们的爬虫流程可以设计如下启动浏览器并导航 使用Playwright启动一个浏览器打开新书榜第一页。等待页面稳定 等待列表容器#plist出现在DOM中确保主要内容已加载。处理动态价格 由于价格是动态加载的我们需要一个策略来触发或等待价格显示。一个可靠的方法是模拟用户行为例如将鼠标移动到每个商品图片上或者直接等待一段时间让前端脚本执行完毕。更精准的方法是监听网络请求找到获取价格的接口但京东的接口通常有反爬措施。这里我们采用折中方案先滚动页面到底部再滚动回顶部触发懒加载和价格渲染然后等待一个固定时间如2秒。提取当前页数据 使用定位器找到所有商品项遍历每一项提取书名、作者、价格等信息。处理分页 定位“下一页”按钮检查其是否可点击即不是灰色或不存在。如果可点击则点击它然后等待新页面加载等待列表容器更新然后重复步骤3和4。循环与终止 持续翻页直到“下一页”按钮变为不可用状态到达最后一页。数据存储 将每一页抓取的数据追加到一个列表或直接写入文件如CSV或JSON。这个策略兼顾了成功率和代码的简洁性。接下来我们就开始一步步实现它。4. 核心代码实现与分步解析我们将把整个爬虫写在一个Python脚本中。我会将代码分成几个函数让结构更清晰。我们主要使用asyncio和playwright.async_api。4.1 初始化与页面导航首先我们编写主函数和初始化代码。import asyncio import csv from playwright.async_api import async_playwright async def main(): # 使用async上下文管理器启动Playwright async with async_playwright() as p: # 启动浏览器。headlessFalse方便调试看到浏览器操作。正式运行可设为True。 browser await p.chromium.launch(headlessFalse, slow_mo100) # slow_mo让动作变慢便于观察 # 创建一个新的浏览器上下文和页面 context await browser.new_context() page await context.new_page() # 目标URL - 京东图书新书榜 url https://book.jd.com/booktop/0-0-0.html?category1713-0-0-0-10001-1 # 导航到目标页面并等待网络状态至少到‘domcontentloaded’ await page.goto(url, wait_untildomcontentloaded) print(f已访问: {url}) # 在这里调用后续的爬取函数 all_books await crawl_book_list(page) # 爬取结束后关闭浏览器 await browser.close() # 保存数据 save_to_csv(all_books, jd_new_books.csv) print(f数据已保存共抓取{len(all_books)}条记录。) if __name__ __main__: asyncio.run(main())代码解析p.chromium.launch(headlessFalse, slow_mo100):headlessFalse让浏览器窗口可见对于调试和确认页面加载是否正确至关重要。slow_mo100表示每个Playwright操作点击、输入等都会放慢100毫秒让你能看清发生了什么。wait_untildomcontentloaded: 这是page.goto()的一个选项表示导航完成的标准是HTML文档被完全加载和解析DOMContentLoaded事件触发但不一定等待所有样式、图片和子框架加载。对于爬虫来说这通常比默认的load等待所有资源加载更快。4.2 等待页面加载与触发动态内容接下来我们实现一个函数来等待列表加载并触发动态价格的渲染。async def wait_for_dynamic_content(page): 等待商品列表加载并触发动态价格渲染。 print(正在等待商品列表加载...) # 1. 等待列表容器ul#plist出现在DOM中 await page.wait_for_selector(ul#plist, stateattached, timeout10000) # 2. 为了确保动态内容如价格加载模拟用户滚动行为。 # 先滚动到页面底部触发可能的懒加载 await page.evaluate(window.scrollTo(0, document.body.scrollHeight)) await asyncio.sleep(1) # 等待滚动后可能的异步加载 # 再滚动回顶部方便后续操作非必须 await page.evaluate(window.scrollTo(0, 0)) # 3. 额外等待一段时间让前端JS完成价格数据的填充。 # 这个时间需要根据网络速度和页面复杂度调整。2-3秒通常足够。 print(等待动态价格渲染...) await asyncio.sleep(2500) # 等待2.5秒 # 也可以尝试更精准的等待等待价格元素出现。但价格元素的类名可能不固定。 # 例如尝试等待一个包含价格的span出现这里只是示例实际选择器需分析页面 # try: # await page.wait_for_selector(li.gl-item .p-price span.price, statevisible, timeout5000) # except: # print(价格元素未在指定时间内出现继续执行。)关键点与避坑指南page.wait_for_selector: 这是Playwright的核心等待函数。stateattached表示元素存在于DOM中即可statevisible要求元素在页面上可见。对于列表容器attached通常就够了。page.evaluate(): 用于在浏览器页面上下文中执行JavaScript代码。这里我们用它来执行滚动操作。asyncio.sleep(): 这是一个“强制等待”是一种简单但不够精确的方法。在复杂的动态页面中有时不得不使用它来给前端脚本足够的执行时间。最佳实践是尽量使用wait_for_selector、wait_for_function等智能等待来替代sleep但在无法定位到确定元素时sleep可以作为备选。务必谨慎设置等待时间太短可能导致数据没加载完太长则降低效率。动态价格的处理 这里采用“滚动固定等待”的组合策略。更高级的做法是监听网络请求page.on(‘request’)/page.on(‘response’)拦截获取价格的XHR或Fetch请求直接从响应中提取数据。但这需要分析网络面板且接口可能有加密参数难度较大。对于入门和大多数场景滚动触发适当等待是性价比最高的方案。4.3 解析单页商品数据现在我们来实现解析当前页面所有图书信息的函数。这是数据提取的核心。async def parse_current_page(page): 解析当前页面上的所有图书信息。 返回一个字典列表每个字典代表一本书。 books [] # 使用locator定位所有商品列表项。京东新书榜的列表项通常是li.gl-item # 更稳妥的方法是先找到列表容器ul#plist再找其下的li子元素 list_container page.locator(ul#plist) # count()方法可以获取匹配到的元素数量 item_count await list_container.locator(li.gl-item).count() print(f当前页面找到 {item_count} 个商品项。) if item_count 0: print(警告未找到商品列表项页面结构可能已变化) return books # 遍历每一个商品项 for i in range(item_count): book_item list_container.locator(li.gl-item).nth(i) book_info {} try: # 1. 提取书名 (通常在class包含‘p-name’的div里的a标签或em标签) name_element book_item.locator(div.p-name a, div.p-name em).first if await name_element.count() 0: book_info[title] (await name_element.text_content()).strip() else: book_info[title] N/A # 2. 提取作者/出版社信息 (通常在class包含‘p-bookdetails’的div里) author_element book_item.locator(span.p-bi-name, div.p-bookdetails span.author).first if await author_element.count() 0: book_info[author_publisher] (await author_element.text_content()).strip() else: book_info[author_publisher] N/A # 3. 提取价格 (这是动态加载的class可能为‘p-price’下的‘price’或‘J-p-xxx’) # 京东价格可能有多个span例如原价和现价。我们取第一个通常是现价 price_element book_item.locator(div.p-price strong i, div.p-price span.price).first if await price_element.count() 0: price_text (await price_element.text_content()).strip() # 清理价格字符串只保留数字和小数点 import re price_clean re.search(r[\d\.], price_text) book_info[price] price_clean.group() if price_clean else price_text else: book_info[price] N/A (可能未加载) # 4. 提取商品链接 (可选) link_element book_item.locator(div.p-img a).first if await link_element.count() 0: book_info[link] await link_element.get_attribute(href) # 补全为完整URL if book_info[link] and book_info[link].startswith(//): book_info[link] https: book_info[link] elif book_info[link] and book_info[link].startswith(/): book_info[link] https://item.jd.com book_info[link] else: book_info[link] N/A except Exception as e: print(f解析第{i1}个商品时出错: {e}) # 即使某个商品解析失败也继续处理下一个 continue books.append(book_info) # 可以打印进度但不要太频繁 if (i1) % 10 0: print(f 已解析 {i1}/{item_count} 个商品...) return books代码解析与实操心得使用Locator链式调用page.locator(‘ul#plist’).locator(‘li.gl-item’)比page.locator(‘ul#plist li.gl-item’)在某些情况下更清晰也更容易处理动态列表。.nth(i) 用于从一组定位器中按索引选取特定的元素。.count() 异步方法用于获取匹配定位器的元素数量。非常重要在遍历前先获取数量避免在循环中因DOM更新导致问题。.text_content()和.get_attribute() 分别用于获取元素的文本内容和属性值。注意text_content()会获取元素及其所有后代元素的文本并合并成一个字符串。异常处理 用try...except包裹每个商品的解析逻辑至关重要。网页结构可能微调某个元素的定位器可能失效。不能让一个商品的解析失败导致整个程序崩溃。记录错误并继续是最佳实践。价格清洗 价格文本可能包含货币符号如¥、空格或其他字符。使用正则表达式re.search(r[\d\.], price_text)可以提取出纯数字和小数点部分得到干净的价格数值。选择器的健壮性 我提供了多个备选的选择器用逗号分隔例如‘div.p-name a, div.p-name em’。这是因为京东的页面结构在不同模块或不同时间可能有细微差别。使用.first可以取第一个匹配到的元素。在实际项目中你需要经常检查页面元素并准备备用选择器。4.4 处理分页逻辑分页是爬虫的另一个核心。我们需要找到“下一页”按钮点击它并等待新内容加载。async def go_to_next_page(page): 尝试点击‘下一页’按钮并等待新页面内容加载。 返回True表示成功翻页False表示已是最后一页。 # 定位‘下一页’按钮。京东的分页按钮class通常是‘pn-next’ next_button page.locator(a.pn-next) # 检查按钮是否存在且未被禁用例如没有‘disabled’类或属性 if await next_button.count() 0: print(“未找到‘下一页’按钮可能已是最后一页。”) return False # 检查按钮是否可点击通过判断其是否可见且未被禁用 is_disabled await next_button.get_attribute(disabled) or await next_button.get_attribute(aria-disabled) is_visible await next_button.is_visible() if is_disabled or not is_visible: print(“‘下一页’按钮不可点击已到达最后一页。”) return False print(“正在翻到下一页...”) # 点击下一页按钮 await next_button.click() # 等待页面导航完成和新内容加载 # 这里我们等待列表容器更新。一个技巧是等待旧的列表项消失或新的列表项出现。 # 更简单的方法是等待页面网络基本空闲并等待列表容器重新稳定。 await page.wait_for_load_state(networkidle) # 等待网络基本空闲 # 再次等待列表容器出现确保新页面的内容已加载 try: await page.wait_for_selector(ul#plist, stateattached, timeout8000) except Exception as e: print(f“等待新页面列表超时或出错: {e}”) # 即使超时也可能已经加载了可以尝试继续解析 pass # 翻页后同样需要触发动态内容加载 await wait_for_dynamic_content(page) return True关键点与避坑指南分页按钮的状态判断 不能只检查元素是否存在。在最后一页时“下一页”按钮可能仍然存在但被添加了disabled属性或aria-disabled属性或者其href是javascript:void(0)。我们需要综合判断其是否可交互。等待策略page.wait_for_load_state(‘networkidle’)会等待页面网络活动变得很少通常500ms内没有超过2个网络请求这对于单页应用SPA或异步加载内容的页面非常有用。但是要注意有些页面可能始终有后台请求如心跳包、广告导致networkidle永远达不到。因此我们结合了wait_for_selector来等待特定的内容元素出现这样更可靠。点击后的等待 点击“下一页”后页面可能不会发生完整的导航即URL不变内容通过AJAX替换。wait_for_load_state(‘networkidle’)和wait_for_selector的组合能很好地应对这两种情况。超时处理 为wait_for_selector设置一个合理的timeout如8秒。如果超时可能页面加载异常但我们用try...except捕获异常并打印日志而不是让程序崩溃有时页面可能已经加载了部分内容爬虫还可以尝试继续。4.5 整合主爬取流程现在我们把上述所有函数整合到主爬取函数crawl_book_list中。async def crawl_book_list(page, max_pages10): 主爬取函数负责协调等待、解析和翻页。 :param page: Playwright页面对象 :param max_pages: 最大爬取页数防止意外无限循环 :return: 所有爬取到的图书列表 all_books [] current_page 1 # 初始页面等待并解析 await wait_for_dynamic_content(page) first_page_books await parse_current_page(page) all_books.extend(first_page_books) print(f“第{current_page}页抓取完成累计{len(all_books)}条记录。”) # 循环翻页 while current_page max_pages: next_page_success await go_to_next_page(page) if not next_page_success: print(“已到达最后一页爬取结束。”) break current_page 1 # 翻页后parse_current_page会基于新的页面内容解析 current_page_books await parse_current_page(page) if not current_page_books: # 如果解析不到数据可能页面有问题 print(f“警告第{current_page}页未解析到数据可能页面加载失败或结构变化。”) # 可以选择重试或退出 break all_books.extend(current_page_books) print(f“第{current_page}页抓取完成累计{len(all_books)}条记录。”) # 礼貌性延迟减轻服务器压力模拟人类操作 await asyncio.sleep(2) if current_page max_pages: print(f“已达到最大爬取页数限制({max_pages}页)爬取结束。”) return all_books设计思路参数max_pages 这是一个安全阀防止因分页逻辑错误比如“下一页”按钮一直可用导致无限循环。你可以根据实际需要调整比如只想爬前5页。循环逻辑 采用while循环每次循环尝试翻页。翻页成功则解析新页面数据失败返回False则退出循环。延迟asyncio.sleep(2) 在每次翻页后主动等待2秒。这是非常重要的道德和技术考量。过于频繁的请求会对目标服务器造成压力可能触发反爬机制如IP被封。添加延迟是模拟人类浏览速度是友好爬虫的体现。4.6 数据存储最后我们将抓取到的数据保存到CSV文件中方便后续分析。def save_to_csv(books_data, filename): 将图书数据列表保存到CSV文件。 if not books_data: print(“没有数据可保存。”) return # 定义CSV文件的列头 fieldnames [title, author_publisher, price, link] try: with open(filename, w, newline, encodingutf-8-sig) as csvfile: # utf-8-sig支持Excel中文 writer csv.DictWriter(csvfile, fieldnamesfieldnames) writer.writeheader() for book in books_data: writer.writerow(book) print(f“数据成功保存到 {filename}”) except Exception as e: print(f“保存CSV文件时出错: {e}”)使用utf-8-sig编码可以确保在Windows系统下用Excel打开CSV文件时中文字符能正常显示。5. 常见问题排查与进阶优化即使代码看起来完美在实际运行中你仍可能会遇到各种问题。下面是我在多次实战中总结的一些常见坑点和解决方案。5.1 元素定位失败问题 控制台报错TimeoutError: Timeout 10000ms exceeded waiting for selector “ul#plist”或者解析时count()为0。排查步骤确认页面是否成功加载 在headlessFalse模式下观察浏览器窗口是否打开了正确的页面页面内容是否完整显示。可能是网络问题或网站反爬如弹出验证码。检查选择器是否过时 网站前端经常改版。用浏览器的开发者工具F12重新检查榜单区域的HTML结构。idplist可能已经变了。你需要更新代码中的选择器使其匹配当前页面的结构。增加等待时间或使用更宽松的等待条件 如果网络慢可以增加wait_for_selector的timeout值如30000毫秒。或者如果列表容器不是ul而是div调整选择器。处理页面弹窗或遮罩 有时网站会有登录弹窗、广告遮罩层它们会阻挡对底层内容的操作。你可以在代码开头尝试关闭它们# 尝试关闭可能的弹窗 (示例选择器需根据实际情况调整) close_btn page.locator(div.popup-close, a.btn-close) if await close_btn.count() 0: await close_btn.click() await asyncio.sleep(1)5.2 动态价格始终抓取不到问题 价格字段总是显示“N/A (可能未加载)”。解决方案增强触发逻辑 修改wait_for_dynamic_content函数。除了滚动可以尝试将鼠标移动到每个商品图片上这更能模拟真实用户行为触发价格请求。# 在wait_for_dynamic_content函数内滚动后添加 items page.locator(li.gl-item) count await items.count() for i in range(min(count, 10)): # 只模拟前10个避免操作过多 await items.nth(i).hover() await asyncio.sleep(0.1) # 短暂间隔 await asyncio.sleep(1) # 等待hover触发的请求完成直接监听网络请求进阶 这是最精准的方法。在开发者工具的“网络”(Network)面板中筛选XHR/Fetch请求当你看到价格出现时寻找包含价格数据的请求。然后使用Playwright拦截该请求。# 在page.goto之前设置请求监听 async def handle_response(response): if api.m.jd.com in response.url and wareBusiness in response.url: # 示例关键词 try: data await response.json() # 从data中提取价格并存储到一个全局字典key可以用商品SKU print(f“拦截到价格数据: {data}”) except: pass page.on(response, handle_response)这种方法需要较强的逆向工程能力但一旦成功爬取效率和稳定性会极大提升。5.3 反爬虫机制应对问题 IP被封锁、请求被重定向到验证页面、返回空白页面或错误数据。应对策略降低请求频率 这是我们一直在做的。翻页间隔asyncio.sleep可以随机化比如await asyncio.sleep(random.uniform(1, 3))使其行为更不像机器人。使用浏览器上下文模拟真实用户Playwright的browser.new_context()可以设置用户代理User-Agent、视口大小、地理位置等。使用常见的UA并设置合理的视口。context await browser.new_context( user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., viewport{width: 1920, height: 1080} )处理验证码 如果遇到验证码自动化解决非常困难。可以考虑手动处理 在headlessFalse模式下如果弹出验证码程序暂停你手动完成验证后代码继续执行。这需要复杂的交互逻辑。使用第三方打码平台 商业项目可能会接入打码API但会增加成本和复杂度。规避 最好的方式是让爬虫行为足够“像人”避免触发验证码。如果频繁触发可能需要更换IP或暂停爬取一段时间。代理IP 对于大规模爬取使用代理IP池是必须的。Playwright启动浏览器时可以指定代理服务器。browser await p.chromium.launch( headlessTrue, proxy{ server: http://your-proxy-server:port, # 如果需要认证 username: user, password: pass } )5.4 程序稳定性与健壮性问题 爬虫运行一段时间后崩溃或数据缺失严重。加固措施全面的异常捕获 像我们在parse_current_page里做的那样在可能出错的代码块特别是网络请求、元素定位、数据解析周围使用try...except。记录错误日志但不要轻易让整个程序停止。重试机制 对于关键操作如page.goto,click可以封装一个重试函数。async def retry_operation(operation, max_retries3, delay2): for attempt in range(max_retries): try: return await operation() except Exception as e: print(f“操作失败第{attempt1}次重试。错误: {e}”) if attempt max_retries - 1: await asyncio.sleep(delay) else: raise e # 重试次数用尽抛出异常 # 使用示例 await retry_operation(lambda: page.goto(url, wait_untildomcontentloaded))定期保存状态 如果爬取数据量很大不要等到全部结束才保存。可以每爬完一页或每N条记录就追加写入文件一次。这样即使程序中途崩溃已经抓取的数据也不会丢失。监控与日志 使用Python的logging模块替代print将运行信息、错误信息输出到文件便于后期排查问题。这个项目从环境搭建到完整代码实现详细展示了如何使用Playwright应对一个具有动态内容和分页的现代电商网站。代码中包含了大量的错误处理和实践经验直接复制运行成功的概率很高。但请记住网页是变化的最核心的技能不是记住这段代码而是学会使用开发者工具分析页面、设计健壮的选择器和等待逻辑、以及优雅地处理各种异常情况。在实际使用时请务必根据目标网站的最新结构调整选择器并始终遵守robots.txt和网站的服务条款做一个负责任的数据获取者。

相关新闻