python版puppeteer——pyppeteer&selenium的加强版——seleniumwire

前言

书接上文,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"

解决方法:

  1. 代理提供商通常会提供白名单校验:将你的主机ip加入白名单就可以替换username,password校验了。
  2. 使用selenium的扩展库:selenium-wire

seleniumwire

SeleniumWire 是一个 Python 库,它是在 Selenium WebDriver 的基础上扩展而来的。它提供了对浏览器的网络请求和响应进行拦截、修改和监控的功能,使得你可以更灵活地控制浏览器与服务器之间的通信过程。

以下是 SeleniumWire 提供的一些主要功能和特点:

  1. 请求和响应拦截:SeleniumWire 允许你拦截浏览器发送的请求和接收的响应,从而可以在请求发送前、发送后、响应接收前、接收后等时机对请求和响应进行修改、记录或拦截。

  2. 修改请求和响应:你可以修改请求头、请求体、查询参数、响应头、响应体等内容,以便在自动化测试过程中模拟不同的场景和测试条件。

  3. 请求过滤:SeleniumWire 支持根据 URL、请求方法、请求头等条件对请求进行过滤,使得你可以只关注感兴趣的请求,忽略其他请求。

  4. 请求记录和导出:SeleniumWire 可以记录浏览器发送和接收的所有网络请求,并且支持将请求记录导出为 HAR 文件,方便分析和调试。

  5. 网络性能分析:通过拦截和记录网络请求,你可以对页面加载时间、资源加载情况等进行性能分析和优化。

  6. 无需修改浏览器配置: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_optionadd_argument 都是用于向 ChromeOptions 添加参数的方法,但它们在使用上有一些区别:

  1. add_experimental_option

    • 用途:用于向 ChromeOptions 添加一些实验性的选项或特性。
    • 适用对象:通常用于添加一些不常用的或特定于浏览器版本的选项。
    • 示例:在 Selenium 中,使用 add_experimental_option 可以添加一些实验性的 Chrome 参数,比如启用/禁用某些功能、设置浏览器性能优化选项等。
  2. 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 中模拟浏览器操作,以尽量避免被网站屏蔽。以下是一些常用参数和选项的介绍:

  1. User-Agent:通过 options.add_argument("--user-agent=Your_User_Agent") 可以设置自定义的 User-Agent 字段,以伪装成不同的浏览器或设备。

  2. 代理:通过 options.add_argument("--proxy-server=your_proxy_ip:your_proxy_port") 可以设置代理服务器,通过不同的代理 IP 来访问网站,避免频繁使用相同的 IP 地址。

  3. 禁用图片加载:通过 options.add_argument("--blink-settings=imagesEnabled=false") 可以禁用图片加载,减少页面请求,提高访问速度。

  4. 禁用 JavaScript:通过 options.add_argument("--disable-javascript") 可以禁用 JavaScript 的执行,有些网站的屏蔽机制可能依赖于 JavaScript 的操作。

  5. 无头模式:通过 options.add_argument("--headless") 可以启用 Chrome 的无头模式,即在后台运行 Chrome,无界面显示,模拟真实浏览器行为。

  6. 禁用 GPU 加速:通过 options.add_argument("--disable-gpu") 可以禁用 GPU 加速,有些网站可能会检测 GPU 信息,通过禁用 GPU 可以降低被检测的概率。

  7. 禁用扩展插件:通过 options.add_argument("--disable-extensions") 可以禁用 Chrome 的扩展插件,减少特定插件对网站访问的影响。

  8. 禁用自动化提示:通过 options.add_experimental_option("excludeSwitches", ["enable-automation"]) 可以禁用自动化提示,模拟真实用户行为。

  9. options.add_argument(‘–ignore-certificate-errors’),options.add_argument(‘–ignore-ssl-errors’)忽略 SSL 证书错误和 SSL 错误,用于处理在使用自签名证书或过期证书的网站时避免弹出警告。

  10. 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())

参数配置

属性参数描述
executablePathstrchrome.exe运行的路径
ignorehttpserrrorsbool忽略https错误,默认false
headlessboolTrue 开始无头浏览器 False关闭无头
dumpiobool设置True 解决浏览器多开卡死 (没有测试过)
下面是args的参数设置下面是args的参数设置下面是args的参数设置
–disable-infobars-关闭自动化提示框
–window-size=1920,1080str设置浏览器大小吗,1920是宽,1080是宽
–log-level=30str日志保存等级, 建议设置越好越好,要不然生成的日志占用的空间会很大 30为warning级别
–start-maximized-窗口最大化模式
–proxy-server=http://localhost:1080str设置代理
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一样会有浏览器特征,所以需要修改,隐藏特征防止被识别。

主要有下面两点:

  1. 去除浏览器自动化参数 --enable-automation
  2. 去除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函数。

更多文档&博客

  1. https://blog.csdn.net/qq_27900321/article/details/129402456
  2. https://github.com/pyppeteer/pyppeteer

Playwright

迁移到 Playwright的原因:

  1. Pyppeteer 是第三方的,好久没有更新了,bug 也不少
  2. Puppeteer 的原生 JS 版本本身就很不稳定,亲测 Playwright 更稳定一些
  3. Playwright 原生支持 Python,而且支持 Google Chrome/Firefox/Safari 三大浏览器

Playwright 是微软出品的浏览器自动化工具,代码质量应该是有足够保证的。而且它还官方支持同步版的 Python API, 同时支持三大浏览器。

安装

  1. pip install playwright
  2. 安装浏览器: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) 后页面加载过程:

  1. 设定 url
  2. 通过网络加载解析页面
  3. 触发 page.on(“domcontentloaded”) 事件
  4. 执行页面的 js 脚本,加载静态资源
  5. 触发 page.on(“laod”) 事件
  6. 页面执行动态加载的脚本
  7. 当 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查看。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Generalzy

文章对您有帮助,倍感荣幸

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值