puppeteer 是基于Node.js 开发的一个工具, 有了它,我们可以利用 JavaScript 控制 Chrome 浏览器的一些操作。当然, puppeteer 也可以用于网络爬虫,其 API 及其完善,功能非常强大。
Pyppeteer 其实是 puppeteer 的 python 实现, 但不是 google 开发的,是由以为来自日本的工程师根据 puppeteer 的一些功能开发出来的非官方版本。
pyppeteer 的背后实际上有一个类似于 Chrome 的浏览器---Chromium, 它执行一些动作,从而进行网页渲染,首先,介绍一下 Chromium 浏览器和 Chrome 浏览器的渊源。
Chromium是Google 为了研发 Chrome启动的项目, 是完全开源的。 二者基于相同的源代码而构建, CHrome 的所有新功能都会先在 Chromium 上实现,待验证稳定后才移植在 Chrome 上, 因此Chromium 的版本更新频率更高, 同时也包含很多新功能。 但作为一款独立的浏览器,Chromium的用户群体要小的多。两款浏览器同根同源,有着同样的 logo ,只是配色不同
总得说,两款浏览器内核一样, 实现方式一样,可以看做开发板和正式版,功能上没有太大区别
pyppeteer 就是依赖 Chromium 浏览器运行的,如果第一次运行 pyppeteer 的时候,没有 安装Chromium 浏览器,程序就会帮我们自动安装和配置好, 免去了繁琐的环境配置等工作。 另外, pyppeteer 是基于 python 新特性 async 实现的,所以它的一些操作执行也支持异步
安装
pip install pyppeteer
快速上手
一个带有 Ajax 接口 还有加密参数的网站,没办法用 requests 直接爬取,但可以用 selenium 爬取,这里使用 pyppeteer 爬取
import asyncio from pyppeteer import launch from pyquery import PyQuery as pqasync def main():browser = await launch()page = await browser.newPage()await page.goto('https://spa2.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())
如果是第一次运行可能会报错:
Starting Chromium download
原因是: 下载 Chromium 慢或者下载失败
解决方法:
1. 进入网址
CNPM Binaries Mirror
选择对应自己系统的文件
选一个最近或者自己需要的日期版本
右键复制链接,不要下载
然后找到自己的python环境中的site-packages中pyppeteer中的chromium_downloader.py文件并打开
chromium_downloader.py 的位置,一般在报错的信息里面可以找到
我的位置:C:\Users\86151\AppData\Roaming\Python\Python311\site-packages\pyppeteer
右键选择打开方式,记事本或者别的什么。
将刚才复制的链接粘贴上去,保存,重新运行上面的代码 OK
Names: ['霸王别姬 - Farewell My Concubine', '这个杀手不太冷 - Léon', '肖申克的救赎 - The Shawshank Redemption', '泰坦尼克号 - Titanic', '罗马假日 - Roman Holiday', '唐伯虎点秋香 - Flirting Scholar', '乱世佳人 - Gone with the Wind', '喜剧之王 - The King of Comedy', '楚门的世界 - The Truman Show', '狮子王 - The Lion King']
这里我们访问了测试网站, 然后等待 .item .name 节点加载出来,随后通过 pyquery 从页面远吗中提取电影名称并输出,最后关闭 Pyppeteer 。 如上面所示,我们成功模拟了页面的加载行为,然后成功提取了上面所有的电影名称
逐行分析:
调用 launch 方法新建了一个 Browser 对选哪个, 并赋值给 browser 变量,这一步相当于启动了浏览器,前面如果报错,就会指向这里
调用了 browser 的 newPage 方法, 新建了一个Page 对象, 并赋值给 page 变量。相当于在浏览器中新建了一个选项卡,这是后虽然启动了一个选项卡,但是还未访问任何页面,浏览器依然是空白的
调用了 page 的 goto 方法相当于在浏览器地址栏中输入了 goto 方法参数中的 URL , 之后浏览器加载对应的页面
调用了 page 的 waitForSelector 方法,传入选择器,页面就会等待选择器对应节点的信息加载出来, 加载出来后, 就立即返回, 否则继续等待直到超时。如果顺利的话,页面会成功加载出来
页面加载完成之后,调用 content 方法,可以获取当前浏览器页面的源代码,这就是 JavaScript 渲染后的结果
进一步, 用 pyquery 解析并提取页面上的电影名称,就得到最终结果了
其中的 async await 是关于异步的方法
再看一个例子:
import asyncio from pyppeteer import launchwidth, height = 1366, 768async def main():browser = await launch()page = await browser.newPage()await page.setViewport({'width': width, 'height': height})await page.goto('https://spa2.scrape.center/')await page.waitForSelector('.item .name')await asyncio.sleep(2)await page.screenshot({'path': 'screen.png'})dimensions = await page.evaluate("""() => {return {width: document.documentElement.clientWidth,height: document.documentElement.clientHeight,deviceScaleFactor: window.devicePixelRatio,}}""")print(dimensions)await browser.close() asyncio.get_event_loop().run_until_complete(main())
{'width': 1366, 'height': 768, 'deviceScaleFactor': 1}
这里我们用到了几个新方法, 设置了页面窗口大小,保存了页面截图, 执行 JavaScript 语句并返回了对应的数据。 其中,再 screenshot 方法里, 通过path 参数传入页面截图的保存路径,另外还可指定截图的保存格式 type, 清晰度 quality , 是否全屏 fullPage 和裁切 clip 等参数
截图如下
可以看到,返回结果是 JavaScript 渲染之后的页面,和我们在浏览器中看到的一样。
我们还调用了 evaluate 方法执行了一些 JavaScript 语句, 这里给 JavaScript 传入了一个函数, 使用 return 方法返回了页面的 宽 ,高 像素大小比率这三个值, 最后得到的是一个 JSON 格式的对象, 内容如下
{'width': 1366, 'height': 768, 'deviceScaleFactor': 1}
官方文档: https://pyppeteer.github.io/pyppeteer/reference.html
说明: 上面代码运行的时候可能报错:
出现这个错误,只要把这里面的
return {width: document.documentElement.clientWidth,height: document.documentElement.clientHeight,deviceScaleFactor: window.devicePixelRatio, }
deviceScaleFactor: window.devicePixelRatio,
删掉就好了,具体原因暂时不知道,反正时好时坏的
launch 方法
launch 方法相当于在桌面上点击浏览器图标,打开浏览器
文档链接: https://pyppeteer.github.io/pyppeteer/reference.html#launcher
方法定义:
pyppeteer.launcher.launch(options:dict=None, **kwargs) -> pyppeteer.browser.Browser
可以看出,launch 方法处于 launcher 模块中, 在声明中没有 特别指定的参数,返回值是 browser 模块中的 Browser 对象, 另外源码可以发现, 这是一个 async 修饰的对象,所以在调用的时候需要使用 await
接下来看看 launch 的参数
ignoreHTTPSErrors (bool) :是否忽略 HTTPS 的错误,默认 False
headless (bool) :是否启用无头模式, 即无界面模式,如果 devtools 参数是 True , 该参数就会被设置为 False , 否则为 True , 即默认开启无界面模式
executablePath (str) : 可执行文件的路径。 指定该参数后就不需要使用默认的 Chromium 浏览器了,可以指定已有的 Chrome 或者 Chromiun
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) : 时间循环对象
下面来稍微使用一下这些参数:
无头模式
一般在生产环境中都是默认为 True 这里来设置为 False 试一下
import asyncio from pyppeteer import launchasync def main():await launch(headless=False)await asyncio.sleep(100)asyncio.get_event_loop().run_until_complete(main())
一个空的浏览器,可以自己看一下版本和相关信息
调试模式
在写爬虫的时候经常需要分析网页结构和网络请求,所以开启调试模式还是很有必要的
直接将 devtools 设置为 True 即可
import asyncio from pyppeteer import launchasync def main():browser = await launch(devtools=True)page = await browser.newPage()await page.goto('https://baidu.com')await asyncio.sleep(30)asyncio.run(main())
当我们开启调试模式时, 就会默认关闭无头模式
禁用提示条
每次打开浏览器都能看到
现在来关闭它,需要设置 args 参数
import asyncio from pyppeteer import launchasync def main():browser = await launch(headless=False, args=['--disable-infobars'])page = await browser.newPage()await page.goto('https://antispider1.scrape.center/')await asyncio.sleep(30)asyncio.run(main())
并没有用,提示条还在
防止检测
查看被检测到的状态
import asyncio from pyppeteer import launchasync def main():browser = await launch(headless=False, args=['--disable-infobars'])page = await browser.newPage()await page.goto('https://antispider1.scrape.center/')await asyncio.sleep(30)asyncio.run(main())
用上面的代码访问,发现并不能展示内容
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(30)asyncio.run(main())
页面加载出来了
页面大小设置
根据情况使用, 调用 page 对象的 setViewporet 方法即可
import asyncio from pyppeteer import launchwidth,height = 1366, 768async def main():browser = await launch(headless=False, args=['--disable-infobars', f'--window-size={width},{height}'])page = await browser.newPage()await page.setViewport({'width': width, 'height': height})await page.evaluateOnNewDocument('Object.defineProperty(navigator, "webdriver", {get: () => undefined})')await page.goto('https://antispider1.scrape.center/')await asyncio.sleep(30)asyncio.run(main())
可以看到窗口的大小改变了
用户数据持久化
问题: 每次打开浏览器的时候都是一个新的浏览器,并没有保存任何之前的信息,例如用户登陆。在淘宝我们登陆之后,就算这次退出,下次打开依旧保持登录状态,这就是因为,一些用户的基本信息在本地保存着,下次打开时,读取了本地信息,所以能保持登录状态。
设置用户目录,在启动浏览器的时候 设置 userDataDir 属性
import asyncio from pyppeteer import launchasync def main():browser = await launch(headless=False, userDataDir='./userdata', args=['--no-sandbox'])page = await browser.newPage()await page.evaluateOnNewDocument('Object.defineProperty(navigator, "webdriver", {get: () => undefined})')await page.goto('https://www.taobao.com')await asyncio.sleep(20)asyncio.run(main())
把 await asyncio.sleep(20) 时间稍微设置长一点,运行之后登录, 然后等结束后,再次运行,就已经处于登陆状态。
问题: 教程里面说会生成一个 userDataDir的文件夹,我这里并没有
如果有,那么关于这个文件夹的介绍可以看官方说明:
https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md
外网,可能打不开
Browser
上面我们了解了 launch 方法,它的返回值是一个 Browser 对选哪个,即浏览器对象, 我们通常将其赋值给 browser 变量(其实就是 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, **kargs)
从这里可以看出, Browser 有很多参数,大多数情况下直接使用 launch 方法或 connect 方法构造浏览器对象即可
browser 作为 Browser 类的实例, 自然有很多操作浏览器的方法,下面我们来看一些常用的
开启无痕模式
可以通过 createIncognitoBrowserContext 方法开启无痕模式
import asyncio from pyppeteer import launchwidth,height = 1200, 768async def main():browser = await launch(headless=False,args=['--disable-infobars', f'window-size={width}, {height}'])context = await browser.createIncognitoBrowserContext()page = await context.newPage()await page.setViewport({'width': width, 'height': height})await page.goto('https://www.baidu.com')await asyncio.sleep(100)asyncio.run(main())
这里调用的就是 browser 的 createIncognitoBrowserContext 方法, 返回值是一个 context 对象,利用 context 对象可以新建选项卡
关闭
使用之后调用 close 方法 关闭浏览器,减少开销
import asyncio from pyppeteer import launchwidth,height = 1200, 768async def main():browser = await launch()page = await browser.newPage()await page.goto('https://spa2.scrape.center/')await asyncio.sleep(10)await browser.close()asyncio.run(main())
page
page 即页面, 对应一个选项卡, 一个网页
选择器
page 对象内置了很多用于选取节点的选择器方法, 例如 J (大写的 j ), 给它传入一个选择器,就能返回匹配到的第一个个节点, 等价于 querySelector 方法, 有 JJ 方法, 给它传入选择器,就会返回所有符合选择器条件的节点列表, 等价于 querySelectorAll 方法
import asyncio from pyppeteer import launchasync def main():browser = await launch()page = await browser.newPage()await page.goto('https://spa2.scrape.center/')await page.waitForSelector('.item .name')j_result1 = await page.J('.item .name')j_result2 = await page.querySelector('.item .name')jj_result1 = await page.JJ('.item .name')jj_result2 = await page.querySelectorAll('.item .name')print(j_result1)print(j_result2)print(jj_result1)print(jj_result2)await browser.close()asyncio.run((main()))
<pyppeteer.element_handle.ElementHandle object at 0x000001D668C72A10>
<pyppeteer.element_handle.ElementHandle object at 0x000001D668C779D0>
[<pyppeteer.element_handle.ElementHandle object at 0x000001D668C76850>,。。。。。
<pyppeteer.element_handle.ElementHandle object at 0x000001D668C7C350>, <pyppeteer.element_handle.ElementHandle object at 0x000001D668C7C390>]
[<pyppeteer.element_handle.ElementHandle object at 0x000001D668C76610>, <pyppeteer.element_handle.ElementHandle object at 0x000001D668C768D0>,。。。。。
<pyppeteer.element_handle.ElementHandle object at 0x000001D668C7D810>]
可以看到 J 和 querySelector 方法返回的都是 ElementHandle 队形
JJ 和 querySelectorAll 方法都是由 ElementHandle 对象组成列表
选项卡操作
前面我们使用了 newPage 新建选项卡, 新建之后怎么获取和切换
先调用 pages 方法 获取所有打开的页面,然后选择其中一个页面调用 bringToFront 方法
import asyncio from pyppeteer import launchasync def main():browser = await launch(headless=False)page = await browser.newPage()await page.goto('https://www.baidu.com')page = await browser.newPage()await page.goto('https://www.bing.com')pages = await browser.pages()print('Pages: ', pages)page1 = pages[1]await page1.bringToFront()await asyncio.sleep(30)await browser.close()asyncio.run((main()))
这里先启动了 Pyppeteer , 然后调用了 newPage 方法新建了两个选项卡,并访问了两个网站,然后切换回了第一个选项卡
页面操作
页面的 加载, 前进,后退,关闭,保存
import asyncio from pyppeteer import launchasync def main():browser = await launch(headless=False)page = await browser.newPage()await page.goto('https://www.baidu.com/')await page.goto('https://spa2.scrape.center/')# # 后退await page.goBack()# # 前进# await page.goForward()# # 刷新await page.reload()# # 保存 PDF# await page.pdf()# # 截图await page.screenshot()# # 设置页面 HTMLawait page.setContent('<h2>Hello world </h2>')# # 设置 User-Agentawait page.setUserAgent('Python')# # 设置 headersawait page.setExtraHTTPHeaders(headers={})# # 关闭await page.close()await browser.close()
其中 前进 和 保存 PDF 有点问题
点击
点击调用 click 方法即可
import asyncio from pyppeteer import launchasync def main():browser = await launch(headless=False)page = await browser.newPage()await page.goto('https://spa2.scrape.center/')await page.waitForSelector('.item .name')await page.click('.item .name', options={'button': 'left','clickCount': 1, # 1 或 2'delay': 3000 # 毫秒})await browser.close()asyncio.run(main())
这里 click 方法中第一个参数就是选择器, 即在哪里操作。 第二个参数是几项配置,具体如下
button : 鼠标按钮, 取值有 left , middle , right
clickCount: 点击次数, 取值有 1 或2 表示 单击或双击
delay: 延迟点击
输入文本
使用 type 方法即可
import asyncio from pyppeteer import launch import timeasync def main():browser = await launch(headless=False)page = await browser.newPage()await page.goto('https://www.taobao.com')await page.type('#q', 'iPad')time.sleep(10)await browser.close()asyncio.run(main())
type 第一个参数是选择器, 第二个参数是要输入的内容
获取信息
Page 对象需要调用 content 方法获取源代码, Cookies 对象调用 cookies 方法获取
import asyncio from pyppeteer import launchasync def main():browser = await launch(headless=False)page = await browser.newPage()print('HTML: ', await page.content())print('Cookies: ', await page.cookies())await browser.close()asyncio.run(main())
执行 JavaScript
pyppeteer 可以执行 JavaScript 语句, 使用 evaluate 即可
import asyncio from pyppeteer import launchwidth,height = 1366, 768async def main():browser = await launch(headless=False)page = await browser.newPage()await page.setViewport({'width': width, 'height': height})await page.goto('https://spa2.scrape.center/')await page.waitForSelector('.item .name')await asyncio.sleep(2)await page.screenshot(path='example.png')dimensions = await page.evaluate(""" () => {return {width: document.documentElement.clientWidth,height: document.documentElement.clientHeight}}""")print(dimensions)await browser.close()asyncio.run(main())
这里我们调用了 evaluate 方法执行 JavaScript 语句, 并获取了结果, 另外 , Pyppeteer 还有
exposeFunction , evaluateOnNewDocument, evaluateHandle 方法,可以了解一下
延时等待
本页最开始的地方,我们演示了 waitForSelector 的用法, 它可以让页面等待某些符合条件的节点加载出来之后再返回结果。 这里我们给 waitForSelector 传入一个 CSS 选择器, 如果找到符合条件的节点,就立马返回结果,否则等待直到超时
除了 waitForSelector 还有很多其他等待方法
waitForFunction : 等待某个 JavaScript 方法执行完毕后返回结果
waitForNavigation : 等待页面跳转,如果没加载出来,就报错
waitForRequest ; 等待某个特定的请求发出
waitForResponse : 等待某个特定请求对应的响应
waitFor: 通用的等待方法
waitForXpath : 等待符合 XPaht 的节点加载出来
总结: pyppeteer 还有很多其他功能, 例如 键盘事件, 鼠标事件, 对话框事件等等
具体参考官网:
https://miyakogi.github.io/pyppeteer/reference.html