Python pyppeteer实战异步爬取|网页截图,保存 PDF,执行JavaScript,设置userDataDir,获取Cookies,

发表时间:2020-02-22

示例 1 :快速上手

接下来我们测试下基本的页面渲染操作,这里我们选用的网址为: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=url)
doc = pq(response.text)
print('Quotes : {0}'.format(doc('.quote').length))
 
# 结果
# Quotes : 0

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

运行结果:Quotes: 0

结果是 0,这就证明使用 requests 是无法正常抓取到相关数据的。

为什么?

因为这个页面是 JavaScript 渲染而成的,我们所看到的内容都是网页加载后又执行了 JavaScript 之后才呈现出来的,因此这些条目数据并不存在于原始 HTML 代码中,而 requests 仅仅抓取的是原始 HTML 代码。

好的,所以遇到这种类型的网站我们应该怎么办呢?

其实答案有很多:

  1. 分析网页源代码数据,如果数据是隐藏在 HTML 中的其他地方,以 JavaScript 变量的形式存在,直接提取就好了。
  2. 分析 Ajax,很多数据可能是经过 Ajax 请求时候获取的,所以可以分析其接口。
  3. 模拟 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()
    url = 'http://quotes.toscrape.com/js/'
    await page.goto(url=url)
    doc = pq(await page.content())
    print('Quotes : {0}'.format(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 渲染页面的爬取了。

模拟网页截图,保存 PDF,执行自定义的 JavaScript 获得特定的内容

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

import asyncio
from pyppeteer import launch
 
 
async def main():
    browser = await launch()
    page = await browser.newPage()
    url = 'http://quotes.toscrape.com/js/'
    await page.goto(url=url)
    await page.screenshot(path='test_screenshot.png')
    await page.pdf(path='test_pdf.pdf')
 
    # 在网页上执行js 脚本
    dimensions = await page.evaluate(pageFunction='''() => {
                return {
                    width: document.documentElement.clientWidth,    // 页面宽度
                    height: document.documentElement.clientHeight,  // 页面高度
                    deviceScaleFactor: window.devicePixelRatio,     // 像素比 1.0000000149011612
                }
            }''', force_expr=False)  # force_expr=False  执行的是函数
 
    print(dimensions)
    await browser.close()
 
 
asyncio.get_event_loop().run_until_complete(main())
 
# 结果
# {'width': 800, 'height': 600, 'deviceScaleFactor': 1}

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

 evaluate 方法执行了一些 JavaScript,JavaScript 传入的是一个函数,使用 return 方法返回了网页的宽高、像素大小比率三个值,最后得到的是一个 JSON 格式的对象。

总之利用 Pyppeteer 我们可以控制浏览器执行几乎所有动作,想要的操作和功能基本都可以实现,用它来自由地控制爬虫当然就不在话下了。

了解了基本的实例之后,我们再来梳理一下 Pyppeteer 的一些基本和常用操作。Pyppeteer 的几乎所有功能都能在其官方文档的 API Reference 里面找到,链接为:https://miyakogi.github.io/pyppeteer/reference.html,用到哪个方法就来这里查询就好了,参数不必死记硬背,即用即查就好。

登录淘宝 (打开网页后,手动输入用户名和密码,可以看到正常跳转到登录后的页面):

import asyncio
from pyppeteer import launch
 
width, height = 1366, 768
 
 
js1 = '''() =>{Object.defineProperties(navigator,{ webdriver:{ get: () => false}})}'''
js2 = '''() => {alert(window.navigator.webdriver)}'''
js3 = '''() => {window.navigator.chrome = {runtime: {}, }; }'''
js4 = '''() =>{Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});}'''
js5 = '''() =>{Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5,6],});}'''
 
 
async def page_evaluate(page):
    # 替换淘宝在检测浏览时采集的一些参数
    # 需要注意,在测试的过程中发现登陆成功后页面的该属性又会变成True
    # 所以在每次重新加载页面后要重新设置该属性的值。
    await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
    await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {},  }; }''')
    await page.evaluate('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
    await page.evaluate('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')
 
 
async def main():
    browser = await launch(
        headless=False,
        # userDataDir='./userdata',
        args=['--disable-infobars', f'--window-size={width},{height}', '--no-sandbox']
    )
    page = await browser.newPage()
 
    await page.setViewport(
        {
            "width": width,
            "height": height
        }
    )
    url = 'https://www.taobao.com'
    await page.goto(url=url)
 
    # await page.evaluate(js1)
    # await page.evaluate(js3)
    # await page.evaluate(js4)
    # await page.evaluate(js5)
 
    await page_evaluate(page)
 
    await asyncio.sleep(100)
    # await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())

如果把上面 js 去掉,发现淘宝可以检测出来, 跳转不到登录后的页面。

window.navigator 对象包含有关访问者浏览器的信息:https://www.runoob.com/js/js-window-navigator.html

js 主要需要修改浏览器的 window.navigator.webdriver、window.navigator.languages等值。

打开正常的浏览器可以看到:

Snipaste_2020-02-22_13-08-58.png

window.navigator.webdriver的值为undefined,而通过pyppeteer控制打开的浏览器该值为True,当被检测到该值为True的时候,则滑动会一直失败,所以我们需要修改该属性。需要注意,在测试的过程中发现登陆成功后页面的该属性又会变成True,所以在每次重新加载页面后要重新设置该属性的值。

async def page_evaluate(page):
    # 替换淘宝在检测浏览时采集的一些参数
    await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
    await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {},  }; }''')
    await page.evaluate('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
    await page.evaluate('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')

另一种方法可以进一步免去淘宝登录的烦恼,那就是设置用户目录。

平时我们已经注意到,当我们登录淘宝之后,如果下次再次打开浏览器发现还是登录的状态。这是因为淘宝的一些关键 Cookies 已经保存到本地了,下次登录的时候可以直接读取并保持登录状态。

那么这些信息保存在哪里了呢?其实就是保存在用户目录下了,里面不仅包含了浏览器的基本配置信息,还有一些 Cache、Cookies 等各种信息都在里面,如果我们能在浏览器启动的时候读取这些信息,那么启动的时候就可以恢复一些历史记录甚至一些登录状态信息了。

这也就解决了一个问题:很多朋友在每次启动 Selenium 或 Pyppeteer 的时候总是是一个全新的浏览器,那就是没有设置用户目录,如果设置了它,每次打开就不再是一个全新的浏览器了,它可以恢复之前的历史记录,也可以恢复很多网站的登录信息。

当然可能时间太久了,Cookies 都过期了,那还是需要登录的。

那么这个怎么来做呢?很简单,在启动的时候设置 userDataDir 就好了,示例如下:

 browser = await launch(
        headless=False,
        userDataDir='./userdata',
        args=['--disable-infobars', f'--window-size={width},{height}']
    )

好,这里就是加了一个 userDataDir 的属性,值为 userdata,即当前目录的 userdata 文件夹。我们可以首先运行一下,然后登录一次淘宝,这时候我们同时可以观察到在当前运行目录下又多了一个 userdata 的文件夹:

Snipaste_2020-02-22_13-10-42.png

用户文件夹

具体的介绍可以看官方的一些说明,如:https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md 这里面介绍了 userdatadir 的相关内容。


命令行启动 chrome 并进入指定的 URL:chrome.exe --disable-infobars --user-data-dir="./userdatadir" --new-window https://login.taobao.com/member/login.jhtml

执行完后会打开 淘宝的登录页面,登录淘宝,然后保存用户名密码,这样登录信息就保存在 userdatadir 目录下了

在执行 chrome.exe --disable-infobars --user-data-dir="./userdatadir" --new-window https://www.taobao.com

可以看到已经时登录状态了。

微配音

文章来源互联网,尊重作者原创,如有侵权,请联系管理员删除。邮箱:417803890@qq.com / QQ:417803890


Python Free

邮箱:417803890@qq.com
QQ:417803890

皖ICP备19001818号
© 2019 copyright www.pythonf.cn - All rights reserved

微信扫一扫关注公众号:

联系方式

Python Free