Python爬虫之pyppeteer的使用(爬虫、获取cookie、截屏插件、防爬绕过)

官方帮助:

下载文本中的网页源码,由于需要向下拉动滚动条所以使用pyppeteer渲染网页,并执行js代码,可是发现开启无界面的时候似乎执行不了js代码,还有异步的时候好像也执行不了js代码

环境部署

pip install

pip install pyppeteer -i https://pypi.douban.com/simple

chromium下载

chromium下载地址:https://npm.taobao.org/mirrors/chromium-browser-snapshots/
下载之后解压之后,通过executablePath属性指定运行浏览器了

启动参数:

  • 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): 时间循环对象。
  • Python爬虫 Pyppeteer 清空input输入框的值

    • await page.evaluate('document.querySelector("#txt_account").value=""')

常见参数

属性数据类型描述
executablePathstrchrome.exe运行的路径
ignorehttpserrrorsbool忽略https错误,默认false
headlessboolTrue 开始无头浏览器 False关闭无头
dumpiobool设置True 解决浏览器多开卡死

args的参数设置:

属性数据类型描述
–disable-infobars-关闭自动化提示框
–window-size=1920,1080str设置浏览器大小吗,1920是宽,1080是宽
–log-level=30str日志保存等级
–start-maximized-窗口最大化模式
–proxy-server=http://localhost:1080str设置代理
userDataDir=D:\userData\str用户文件保存地址
import asyncio
from pyppeteer import launch
import re, os,time


async def create_page():
    browser = await launch(headless=True, dumpio=True)
    return browser


async def close_page(browser):
    await browser.close()


async def start(sem, url):
    # print(url)
    async with sem: #控制协程的并发量
        page = await browser.newPage()
        await page.goto(url)
        # for i in range(10): # 执行js代码向下滚动滚动条
        #     dimensions = await page.evaluate(f'var q=document.documentElement.scrollTop={i * 1000}')
        #     await asyncio.sleep(1)
        # sn = re.search('sn=(.*?)&', url).group(1)
        # with open(os.path.join(r'E:\study\out', f'{sn}.html'), 'ab') as f:
        #     data = await page.content()
        #     f.write(data.encode('utf8'))
        print(await page.content())
        await page.close()


if __name__ == '__main__':
    sem = asyncio.Semaphore(5)
    fundlist = []
    path = r'C:\Users\Admin\Desktop\新建 文本文档.txt'
    loop = asyncio.get_event_loop() 
    browser = loop.run_until_complete(create_page()) #创建一个浏览器对象
    for line in open(path, 'r'):
        fundlist.append(line)
    task = [loop.create_task(start(sem, url)) for url in fundlist]
    loop.run_until_complete(asyncio.wait(task))
    loop.run_until_complete(close_page(browser))

使用pyppeteer时有个bug会报错,将源码改动下就ok
链接: 这里.

launch_kwargs = {
        # 控制是否为无头模式
        "headless": False,
        # chrome启动命令行参数
        "args": [
            # 浏览器代理 配合某些中间人代理使用
            "--proxy-server=http://127.0.0.1:8008",
            # 最大化窗口
            "--start-maximized",
            # 取消沙盒模式 沙盒模式下权限太小
            "--no-sandbox",
            # 不显示信息栏  比如 chrome正在受到自动测试软件的控制 ...
            "--disable-infobars",
            # log等级设置 在某些不是那么完整的系统里 如果使用默认的日志等级 可能会出现一大堆的warning信息
            "--log-level=3",
            # 设置UA
            "--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
        ],
       	"dumpio":True,
       	# 当界面开多了时会卡住,设置这个参数就不会了
        # 用户数据保存目录 这个最好也自己指定一个目录
        # 如果不指定的话,chrome会自动新建一个临时目录使用,在浏览器退出的时候会自动删除临时目录
        # 在删除的时候可能会删除失败(不知道为什么会出现权限问题,我用的windows) 导致浏览器退出失败
        # 然后chrome进程就会一直没有退出 CPU就会狂飙到99%
        "userDataDir": "",
    }

设置viewport 自动获取当前屏幕大小并设置viewport

# coding:utf8
import asyncio
from pyppeteer import launch


def screen_size():
    """使用tkinter获取屏幕大小"""
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, height


async def main():
    launch_kwargs = {
        "headless": False
    }
    # 启动浏览器
    browser = await launch(launch_kwargs)
    # 打开标签页
    page = await browser.newPage()
    # 默认 800 * 600 一般是不够的
    print(page.viewport)
    #
    width, height = screen_size()
    # 设置网页可视区域大小
    await page.setViewport({
        "width": width,
        "height": height
    })
    await browser.close()
    return


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

导出或加载cookie

    # 取出cookie
    cookies = await page.cookies()
    # 这里可以做些什么 :)
    pass
    # 然后导入cookie
    await page.setCookie(*cookies)

完整的一个实例

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@time:2020/04/04
"""


import asyncio
import logging
import tkinter

from pyppeteer import launch, launcher
from lxml import etree


async def main():

    # 浏览器 启动参数
    start_parm = {
        # 启动chrome的路径
        "executablePath": r"C:\Users\yq\AppData\Local\pyppeteer\pyppeteer\local-chromium\722234\chrome-win\chrome.exe",
        # 关闭无头浏览器
        "headless": False,

        "args": [
            '--disable-infobars',  # 关闭自动化提示框
            # '--window-size=1920,1080',  # 窗口大小
            '--log-level=30',  # 日志保存等级, 建议设置越好越好,要不然生成的日志占用的空间会很大 30为warning级别
            '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',  # UA
            '--no-sandbox',  # 关闭沙盒模式
            '--start-maximized',  # 窗口最大化模式
            # '--proxy-server=http://localhost:1080'  # 代理
            r'userDataDir=D:\project_demo\python_demo\spider_demo\JavaScript 逆向系列课\userdata'  # 用户文件地址
        ],
    }

    await page.goto('https://www.httpbin.org/headers')
    page_text = await page.content()
    input('----------------')
    await browser.close()

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

窗口/可视区最大化

但窗口设置最大化(–start-maximized)或窗口大小(–window-size=1920,1080)时,发现自己页面可视区域没有变化。成下面图片显示效果

设置可视化参数,代码如下

# !/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@time:2020/04/04
"""
import asyncio
import tkinter

from pyppeteer import launcher

# 注意 在导入launch之前先把默认参数改了
# 去除自动化 启动参数
launcher.AUTOMATION_ARGS.remove("--enable-automation")
from pyppeteer import launch


async def main():
    # 浏览器 启动参数
    start_parm = {
        # 启动chrome的路径
        "executablePath": r"C:\Users\yq\AppData\Local\pyppeteer\pyppeteer\local-chromium\722234\chrome-win\chrome.exe",
        # 关闭无头浏览器
        "headless": False,
        "args": [
            '--disable-infobars',  # 关闭自动化提示框
            '--no-sandbox',  # 关闭沙盒模式
            '--start-maximized',  # 窗口最大化模式

        ],
    }
    browser = await launch(**start_parm)
    page = await browser.newPage()

    # 查看当前 桌面视图大小
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    print(f'设置窗口为:width:{width} height:{height}')
    
    # 设置网页 视图大小
    await page.setViewport(viewport={'width': width, 'height': height})
    await page.goto('https://www.baidu.com')
    input('----------------')
    await browser.close()


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

隐藏浏览器特征

pyppeteer跟selenium一样会有浏览器特征,所以需要修改,隐藏特征防止被识别。
主要有下面两点:

  1. 去除浏览器自动化参数 --enable-automation
  2. 去除window.navigator.webdriver等检测
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@time:2020/04/04
"""


import asyncio
import logging
import tkinter

from pyppeteer import launcher
# 第一步 去除浏览器自动化参数
# 必须在 from pyppeteer import launch 前去除参数
# 去除自动化 启动参数
launcher.AUTOMATION_ARGS.remove("--enable-automation")

from pyppeteer import launch
from lxml import etree


async def main():
    # 浏览器 启动参数
    start_parm = {
        # 启动chrome的路径
        "executablePath": r"C:\Users\yq\AppData\Local\pyppeteer\pyppeteer\local-chromium\722234\chrome-win\chrome.exe",
        # 关闭无头浏览器
        "headless": False,

        "args": [
            '--disable-infobars',  # 关闭自动化提示框
            # '--window-size=1920,1080',  # 窗口大小
            '--log-level=30',  # 日志保存等级, 建议设置越好越好,要不然生成的日志占用的空间会很大 30为warning级别
            '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',  # UA
            '--no-sandbox',  # 关闭沙盒模式
            '--start-maximized',  # 窗口最大化模式
            # '--proxy-server=http://localhost:1080'  # 代理
            r'userDataDir=D:\project_demo\python_demo\spider_demo\JavaScript 逆向系列课\userdata'  # 用户文件地址
        ],
    }
    
    browser = await launch(**start_parm)
    page = await browser.newPage()
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()

    await page.setViewport(viewport={'width': width, 'height': height})
	
	# 第二步,修改 navigator.webdriver检测
	# 其实各种网站的检测js是不一样的,这是比较通用的。有的网站会检测运行的电脑运行系统,cpu核心数量,鼠标运行轨迹等等。
    # 反爬js
    js_text = """
() =>{ 
    Object.defineProperties(navigator,{ webdriver:{ get: () => false } });
    window.navigator.chrome = { runtime: {},  };
    Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
    Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], });
 }
    """
    await page.evaluateOnNewDocument(js_text)  # 本页刷新后值不变,自动执行js
    await page.goto('https://www.httpbin.org/headers')
    page_text = await page.content()
    print(page_text)
    input('==========')
    await browser.close()

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

拦截请求

可以对出现的请求,进行拦截 类似mitmproxy。

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@time:2020/04/04
"""

import asyncio
import json

from jsonpath import jsonpath
from pyppeteer import launcher

launcher.AUTOMATION_ARGS.remove("--enable-automation")

from pyppeteer import launch

from pyppeteer.network_manager import Request, Response


async def intercept_request(req:Request):
    await req.continue_()  # 请求,看源码可以重新编写请求


async def intercept_response(res:Response):
    if 'ext2020/apub/json/prevent.new' in res.url:
        print('拦截到请求')
        json_text = await res.text()
        title_li = jsonpath(json.loads(json_text), '$..title')
        for title in title_li:
            print(title)
    pass


async def main():
    # 浏览器 启动参数
    start_parm = {
        # 启动chrome的路径
        "executablePath": r"C:\Users\yq\AppData\Local\pyppeteer\pyppeteer\local-chromium\722234\chrome-win\chrome.exe",
        # 关闭无头浏览器 默认是无头启动的
        "headless": False,
        "args": [
            '--disable-infobars',  # 关闭自动化提示框
            # '--no-sandbox',  # 关闭沙盒模式
            '--start-maximized',  # 窗口最大化模式
            '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
            # UA

        ],

    }
    # 创建浏览器对象,可以传入 字典形式参数
    browser = await launch(**start_parm)

    # 创建一个页面对象, 页面操作在该对象上执行
    page = await browser.newPage()
    await page.setJavaScriptEnabled(enabled=True)

    # 启用拦截器
    await page.setRequestInterception(True)
    page.on('request', intercept_request) 
    page.on('response', intercept_response)
    
    js_text = """
    () =>{ 
        Object.defineProperties(navigator,{ webdriver:{ get: () => false } });
        window.navigator.chrome = { runtime: {},  };
        Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
        Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], });
     }
        """
    await page.evaluateOnNewDocument(js_text)  # 本页刷新后值不变,自动执行js
    await page.goto('https://news.qq.com/')  # 页面跳转


    await browser.close()


asyncio.get_event_loop().run_until_complete(main())   # 创建异步池并执行main函数。

按钮路径获取(page.click)

谷歌浏览器 -> copy -> copy js path

获取验证码

新建浏览器,进行登录,由于验证码的识别准确率不是百分之百,需要多次尝试。

    async def main(self, username, pwd, url):  # 定义main协程函数,

        login_count = 0

        # 打开浏览器
        browser = await launch(
            {'headless': False, "userDataDir": r"./temp_data", 'args': ['--no-sandbox'], })

        # 登录检测
        while login_count < 10:
            # 登录
            await self.login(browser, username, pwd, url)

            # 检测是否登录成功
            if await self.check_login(browser):
                break
            else:
                login_count += 1

        # 尝试登录次数大于10就退出
        if login_count > 10:
            print("login failed!")
            await browser.close()
            return
		
		do_something()

        await browser.close()

可以替换程序中验证操作函数,实现不同的验证方式。
其中提交过程采用了xpath定位提交按钮。

    async def login(self, browser, username, pwd, url):
        page = await browser.newPage()  # 启动个新的浏览器页面
        await page.setUserAgent(
            'Mozilla/5.0 (Windows NT 6.1; WOW64) '
            'AppleWebKit/537.36 (KHTML, like Gecko) '
            'Chrome/68.0.3440.106 Safari/537.36')

        await page.goto(url)  # 访问登录页面

        # 就是在浏览器运行的时候,始终让window.navigator.webdriver=false
        # navigator是windiw对象的一个属性,同时修改plugins,languages,navigator 且让
        await page.evaluate(
            '''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')

        # 以下为插入中间js,将淘宝会为了检测浏览器而调用的js修改其结果。
        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], }); }''')
         
        time.sleep(2)
        
        # 使用type选定页面元素,并修改其数值,用于输入账号密码,修改的速度仿人类操作,因为有个输入速度的检测机制
        # 因为 pyppeteer 框架需要转换为js操作,而js和python的类型定义不同,所以写法与参数要用字典,类型导入
        await page.type('#username', username, {'delay': self.input_time_random() - 50})
        await page.type('#password', pwd, {'delay': self.input_time_random()})

        # await page.screenshot({'path': './picture/headless-test-result.png'})    # 截图测试

        time.sleep(1)
        
        # 验证码操作
		verification_code(page);

        # 点击提交
        submit = await page.xpath("//button[@class='auth_login_btn primary full_width']")
        await submit[0].click()

        time.sleep(1)

验证码识别和输入

我在这里利用了某网站的验证码识别api,通过http方式就能上传验证码图片,并获取验证码。这个网站每天有固定的1000张图片免费次数,足够我们使用。第一个请求链接的用户名和密码换成我们注册该网站的用户名和密码即可。具体可以看官方的API文档。
该网站地址:http://fast.95man.com/

    # 验证码登录
    async def verification_code(self, page):
        await page.waitFor(5 * 1000)                                     # 等待验证码图片加载
        yazhengma = await page.waitForSelector('#captchaImg')            # 定位验证码元素
        await yazhengma.screenshot({'path': './picture/yazhengma.png'})  # 保存验证码图片

        # 获取验证码
        code = self.get_code('./picture/yazhengma.png')

        # 输入验证码
        await page.type('#captchaResponse', code, {'delay': self.input_time_random()})

    def get_code(self, file_path):
            # 以下为GET请求
            url = 'http://api.95man.com:8888/api/Http/UserTaken?user=username&pwd=password&isref=0'
            token_request = requests.get(url)
            token_raw = str(token_request.content)

            # 切片获取token
            token = token_raw[4: -1]

            print(token)

            # 发送图片解析请求
            url = "http://api.95man.com:8888/api/Http/Recog?Taken=" + token + "&imgtype=1&len=4"
            file_path = file_path
            files = {'file': open(file_path, 'rb')}

            # 上传图片
            r = requests.post(url, files=files)
            print(r.url, r.text)

            # 切片获取验证码
            return r.text[6:10]

防检测的一些方法

1、常用小功能

    async def init_pyppeteer(self):
        self.browser = await pyppeteer.launch({'headless': False,
                                               # 'userDataDir': './userdata',# 用户临时目录,保存cookie可以开启
                                               'args': [
                                                   # '--window-size={1300},{800}',
                                                   '--start-maximized',  # 最大化窗口
                                                   '--proxy-server=http://118.24.51.247:1443',#浏览器代理 配合某些中间人代理使用
                                                   # '--load-extension={}'.format(chrome_extension),  # 加载插件
                                                   # '--disable-extensions-except={}'.format(chrome_extension),
                                                   # '--disable-extensions',
                                                   '--hide-scrollbars',
                                                   '--disable-bundled-ppapi-flash',
                                                   '--mute-audio',
                                                   '--no-sandbox',  # 取消沙盒模式 沙盒模式下权限太小
                                                   '--no-sandbox',  # 不显示信息栏  比如 chrome正在受到自动测试软件的控制
                                                   '--disable-setuid-sandbox',
                                                   '--disable-gpu',
                                                   '--disable-infobars'
                                                   # log等级设置 在某些不是那么完整的系统里 如果使用默认的日志等级 可能会出现一大堆的warning信息
                                               ],
                                               'dumpio': True,  # 减少内存消耗
                                               # "slowMo": 25  # 让执行慢下来
                                               })
        self.page = await self.browser.newPage()
        width, height = self.screen_size()
        await self.page.setViewport({
            "width": width,
            "height": height
        })
        # 设置浏览器头部
        await self.page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                                     '(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299')
        await self.page.evaluateOnNewDocument('() =>{ Object.defineProperties(navigator,'
                                         '{ webdriver:{ get: () => false } }) }')  # 本页刷新后值不变

1.1、绕过对方网站监测

import pyppeteer
async def page_evaluate(self, page):
    '''window.navigator.webdriver=false'''
    await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => undefined } }) }''')  # 以下为插入中间js,将淘宝会为了检测浏览器而调用的js修改其结果。
    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(self):
    browser = await pyppeteer.launch()
    page = await browser.newPage()
    await self.page_evaluate(page)

1.2、网络通信异常处理

 await page.goto(h5_detail_url,waitUntil=["networkidle0", "load", "domcontentloaded"],options={'timeout': 30000})

1.3、禁止渲染

# # 是否启用JS,enabled设为False,则无渲染效果
await self.page.setJavaScriptEnabled(enabled=False)

1.4、等待元素加载

 #waitForSelector 默认为30000(30秒),为0禁用超时
await self.page.waitForSelector('.shop_list .clearfix span.tit_shop',{'timeout': 9000}) #等待元素加载
await asyncio.sleep(2)

1.5、滚动浏览器

使用js滚动到某个元素

 # 使用js滚动到某个元素
await self.page.evaluate('document.querySelector(".page_al").scrollIntoView();')

滚动到浏览器底部

#滚动到浏览器底部
await self.page.evaluate('window.scrollBy(0, document.body.scrollHeight)')

滚动多少像素

#浏览器向上滚动400个像素
await self.page.evaluate('window.scrollBy(0,-400)')

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨痕诉清风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值