1. 项目概述为什么是Playwright如果你正在用Python做数据采集或者更直白点说写爬虫那你一定绕不开浏览器自动化这个坎。从早期的Selenium到后来的Puppeteer再到如今风头正劲的Playwright工具一直在进化。今天我们不聊那些老生常谈就聚焦在Playwright上聊聊它凭什么能被称为“新一代数据采集神器”以及我们这些搞数据的人到底该怎么用它。简单来说Playwright是一个由微软开源的浏览器自动化库它支持Chromium、Firefox和WebKitSafari的引擎三大浏览器引擎。这意味着你用一套代码就能在几乎所有现代浏览器上运行你的自动化脚本。但这还不是它最吸引我的地方。真正让我决定把项目里的Selenium逐步替换成Playwright的是它在处理现代Web应用时那种“稳”和“快”。现在的前端页面大量使用动态加载、单页应用SPA、复杂的JavaScript交互甚至还有WebSocket。传统的工具在处理这些场景时要么需要写一堆等待和重试逻辑要么就直接卡住报错。Playwright在设计之初就考虑到了这些它提供了一套更智能的API来等待元素、拦截网络请求、模拟各种输入设备让脚本的稳定性和执行效率上了不止一个台阶。对于数据采集这个场景Playwright的优势就更明显了。很多有价值的数据并不在初始的HTML里而是通过AJAX或WebSocket“吐”出来的。你需要一个能真正“运行”JavaScript并且能轻松捕获这些异步请求的工具。Playwright的网络拦截Route和请求/响应监听功能让抓取这类数据变得异常简单。你可以像在浏览器开发者工具的Network面板里一样清晰地看到所有进出的数据然后精准地提取你需要的信息。这比去逆向解析一堆混淆的JS代码或者靠猜测去模拟点击触发数据加载要高效和可靠得多。所以无论你是数据工程师、分析师还是业务运营只要你的工作涉及到从网站上获取那些“看得到但不好拿”的数据Playwright都值得你花时间深入了解。它降低了浏览器自动化的门槛同时又把能力上限提得很高。接下来我们就从零开始一步步把它用起来。2. 环境准备与核心安装避坑指南工欲善其事必先利其器。Playwright的安装看似简单但有几个关键点如果没处理好后面可能会遇到各种稀奇古怪的问题。这里我会结合我自己的踩坑经验把每一步都讲透。2.1 Python环境与包管理器的选择首先确保你有一个健康的Python环境。我强烈建议使用Python 3.8或更高版本。检查你的Python版本python --version # 或 python3 --version关于包管理器pip是标准选择。但如果你在做多个项目强烈建议使用虚拟环境Virtual Environment来隔离依赖。这能避免不同项目间包版本冲突的噩梦。创建并激活虚拟环境以项目目录playwright-demo为例# 创建项目目录并进入 mkdir playwright-demo cd playwright-demo # 创建虚拟环境Windows python -m venv venv # 激活虚拟环境Windows venv\Scripts\activate # 创建虚拟环境macOS/Linux python3 -m venv venv # 激活虚拟环境macOS/Linux source venv/bin/activate激活后你的命令行提示符前通常会显示(venv)表示你正在虚拟环境中操作。注意有些教程会推荐conda但对于Playwright来说用pip在虚拟环境中安装是最直接、问题最少的路径。conda的包更新有时会滞后可能导致浏览器驱动版本不匹配。2.2 Playwright库的安装与版本策略安装Playwright Python库本身非常简单pip install playwright但是这里有一个至关重要的细节Playwright库和它要控制的浏览器Chromium, Firefox, WebKit是分开的。上面这条命令只安装了控制这些浏览器的Python客户端库。接下来你需要安装浏览器本身。Playwright提供了一个命令行工具来管理浏览器playwright install这条命令会下载Chromium、Firefox和WebKit的专用版本。这些版本是经过Playwright团队测试和优化的与API的兼容性最好。千万不要用你自己系统里安装的Chrome或Firefox去替换它们极大概率会出兼容性问题。安装慢怎么办这是最常见的问题。因为要从国外下载几百兆的浏览器二进制文件速度可能很慢甚至失败。解决方案是使用镜像源。在运行playwright install之前设置环境变量# Windows (PowerShell) $env:PLAYWRIGHT_DOWNLOAD_HOST https://npmmirror.com/mirrors/playwright # macOS/Linux export PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwright然后再执行playwright install速度会快很多。如果某个浏览器安装失败可以单独安装例如只安装最常用的Chromiumplaywright install chromium。版本管理playwright install默认安装与当前playwright库版本匹配的浏览器。如果你想安装其他版本的浏览器或者需要锁定版本以确保团队环境一致可以指定版本号playwright install chromiumversion-tag版本标签可以在Playwright的GitHub Release页面找到。对于生产环境锁定版本是很好的实践。2.3 验证安装与第一个脚本安装完成后写一个最简单的脚本来验证一切是否正常。创建一个文件test_install.pyfrom playwright.sync_api import sync_playwright with sync_playwright() as p: # 启动Chromium浏览器headlessFalse表示显示界面 browser p.chromium.launch(headlessFalse) # 打开一个新页面 page browser.new_page() # 导航到百度 page.goto(https://www.baidu.com) # 等待3秒方便我们观察 page.wait_for_timeout(3000) # 关闭浏览器 browser.close()运行这个脚本python test_install.py如果能看到一个浏览器窗口自动打开并跳转到百度首页停留3秒后关闭那么恭喜你Playwright环境已经成功搭建实操心得在开发调试阶段我习惯将launch参数中的headless设为False这样能直观地看到浏览器在做什么对于调试脚本逻辑、观察页面加载状态非常有帮助。等到脚本稳定后再改为True进行无头模式运行这样效率更高也更适合在服务器上执行。3. 核心API与数据采集基础操作解析环境搞定我们就进入正题。Playwright的API设计非常直观核心对象就那几个Browser浏览器、BrowserContext浏览器上下文类似一个独立的会话、Page页面即标签页、Frame框架页面内的iframe和Locator定位器用于查找元素。掌握它们你就掌握了Playwright的八成功力。3.1 浏览器启动与页面导航的精细化控制启动浏览器时有一堆参数可以配置直接影响脚本的稳定性、速度和资源占用。from playwright.sync_api import sync_playwright with sync_playwright() as p: # 一个更贴近真实数据采集场景的启动配置 browser p.chromium.launch( headlessFalse, # 调试时设为False生产环境设为True slow_mo50, # 将每个操作放慢50毫秒方便观察调试神器 args[ --disable-blink-featuresAutomationControlled, # 禁用自动化控制特征有助于防检测 --start-maximized # 启动时最大化窗口 ] ) # 创建一个浏览器上下文可以独立设置用户代理、视口、Cookie等 context browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ... # 自定义UA ) # 在上下文中打开新页面 page context.new_page() # 导航到目标网址并等待页面达到“网络空闲”状态 page.goto(https://www.example.com, wait_untilnetworkidle) # networkidle 表示在至少500毫秒内没有超过2个网络连接时认为导航完成 # 其他选项load(DOMContentLoaded), domcontentloaded, commit # ... 后续操作 # 关闭上下文和浏览器 context.close() browser.close()为什么用BrowserContext它相当于一个独立的浏览器会话。你可以创建多个context每个都有独立的Cookie、本地存储和缓存互不干扰。这在需要模拟多个用户登录或者进行多账号操作时非常有用。数据采集时用一个干净的context开始每次任务能避免上次任务的残留数据影响本次结果。3.2 元素定位与交互告别脆弱的XPath找到页面上的元素并与之交互是自动化的核心。Playwright推荐使用LocatorAPI它比直接使用page.query_selector()更强大、更稳定。# 假设我们要在百度搜索框输入关键词并搜索 # 1. 导航到百度 page.goto(https://www.baidu.com) # 2. 定位搜索框 - 使用get_by_*系列方法可读性最好也最稳定 search_box page.get_by_role(textbox, name百度一下) # 通过ARIA角色定位 # 或者更通用的通过placeholder属性 search_box page.locator(input[namewd]) # 或者使用CSS Selector最常用 search_box page.locator(#kw) # 百度搜索框的id是kw # 3. 在搜索框中输入内容 search_box.fill(Playwright 数据采集) # fill方法会先清空输入框再输入文本比type更高效 # 4. 定位搜索按钮并点击 search_button page.get_by_role(button, name百度一下) # 或者 search_button page.locator(#su) search_button.click() # 5. 等待搜索结果加载 page.wait_for_load_state(networkidle) # 或者等待某个特定的结果元素出现 page.wait_for_selector(.result.c-container, statevisible)定位策略优先级建议首选page.get_by_*方法如get_by_role(),get_by_text(),get_by_label()。这些方法基于可访问性属性是W3C推荐的方式即使页面结构微调也最不容易失效。其次page.locator()配合CSS SelectorCSS选择器性能好写法简洁。优先使用id、class、属性选择器。尽量避免使用复杂的XPath尤其是包含索引如div[3]或完整路径的XPath前端代码稍有改动就会导致定位失败。最后考虑文本定位page.get_by_text(部分文本)适用于按钮、链接等有明确文本的元素。注意事项现代前端框架如React, Vue生成的页面元素id可能是动态的。此时不要依赖id而是观察其稳定的class、># 1. 自动等待Playwright的核心优势之一 # 在执行如 click, fill 等操作时Playwright会自动等待元素可操作可见、稳定、未被遮挡 # 这省去了大量自己写 time.sleep 的功夫 page.locator(#submit).click() # 内部会自动等待#submit按钮可点击 # 2. 显式等待用于等待特定条件成立 # 等待元素出现 page.wait_for_selector(.list-item, stateattached) # 等待元素可见 page.wait_for_selector(.list-item, statevisible) # 等待元素隐藏或消失 page.wait_for_selector(.loading-spinner, statehidden) # 等待特定事件如弹窗 page.once(dialog, lambda dialog: dialog.accept()) # 等待页面跳转导航 page.wait_for_url(**/search**) # 使用通配符匹配URL # 3. 等待网络请求/响应 - 数据采集大杀器 # 监听并等待某个特定API请求的响应 with page.expect_response(**/api/data**) as response_info: page.locator(#load-more).click() # 点击“加载更多”按钮 response response_info.value # 此时response包含了API返回的完整数据可以直接解析JSON data response.json() print(data) # 4. 超时控制所有等待方法都可以设置超时时间毫秒 try: page.wait_for_selector(.dynamic-content, timeout10000) # 10秒超时 except TimeoutError: print(元素在10秒内未出现可能页面状态异常或选择器需要调整)我的经验是优先使用Playwright的自动等待和基于事件/网络的等待expect_response,wait_for_url。这比固定时间的sleep或等待某个元素可见要精准得多。例如点击一个按钮后数据是通过AJAX加载的那么等待对应的API响应完成就是最可靠的信号。这直接反映了“数据已就绪”这一业务状态。4. 高级特性应对复杂数据采集场景基础操作能解决大部分问题但遇到反爬、动态内容、文件下载等复杂场景就需要祭出Playwright的高级功能了。4.1 网络拦截与请求/响应监听这是Playwright对于数据采集者而言最强大的功能。你可以监听、修改甚至阻断任何网络请求。# 监听所有请求和响应用于调试了解页面加载了哪些资源 page.on(request, lambda request: print(f {request.method} {request.url})) page.on(response, lambda response: print(f {response.status} {response.url})) # 更实用的拦截特定请求并返回自定义响应或直接获取数据 from playwright.sync_api import Route # 案例拦截图片请求不加载以加快速度 def handle_route(route: Route): if route.request.resource_type image: # 阻断图片请求 route.abort() else: # 继续其他请求 route.continue_() page.route(**/*, handle_route) # 案例拦截API请求直接获取其响应数据 import json def intercept_api(route: Route): # 只处理目标API if /api/products in route.request.url: # 继续请求并获取响应 response route.continue_() # 注意这里需要异步获取响应体在同步API中需稍作处理 # 更常见的做法是在 page.on(response) 事件中处理 pass else: route.continue_() # 更常见的模式在响应事件中捕获数据 captured_data [] def on_response(response): if /api/products in response.url and response.status 200: # 注意response.body() 返回的是字节需要解码和解析 # 这里为了演示我们假设API返回JSON try: # 在实际同步代码中需要确保能正确获取到body # 一种模式是使用 page.wait_for_response pass except: pass page.on(response, on_response) # 实际项目中更推荐使用 page.wait_for_response 或 context.expect_response # 它能返回一个Response对象并且是同步等待的 page.goto(https://example.com) response page.wait_for_response(**/api/products**) data response.json() # 现在data就是API返回的JSON数据可以直接用于分析通过监听网络你相当于拥有了一个程序化的“开发者工具Network面板”。很多网站的核心数据都通过XHR/Fetch请求获取直接解析这些请求的响应比从渲染后的HTML中爬取要干净、高效得多而且数据结构化程度高通常是JSON。4.2 处理iframe、弹窗与多页面现代网页中登录框、视频播放器、第三方组件常常嵌套在iframe里。Playwright处理它们很简单。# 处理iframe page.goto(https://example.com) # 通过元素选择器定位iframe元素 iframe_element page.frame_locator(iframe[namelogin-frame]) # 现在可以在iframe内部进行操作了 iframe_element.locator(input#username).fill(my_user) iframe_element.locator(input#password).fill(my_pass) iframe_element.locator(button[typesubmit]).click() # 处理弹窗alert, confirm, prompt # 在触发弹窗的操作之前先监听dialog事件 page.on(dialog, lambda dialog: dialog.accept()) # 自动点击“确定” # 或者更精细的控制 def handle_dialog(dialog): print(f弹窗消息: {dialog.message}) if dialog.type confirm: dialog.dismiss() # 点击“取消” else: dialog.accept() page.on(dialog, handle_dialog) # 处理新打开的标签页多页面 # 在点击一个会打开新标签页的链接前监听‘popup’事件 with page.expect_popup() as popup_info: page.locator(a[target_blank]).click() # 点击链接 new_page popup_info.value # 获取新页面的Page对象 # 现在可以操作新页面了 new_page.wait_for_load_state() print(new_page.title()) # 操作完毕后关闭 new_page.close()4.3 执行JavaScript与提取动态数据有些数据可能深藏在JavaScript对象里或者需要通过执行特定的JS函数才能获取。Playwright允许你在页面上下文中执行任意JavaScript代码。# 在页面中执行JavaScript并获取返回值 page.goto(https://example.com) # 执行简单JS获取浏览器环境信息 dimensions page.evaluate(() ({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight })) print(f视口尺寸: {dimensions}) # 执行JS与页面元素交互 # 例如滚动到页面底部用于触发懒加载 page.evaluate(window.scrollTo(0, document.body.scrollHeight)) page.wait_for_timeout(2000) # 等待滚动后内容加载 # 提取复杂数据获取页面中所有图片的src all_image_srcs page.evaluate(() { const images Array.from(document.querySelectorAll(img)); return images.map(img img.src); }) print(f找到 {len(all_image_srcs)} 张图片) # 将Python变量传入JS上下文 search_keyword Playwright # 将search_keyword作为参数传入JS函数 result page.evaluate((keyword) window.myApp.search(keyword), search_keyword)page.evaluate()是你与页面JavaScript世界沟通的桥梁。当常规的定位和属性获取无法拿到数据时比如数据在script标签的JS变量中或者是由JS动态计算出来的这个方法就派上用场了。你可以像在浏览器控制台里一样操作DOM和JS对象。5. 实战构建一个健壮的数据采集脚本让我们把上面的知识点串起来写一个完整的、有一定健壮性的数据采集脚本。假设我们要从某个电商网站以京东商品搜索页为例采集商品列表信息并处理分页。import time import json from playwright.sync_api import sync_playwright, TimeoutError def scrape_jd_products(keyword, max_pages3): 采集京东搜索商品数据 :param keyword: 搜索关键词 :param max_pages: 最大采集页数 :return: 商品数据列表 all_products [] with sync_playwright() as p: # 1. 启动浏览器配置反检测参数 browser p.chromium.launch( headlessFalse, # 调试时可设为False args[ --disable-blink-featuresAutomationControlled, --disable-dev-shm-usage, --no-sandbox ] ) # 创建上下文模拟更真实的浏览器环境 context browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., # 可以额外设置Cookie、语言偏好等 localezh-CN ) page context.new_page() # 2. 导航到京东首页 print(f正在打开京东首页...) page.goto(https://www.jd.com, wait_untilnetworkidle) # 3. 在搜索框输入关键词并搜索 print(f正在搜索关键词: {keyword}) search_input page.locator(#key) search_input.fill(keyword) page.locator(button:has-text(搜索)).click() # 等待搜索结果页面加载完成 page.wait_for_url(**/search**) page.wait_for_load_state(networkidle) time.sleep(2) # 稍等片刻确保商品列表渲染 current_page 1 while current_page max_pages: print(f正在采集第 {current_page} 页...) # 4. 定位商品列表容器 # 京东的商品列表项通常有特定的class这里需要根据实际情况调整选择器 # 使用更稳定的选择器避免依赖可能变化的索引 product_items page.locator(.gl-item) # 这是一个示例选择器实际需调整 item_count product_items.count() print(f本页找到 {item_count} 个商品项) # 5. 遍历当前页商品提取信息 for i in range(item_count): try: item product_items.nth(i) # 提取商品信息这里的选择器需要根据京东实际页面结构调整 # 使用更健壮的提取方式增加等待和错误处理 product_info {} # 商品标题 title_elem item.locator(.p-name a em) if title_elem.count() 0: product_info[title] title_elem.first.text_content().strip() else: product_info[title] N/A # 价格可能动态加载 price_elem item.locator(.p-price strong i) if price_elem.count() 0: product_info[price] price_elem.first.text_content().strip() else: product_info[price] N/A # 店铺名 shop_elem item.locator(.p-shop a) if shop_elem.count() 0: product_info[shop] shop_elem.first.text_content().strip() else: product_info[shop] N/A # 商品链接 link_elem item.locator(.p-name a) if link_elem.count() 0: product_info[link] link_elem.first.get_attribute(href) if product_info[link] and not product_info[link].startswith(http): product_info[link] https: product_info[link] else: product_info[link] N/A all_products.append(product_info) print(f 已提取: {product_info.get(title, N/A)[:30]}...) except Exception as e: print(f 提取第{i1}个商品时出错: {e}) continue # 跳过这个商品继续下一个 # 6. 尝试翻到下一页 if current_page max_pages: next_button page.locator(a.pn-next:not(.disabled)) # 寻找可点击的“下一页”按钮 if next_button.count() 0: print(翻到下一页...) # 点击前先滚动到按钮位置确保其可见 next_button.scroll_into_view_if_needed() next_button.click() # 等待新页面加载。可以等待URL变化或等待列表区域更新 page.wait_for_timeout(3000) # 等待页面加载和可能的AJAX # 更精确的等待等待新的商品项出现并且旧的商品项可能消失或更新 page.wait_for_function(() { const items document.querySelectorAll(.gl-item); return items.length 0; }) current_page 1 else: print(已到达最后一页或未找到下一页按钮。) break else: break # 7. 关闭资源 print(采集完成正在关闭浏览器...) context.close() browser.close() print(f总计采集到 {len(all_products)} 条商品数据。) return all_products if __name__ __main__: # 使用示例 keyword Python编程 products scrape_jd_products(keyword, max_pages2) # 将数据保存为JSON文件 with open(fjd_products_{keyword}.json, w, encodingutf-8) as f: json.dump(products, f, ensure_asciiFalse, indent2) print(f数据已保存至 jd_products_{keyword}.json)这个脚本包含了从启动、导航、定位、循环提取、翻页到保存数据的完整流程并加入了一些基本的错误处理和等待逻辑。请注意京东网站的实际HTML结构可能发生变化上述代码中的CSS选择器如.gl-item,.p-name需要你使用浏览器的开发者工具实时查看并调整这是编写任何数据采集脚本的必经步骤。6. 常见问题排查与性能优化技巧即使按照最佳实践来写在实际运行中还是会遇到各种问题。这里我总结了一些高频问题和优化技巧。6.1 元素找不到或操作超时这是最常见的问题没有之一。问题TimeoutError: Timeout 30000ms exceeded.排查思路选择器是否正确打开浏览器开发者工具F12在Console里输入document.querySelector(你的选择器)看是否能找到元素。检查元素是否在iframe里。页面是否加载完成你的page.goto()或点击操作后是否使用了正确的等待条件对于SPA单页应用networkidle可能不适用需要等待特定元素出现wait_for_selector。元素是否被遮挡有时弹窗、悬浮广告会挡住你要点击的按钮。可以尝试先关闭这些干扰元素或者使用locator.hover()、locator.scroll_into_view_if_needed()让元素进入可操作状态。是否有反爬机制网站可能检测到自动化工具。尝试在new_context中设置更真实的user_agent并添加--disable-blink-featuresAutomationControlled启动参数。更复杂的情况可能需要随机化操作间隔、使用代理IP池。6.2 脚本被网站检测并屏蔽越来越多的网站部署了反爬虫和自动化检测。症状访问被拒绝、出现验证码、数据返回为空或异常。应对策略伪装得更像真人context.new_context(user_agent一个真实的浏览器UA)context.new_context(viewpoint{...})设置合理的视口大小。browser.launch(args[--disable-blink-featuresAutomationControlled])禁用自动化标志。模拟人类行为在关键操作间加入随机延迟page.wait_for_timeout(random.randint(1000, 3000))。使用page.mouse.move(x, y)模拟鼠标移动轨迹。避免执行速度过快。使用浏览器上下文隔离每次任务使用全新的BrowserContext避免Cookie和指纹关联。终极方案对于验证码可以考虑接入第三方打码平台或者对于简单图形验证码使用OCR库如pytesseract尝试识别。但请注意法律和网站服务条款的边界。6.3 性能优化让采集更快更省资源当需要采集大量页面时性能至关重要。1. 启用无头模式和无沙盒模式生产环境务必设置headlessTrue。在Linux服务器上可能还需要args[--no-sandbox, --disable-dev-shm-usage]以避免资源问题。2. 复用浏览器实例不要为每个任务都启动和关闭浏览器。可以启动一个浏览器实例为每个独立任务创建新的BrowserContext和Page。任务完成后关闭上下文即可浏览器进程保持。browser p.chromium.launch(headlessTrue) def scrape_task(url): context browser.new_context() page context.new_page() # ... 执行采集任务 ... context.close() # 关闭上下文但浏览器还在3. 拦截不必要资源如前所述使用page.route()拦截图片、样式表、字体甚至广告脚本可以极大提升页面加载速度。def block_assets(route): blocked_resource_types [image, stylesheet, font, media] if route.request.resource_type in blocked_resource_types: route.abort() else: route.continue_() page.route(**/*, block_assets)4. 并行处理Playwright支持异步APIasync_api。对于I/O密集型的网络请求使用异步可以大幅提升吞吐量。你可以结合asyncio创建多个Page甚至多个BrowserContext来并行采集不同页面。import asyncio from playwright.async_api import async_playwright async def scrape_page(url, context): page await context.new_page() await page.goto(url) # ... 采集逻辑 ... await page.close() async def main(): async with async_playwright() as p: browser await p.chromium.launch() context await browser.new_context() tasks [scrape_page(url, context) for url in url_list] await asyncio.gather(*tasks) await browser.close()注意并行度不是越高越好需要根据目标网站承受能力和自身机器性能进行调整。6.4 数据提取不完整或格式错乱问题提取到的文本包含多余空格、换行或者动态加载的数据没抓到。解决清理文本使用.text_content().strip()后再用正则或字符串方法清理多余空白字符。等待动态数据对于滚动加载懒加载的内容需要在每次滚动后等待新元素出现。对于点击“加载更多”按钮使用page.wait_for_response()等待对应的API调用完成。检查数据来源始终优先考虑从网络请求XHR/Fetch中直接获取结构化数据JSON这比从HTML中解析要可靠和高效得多。多使用page.on(response, ...)或page.wait_for_response()来发现数据接口。7. 项目结构与代码组织建议当你的采集脚本越来越复杂管理多个任务、处理不同网站、存储数据、处理异常就变得重要。一个好的项目结构能让你事半功倍。playwright_data_collector/ ├── config/ # 配置文件 │ ├── settings.yaml # 通用设置超时时间、重试次数等 │ └── sites/ # 不同网站的配置选择器、URL模式等 │ ├── jd_config.yaml │ └── taobao_config.yaml ├── core/ # 核心功能模块 │ ├── browser_manager.py # 浏览器启动、上下文管理 │ ├── page_actions.py # 封装的常用页面操作登录、滚动、提取 │ └── network_interceptor.py # 网络请求拦截与监听器 ├── spiders/ # 具体的采集脚本蜘蛛 │ ├── base_spider.py # 爬虫基类封装通用逻辑 │ ├── jd_spider.py # 京东采集脚本 │ └── taobao_spider.py ├── utils/ # 工具函数 │ ├── logger.py # 日志配置 │ ├── file_io.py # 文件读写JSON, CSV │ └── proxy_rotator.py # 代理IP管理 ├── data/ # 采集到的数据 │ └── raw/ # 原始数据 ├── logs/ # 运行日志 └── main.py # 主程序入口调度任务关键点配置与代码分离将不同网站的选择器、URL、登录信息等放在配置文件中修改时无需动代码。封装通用操作将打开浏览器、登录、处理验证码、提取数据模板等操作封装成函数或类方法避免代码重复。完善的日志使用Python的logging模块记录脚本运行状态、错误信息方便后期排查问题。异常处理与重试在网络请求、元素查找等可能失败的环节加入try...except并实现重试机制如tenacity库。数据存储根据数据量选择存储方式。小批量可以用JSON或CSV文件大批量建议用数据库SQLite, MySQL, MongoDB。考虑增量采集避免重复抓取。从我自己的经验来看前期多花点时间在项目结构设计和代码封装上后期维护和扩展新采集任务时会轻松很多。Playwright是一个强大的工具但把它用好的关键还是在于对目标网站结构的深刻理解以及编写出稳定、可维护的脚本逻辑。