Google推出的爬虫新神器:Pyppeteer,神挡杀神,佛挡杀佛!

若是你们对 Python 爬虫有所了解的话,想必你应该据说过 Selenium 这个库,这其实是一个自动化测试工具,如今已经被普遍用于网络爬虫中来应对 JavaScript 渲染的页面的抓取。html

但 Selenium 用的时候有个麻烦事,就是环境的相关配置,得安装好相关浏览器,好比 Chrome、Firefox 等等,而后还要到官方网站去下载对应的驱动,最重要的还须要安装对应的 Python Selenium 库,确实是否是很方便,另外若是要作大规模部署的话,环境配置的一些问题也是个头疼的事情。git

那么本节就介绍另外一个相似的替代品,叫作 Pyppeteer。注意,是叫作 Pyppeteer,不是 Puppeteer。Puppeteer 是 Google 基于 Node.js 开发的一个工具,有了它咱们能够经过 JavaScript 来控制 Chrome 浏览器的一些操做,固然也能够用做网络爬虫上,其 API 极其完善,功能很是强大。而 Pyppeteer 又是什么呢?它其实是 Puppeteer 的 Python 版本的实现,但他不是 Google 开发的,是一位来自于日本的工程师依据 Puppeteer 的一些功能开发出来的非官方版本。github

在 Pyppetter 中,实际上它背后也是有一个相似 Chrome 浏览器的 Chromium 浏览器在执行一些动做进行网页渲染,首先说下 Chrome 浏览器和 Chromium 浏览器的渊源。web

Chromium 是谷歌为了研发 Chrome 而启动的项目,是彻底开源的。两者基于相同的源代码构建,Chrome 全部的新功能都会先在 Chromium 上实现,待验证稳定后才会移植,所以 Chromium 的版本更新频率更高,也会包含不少新的功能,但做为一款独立的浏览器,Chromium 的用户群体要小众得多。两款浏览器“同根同源”,它们有着一样的 Logo,但配色不一样,Chrome 由蓝红绿黄四种颜色组成,而 Chromium 由不一样深度的蓝色构成。

总的来讲,两款浏览器的内核是同样的,实现方式也是同样的,能够认为是开发版和正式版的区别,功能上基本是没有太大区别的。浏览器

Pyppeteer 就是依赖于 Chromium 这个浏览器来运行的。那么有了 Pyppeteer 以后,咱们就能够免去那些繁琐的环境配置等问题。若是第一次运行的时候,Chromium 浏览器没有安全,那么程序会帮咱们自动安装和配置,就免去了繁琐的环境配置等工做。另外 Pyppeteer 是基于 Python 的新特性 async 实现的,因此它的一些执行也支持异步操做,效率相对于 Selenium 来讲也提升了。安全

那么下面就让咱们来一块儿了解下 Pyppeteer 的相关用法吧。网络

安装

首先就是安装问题了,因为 Pyppeteer 采用了 Python 的 async 机制,因此其运行要求的 Python 版本为 3.5 及以上。less

安装方式很是简单:异步

pip3 install pyppeteer

好了,安装完成以后咱们命令行下测试下:async

>>> import pyppeteer

若是没有报错,那么就证实安装成功了。

快速上手

接下来咱们测试下基本的页面渲染操做,这里咱们选用的网址为:http://quotes.toscrape.com/js/,这个页面是 JavaScript 渲染而成的,用基本的 requests 库请求获得的 HTML 结果里面是不包含页面中所见的条目内容的。

为了证实 requests 没法完成正常的抓取,咱们能够先用以下代码来测试一下:

import requests
from pyquery import PyQuery as pq

url = 'http://quotes.toscrape.com/js/'
response = requests.get(url)
doc = pq(response.text)
print('Quotes:', doc('.quote').length)

这里首先使用 requests 来请求网页内容,而后使用 pyquery 来解析页面中的每个条目。观察源码以后咱们发现每一个条目的 class 名为 quote,因此这里选用了 .quote 这个 CSS 选择器来选择,最后输出条目数量。

运行结果:

Quotes: 0

结果是 0,这就证实使用 requests 是没法正常抓取到相关数据的。由于什么?由于这个页面是 JavaScript 渲染而成的,咱们所看到的内容都是网页加载后又执行了 JavaScript 以后才呈现出来的,所以这些条目数据并不存在于原始 HTML 代码中,而 requests 仅仅抓取的是原始 HTML 代码。

好的,因此遇到这种类型的网站咱们应该怎么办呢?

其实答案有不少:

  • 分析网页源代码数据,若是数据是隐藏在 HTML 中的其余地方,以 JavaScript 变量的形式存在,直接提取就行了。
  • 分析 Ajax,不少数据多是通过 Ajax 请求时候获取的,因此能够分析其接口。
  • 模拟 JavaScript 渲染过程,直接抓取渲染后的结果。

而 Pyppeteer 和 Selenium 就是用的第三种方法,下面咱们再用 Pyppeteer 来试试,若是用 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('http://quotes.toscrape.com/js/')
    doc = pq(await page.content())
    print('Quotes:', doc('.quote').length)
    await browser.close()

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

运行结果:

Quotes: 10

看运行结果,这说明咱们就成功匹配出来了 class 为 quote 的条目,总数为 10 条,具体的内容能够进一步使用 pyquery 解析查看。

那么这里面的过程发生了什么?

实际上,Pyppeteer 整个流程就完成了浏览器的开启、新建页面、页面加载等操做。另外 Pyppeteer 里面进行了异步操做,因此须要配合 async/await 关键词来实现。

首先, launch 方法会新建一个 Browser 对象,而后赋值给 browser,而后调用 newPage  方法至关于浏览器中新建了一个选项卡,同时新建了一个 Page 对象。而后 Page 对象调用了 goto 方法就至关于在浏览器中输入了这个 URL,浏览器跳转到了对应的页面进行加载,加载完成以后再调用 content 方法,返回当前浏览器页面的源代码。而后进一步地,咱们用 pyquery 进行一样地解析,就能够获得 JavaScript 渲染的结果了。

另外其余的一些方法如调用 asyncio 的 get_event_loop 等方法的相关操做则属于 Python 异步 async 相关的内容了,你们若是不熟悉能够了解下 Python 的 async/await 的相关知识。

好,经过上面的代码,咱们就能够完成 JavaScript 渲染页面的爬取了。

在这个过程当中,咱们没有配置 Chrome 浏览器,没有配置浏览器驱动,免去了一些繁琐的步骤,一样达到了 Selenium 的效果,还实现了异步抓取,爽歪歪!

接下来咱们再看看另一个例子,这个例子能够模拟网页截图,保存 PDF,另外还能够执行自定义的 JavaScript 得到特定的内容,代码以下:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch()
    page = await browser.newPage()
    await page.goto('http://quotes.toscrape.com/js/')
    await page.screenshot(path='example.png')
    await page.pdf(path='example.pdf')
    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.get_event_loop().run_until_complete(main())

这里咱们又用到了几个新的 API,完成了网页截图保存、网页导出 PDF 保存、执行 JavaScript 并返回对应数据。

首先 screenshot 方法能够传入保存的图片路径,另外还能够指定保存格式 type、清晰度 quality、是否全屏 fullPage、裁切 clip 等各个参数实现截图。

截图的样例以下:

能够看到它返回的就是 JavaScript 渲染后的页面。

pdf 方法也是相似的,只不过页面保存格式不同,最后获得一个多页的 pdf 文件,样例以下:

可见其内容也是 JavaScript 渲染后的内容,另外这个方法还能够指定放缩大小 scale、页码范围 pageRanges、宽高 width 和 height、方向 landscape 等等参数,导出定制化的 pdf 用这个方法就十分方便。

最后咱们又调用了 evaluate 方法执行了一些 JavaScript,JavaScript 传入的是一个函数,使用 return 方法返回了网页的宽高、像素大小比率三个值,最后获得的是一个 JSON 格式的对象,内容以下:

{'width': 800, 'height': 600, 'deviceScaleFactor': 1}

OK,实例就先感觉到这里,还有太多太多的功能还没说起。

总之利用 Pyppeteer 咱们能够控制浏览器执行几乎全部动做,想要的操做和功能基本均可以实现,用它来自由地控制爬虫固然就不在话下了。

详细用法

了解了基本的实例以后,咱们再来梳理一下 Pyppeteer 的一些基本和经常使用操做。Pyppeteer 的几乎全部功能都能在其官方文档的 API Reference 里面找到,连接为:https://miyakogi.github.io/py...,用到哪一个方法就来这里查询就行了,参数没必要死记硬背,即用即查就好。

开启浏览器

使用 Pyppeteer 的第一步即是启动浏览器,首先咱们看下怎样启动一个浏览器,其实就至关于咱们点击桌面上的浏览器图标同样,把它开起来。用 Pyppeteer 完成一样的操做,只须要调用 launch 方法便可。

咱们先看下 launch 方法的 API,连接为:

https://miyakogi.github.io/py...

其方法定义以下:

pyppeteer.launcher.launch(options: dict = None, **kwargs) → pyppeteer.browser.Browser

能够看到它处于 launcher 模块中,参数没有在声明中特别指定,返回类型是 browser 模块中的 Browser 对象,另外观察源码发现这是一个 async 修饰的方法,因此调用它的时候须要使用 await。

接下来看看它的参数:

  • 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): 时间循环对象。

好了,知道这些参数以后,咱们能够先试试看。

首先能够试用下最经常使用的参数 headless,若是咱们将它设置为 True 或者默认不设置它,在启动的时候咱们是看不到任何界面的,若是把它设置为 False,那么在启动的时候就能够看到界面了,通常咱们在调试的时候会把它设置为 False,在生产环境上就能够设置为 True,咱们先尝试一下关闭 headless 模式:

import asyncio
from pyppeteer import launch

async def main():
    await launch(headless=False)
    await asyncio.sleep(100)

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

运行以后看不到任何控制台输出,可是这时候就会出现一个空白的 Chromium 界面了:

关闭 Headless 模式以后的界面

可是能够看到这就是一个光秃秃的浏览器而已,看一下相关信息:

看到了,这就是 Chromium,上面还写了开发者内部版本,能够认为是开发版的 Chrome 浏览器就好。

另外咱们还能够开启调试模式,好比在写爬虫的时候会常常须要分析网页结构还有网络请求,因此开启调试工具仍是颇有必要的,咱们能够将 devtools 参数设置为 True,这样每开启一个界面就会弹出一个调试窗口,很是方便,示例以下:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(devtools=True)
    page = await browser.newPage()
    await page.goto('https://www.baidu.com')
    await asyncio.sleep(100)

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

刚才说过 devtools 这个参数若是设置为了 True,那么 headless 就会被关闭了,界面始终会显现出来。在这里咱们新建了一个页面,打开了百度,界面运行效果以下:

这时候咱们能够看到上面的一条提示:"Chrome 正受到自动测试软件的控制",这个提示条有点烦,那咋关闭呢?这时候就须要用到 args 参数了,禁用操做以下:

browser = await launch(headless=False, args=['--disable-infobars'])

这里就再也不写完整代码了,就是在 launch 方法中,args 参数经过 list 形式传入便可,这里使用的是 --disable-infobars 的参数。

另外有人就说了,这里你只是把提示关闭了,有些网站仍是会检测到是 webdriver 吧,好比淘宝检测到是 webdriver 就会禁止登陆了,咱们能够试试:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(headless=False)
    page = await browser.newPage()
    await page.goto('https://www.taobao.com')
    await asyncio.sleep(100)

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

运行时候进行一下登陆,而后就会弹出滑块,本身手动拖动一下,而后就报错了,界面以下:

淘宝登陆失败<

爬虫的时候看到这界面是很让人崩溃的吧,并且这时候咱们还发现了页面的 bug,整个浏览器窗口比显示的内容窗口要大,这个是某些页面会出现的状况,让人看起来很不爽。

咱们能够先解决一下这个显示的 bug,须要设置下 window-size 还有 viewport,代码以下:

import asyncio
from pyppeteer import launch

width, height = 1366, 768

async def main():
    browser = await launch(headless=False,
                           args=[f'--window-size={width},{height}'])
    page = await browser.newPage()
    await page.setViewport({'width': width, 'height': height})
    await page.goto('https://www.taobao.com')
    await asyncio.sleep(100)

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

这样整个界面就正常了:

image

<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em; overflow-wrap: break-word !important;">正常的界面</figcaption>

OK,那刚才所说的 webdriver 检测问题怎样来解决呢?其实淘宝主要经过

window.navigator.webdriver 来对 webdriver 进行检测,因此咱们只须要使用 JavaScript 将它设置为 false 便可,代码以下:

import asyncio
from pyppeteer import launch


async def main():
    browser = await launch(headless=False, args=['--disable-infobars'])
    page = await browser.newPage()
    await page.goto('https://login.taobao.com/member/login.jhtml?redirectURL=https://www.taobao.com/')
    await page.evaluate(
        '''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
    await asyncio.sleep(100)

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

这里没加输入用户名密码的代码,固然后面能够自行添加,下面打开以后,咱们点击输入用户名密码,而后这时候会出现一个滑动条,这里滑动的话,就能够经过了,如图所示:

image

<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em; overflow-wrap: break-word !important;">淘宝滑动条验证经过</figcaption>

OK,这样的话咱们就成功规避了 webdriver 的检测,使用鼠标拖动模拟就能够完成淘宝的登陆了。

还有另外一种方法能够进一步免去淘宝登陆的烦恼,那就是设置用户目录。平时咱们已经注意到,当咱们登陆淘宝以后,若是下次再次打开浏览器发现仍是登陆的状态。这是由于淘宝的一些关键 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())

好,这里就是加了一个 userDataDir 的属性,值为 userdata,即当前目录的 userdata 文件夹。咱们能够首先运行一下,而后登陆一次淘宝,这时候咱们同时能够观察到在当前运行目录下又多了一个 userdata 的文件夹,里面的结构是这样子的:

用户文件夹

再次运行上面的代码,这时候能够发现如今就已是登陆状态了,不须要再次登陆了,这样就成功跳过了登陆的流程。固然可能时间过久了,Cookies 都过时了,那仍是须要登陆的。

相关文章
相关标签/搜索