爬虫的新模块pyppeteer的使用

偶尔善良 提交于 2019-12-20 22:38:05

安装

python3 -m pip install pyppeteer

最好是py3.5+

手动安装

你懂的,天朝网络环境很复杂,如果要用pyppeteer自己绑定的chromium,半天都下载不下来,所以我们要手动安装,然后在程序里面指定executablePath

下载地址

模块介绍

启动pyppeteer.launch

launch 浏览器,可以传入一个字典来配置几个options,比如:

browser = await pyppeteer.launch({
    'headless': False, # 关闭无头模式
    'devtools': True, # 打开 chromium 的 devtools
    // 'executablePath': '你下载的Chromium.app/Contents/MacOS/Chromiu', //# 浏览器的存放地址
    'args': [ 
        '--disable-extensions',
        '--hide-scrollbars',
        '--disable-bundled-ppapi-flash',
        '--mute-audio',
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-gpu',
    ],
    'dumpio': True,  
})
browser = await launch({'headless': False,'dumpio':True, 'autoClose':False,'args': ['--no-sandbox', '--window-size=1366,850']})
await page.setViewport({'width':1366,'height':768})

注入脚本page.evaluate

await page.evaluate("""
    () =>{
        Object.defineProperties(navigator,{
            webdriver:{
            get: () => false
            }
        })
    }
""")

我们会看到这一步非常关键,因为puppeteer出于政策考虑(这个词用的不是很好,就是那个意思)会设置window.navigator.webdrivertrue,告诉网站我是一个 webdriver 驱动的浏览器。有些网站比较聪明(反爬措施做得比较好),就会通过这个来判断对方是不是爬虫程序。

截获response和request

await page.setRequestInterception(True)
page.on('request', intercept_request)
page.on('response', intercept_response)

intercept_requestintercept_response相当于是注册的两个回调函数,在浏览器发出请求和获取到请求之前指向这两个函数。

比如可以这样禁止获取图片、多媒体资源和发起 websocket 请求:

async def intercept_request(req):
    """请求过滤"""
    if req.resourceType in ['image', 'media', 'eventsource', 'websocket']:
        await req.abort()
    else:
        await req.continue_()

然后每次获取到请求之后将内容打印出来(这里只打印了fetchxhr类型response 的内容):

async def intercept_response(res):
    resourceType = res.request.resourceType
    if resourceType in ['xhr', 'fetch']:
        resp = await res.text()
        print(resp)

一共有哪些resourceType,pyppeteer文档里面有:

页面自动下拉的代码

健壮的代码

请求钩子

async def request_check(req):
    '''请求过滤'''
    if req.resourceType in ['image', 'media', 'eventsource', 'websocket']:
        await req.abort()
    else:
        await req.continue_()

await page.setRequestInterception(True)
page.on('request', request_check)

判断加载是否完成

async def goto(page, url):
    while True:
        try:
            await page.goto(url, {
                'timeout': 0,
                'waitUntil': 'networkidle0'
            })
            break
        except (pyppeteer.errors.NetworkError,
                pyppeteer.errors.PageError) as ex:
            # 无网络 'net::ERR_INTERNET_DISCONNECTED','net::ERR_TUNNEL_CONNECTION_FAILED'
            if 'net::' in str(ex):
                await asyncio.sleep(10)
            else:
                rais

注入JS文件

from pathlib import Path

CURDIR = Path(__file__).parent
JS_AJAX_HOOK_LIB = str(CURDIR / 'static' / 'ajaxhook.min.js')

await page.addScriptTag(path=JS_AJAX_HOOK_LIB)

这样注入的js文件不能有中文,因为 pyppeteer 里面打开文件用的是默认编码,可以 hook 住
open 函数来解决。

# 因为 pyppeteer 库里面 addScriptTag 用的是系统默认编码,导致js文件里面不能有中文
pyppeteer.frame_manager.open = lambda file: open(file, encoding='utf8')

完整的跑一个小项目

import asyncio
from pyppeteer import launch
import time

async def main():exepath = 'C:/Users/tester02/AppData/Local/Google/Chrome/Application/chrome.exe'
    browser = await launch({'executablePath': exepath, 'headless': False, 'slowMo': 30})
    page = await browser.newPage()
    await page.setViewport({'width': 1366, 'height': 768})
    await page.goto('http://192.168.2.66') // # 请求网址
    await page.type("#Login_Name_Input", "test02") # 第一个参数是选择器,选中某个元素,第二个参数是要输入的内容
    await page.type("#Login_Password_Input", "12345678", )
    await page.waitFor(1000) # 停顿
    await page.click("#Login_Login_Btn")
    await page.waitFor(3000)
    await browser.close()# 关闭浏览器

asyncio.get_event_loop().run_until_complete(main()) # 执行main函数

登陆谷歌

import asyncio
import time
from pyppeteer import launch


async def gmailLogin(username, password, url):
    #'headless': False如果想要浏览器隐藏更改False为True
    # 127.0.0.1:1080为代理ip和端口,这个根据自己的本地代理进行更改,如果是vps里或者全局模式可以删除掉'--proxy-server=127.0.0.1:1080'
    browser = await launch({'headless': False, 'args': ['--no-sandbox', '--proxy-server=127.0.0.1:1080']})
    page = await browser.newPage()
    await page.setUserAgent(
        'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36')

    await page.goto(url)

    # 输入Gmail
    await page.type('#identifierId', username)
    # 点击下一步
    await page.click('#identifierNext > content')
    page.mouse  # 模拟真实点击
    time.sleep(10)
    # 输入password
    await page.type('#password input', password)
    # 点击下一步
    await page.click('#passwordNext > content > span')
    page.mouse  # 模拟真实点击
    time.sleep(10)
    # 点击安全检测页面的DONE
    # await page.click('div > content > span')#如果本机之前登录过,并且page.setUserAgent设置为之前登录成功的浏览器user-agent了,
    # 就不会出现安全检测页面,这里如果有需要的自己根据需求进行更改,但是还是推荐先用常用浏览器登录成功后再用python程序进行登录。

    # 登录成功截图
    await page.screenshot({'path': './gmail-login.png', 'quality': 100, 'fullPage': True})
    #打开谷歌全家桶跳转,以Youtube为例
    await page.goto('https://www.youtube.com')
    time.sleep(10)


if __name__ == '__main__':
    username = '你的gmail包含@gmail.com'
    password = r'你的gmail密码'
    url = 'https://gmail.com'
    loop = asyncio.get_event_loop()
    loop.run_until_complete(gmailLogin(username, password, url))
# 代码由三分醉编写,网址www.sanfenzui.com,参考如下文章:
# https://blog.csdn.net/Chen_chong__/article/details/82950968

登陆淘宝

async def taobao_login(username, password, url):
    """
    淘宝登录主程序
    :param username: 用户名
    :param password: 密码
    :param url: 登录网址
    :return: 登录cookies
    """
    ###
    await page.click('#J_QRCodeLogin > div.login-links > a.forget-pwd.J_Quick2Static')
    page.mouse
    time.sleep(1)
    # 输入用户名,密码
    await page.type('#TPL_username_1', username, {'delay': input_time_random() - 50})   # delay是限制输入的时间
    await page.type('#TPL_password_1', password, {'delay': input_time_random()})
    time.sleep(2)
    # 检测页面是否有滑块。原理是检测页面元素。
    slider = await page.Jeval('#nocaptcha', 'node => node.style')  # 是否有滑块

    if slider:
        print('当前页面出现滑块')
        # await page.screenshot({'path': './headless-login-slide.png'}) # 截图测试
        flag, page = await mouse_slide(page=page)  # js拉动滑块过去。
        if flag:
            await page.keyboard.press('Enter')  # 确保内容输入完毕,少数页面会自动完成按钮点击
            print("print enter", flag)
            await page.evaluate('''document.getElementById("J_SubmitStatic").click()''')  # 如果无法通过回车键完成点击,就调用js模拟点击登录按钮。
            time.sleep(2)
            cookies_list = await page.cookies()
            print(cookies_list)
            return await get_cookie(page)  # 导出cookie 完成登陆后就可以拿着cookie玩各种各样的事情了。
    else:
        print("")
        await page.keyboard.press('Enter')
        print("print enter")
        await page.evaluate('''document.getElementById("J_SubmitStatic").click()''')
        await page.waitFor(20)
        await page.waitForNavigation()

        try:
            global error  # 检测是否是账号密码错误
            print("error_1:", error)
            error = await page.Jeval('.error', 'node => node.textContent')
            print("error_2:", error)
        except Exception as e:
            error = None
        finally:
            if error:
                print('确保账户安全重新入输入')
                # 程序退出。
                loop.close()
            else:
                print(page.url)
                return await get_cookie(page)

今日头条文章

import asyncio
from pyppeteer import launch

async def main():
    # headless参数设为False,则变成有头模式
    browser = await launch(
        # headless=False
    )
    
    page = await browser.newPage()
    
    # 设置页面视图大小
    await page.setViewport(viewport={'width':1280, 'height':800})
    
    # 是否启用JS,enabled设为False,则无渲染效果
    await page.setJavaScriptEnabled(enabled=True)
    
    await page.goto('https://www.toutiao.com/')
    
    # 打印页面cookies
    print(await page.cookies())
    
    # 打印页面文本
    print(await page.content())
    
    # 打印当前页标题
    print(await page.title())
    
    # 抓取新闻标题
    title_elements = await page.xpath('//div[@class="title-box"]/a')
    for item in title_elements:
        # 获取文本
        title_str = await (await item.getProperty('textContent')).jsonValue()
        print(await item.getProperty('textContent'))
        # 获取链接
        title_link = await (await item.getProperty('href')).jsonValue()
        print(title_str)
        print(title_link)
    
    # 关闭浏览器
    await browser.close()
    
asyncio.get_event_loop().run_until_complete(main())

Pyppeteer和Puppeteer的不同点

  1. Pyppeteer支持字典和关键字传参,Puppeteer只支持字典传参
# Puppeteer只支持字典传参
browser = await launch({'headless': True})
# Pyppeteer支持字典和关键字传参
browser = await launch({'headless': True})
browser = await launch(headless=True)
  1. 元素选择器方法名 $变为querySelector
# Puppeteer使用$符
Page.$()/Page.$$()/Page.$x()
# Pyppeteer使用Python风格的函数名
Page.querySelector()/Page.querySelectorAll()/Page.xpath()
# 简写方式为:
Page.J(), Page.JJ(), and Page.Jx()
  1. Page.evaluate() 和 Page.querySelectorEval()的参数

Puppeteer的evaluate()方法使用JavaScript原生函数或JavaScript表达式字符串。Pyppeteer的evaluate()方法只使用JavaScript字符串,该字符串可以是函数也可以是表达式,Pyppeteer会进行自动判断。但有时会判断错误,如果字符串被判断成了函数,并且报错,可以添加选项force_expr=True,强制Pyppeteer作为表达式处理。

获取页面内容:

content = await page.evaluate('document.body.textContent', force_expr=True)

获取元素的内部文字:

element = await page.querySelector('h1')
title = await page.evaluate('(element) => element.textContent', element)

爬虫例子

# -*- coding: utf-8 -*-

import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq

# 最好指定一下自己浏览器的位置,如果不指定会自动下载,太慢了...
executable_path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"


# 示例一: 渲染页面
async def crawl_page():
    # 打开浏览器
    browser = await launch(executablePath=executable_path)

    # 打开tab
    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()


# 示例二:截图,保存pdf,执行js
async def save_pdf():
    browser = await launch(executablePath=executable_path)
    page = await browser.newPage()
    await page.goto('http://quotes.toscrape.com/js/')

    # 网页截图保存
    await page.screenshot(path='example.png')

    # 网页导出 PDF 保存
    await page.pdf(path='example.pdf')

    # 执行 JavaScript
    dimensions = await page.evaluate('''() => {
            return {
                width: document.documentElement.clientWidth,
                height: document.documentElement.clientHeight,
                deviceScaleFactor: window.devicePixelRatio,
            }
        }''')

    print(dimensions)

    await browser.close()


if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(crawl_page())
    # asyncio.get_event_loop().run_until_complete(save_pdf(

如何建立一个cookie池

必须多个账号,那么如何注册多个淘宝账号呢。。

  1. 可以通过第三方提供手机号验证码服务商,通过pyppeteer注册账号,保存账号信息
  2. 登录账号并保存在redis
  3. 开线程检查账号是否已过期,若过期重新登录即可
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!