前言
之前对 Playwright 一直是懵懵懂懂的状态,用起来总觉得隔着一层纱。最近深入学习后,基本掌握了其主要概念,同时也基于自己的理解做了一个封装库。本文将分享我对 Playwright 的理解,以及如何将其封装成一个可以像普通浏览器一样使用的自动化工具。
一、Playwright 核心概念:一个生动的比喻
很多人初次接触 Playwright 时,会被 Playwright、Browser、Context、Page 这些概念搞晕。让我用一个生动的比喻来解释:
🎯 核心比喻
| 概念 | 比喻 | 说明 |
|---|---|---|
| Playwright | 司机 | 整个自动化过程的控制者,负责启动和管理整个流程 |
| Browser | 车辆 | 实际的浏览器实例,可以是 Chrome、Firefox 或 WebKit |
| Context | 车厢 | 独立的浏览器会话,拥有自己的 cookies、storage 和上下文 |
| Page | 窗户 | 每个标签页或窗口,用户通过它与网页交互 |
关键理解:
- 一列火车可以有多节车厢(多个 Context)
- 每节车厢可以有多个窗户(多个 Page)
- 只有司机(Playwright)能启动和停止整列火车
二、资源管理:必须关闭 Browser
这是很多初学者容易踩的坑:如果不关闭 Browser,会有进程残留!
⚠️ 正确关闭顺序
python
# ✅ 正确做法:按顺序关闭
page.close() # 关闭页面(可选)
context.close() # 关闭上下文
browser.close() # 关闭浏览器 ← 必须!
playwright.stop() # 停止 Playwright
💡 特殊情况:永久 Context
如果使用永久上下文(Persistent Context),情况会简单一些:
python
# 永久上下文会自动管理,只需要关闭它即可
context = playwright.chromium.launch_persistent_context(...)
# 使用完后:
context.close() # 关闭后,browser 也会自动关闭
这是因为永久上下文本质上是一个完整的浏览器实例,关闭它就相当于关闭了整个浏览器。
三、我对 Playwright 的封装
🎯 封装目的
使用 requests 库时,有些网站需要运行 JavaScript 才能正常获取内容。传统的 Playwright 使用方式需要频繁创建和销毁浏览器,体验不够流畅。
我封装了一个 单例模式 的 Playwright 工具,实现:
- ✅ 永久上下文,像普通浏览器一样使用
- ✅ 自动管理下载
- ✅ 单例模式,全局唯一实例
- ✅ 自动释放资源,防止进程残留
📦 完整代码
python
# myplaywright.py
import platform
import atexit
from pathlib import Path
from typing import Optional
from playwright.sync_api import sync_playwright, Playwright, BrowserContext, Page, Download
class MyPlaywright:
'''自定义Playwright单例类'''
_instance: Optional['MyPlaywright'] = None
_playwright: Optional[Playwright] = None
_context: Optional[BrowserContext] = None
_page: Optional[Page] = None
_is_initialized: bool = False
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if self._is_initialized: return
self._is_initialized = True
self._base_dir = Path.cwd()
self._chromium_data_dir = self._base_dir / "chromium_data"
self._downloads_dir = self._base_dir / "downloads"
self._chromium_data_dir.mkdir(exist_ok=True)
self._downloads_dir.mkdir(exist_ok=True)
self._is_windows = platform.system() == "Windows"
atexit.register(self.release) # 注册退出时释放资源
def _get_headless(self) -> bool:
'''Windows 显示浏览器,Linux 无头模式'''
return not self._is_windows
@property
def playwright(self) -> Playwright:
if self._playwright is None:
self._playwright = sync_playwright().start()
return self._playwright
@property
def context(self) -> BrowserContext:
if self._context is None:
self.reset_context()
return self._context
def reset_context(self, headless: Optional[bool] = None) -> BrowserContext:
'''重置浏览器上下文'''
if self._context:
self._context.close()
is_headless = headless if headless is not None else self._get_headless()
self._context = self.playwright.chromium.launch_persistent_context(
user_data_dir=self._chromium_data_dir,
headless=is_headless,
bypass_csp=True, # 跳过内容安全策略
args=['--start-maximized'],
no_viewport=True,
accept_downloads=True,
downloads_path=self._downloads_dir,
)
return self._context
@property
def page(self) -> Page:
'''获取页面单实例'''
if self._page is None:
self._page = self.context.pages[0]
self._page.on('download', self._on_download)
return self._page
def _on_download(self, download: Download):
'''下载事件处理'''
print(f"开始下载 {download.suggested_filename}...")
download_path = self._downloads_dir / download.suggested_filename
download.save_as(download_path)
print(f"下载完成: {download_path}")
def release(self):
'''释放资源'''
try:
if self._context:
self._context.close()
if self._playwright:
self._playwright.stop()
self._context = None
self._playwright = None
except Exception:
pass
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.release()
🚀 使用示例
python
# 方式1:上下文管理器(推荐)
with MyPlaywright() as pw:
pw.page.goto("https://www.baidu.com")
print(pw.page.title())
# 退出时自动释放资源
# 方式2:直接使用
pw = MyPlaywright()
pw.page.goto("https://example.com")
# 程序退出时自动释放资源
✨ 封装亮点
| 特性 | 说明 |
|---|---|
| 单例模式 | 全局只有一个实例,避免重复启动浏览器 |
| 永久上下文 | 浏览器状态持久化,cookies 不会丢失 |
| 自动下载 | 监听下载事件,自动保存文件 |
| 跨平台 | Windows 显示浏览器,Linux 自动无头 |
| 资源托管 | 使用 atexit 确保程序退出时释放资源 |
四、总结
🎯 学习要点
- 理解层次关系:Playwright → Browser → Context → Page
- 比喻记忆:司机、火车、车厢、窗户
- 资源管理:必须关闭 Browser,永久 Context 只需关闭自身
- 实际应用:封装成单例模式,打造永久化的自动化浏览器
📚 参考资料
本文为本人原创,首发于掘金,同步发布于 CSDN、知乎等平台。
如果你有任何问题或想法,欢迎在评论区交流!