目录
前言
书接上文,selenium添加代理:
rom selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.proxy import Proxy, ProxyType
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
# 代理供应商
proxy_host = 'your_host'
proxy_port = 'your_port'
proxy_username = 'your_username'
proxy_password = 'your_password'
# 创建代理对象
proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = f'http://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}'
proxy.ssl_proxy = f'http://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}'
# 设置Chrome浏览器驱动程序使用代理
options = webdriver.ChromeOptions()
options.add_argument('--proxy-server=%s' % proxy.http_proxy)
options.add_argument('--disable-notifications')
options.add_argument('--disable-popup-blocking')
options.add_argument('--disable-infobars')
options.add_argument('--mute-audio')
driver = webdriver.Chrome(options=options)
对于无校验的代理,也就是代理形式为:
{proxy_host}:{proxy_port}
这样加没问题,但
对于需要校验用户名,密码的代理,也就是代理形式为:
proxy.http_proxy = f'http://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}'
proxy.ssl_proxy = f'http://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}'
访问页面就会出现"ERR_NO_SUPPORTED_PROXIES"
解决方法:
- 代理提供商通常会提供
白名单
校验:将你的主机ip加入白名单就可以替换username,password校验了。 - 使用selenium的扩展库:selenium-wire
seleniumwire
SeleniumWire 是一个 Python 库,它是在 Selenium WebDriver 的基础上扩展而来的。它提供了对浏览器的网络请求和响应进行拦截、修改和监控的功能,使得你可以更灵活地控制浏览器与服务器之间的通信过程。
以下是 SeleniumWire 提供的一些主要功能和特点:
-
请求和响应拦截:SeleniumWire 允许你拦截浏览器发送的请求和接收的响应,从而可以在请求发送前、发送后、响应接收前、接收后等时机对请求和响应进行修改、记录或拦截。
-
修改请求和响应:你可以修改请求头、请求体、查询参数、响应头、响应体等内容,以便在自动化测试过程中模拟不同的场景和测试条件。
-
请求过滤:SeleniumWire 支持根据 URL、请求方法、请求头等条件对请求进行过滤,使得你可以只关注感兴趣的请求,忽略其他请求。
-
请求记录和导出:SeleniumWire 可以记录浏览器发送和接收的所有网络请求,并且支持将请求记录导出为 HAR 文件,方便分析和调试。
-
网络性能分析:通过拦截和记录网络请求,你可以对页面加载时间、资源加载情况等进行性能分析和优化。
-
无需修改浏览器配置:SeleniumWire 是在 Selenium WebDriver 的基础上扩展的,因此无需额外的浏览器配置或插件即可使用。
以下是一个简单的示例代码,演示了如何使用 SeleniumWire 进行网络请求的拦截和修改:
from seleniumwire import webdriver
# 创建 Chrome WebDriver,并将日志级别设置为 INFO,用于查看网络请求日志
driver = webdriver.Chrome(seleniumwire_options={'log_level': 0})
# 访问目标网页
driver.get('https://www.example.com')
# 获取所有的网络请求
for request in driver.requests:
if request.response:
print(request.url, request.response.status_code)
# 关闭 WebDriver
driver.quit()
在上述示例代码中,创建了一个 Chrome WebDriver,并使用 seleniumwire_options
参数将日志级别设置为 0(INFO),以便在控制台查看网络请求日志。然后,我们访问了一个网页,并获取了所有的网络请求,打印了请求的 URL 和响应的状态码。
总体而言,SeleniumWire 提供了强大的网络请求拦截和修改功能,可以更灵活地控制和分析浏览器与服务器之间的通信过程。它在自动化测试、网络性能分析和数据采集等场景中都有很多实用价值。
安装
pip install selenium-wire
创建web driver
确保你从seleniumwire导入webdriver库:
from seleniumwire import webdriver
然后,只需像直接使用Selenium一样实例化WebDriver。你可以传递任何所需的功能或浏览器特定选项,比如可执行路径、无头模式等。Selenium Wire也有自己的选项,可以通过seleniumwire_options属性传递进去。
# Create the driver with no options (use defaults)
driver = webdriver.Chrome()
# Or create using browser specific options and/or seleniumwire_options options
driver = webdriver.Chrome(
options = webdriver.ChromeOptions(...),
seleniumwire_options={...}
)
设置代理
options = {
'proxy': {
'http': 'http://username:password@host:port',
'https': 'https://username:password@host:port',
}
}
browser = webdriver.Chrome(path_to_driver, seleniumwire_options=options)
而且最最最最最最重要的是,它支持热加载代理,如下,内部实现了proxy的setter和getter:
@property
def proxy(self) -> Dict[str, Any]:
...
return conf
@proxy.setter
def proxy(self, proxy_conf: Dict[str, Any]):
...
牛刀小试:
from seleniumwire import webdriver
proxy = next_proxy()
options = {
'proxy': {
'http': proxy["http"],
'https': proxy["https"],
}
}
browser = webdriver.Chrome(seleniumwire_options=options)
browser.get("https://myip.ipip.net/")
print(browser.proxy)
print(browser.page_source)
proxy = next_proxy()
browser.proxy = {"http": proxy["http"], "https": proxy["https"]}
browser.get("https://myip.ipip.net/")
print(browser.proxy)
print(browser.page_source)
--------------------------------------------------------------------
当前 IP:xxxxxxxx 来自于:中国 安徽
当前 IP:xxxxxxxx 来自于:中国 陕西
反屏蔽
我们使用https://bot.sannysoft.com/
来查看请求是否完全被判定为用户浏览器:
先不加任何参数直接访,100%会被检测出来web driver
from seleniumwire import webdriver
browser = webdriver.Chrome()
browser.get("https://bot.sannysoft.com/")
# 此处用于阻塞解释器
input(">>>")
除了我关于selenium的另一篇博客:https://blog.csdn.net/General_zy/article/details/128712331 介绍的反屏蔽方法,此处再介绍一些方法:
(或者也可以使用另一个库:pip install undetected-chromedriver)
修改window.navigator.webdriver关键字返回结果
options = webdriver.ChromeOptions()
# 此步骤很重要,设置为开发者模式,防止被各大网站识别出来使用了Selenium
driver = webdriver.Chrome(options=options)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
options追加参数
add_experimental_option
和 add_argument
都是用于向 ChromeOptions 添加参数的方法,但它们在使用上有一些区别:
-
add_experimental_option
:- 用途:用于向 ChromeOptions 添加一些实验性的选项或特性。
- 适用对象:通常用于添加一些不常用的或特定于浏览器版本的选项。
- 示例:在 Selenium 中,使用
add_experimental_option
可以添加一些实验性的 Chrome 参数,比如启用/禁用某些功能、设置浏览器性能优化选项等。
-
add_argument
:- 用途:用于向 ChromeOptions 添加普通的命令行选项或配置参数。
- 适用对象:通常用于添加一些常规的浏览器配置参数,比如设置 User-Agent、启用/禁用 JavaScript、设置代理服务器等。
- 示例:在 Selenium 中,使用
add_argument
可以添加一些普通的 Chrome 命令行选项,比如设置 User-Agent,可以使用options.add_argument("--user-agent=Your_User_Agent")
。
总的来说,add_experimental_option
更多用于添加一些不常用或实验性的选项,而 add_argument
则更常用于添加普通的浏览器配置参数。在实际使用中,可以根据具体需求选择使用哪种方法。
通过 options = webdriver.ChromeOptions()
来添加参数的方式主要是在 Selenium 中模拟浏览器操作,以尽量避免被网站屏蔽。以下是一些常用参数和选项的介绍:
-
User-Agent:通过
options.add_argument("--user-agent=Your_User_Agent")
可以设置自定义的 User-Agent 字段,以伪装成不同的浏览器或设备。 -
代理:通过
options.add_argument("--proxy-server=your_proxy_ip:your_proxy_port")
可以设置代理服务器,通过不同的代理 IP 来访问网站,避免频繁使用相同的 IP 地址。 -
禁用图片加载:通过
options.add_argument("--blink-settings=imagesEnabled=false")
可以禁用图片加载,减少页面请求,提高访问速度。 -
禁用 JavaScript:通过
options.add_argument("--disable-javascript")
可以禁用 JavaScript 的执行,有些网站的屏蔽机制可能依赖于 JavaScript 的操作。 -
无头模式:通过
options.add_argument("--headless")
可以启用 Chrome 的无头模式,即在后台运行 Chrome,无界面显示,模拟真实浏览器行为。 -
禁用 GPU 加速:通过
options.add_argument("--disable-gpu")
可以禁用 GPU 加速,有些网站可能会检测 GPU 信息,通过禁用 GPU 可以降低被检测的概率。 -
禁用扩展插件:通过
options.add_argument("--disable-extensions")
可以禁用 Chrome 的扩展插件,减少特定插件对网站访问的影响。 -
禁用自动化提示:通过
options.add_experimental_option("excludeSwitches", ["enable-automation"])
可以禁用自动化提示,模拟真实用户行为。 -
options.add_argument(‘–ignore-certificate-errors’),options.add_argument(‘–ignore-ssl-errors’)忽略 SSL 证书错误和 SSL 错误,用于处理在使用自签名证书或过期证书的网站时避免弹出警告。
-
options.add_argument(‘–disable-logging’),options.add_argument(‘–log-level=3’) 禁用日志记录,以减少日志文件的输出。
options = webdriver.ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--disable-gpu")
browser = webdriver.Chrome(options=options)
with open('stealth.min.js') as f:
js = f.read()
browser.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': js
})
browser.get("https://bot.sannysoft.com/")
# 此处用于阻塞解释器
input(">>>")
pyppeteer
是 Puppeteer 的 Python 版本的实现,但他不是 Google 开发的,是一位来自于日本的工程师依据 Puppeteer 的一些功能开发出来的非官方版本。
在 Pyppetter 中,实际上它背后也是有一个类似 Chrome 浏览器的 Chromium 浏览器在执行一些动作进行网页渲染。
Chromium 是谷歌为了研发 Chrome 而启动的项目,是完全开源的。二者基于相同的源代码构建,Chrome 所有的新功能都会先在 Chromium 上实现,待验证稳定后才会移植,因此 Chromium 的版本更新频率更高,也会包含很多新的功能,但作为一款独立的浏览器,Chromium 的用户群体要小众得多。两款浏览器“同根同源”,它们有着同样的 Logo,但配色不同,Chrome 由蓝红绿黄四种颜色组成,而 Chromium 由不同深度的蓝色构成。
puppeteer
Puppeteer是一个基于Node.js的库,它提供了一个高级API,可以通过DevTools协议控制Chrome/Chromium浏览器。Puppeteer默认以无界面模式运行,但可以配置为在完整(“headful”)Chrome/Chromium浏览器中运行。
中文文档:https://puppeteer.bootcss.com/
安装
pip install pyppeteer
官方github库提示:
注意:此存储库未维护,并且已经很长时间没有进行任何重大更改。请考虑使用 playwright-python 作为替代方案。
如果您希望对此代码进行全面更新,请与我联系。
pyppeteer的语法和puppeteer相同,可以参考puppeteer文档。
快速入门
pyppeteer是基于asyncio的,所以需要引入async,await关键字:
import asyncio
from pyppeteer import launch
async def main():
browser = await launch()
page = await browser.newPage()
await page.goto('https://example.com')
await page.screenshot({'path': 'example.png'})
await browser.close()
# python3.10后run函数成为一个稳定的版本
# 推荐使用run函数
asyncio.run(main())
更多demo:https://www.jianshu.com/p/f1a8fb7037d7
在页面上执行Js:
import asyncio
from pyppeteer import launch
async def main():
browser = await launch()
page = await browser.newPage()
await page.goto('https://example.com')
await page.screenshot({'path': 'example.png'})
dimensions = await page.evaluate('''() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio,
}
}''')
print(dimensions)
# >>> {'width': 800, 'height': 600, 'deviceScaleFactor': 1}
await browser.close()
asyncio.run(main())
参数配置
属性 | 参数 | 描述 |
---|---|---|
executablePath | str | chrome.exe运行的路径 |
ignorehttpserrrors | bool | 忽略https错误,默认false |
headless | bool | True 开始无头浏览器 False关闭无头 |
dumpio | bool | 设置True 解决浏览器多开卡死 (没有测试过) |
下面是args的参数设置 | 下面是args的参数设置 | 下面是args的参数设置 |
–disable-infobars | - | 关闭自动化提示框 |
–window-size=1920,1080 | str | 设置浏览器大小吗,1920是宽,1080是宽 |
–log-level=30 | str | 日志保存等级, 建议设置越好越好,要不然生成的日志占用的空间会很大 30为warning级别 |
–start-maximized | - | 窗口最大化模式 |
–proxy-server=http://localhost:1080 | str | 设置代理 |
userDataDir=D:\userData\ | str | 用户文件保存地址 |
其他参数:
- ignoreHTTPSErrors (bool): 是否要忽略 HTTPS 的错误,默认是 False。
- headless (bool): 是否启用 Headless 模式,即无界面模式,如果 devtools 这个参数是 True 的话,那么该参数就会被设置为 False,否则为 True,即默认是开启无界面模式的。
- executablePath (str): 可执行文件的路径,如果指定之后就不需要使用默认的 Chromium 了,可以指定为已有的 Chrome 或 Chromium。
- slowMo (int|float): 通过传入指定的时间,可以减缓 Pyppeteer 的一些模拟操作。
- args (List[str]): 在执行过程中可以传入的额外参数。
- ignoreDefaultArgs (bool): 不使用 Pyppeteer 的默认参数,如果使用了这个参数,那么最好通过 args 参数来设定一些参数,否则可能会出现一些意想不到的问题。这个参数相对比较危险,慎用。
- handleSIGINT (bool): 是否响应 SIGINT 信号,也就是可以使用 Ctrl + C 来终止浏览器程序,默认是 True。
- handleSIGTERM (bool): 是否响应 SIGTERM 信号,一般是 kill 命令,默认是 True。
- handleSIGHUP (bool): 是否响应 SIGHUP 信号,即挂起信号,比如终端退出操作,默认是 True。
- dumpio (bool): 是否将 Pyppeteer 的输出内容传给 process.stdout 和 process.stderr 对象,默认是 False。
- userDataDir (str): 即用户数据文件夹,即可以保留一些个性化配置和操作记录。
- env (dict): 环境变量,可以通过字典形式传入。
- devtools (bool): 是否为每一个页面自动开启调试工具,默认是 False。如果这个参数设置为 True,那么 headless 参数就会无效,会被强制设置为 False。
- logLevel (int|str): 日志级别,默认和 root logger 对象的级别相同。
- autoClose (bool): 当一些命令执行完之后,是否自动关闭浏览器,默认是 True。
- loop (asyncio.AbstractEventLoop): 时间循环对象。
async def main():
proxy = next_proxy()
# 创建浏览器启动选项
browser_options = {
'args': [
# f'--proxy-server={proxy.get("https")}', # 设置代理,替换为你的代理IP和端口
'--no-sandbox', # 非沙盒模式,
'--disable-infobars', # 关闭自动化提示框
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
],
'headless': True # 无头模式,不显示浏览器窗口
}
# 启动浏览器并创建页面
browser = await launch(options=browser_options)
page = await browser.newPage()
resp = await page.goto('https://myip.ipip.net/')
print(await resp.text())
await browser.close()
if __name__ == '__main__':
# test()
# test_with_proxy()
asyncio.run(main())
注意,pyppeteer也只支持无认证的代理。
隐藏浏览器特征
pyppeteer跟selenium一样会有浏览器特征,所以需要修改,隐藏特征防止被识别。
主要有下面两点:
- 去除浏览器自动化参数 --enable-automation
- 去除window.navigator.webdriver等检测
from pyppeteer import launcher
# 第一步 去除浏览器自动化参数
# 必须在 from pyppeteer import launch 前去除参数
# 去除自动化 启动参数
launcher.AUTOMATION_ARGS.remove("--enable-automation")
# 第二步,修改 navigator.webdriver检测
# 其实各种网站的检测js是不一样的,这是比较通用的。有的网站会检测运行的电脑运行系统,cpu核心数量,鼠标运行轨迹等等。
# 反爬js
js_text = """
() =>{
Object.defineProperties(navigator,{ webdriver:{ get: () => false } });
window.navigator.chrome = { runtime: {}, };
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], });
}
"""
await page.evaluateOnNewDocument(js_text) # 本页刷新后值不变,自动执行js
拦截请求
from pyppeteer import launcher
launcher.AUTOMATION_ARGS.remove("--enable-automation")
from pyppeteer import launch
from pyppeteer.network_manager import Request, Response
async def intercept_request(req:Request):
await req.continue_() # 请求,看源码可以重新编写请求
async def intercept_response(res:Response):
if 'ext2020/apub/json/prevent.new' in res.url:
print('拦截到请求')
json_text = await res.text()
title_li = jsonpath(json.loads(json_text), '$..title')
for title in title_li:
print(title)
pass
async def main():
# 浏览器 启动参数
start_parm = {
# 启动chrome的路径
"executablePath": r"C:\Users\yq\AppData\Local\pyppeteer\pyppeteer\local-chromium\722234\chrome-win\chrome.exe",
# 关闭无头浏览器 默认是无头启动的
"headless": False,
"args": [
'--disable-infobars', # 关闭自动化提示框
# '--no-sandbox', # 关闭沙盒模式
'--start-maximized', # 窗口最大化模式
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
# UA
],
}
# 创建浏览器对象,可以传入 字典形式参数
browser = await launch(**start_parm)
# 创建一个页面对象, 页面操作在该对象上执行
page = await browser.newPage()
await page.setJavaScriptEnabled(enabled=True)
# 启用拦截器
await page.setRequestInterception(True)
page.on('request', intercept_request)
page.on('response', intercept_response)
js_text = """
() =>{
Object.defineProperties(navigator,{ webdriver:{ get: () => false } });
window.navigator.chrome = { runtime: {}, };
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], });
}
"""
await page.evaluateOnNewDocument(js_text) # 本页刷新后值不变,自动执行js
await page.goto('https://news.qq.com/') # 页面跳转
await browser.close()
asyncio.run() # 创建异步池并执行main函数。
更多文档&博客
Playwright
迁移到 Playwright的原因:
- Pyppeteer 是第三方的,好久没有更新了,bug 也不少
- Puppeteer 的原生 JS 版本本身就很不稳定,亲测 Playwright 更稳定一些
- Playwright 原生支持 Python,而且支持 Google Chrome/Firefox/Safari 三大浏览器
Playwright 是微软出品的浏览器自动化工具,代码质量应该是有足够保证的。而且它还官方支持同步版的 Python API, 同时支持三大浏览器。
安装
pip install playwright
- 安装浏览器:
python -m playwright install
文档地址:https://playwright.dev/python/docs/api/class-playwright
快速入门
同步接口:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# 打开三种浏览器并截屏
for browser_type in [p.chromium, p.firefox, p.webkit]:
browser = browser_type.launch()
page = browser.new_page()
page.goto('http://playwright.dev')
page.screenshot(path=f'example-{browser_type.name}.png')
browser.close()
异步接口:
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
for browser_type in [p.chromium, p.firefox, p.webkit]:
browser = await browser_type.launch()
page = await browser.new_page()
await page.goto('http://playwright.dev')
await page.screenshot(path=f'example-{browser_type.name}.png')
await browser.close()
asyncio.run(main())
新概念:Context
和 Puppeteer 不同的是,Playwright 新增了 context 的概念,每个 context 就像是一个独立的匿名模式会话,非常轻量,但是又完全隔离。比如说,可以在两个 context 中登录两个不同的账号,也可以在两个 context 中使用不同的代理。
通过 context 还可以设置 viewport, user_agent 等。
context = browser.new_context(
user_agent='My user agent'
)
context = browser.new_context(
viewport={ 'width': 1280, 'height': 1024 }
)
context = browser.new_context(
http_credentials={"username": "bill", "password": "pa55w0rd"}
)
# new_context 其他几个比较有用的选项:
ignore_https_errors=False
proxy={"server": "http://example.com:3128", "bypass": ".example.com", "username": "", "password": ""}
extra_http_headers={"X-Header": ""}
context 中有一个很有用的函数context.add_init_script, 可以让我们设定在调用 context.new_page 的时候在页面中执行的脚本。
# hook 新建页面中的 Math.random 函数,总是返回 42
context.add_init_script(script="Math.random = () => 42;")
# 或者写在一个文件里
context.add_init_script(path="preload.js")
还可以使用 context.expose_binding 和 context.expose_function 来把 Python 函数暴露到页面中,还是使用 add_init_script 暴露 JS 函数方便一些。
和 Puppeteer 一样,Playwright 的核心概念依然是 page, 核心 API 几乎都是 page 对象的方法。可以通过 context 来创建 page.
页面基本操作
按照官网文档,调用 page.goto(url) 后页面加载过程:
- 设定 url
- 通过网络加载解析页面
- 触发 page.on(“domcontentloaded”) 事件
- 执行页面的 js 脚本,加载静态资源
- 触发 page.on(“laod”) 事件
- 页面执行动态加载的脚本
- 当 500ms 都没有新的网络请求的时候,触发 networkidle 事件
page.goto(url) 会跳转到一个新的链接。默认情况下 Playwright 会等待到 load 状态。如果我们不关心加载的 CSS 图片等信息,可以改为等待到 domcontentloaded 状态,如果页面是 ajax 加载,那么我们需要等待到 networkidle 状态。如果 networkidle 也不合适的话,可以采用 page.wait_for_selector 等待某个元素出现。不过对于 click 等操作会自动等待。
page.goto(url, referer="", timeout=30, wait_until="domcontentloaded|load|networkidle")
Playwright 会自动等待元素处于可操作的稳定状态。当然也可以用 page.wait_for_* 函数来手工等待:
page.wait_for_event("event", event_predict, timeout)
page.wait_for_function(js_function)
page.wait_for_load_state(state="domcontentloaded|load|networkidle", timeout)
page.wait_for_selector(selector, timeout)
page.wait_for_timeout(timeout) # 不推荐使用
对页面的操作方法主要有:
# selector 指的是 CSS 等表达式
page.click(selector)
page.fill(selector, value) # 在 input 中填充值
# 例子
page.click("#search")
获取页面中的数据的主要方法有:
page.url # url
page.title() # title
page.content() # 获取页面全文
page.inner_text(selector) # element.inner_text()
page.inner_html(selector)
page.text_content(selector)
page.get_attribute(selector, attr)
# eval_on_selector 用于获取 DOM 中的值
page.eval_on_selector(selector, js_expression)
# 比如:
search_value = page.eval_on_selector("#search", "el => el.value")
# evaluate 用于获取页面中 JS 中的数据,比如说可以读取 window 中的值
result = page.evaluate("([x, y]) => Promise.resolve(x * y)", [7, 8])
print(result) # prints "56"
选择器表达式
在上面的代码中,使用了 CSS 表达式(比如#button)来选取元素。实际上,Playwright 还支持 XPath 和自己定义的两种简单表达式,并且是自动识别的。
# 通过文本选择元素,这是 Playwright 自定义的一种表达式
page.click("text=login")
# 直接通过 id 选择
page.click("id=login")
# 通过 CSS 选择元素
page.click("#search")
# 除了常用的 CSS 表达式外,Playwright 还支持了几个新的伪类
# :has 表示包含某个元素的元素
page.click("article:has(div.prome)")
# :is 用来对自身做断言
page.click("button:is(:text('sign in'), :text('log in'))")
# :text 表示包含某个文本的元素
page.click("button:text('Sign in')") # 包含
page.click("button:text-is('Sign is')") # 严格匹配
page.click("button:text-matches('\w+')") # 正则
# 还可以根据方位匹配
page.click("button:right-of(#search)") # 右边
page.click("button:left-of(#search)") # 左边
page.click("button:above(#search)") # 上边
page.click("button:below(#search)") # 下边
page.click("button:near(#search)") # 50px 之内的元素
# 通过 XPath 选择
page.click("//button[@id='search'])")
# 所有 // 或者 .. 开头的表达式都会默认为 XPath 表达式
对于 CSS 表达式,还可以添加前缀css=来显式指定,比如说 css=.login 就相当于 .login.
除了上面介绍的四种表达式以外,Playwright 还支持使用 >> 组合表达式,也就是混合使用四种表达式。
page.click('css=nav >> text=Login')
复用 Cookies 等认证信息
在 Puppeteer 中,复用 Cookies 也是一个老大难问题了。这个是 Playwright 特别方便的一点,他可以直接导出 Cookies 和 LocalStorage, 然后在新的 Context 中使用。
# 保存状态
import json
storage = context.storage_state()
with open("state.json", "w") as f:
f.write(json.dumps(storage))
# 加载状态
with open("state.json") as f:
storage_state = json.loads(f.read())
context = browser.new_context(storage_state=storage_state)
监听事件
通过 page.on(event, fn) 可以来注册对应事件的处理函数:
def log_request(intercepted_request):
print("a request was made:", intercepted_request.url)
page.on("request", log_request)
# sometime later...
page.remove_listener("request", log_request)
其中比较重要的就是 request 和 response 两个事件
拦截更改网络请求
可以通过 page.on(“request”) 和 page.on(“response”) 来监听请求和响应事件。
from playwright.sync_api import sync_playwright as playwright
def run(pw):
browser = pw.webkit.launch()
page = browser.new_page()
# Subscribe to "request" and "response" events.
page.on("request", lambda request: print(">>", request.method, request.url))
page.on("response", lambda response: print("<<", response.status, response.url))
page.goto("https://example.com")
browser.close()
with playwright() as pw:
run(pw)
其中 request 和 response 的属性和方法,可以查阅文档:https://playwright.dev/python/docs/api/class-request
通过 context.route, 还可以伪造修改拦截请求等。比如说,拦截所有的图片请求以减少带宽占用:
context = browser.new_context()
page = context.new_page()
# route 的参数默认是通配符,也可以传递编译好的正则表达式对象
context.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
context.route(re.compile(r"(\.png$)|(\.jpg$)"), lambda route: route.abort())
page.goto("https://example.com")
browser.close()
其中 route 对象的相关属性和方法,可以查阅文档:https://playwright.dev/python/docs/api/class-route
灵活设置代理
Playwright 还可以很方便地设置代理。Puppeteer 在打开浏览器之后就无法在更改代理了,对于爬虫类应用非常不友好,而 Playwright 可以通过 Context 设置代理,这样就非常轻量,不用为了切换代理而重启浏览器。
context = browser.new_context(
proxy={"server": "http://example.com:3128", "bypass": ".example.com", "username": "", "password": ""}
)
示例:
from playwright.sync_api import sync_playwright
if __name__ == '__main__':
with sync_playwright() as p:
browser = p.chromium.launch(proxy={"server": "per-context"})
pxy = next_proxy()
context1 = browser.new_context(
proxy={"server": pxy["http"], "username": "", "password": ""})
page1 = context1.new_page()
page1.goto("https://myip.ipip.net/")
print(page1.content())
pxy2 = next_proxy()
context2 = browser.new_context(
proxy={"server": pxy2["http"], "username": "", "password": ""})
page2 = context2.new_page()
page2.goto("https://myip.ipip.net/")
print(page2.content())
browser.close()
杀手级功能:录制操作直接生成代码
Playwright 的命令行还内置了一个有趣的功能:可以通过录制你的点击操作,直接生成 Python 代码。
python -m playwright codegen http://example.com/
Playwright 还有很多命令行功能,比如生成截图等等,可以通过 python -m playwright -h
查看。