前言
Playwright 由微软开源,自动管理浏览器驱动、内置浏览器指纹伪装、自带网络拦截、异步多浏览器内核,相比 Selenium 启动更快、资源占用更低、原生规避 webdriver 检测,完美适配 Vue/SPA 单页、动态接口加密、JS 强校验站点。本章实现无头静默抓取、请求拦截抓接口、Cookie 注入、代理 + UA 动态轮换、懒加载滚动采集,无缝对接前期代理池、UA 池、Cookie 池整套爬虫基建,覆盖单机与分布式项目改造落地。
本文所需依赖官方文档超链接:
- Playwright 官方文档
- Requests 官方文档
- Redis-Py 官方文档
一、Playwright 对比 Selenium 核心优势
表格
| 功能项 | Selenium | Playwright |
|---|---|---|
| 驱动管理 | 需手动 / 半自动适配浏览器驱动 | 内置 Chromium/Firefox/Webkit,一键安装浏览器 |
| 爬虫指纹 | 原生 webdriver 标记极易被检测 | 底层屏蔽自动化特征,默认绕过 navigator.webdriver 校验 |
| 资源开销 | 进程笨重,多实例内存占用高 | 轻量化,单实例资源占用减少 40%+ |
| 网络操控 | 仅能抓页面 DOM,拦截请求繁琐 | 原生拦截 ajax/fetch,直接截取后端接口 JSON |
| 等待逻辑 | 依赖 WebDriverWait 手动编写 | 自动智能等待页面稳定,无需手动配置等待时间 |
二、环境安装与浏览器初始化
bash
运行
# 安装依赖包 pip install playwright==1.44.0 # 自动下载对应浏览器内核 playwright install chromium2.1 基础无头配置封装
python
运行
from playwright.sync_api import sync_playwright import random def get_browser_context(ua:str=None, proxy_str:str=None): """生成浏览器上下文,支持自定义UA、代理""" pw = sync_playwright().start() # 启动无头chromium browser = pw.chromium.launch( headless=True, args=["--no-sandbox","--disable-dev-shm-usage"] ) context_args = {} # 配置UA if ua: context_args["user_agent"] = ua # 配置代理 ip:port if proxy_str: ip,port = proxy_str.split(":") context_args["proxy"] = {"server":f"http://{ip}:{port}"} # 创建浏览器上下文(一个上下文=独立无痕环境) context = browser.new_context(**context_args) page = context.new_page() return pw,browser,context,page原理说明
Playwright 以 Context 隔离环境,同一浏览器可创建多个独立上下文,互不共享 Cookie、缓存、IP,实现低成本多账号隔离。
三、基础动态页面抓取(替代 Selenium 常规 DOM 解析)
python
运行
def playwright_crawl(target_url,rand_ua=None,rand_proxy=None): pw,browser,ctx,page = get_browser_context(ua=rand_ua,proxy_str=rand_proxy) try: # 访问页面,自动等待页面资源加载完毕 page.goto(target_url,timeout=30000,wait_until="networkidle") # 提取商品列表 goods_list = [] item_nodes = page.locator(".goods-item").all() for item in item_nodes: title = item.locator(".title").text_content() price = item.locator(".price").text_content() goods_list.append({"title":title,"price":price}) return goods_list except Exception as e: print("抓取异常:",str(e)) return [] finally: # 资源释放 browser.close() pw.stop() if __name__ == "__main__": res = playwright_crawl("https://demo-vue.com/goods") print(res)wait_until="networkidle":等待页面 500ms 无新网络请求再解析,完美适配异步渲染接口。
四、核心高阶:拦截 Ajax 接口,直接抓取后端 JSON(爬虫提速关键)
绕开 DOM 解析,拦截页面 fetch/ajax 接口,直接拿到原始接口返回 JSON,大幅提升解析效率:
python
运行
import json def intercept_api_crawl(target_url): pw,browser,ctx,page = get_browser_context() api_data = {} # 注册请求拦截回调 def handle_route(route,request): # 匹配商品数据接口关键词 if "api/goods/list" in request.url: resp = route.fetch() json_data = resp.json() api_data["goods"] = json_data else: route.continue_() # 开启路由拦截 page.route("**/*",handle_route) page.goto(target_url,wait_until="networkidle") browser.close() pw.stop() return api_data # 调用直接获取接口原始数据 # data = intercept_api_crawl("https://demo-vue.com/list")五、Cookie 池对接:上下文注入 Cookie 免登录访问会员页面
复用之前 Redis Cookie 池返回的 Cookie 字符串,注入浏览器实现免登:
python
运行
def set_cookie_to_context(context,cookie_str,domain): cookie_arr = [] for ck_item in cookie_str.split("; "): k,v = ck_item.split("=",1) cookie_arr.append({ "name":k, "value":v, "domain":domain }) context.add_cookies(cookie_arr) def vip_data_crawl(url,cookie_str,domain,rand_ua=None,rand_proxy=None): pw,browser,ctx,page = get_browser_context(ua=rand_ua,proxy_str=rand_proxy) set_cookie_to_context(ctx,cookie_str,domain) page.goto(url,wait_until="networkidle") vip_text = page.locator(".vip-content").text_content() browser.close() pw.stop() return vip_text六、联动代理池 + UA 池三合一自动化轮换
对接前文 Flask 接口,每次爬虫自动取用全新 UA + 代理:
python
运行
import requests PROXY_API = "http://127.0.0.1:5010/get_proxy" UA_API = "http://127.0.0.1:5010/get_ua" COOKIE_API = "http://127.0.0.1:5011/get_cookie" def get_all_pool_resource(): # 获取代理 p_res = requests.get(PROXY_API).json() proxy = p_res["proxy"] if p_res["code"]==200 else None # 获取UA ua_res = requests.get(UA_API).json() ua = ua_res["ua"] if ua_res["code"]==200 else None # 获取Cookie ck_res = requests.get(COOKIE_API).json() ck = ck_res["cookie"] if ck_res["code"]==200 else None return proxy,ua,ck def full_anti_crawl(url,domain): proxy,ua,cookie = get_all_pool_resource() pw,browser,ctx,page = get_browser_context(ua=ua,proxy_str=proxy) if cookie: set_cookie_to_context(ctx,cookie,domain) page.goto(url,wait_until="networkidle") res = page.content() browser.close() pw.stop() return res七、页面懒加载滚动采集(无限滚动列表)
模拟页面下拉,循环触发异步加载直到无新数据:
python
运行
def scroll_all_load(page): while True: # 页面下滑JS page.evaluate("window.scrollTo(0,document.body.scrollHeight)") page.wait_for_timeout(1000) # 判断页面高度是否变化 curr_h = page.evaluate("return document.body.scrollHeight") old_h = page.evaluate("return window.innerHeight + window.scrollY") if curr_h <= old_h: break item_all = page.locator(".goods-item").all() return [i.text_content() for i in item_all]八、异步版 Playwright(高并发爬虫优选)
IO 密集爬虫选用 async 异步写法,单进程并发数十个页面:
python
运行
from playwright.async_api import async_playwright import asyncio async def async_crawl(url): pw = await async_playwright().start() browser = await pw.chromium.launch(headless=True) ctx = await browser.new_context() page = await ctx.new_page() await page.goto(url,wait_until="networkidle") data = await page.locator(".goods-item").all_text_contents() await browser.close() await pw.stop() return data # 多任务并发 async def run_batch(): task_list = [async_crawl("url1"),async_crawl("url2")] result = await asyncio.gather(*task_list) print(result) # asyncio.run(run_batch())九、Linux 服务器部署规范与进程优化
- 服务器必加启动参数:
args=["--no-sandbox","--disable-dev-shm-usage"]; - 后台部署:nohup 启动脚本,避免 ssh 断开进程销毁;
- 批量并发控制:单服务器限制 Chromium 实例数量,防止内存溢出。
十、常见问题排查优化表
表格
| 故障现象 | 解决方案 |
|---|---|
| 部分站点跳转人机验证 | 提升代理质量,增加 Cookie 账号基数,降低请求速率 |
| 接口拦截不到目标 API | 放宽 route 匹配规则,改用关键词模糊匹配 URL |
| 内存持续上涨 | 每次抓取完毕严格关闭 browser 与 playwright 实例,杜绝进程残留 |
| 代理连接失败 | 捕获异常自动回调代理池失效接口,剔除坏 IP |