Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

selenium不行的工作,pyppeteer上

2022/05/07 爬虫 环境配置 Python
Word count: 2,036 | Reading time: 9min

验证码问题

该网站对频率检测十分敏感,很快就会出现"按住"验证码,模拟按住倒不难,但是按完以后有时并不能解决验证…

但是意外发现,在“无痕模式”下进行重新登录后,就可以解决验证…但是通过requests进行模拟登录后cookies还是无效。

解决方案

既然模拟不行,那么就手动,因此想用selenium来模拟点击登陆。但是因为俄罗斯的问题,无法登陆。

被封

于是只好更换selenium为pyppeteer。之前就听说了pyppeteer是可以代替selenium的模拟神器,相比Selenium 不太方便的地方:比如速度太慢、对版本配置要求严苛,最麻烦是经常要更新对应的驱动,还有些网页是可以检测到是否使用了Selenium ,pyppeteer都有提升,这次也借机会学习一下。

安装:pip install pyppeteer, 需要Python3.6以上(因为用了async)

pyppeteer

最简单demo:打开网页,并截图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import asyncio
from pyppeteer import launch

async def main():
# 启动浏览器
browser = await launch(headless=True)
# 打开并跳转页面
page = await browser.newPage()
await page.goto('https://example.com')
# 截图
await page.screenshot({'path': 'example.png'})
await browser.close()

asyncio.get_event_loop().run_until_complete(main())

执行JS,相当于requests-html中的render

1
2
3
4
5
6
7
8
9
10
11
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()

元素选择器

Page.querySelector()/Page.querySelectorAll()/Page.xpath()缩写为Page.J(), Page.JJ(), and Page.Jx().

xpath进阶:

  • starts-with 顾名思义,匹配一个属性开始位置的关键字
  • contains 匹配一个属性值中包含的字符串
  • text() 匹配的是显示文本信息: //a[contains(text(), "百度搜索")]

xpath的contains用法

元素操作:点击、输入

  • pyppeteer.input.Mouse
    • click(x: float*, y: float, options: dict = None*, ***kwargs)
      • button (str): left, right, or middle, defaults to left
        • 右键点击:await page.click('a#cccccc',{"button": "right"})
      • clickCount (int): defaults to 1.
      • delay (int|float): Time to wait between mousedown and mouseup in milliseconds. Defaults to 0.
    • down(options: dict = None, *kwargs) `
      • button (str): left, right, or middle, defaults to left.
      • clickCount (int): defaults to 1.
    • move(x: float, y: float, options: dict = None, **kwargs*)
    • p(options: dict = None, **kwargs*)
1
2
3
4
5
6
7
8
9
10
11
# 使用xpath
await page.waitForXPath('//input[contains(@id, "mat-input")]')
await asyncio.sleep(10)
inputs = await page.xpath('//input[contains(@id, "mat-input")]')
print(len(inputs))
await inputs[1].type('janetgao@indiana.edu')
await inputs[2].type('KAProject2021')
# 使用selector
await page.waitForSelector('#mat-input-1')
await page.type('#mat-input-1', 'janetgao@indiana.edu')
await page.type('#mat-input-2', 'KAProject2021')

launch

  • headless:无界面模式

  • devtools :启用开发者助手

  • userDataDir='./userdata': 记录些用户信息,比如cookies

  • dumpio=True 防止浏览器开多个页面而卡死

  • executablePath: 指定自定义的chrome路径

  • 参数args设置,可以类比selenium

    • --start-maximized: 页面全屏

    • --window-size={width},{height}:设置窗口大小,区别于页面的page.setViewport({'width': 1366, 'height': 768})

    • --disable-infobars:不显示信息栏 比如 chrome正在受到自动测试软件的控制

    • --incognito:百度搜到的chrome.exe无痕命令(selenium是如此,pyppeteer不推荐)

    • --user-agent: 比如'--user-agent=Mozilla/5.0'

    • --proxy-server={proxy}: 浏览器代理 配合某些中间人代理使用

    • --no-sandbox, # 取消沙盒模式 沙盒模式下权限太小

    • '--log-level=3'

    • '--load-extension={}'.format(chrome_extension)'--disable-extensions-except={}'.format(chrome_extension) # 加载插件

      1
      2
      3
      4
      5
      chrome_extension_path = "插件所在目录"
      args = [
      "--load-extension={}".format(chrome_extension_path),
      "--disable-extensions-except={}".format(chrome_extension_path),
      ]

      pyppeteer控制Chromium在隐身模式下启用插件

  • more: launch参数说明

执行JS:

  • page.evaluate('window.scrollBy(100, document.body.scrollHeight)')

  • await page.evaluate('alert("在浏览器执行js脚本!")')

    1
    2
    3
    # 相当于拿到了dom对象
    element = await page.J('#ul>a[name="tj_trtieba"]')
    print(await page.evaluate('el => el.innerHTML', element))
  • page.evaluateOnNewDocument(pageFunction[, …args]), 指定的函数在所属的页面被创建并且所属页面的任意script执行之前被调用。常用于修改页面JS环境。

    1
    2
    3
    4
    page = await browser.newPage()
    await page.setViewPort({'width': 1366, 'height': 768})
    await page.evaluateOnNewDocument('''() => {
    Object.defineProperty(navigator, 'webdriver', {get: () => false });

    附—ElementHandle元素操作:

    1. 获取元素边界框坐标:boundingBox(),返回元素的边界框(相对于主框架)=> x 坐标、 y 坐标、width、height
    2. 元素是否可见:isIntersectingViewport()
    3. 上传文件:uploadFile(*filpaths)
    4. ElementHandle 类 转 Frame类:contentFrame(),如果句柄未引用iframe,则返回None。
    5. 聚焦该元素:focus()
    6. 与鼠标相关:hover () ,将鼠标悬停到元素上面
    7. 与键盘相关:press (key[, options]),按键,key 表示按键的名称,option可配置:
      • text (string) - 如果指定,则使用此文本生成输入事件
      • delay (number) - keydown 和 keyup 之间等待的时间。默认是 0
      • page.keyboard.down('Shift'):按下shift

    more: 页面元素操作

安全性:

  1. 在使用 Pyppeteer 仍然会遇到无头浏览器检测,这里安利一个第三方库「pyppeteer-stealth」,「puppeteer-extra-plugin-stealth」引用Github上的说明「Applies various evasion techniques to make detection of headless puppeteer harder.」「A plugin for puppeteer-extra to prevent detection.」(Github地址:https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth )。可见,「pyppeteer-stealth」也是用于防止机器人检测的。用法也很简单,给page加上stealth
1
2
3
4
5
6
7
8
9
10
11
12
13
import asyncio
from pyppeteer import launch
from pyppeteer_stealth import stealth

async def main():
browser = await launch(headless=True)
page = await browser.newPage()

await stealth(page) # <-- Here

await page.goto("https://bot.sannysoft.com/")
await browser.close()
asyncio.get_event_loop().run_until_complete(main())

这样就可以省去隐藏WebDriver等操作,可谓省时省力省心。

  1. await page.evaluateOnNewDocument('Object.defineProperty(navigator,"webdriver",{get:()=>undefined})')
  2. stealth.min.js最完美方案!模拟浏览器如何正确隐藏特征

无痕隐身模式访问

1
2
3
4
5
6
7
8
9
async def start_browser():
browser = await launch(**{"headless": False})
# 创建隐身上下文
browser_context = await browser.createIncognitoBrowserContext()
page = await browser_context.newPage()

await page.goto("http://www.baidu.com")
await browser.close()
return

较全的API说明:pyppeteer的基本使用页面信息、设置

问题:按钮不生效

网页输入账号和密码之前,按钮状态为disable=true,两个都输入后才能按下,而使用pyppeteer时,发现无论怎么尝试都不能使得其disable变化(用开发者助手修改后,无法生成正确的提交请求)。突然巧合之中,想到了是不是元素没加载成功,于是确实在开发者助手的console中看到有很多不支持的,比如angular、bootstrap……

于是百度问题,看看是不是什么设置没开,但都没找到。后来想到升级下pyppeteer版本会不会好一点,发现自己的版本为1.0.2,已经是最新的了(https://github.com/miyakogi/pyppeteer/issues/306)。由于不通过程序直接通过浏览器进入程序也出现了无法点击的问题。再加上下面有人提出chromium有点难下载,并通过源码找道了默认的配置和设置,于是经过一阵的折腾过后,我发现**“按钮无法点击”的问题就在于chromium版本过低**。

1
2
3
4
5
6
7
8
9
import pyppeteer.chromium_downloader
print('默认版本是:{}'.format(pyppeteer.__chromium_revision__))
print('可执行文件默认路径:{}'.format(pyppeteer.chromium_downloader.chromiumExecutable.get('win64')))
print('win64平台下载链接为:{}'.format(pyppeteer.chromium_downloader.downloadURLs.get('win64')))
"""
默认版本是:588429
可执行文件默认路径:C:\Users\mrli\AppData\Local\pyppeteer\pyppeteer\local-chromium\588429\chrome-win32\chrome.exe
win64平台下载链接为:https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/588429/chrome-win32.zip
"""

登录这个storage的网站可以看到,上面所记录的chromium全都是2013年的版本,必然版本落后了。于是下载最新的chromium覆盖C:\Users\mrli\AppData\Local\pyppeteer\pyppeteer\local-chromium\588429\chrome-win32目录后再运行,按钮不能点击的问题就消失了!~😄

chromium下载地址:https://registry.npmmirror.com/binary.html?path=chromium-browser-snapshots/Win_x64/,注:chromium跟chromedriver不一样,他跟自己已经安装的chrome并没有关联。chromium自带浏览器引擎核心,所以直接下载最新的版本即可,比如我直接下载的就是999303。

启发于:使用pyppeteer 下载chromium 报错 或速度慢pyppeteer使用时常见的bug及解决办法【转载】

Author: Mrli

Link: https://nymrli.top/2022/05/05/selenium不行的工作-pyppeteer上/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
toy reimplementation of an event loop in Python[翻译]
NextPost >
mitmproxy拦截代理——PC版HttpCanary
CATALOG
  1. 1. 验证码问题
    1. 1.1. pyppeteer
    2. 1.2. 问题:按钮不生效