python模块详解 | pyppeteer
Selenium & Pyppeteer
selenium的功能十分强大,但很多时候我们发现他也有其自身的缺点,比如:
- 需要安装好相关的浏览器,Chrome或Firefox等
- 需要下载并配置对应版本的驱动
因此如果要做大规模部署的话,环境配置方面其实是一个头疼的事情。其实有另一个类似的替代品,叫作 Pyppeteer。
Pyppeteer 介绍
Puppeteer 是 Google 基于 Node.js 开发的一个工具,有了它我们可以通过 JavaScript 来控制 Chrome 浏览器的一些操作,当然也可以用作网络爬虫上,其 API 极其完善,功能非常强大,Selenium 当然同样可以做到。
而 Pyppeteer 又是什么呢?它实际上是 Puppeteer 的 Python 版本的实现,但它不是 Google 开发的,是一位来自于日本的工程师依据 Puppeteer 的一些功能开发出来的非官方版本。
在 Pyppetter 中,实际上它背后也是有一个类似 Chrome 浏览器的 Chromium 浏览器在执行一些动作进行网页渲染,首先说下 Chrome 浏览器和 Chromium 浏览器的渊源。
Chromium 是谷歌为了研发 Chrome 而启动的项目,是完全开源的。二者基于相同的源代码构建,Chrome 所有的新功能都会先在 Chromium 上实现,待验证稳定后才会移植,因此 Chromium 的版本更新频率更高,也会包含很多新的功能,但作为一款独立的浏览器,Chromium 的用户群体要小众得多。两款浏览器“同根同源”,它们有着同样的 Logo,但配色不同,Chrome 由蓝红绿黄四种颜色组成,而 Chromium 由不同深度的蓝色构成。
总的来说,两款浏览器的内核是一样的,实现方式也是一样的,可以认为是开发版和正式版的区别,功能上基本是没有太大区别的。
Pyppeteer 就是依赖于 Chromium 这个浏览器来运行的。那么有了 Pyppeteer 之后,我们就可以免去那些烦琐的环境配置等问题。如果第一次运行的时候,Chromium 浏览器没有安装,那么程序会帮我们自动安装和配置,就免去了烦琐的环境配置等工作。另外 Pyppeteer 是基于 Python 的新特性 async 实现的,所以它的一些执行也支持异步操作,效率相对于 Selenium 来说也提高了。
由于 Pyppeteer 采用了 Python 的 async 机制,所以其运行要求的 Python 版本为 3.5 及以上。
pip install pyppeteer
快速上手
import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq
async def main():
browser = await launch()
page = await browser.newPage()
await page.goto('https://dynamic2.scrape.center/')
await page.waitForSelector('.item .name')
doc = pq(await page.content())
names = [item.text() for item in doc('.item .name').items()]
print('Names:', names)
await browser.close()
asyncio.get_event_loop().run_until_complete(main())
官方文档 - https://miyakogi.github.io/pyppeteer/reference.html
launch
使用 Pyppeteer 的第一步便是启动浏览器,在puppeteer中需要调用「launch」方法去实现,launch方法的API:
pyppeteer.launcher.launch(options: dict = None, **kwargs) → pyppeteer.browser.Browser
- 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):事件循环对象。
观察launch
方法源码发现他是被async
修饰的,是一个协程对象,因此使用时需要加上await
关键字。
无头模式
headless
参数默认是设为True,也就是不显示浏览器界面,改为False便可以看到界面了,一般我们在开发阶段需要调试的时候会将其设置为False,在生产环境下就可以设置为True。
browser = await launch(headless=False)
禁用"Chrome 正受到自动测试软件的控制"提示条
browser = await launch(args=['--disable-infobars'])
浏览器大小设置
在初始化时加入 defaultViewport字典值即可:
browser = await pyppeteer.launch(
{'headless': False,
'userDataDir': UserDataDir,
'defaultViewport': {'width': 1800, 'height': 1000}
}
)
or
browser = await pyppeteer.launch(
headless=False,
args=['--disable-infobars', f'--window-size={width},{height}']
)
附上获取屏幕大小的代码:
def screen_size():
"""使用tkinter获取屏幕大小"""
import tkinter
tk = tkinter.Tk()
width = tk.winfo_screenwidth()
height = tk.winfo_screenheight()
tk.quit()
return width, height
用户数据持久化 🌹
每次我们打开 Pyppeteer 的时候都是一个新的空白的浏览器。而且如果遇到了需要登录的网页之后,如果我们这次登录上了,下一次再启动又是空白了,又得登录一次,这的确是一个问题。
比如以淘宝举例,平时我们逛淘宝的时候,在很多情况下关闭了浏览器再打开,淘宝依然还是登录状态。这是因为淘宝的一些关键 Cookies 已经保存到本地了,下次登录的时候可以直接读取并保持登录状态。
那么这些信息保存在哪里了呢?其实就是保存在用户目录下了,里面不仅包含了浏览器的基本配置信息,还有一些 Cache、Cookies 等各种信息都在里面,如果我们能在浏览器启动的时候读取这些信息,那么启动的时候就可以恢复一些历史记录甚至一些登录状态信息了。
这也就解决了一个问题:很多时候你在每次启动 Selenium 或 Pyppeteer 的时候总是一个全新的浏览器,那这究其原因就是没有设置用户目录,如果设置了它,每次打开就不再是一个全新的浏览器了,它可以恢复之前的历史记录,也可以恢复很多网站的登录信息。
那么这个怎么来做呢?很简单,在启动的时候设置 userDataDir 就好了,示例如下:
import asyncio
from pyppeteer import launch
async def main():
browser = await launch(headless=False, userDataDir='./userdata', args=['--disable-infobars'])
page = await browser.newPage()
await page.goto('https://www.taobao.com')
await asyncio.sleep(100)
asyncio.get_event_loop().run_until_complete(main())
具体的介绍可以看官方的一些说明,如: https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md,这里面介绍了 userdatadir 的相关内容。
再次运行上面的代码,这时候可以发现现在就已经是登录状态了,不需要再次登录了,这样就成功跳过了登录的流程。当然可能时间太久了,Cookies 都过期了,那还是需要登录的。
browser
launch 方法,其返回的就是一个 Browser 对象,即浏览器对象,Browser 类的定义:
class pyppeteer.browser.Browser(connection: pyppeteer.connection.Connection, contextIds: List[str], ignoreHTTPSErrors: bool, setDefaultViewport: bool, process: Optional[subprocess.Popen] = None, closeCallback: Callable[[], Awaitable[None]] = None, **kwargs)
开启无痕模式
browser = await launch(headless=False)
context = await browser.createIncognitoBrowserContext()
page = await context.newPage()
关闭
await browser.close()
page
页面大小设置
窗口大小可以通过 Page 的 setViewport 方法设置:
await page.setViewport({'width':width,'height':height})
防止检测
Pyppeteer 的 Page 对象有一个方法叫作 evaluateOnNewDocument,意思就是在每次加载网页的时候执行某个语句,所以这里我们可以执行一下将 WebDriver 隐藏的命令,改写如下:
import asyncio
from pyppeteer import launch
async def main():
browser = await launch(headless=False, args=['--disable-infobars'])
page = await browser.newPage()
await page.evaluateOnNewDocument('Object.defineProperty(navigator, "webdriver", {get: () => undefined})')
await page.goto('https://antispider1.scrape.center/')
await asyncio.sleep(100)
asyncio.get_event_loop().run_until_complete(main())
选项卡操作
page = browser.newPage()
- 新建page.pages()
- 获取所有的页面page.bringToFront()
- 切换到该页面的对应的选项卡page.goBack()
- 后退page.goForward()
- 前进page.reload()
- 刷新page.pdf()
- 保存PDFpage.screenshot()
- 截图path
(str):保存图像的文件路径。屏幕截图类型将从文件扩展名中推断出来。type
(str):指定屏幕截图类型,可以是jpeg
或png
。默认为png
。quality
(int):图像的质量,在 0-100 之间。不适用于png
图像。fullPage
(bool):如果为 true,请截取完整的可滚动页面。默认为False
。clip
(字典):指定页面剪切区域的对象。此选项应包含以下字段:x
(int):剪辑区域左上角的 x 坐标。y
(int):剪辑区域左上角的 y 坐标。width
(int):剪切区域的宽度。height
(int):剪切区域的高度。
omitBackground
(bool):隐藏默认的白色背景并允许捕获具有透明度的屏幕截图。encoding
(str):图像的编码可以是'base64'
或'binary'
。默认为'binary'
。
page.setViewport()
- 设置页面大小width
(int):以像素为单位的页面宽度。height
(int):以像素为单位的页面高度。deviceScaleFactor
(float):默认为 1.0。isMobile
(bool):默认为False
。hasTouch
(bool):默认为False
。isLandscape
(bool):默认为False
。
page.setContent()
- 设置该页面HTMLpage.setCookies()
- 设置cookiespage.setUserAgent()
- 设置User Agentpage.setExtraHTTPHeaders(headers={})
- 设置headerspage.evaluate()
- 执行js语句page.close()
- 关闭
选项卡信息
page.content()
- 获取源代码page.cookies()
- 获取cookiespage.title()
- 获取页面标签
选择器
page.waitForSelector('.item .name')
- 等待元素出现page.J()/page.querySelector()
- css选择器,选取第一个节点page.JJ()/page.querySelectorAll()
- css选择器,选取所有节点page.Jeval()/page.querySelectorEval()
- 功能多一点,可以选出网页文本或者属性指page.Jx()/page.xpath()
- xpath选择器page.addScriptTag()
- 将脚本标记添加到此页面, 返回 ElementHandle 其中一个 url,path 或 content 选择是必要的url
(字符串):要添加的脚本的 URL。path
(字符串):要添加的本地 JavaScript 文件的路径。content
(字符串):要添加的 JavaScript 字符串。type
(字符串):脚本类型。使用module
以加载一个 JavaScript ES6 模块。
page.addStyleTag()
- 将样式或链接标记添加到此页面, 返回 ElementHandle 其中一个url
,path
或content
选择是必要的。url
(字符串):要添加的链接标记的 URL。path
(字符串):要添加的本地 CSS 文件的路径。content
(字符串):要添加的 CSS 字符串。
点击
await asyncio.gather(
page.waitForNavigation(waitOptions),
page.click(
selector='',
clickOptions={
'button':'left', # left or right
'clickCount':1, # 1 or 2
'delay':3000 # 毫秒
}
)
)
selector
- css选择器语句clickOptions
- 选项参数button
- 鼠标按钮,left、middle、rightclickCount
- 点击次数,1 or 2delay
- 延迟点击,单位毫秒
输入文本
page.type()
- 第一个参数为 css 选择器,第二个为文本内容
page.type('.item .username','chenxuefan')
延迟等待
等待某些符合条件的节点加载出来再返回
-
page.waitFor()
- 通用的等待方法 -
page.waitForFunction()
- 等待某个 JavaScript 方法执行完毕或返回结果 -
page.waitForNavigation()
- 等待页面跳转,如果没加载出来就会报错navigationPromise = async.ensure_future(page.waitForNavigation()) await page.click('a.my-link') # indirectly cause a navigation await navigationPromise # wait until navigation finishes # or await asyncio.wait([ page.click('a.my-link'), page.waitForNavigation(), ])
-
page.waitForRequest()
- 等待某个特定的请求被发出 -
page.waitForResponse()
- 等待某个特定的请求收到了回应 -
page.waitForSelector()
- 等待符合选择器的节点加载出来 -
page.waitForPath()
- 等待符合 XPath 的节点加载出来
更多参考