Pyppeteer库之四:Pyppeteer的页面操作(下)

执行自定义的 JS 脚本

Pyppeteer Page 对象提供了一系列 evaluate 方法,你可以通过他们来执行一些自定义的 JS 代码,主要提供了下面三个 API:

(1) page.evaluate ( pageFunction [, …args] ) ,返回 pageFunction 执行的结果,pageFunction 表示要在页面执行的函数或表达式, args 表示传入给 pageFunction 的参数

示例:

    await page.goto('https://www.baidu.com')
    # 输出字符串
    await page.evaluate('alert("在浏览器执行js脚本!")')
    # 将元素作为参数传入 page.evaluate
    element = await page.J('#u1>a[name="tj_trtieba"]')
    print(await page.evaluate('el => el.innerHTML', element))
    print(await page.evaluate('el => el.href', element))
    # 执行函数
    el = await page.evaluate('() => document.querySelector("#su").value')
    print(el)

(2) page.evaluateHandle(pageFunction[, …args]) ,此方法和 page.evaluate 的唯一区别是此方法返回的是页内类型 ( JSHandle )

示例:

    await page.goto('https://www.baidu.com')
    el = await page.evaluateHandle('() => document.querySelector("#su").value')
    print(type(el))
    print(el.toString())

(3) page.evaluateOnNewDocument(pageFunction[, …args]) ,指定的函数在所属的页面被创建并且所属页面的任意 script 执行之前被调用。常用于修改页面 JS 环境。

以下为插入中间 JS ,将淘宝会为了检测浏览器而调用的 JS 修改其结果:

import asyncio
from pyppeteer import launch


async def main():
    browser = await launch({
        'headless': False,
        'args': ['--no-sandbox', '--window-size=1366,768']
    })
    page = await browser.newPage()
    await page.setViewport({'width': 1366, 'height': 768})
    await page.evaluateOnNewDocument('''() => {
        Object.defineProperty(navigator, 'webdriver', { get: () => false });
    }''')
    await page.goto('https://login.taobao.com')
    await page.evaluate('alert(navigator.webdriver)')
    await browser.close()


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


元素操作

ElementHandle 表示页内的DOM元素,你可以通过 page.querySelector() 方法创建。DOM 元素具有和 page 相同的某些方法:J()、JJ()、Jeval()、JJeval()、screenshot()、type()、click()、tap()。此外,还有一些好用的方法:

(1) 获取元素边界框坐标:boundingBox(),返回元素的边界框(相对于主框架)=> x 坐标、 y 坐标、width、height

(2) 元素是否可见:isIntersectingViewport()

(3) 上传文件:uploadFile(*filpaths)

(4) ElementHandle 类 转 Frame类:contentFrame(),如果句柄未引用iframe,则返回None。

(5) 聚焦该元素:focus()

(6) 与鼠标相关:hover () ,将鼠标悬停到元素上面

(7) 与键盘相关:press (key[, options]),按键,key 表示按键的名称,option可配置:

  • text (string) - 如果指定,则使用此文本生成输入事件

  • delay (number) - keydown 和 keyup 之间等待的时间。默认是 0



鼠标事件

Mouse 类在相对于视口左上角的主框架 CSS 像素中运行。

(1) page.mouse.down([options]) 按下鼠标,options 可配置:

  • button(str) 按下了哪个键,可选值为 [ left, right, middle ], 默认是 left, 表示鼠标左键

  • clickCount(int) 按下的次数,单击,双击或者其他次数

(2) page.mouse.up([options]) 松开鼠标,options 同上

(3) page.mouse.move(x, y, [options]) 移动鼠标到指定位置,options.steps 表示移动的步长

(4) page.mouse.click(x, y, [options]) 鼠标点击指定的位置,其实是 mouse.move 和 mouse.down 或 mouse.up 的快捷操作



模拟登录的验证码处理

可能用到的方法:

  • ElementHandle.boundingBox()、ElementHandle.hover ()

  • mouse.down()、mouse.move()、mouse.up()、mouse.click()


实例一:淘宝验证码 拖动滑块

(1) 淘宝的验证码验证模块会检测浏览器环境,要注入JS ;

(2) 尽可能模拟用户操作,随机数减慢 Pyppeteer 的执行速度;

示例:

import asyncio
import random
from pyppeteer import launch


async def main():
    browser = await launch({
        'headless': False,
        'args': ['--no-sandbox', '--window-size=1366,768']
    })
    page = await browser.newPage()
    await page.setViewport({'width': 1366, 'height': 768})
    await page.evaluateOnNewDocument('''() =>{ 
        Object.defineProperties(navigator,{ webdriver:{ get: () => false } });
    }''')
    await page.evaluateOnNewDocument('''() =>{ 
        window.navigator.chrome = { runtime: {},  }; 
    }''')
    await page.evaluateOnNewDocument('''() =>{ 
        Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); 
    }''')
    await page.evaluateOnNewDocument('''() =>{ 
        Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); 
    }''')
    await page.goto('https://login.taobao.com')
    await asyncio.sleep(2)
    try:
        await page.click('div.login-links > a.forget-pwd.J_Quick2Static')
    except:
        pass
    await asyncio.sleep(2)
    await page.type('#TPL_username_1', '123123123', {'delay': random.randint(60, 121)})
    await page.type('#TPL_password_1', '1234567890', {'delay': random.randint(100, 151)})
    await asyncio.sleep(1.5)
    try:
        el = await page.querySelector('#nc_1_n1z')
        box = await el.boundingBox()
        await page.hover('#nc_1_n1z')
        await page.mouse.down()
        await page.mouse.move(box['x'] + random.randint(333, 999), box['y'], {'steps': 5})
        await page.mouse.up()
    except:
        pass
    await asyncio.sleep(1.8)
    await page.click('#J_SubmitStatic')
    await asyncio.sleep(5)
    await browser.close()


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


实例二:铁路12306点触验证码

(1) 分析12306的验证码;这个东西是长这样的:

Pyppeteer库之四:Pyppeteer的页面操作(下)_第1张图片
鼠标点击的位置,可以取值各个图片的中心点:

这个值可以计算:

  • width : 3737 * 337 * 537 * 7;即 37,111,185,259
  • height(0):70
  • height(1):70 + (190-30) / 2 ,即 150

当验证码图片的坐标为 x,y 时;鼠标点击第2、7张图片的位置可以表达为 (x+111, y+70),(x+185, y+150)

示例:

import asyncio
import random
from pyppeteer import launch


async def main():
    browser = await launch({
        'headless': False,
        'args': [f'--window-size=1366,768', '--no-sandbox']
    })
    page = await browser.newPage()
    await page.goto('https://kyfw.12306.cn/otn/login/init',
                    {'waitUntil': 'networkidle0'})
    await page.setViewport({'width': 1366, 'height': 768})
    # 等待验证码加载
    code = await page.waitForFunction(
        '''() => document.querySelector("img.touclick-image")''')
    # 验证码截图
    await code.screenshot({'path': 'code.png'})
    # 获取验证码坐标
    box = await code.boundingBox()
    await page.waitFor(2 * 1000)
    # 点击第2张图片
    await page.mouse.click(box['x']+111, box['y']+70)
    await page.waitFor(random.randint(567, 3456))
    # 点击第7张图片
    await page.mouse.click(box['x']+185, box['y']+150)
    await page.waitFor(3 * 1000)
    await browser.close()


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

(2) 打码平台:12306的验证码识别有点反人类;对接打码平台是比较不错的选择;原理就是把验证码图片以字节的方式发给他们,返回一个字符串,例如:183,68|193,161;

超级鹰打码平台API:

chaojiying.py


#!/usr/bin/env python
# coding:utf-8

import requests
from hashlib import md5


class CodeInfo(object):

    def __init__(self):
        self.username = '用户名'
        self.password = md5('密码'.encode('utf8')).hexdigest()
        self.soft_id = '96001'  # 用户中心 >> 软件ID ,生成一个替换 96001
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        self.headers = {
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }


    def process(self, im, codetype):
        url = 'http://upload.chaojiying.net/Upload/Processing.php'
        params = {'codetype': codetype}
        params.update(self.base_params)
        files = {'userfile': ('ccc.jpg', im)}
        r = requests.post(url, data=params, files=files, headers=self.headers)
        return r.json()


    def report(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        url = 'http://upload.chaojiying.net/Upload/ReportError.php'
        params = {'id': im_id}
        params.update(self.base_params)
        r = requests.post(url, data=params, headers=self.headers)
        return r.json()


if __name__ == '__main__':
    im = open('code.png', 'rb').read()
    """
    9004 验证码类型
    参考 http://www.chaojiying.com/price.html
    """
    answer = CodeInfo().process(im, 9004)
    print(answer)

登录12306的例子:

import asyncio
import random
from pyppeteer import launch
from chaojiying import CodeInfo


def pic_info():
    im = open('code.png', 'rb').read()
    answer = CodeInfo().process(im, 9004)
    print(answer)
    return answer['pic_str']


async def main():
    browser = await launch({
        'headless': False,
        'args': ['--window-size=1366,768', '--no-sandbox']
    })
    page = await browser.newPage()
    await page.goto('https://kyfw.12306.cn/otn/login/init',
                    {'waitUntil': 'networkidle0'})
    await page.setViewport({'width': 1366, 'height': 768})
    code = await page.waitForFunction(
        '''() => document.querySelector("img.touclick-image")''')
    await code.screenshot({'path': 'code.png'})
    await page.waitFor(2 * 1000)
    await page.type('#username', '[email protected]',
                    {'delay': random.randint(60, 121)})
    await page.waitFor(random.randint(345, 1234))
    await page.type('#password', '1234567890',
                    {'delay': random.randint(100, 151)})
    pic_str = pic_info()
    points = list(set(pic_str.split('|')))
    box = await code.boundingBox()
    for point in points:
        p = point.split(',')
        await page.mouse.click(box['x']+int(p[0]), box['y']+int(p[1]))
        await page.waitFor(random.randint(567, 3456))
    await page.click('#loginSub')
    await page.waitFor(5 * 1000)
    await browser.close()


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


键盘事件

Keyboard 提供一个接口来管理虚拟键盘. 高级接口为 keyboard.type, 其接收原始字符, 然后在你的页面上生成对应的 keydown, keypress/input, 和 keyup 事件。

为了更精细的控制(虚拟键盘), 你可以使用 keyboard.down, keyboard.up 和 keyboard.sendCharacter 来手动触发事件, 就好像这些事件是由真实的键盘生成的。

键盘的几个API如下:

  • keyboard.down(key[, options]) 触发 keydown 事件
  • keyboard.press(key[, options]) 按下某个键,key 表示键的名称,比如‘ArrowLeft’ 向左键;
  • keyboard.sendCharacter(char) 输入一个字符
  • keyboard.type(text, options) 输入一个字符串
  • keyboard.up(key) 触发 keyup 事件

持续按下 Shift 来选择一些字符串并且删除的例子:
import asyncio
from pyppeteer import launch


async def main():
    browser = await launch({'headless': False})
    page = await browser.newPage()
    await page.goto('https://www.baidu.com', {'waitUntil': 'networkidle0'})
    el = await page.J('#kw')
    await el.focus()
    await page.keyboard.type('Hello, World!')
    await page.keyboard.press('ArrowLeft')
    await page.keyboard.down('Shift')
    for _ in ' World':
        await page.keyboard.press('ArrowLeft')
    await page.keyboard.up('Shift')
    await page.keyboard.press('Backspace')
    # 结果字符串最终为 'Hello!'
    await asyncio.sleep(5)
    await browser.close()


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

按下 A的例子:

    await page.keyboard.down('Shift')
    await page.keyboard.press('KeyA')
    await page.keyboard.up('Shift')

详细的键名映射可以看源码:
Lib\site-packages\pyppeteer\us_keyboard_layout.py



内嵌框架

可以通过 Page.frames、ElementHandle.contentFrame 方法获取,同时具有和 page 多个方法;

**其它:

  • childFrames 获取子框架,返回列表
  • parentFrame 返回父框架
  • content() 返回框架的 html 内容
  • url 获取 url
  • name 获取 name
  • title() 获取 title


例子:

import asyncio
from pyppeteer import launch


async def main():
    browser = await launch({'headless': False})
    page = await browser.newPage()
    await page.goto('http://www.4399.com', {'waitUntil': 'networkidle0'})
    await page.click('#login_tologin')
    await asyncio.sleep(1)
    frame = page.frames[1]
    await frame.type('#username', '123456789')
    await frame.type('#j-password', '998765433')
    await asyncio.sleep(5)
    await browser.close()


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

或者:

    await page.click('#login_tologin')
    await asyncio.sleep(1)
    element = await page.J('iframe')
    frame = await element.contentFrame()

你可能感兴趣的:(python爬虫)