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

tech2022-10-13  95

执行自定义的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('#ul>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(*filepaths)

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

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

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

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

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的验证码;这个东西是长这样的:

鼠标点击的位置,可以取值各个图片的中心点:

这个值可以计算:

width: 37, 37 * 3, 37 * 5, 37 * 7; 即37, 111, 185, 259height(0): 70height(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', '123456789@qq.com', {'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.press('ArrowLeft') await page.keyboard.up('Shift') await page.keyboard.press('Backspcae') # 结果字符串最终为'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 获取 urlname 获取 nametitle() 获取 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()

 

最新回复(0)