文章目录
介绍
Playwright 是一个用于自动化浏览器操作的开源工具,由 Microsoft 开发和维护。它支持多种浏览器(包括 Chromium、Firefox 和 WebKit)和多种编程语言(如 Python、JavaScript 和 C#),可以用于测试、爬虫、自动化任务等场景。
安装
环境安装
python版本的Playwright官网文档:
https://playwright.dev/python/docs/intro
系统要求:
- Python 3.8 或更高版本。
- Windows 10+、Windows Server 2016+ 或适用于 Linux 的 Windows 子系统 (WSL)。
- MacOS 12 Monterey 或 MacOS 13 Ventura。
- Debian 11、Debian 12、Ubuntu 20.04 或 Ubuntu 22.04。
安装playwright的python版本
- pip install playwright
安装Playwright所需的所有工具插件和所支持的浏览器
- playwright install
- 该步骤耗时较长
屏幕录制
创建一个py文件,比如:main.py
在终端中,执行如下指令:
playwright codegen -o main.py
playwright codegen --viewport-size=800,600 www.baidu.com -o main.py
- 模拟手机设备进行网络请求(只支持手机模拟器,无需单独安装)
- 访问指定网址,并且设置浏览器窗口大小
playwright codegen --device="iPhone 13" -o main.py
保留记录cookie
- 在屏幕录制时,进行登录操作,登录后,cookie信息会被保存到auth.json文件中
playwright codegen --save-storage=auth.json http://download.java1234.com/
- 基于auth.json进行屏幕录制,会自动进入到登录成功后的页面中
playwright codegen --load-storage=auth.json http://download.java1234.com/ -o main.py
基本使用
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# headless 是否是无头浏览器
bro=p.chromium.launch(headless=False)
page=bro.new_page()
# 访问的网站
page.goto("https://www.baidu.com")
# 等待时长
page.wait_for_timeout(1000)
# 获取网页标头
title=page.title()
# 获取网站源码
content=page.content()
print(title,content)
page.close()
bro.close()
元素定位
CSS选择器定位
- 语法结构:page.locator()
- 参数:标签/id/层级/class 选择器
- 交互操作:
- 点击元素,
click()
方法 - 元素内输入文本,
fill()
方法
- 点击元素,
import random
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# headless 是否是无头浏览器
bro = p.chromium.launch(headless=False)
page = bro.new_page()
# 访问的网站
page.goto("https://www.baidu.com")
# 等待时长
page.wait_for_timeout(1000)
# 定位并输入python
page.locator("#kw").fill("python")
# 定位搜索按钮,进行搜索 #id定位 .class定位
page.locator("#su").click()
page.wait_for_timeout(1000)
# 网页回退
page.go_back()
page.wait_for_timeout(1000)
# 标签+属性定位
page.locator("input#kw").fill("人工智能")
page.locator("#su").click()
page.go_back()
page.wait_for_timeout(1000)
# 层级定位
page.locator('#form > span > input#kw').fill('数据分析')
page.locator('#su').click()
page.wait_for_timeout(1000)
page.go_back()
page.wait_for_timeout(1000)
# 聚焦于当前标签
page.locator('#form > span > input#kw').focus()
input_text = 'Hello, World!'
for char in input_text:
page.keyboard.type(char, delay=random.randint(300, 600))
# 定位搜索按钮,进行点击操作
page.locator('#su').click()
page.close()
bro.close()
xpath定位
page.locator(xpath表达式)
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
bro = p.chromium.launch(headless=False,slow_mo=2000)
page = bro.new_page()
page.goto('https://www.bilibili.com/')
#xpath定位
page.locator('//*[@id="nav-searchform"]/div[1]/input').fill('Python教程')
page.locator('//*[@id="nav-searchform"]/div[2]').click()
page.close()
bro.close()
count
const count = await page.locator('div.my-class').count();
console.log(count); // 输出匹配 'div.my-class' 的元素数量
nth(index)
const element =page.locator('button').nth(1); // 获取第二个按钮元素
element.click();
inner_text()
const element = await page.locator('h1');
const text = await element.inner_text();
console.log(text); // 输出 h1 元素的内部文本
get_by_text(xxx)
const element =page.locator('button:text("Click me")'); // 定位包含文本 "Click me" 的按钮
element.click();
get_attribute(attrName)
const element = page.locator('img');
const src = element.get_attribute('src');
console.log(src); // 输出图像的 src 属性值
Context管理多个页面
浏览器的上下文管理对象Context可以用于管理Context打开/创建的多个page页面。并且可以创建多个Context对象,那么不同的Context对象打开/创建的page之间是相互隔离的(每个Context上下文都有自己的Cookie、浏览器存储和浏览历史记录)
使用示例一
from playwright.sync_api import sync_playwright
def swith_page(context, title):
for i in context.pages:
if i.title() == title:
print(title)
i.bring_to_front()
return i
with sync_playwright() as p:
bro = p.chromium.launch(headless=False)
# 多个窗口管理器
context = bro.new_context()
page = context.new_page()
page.goto("https://baidu.com")
a_list = page.locator('//div[@id="s-top-left"]/a').all()
for a in a_list:
a.click()
win = swith_page(context, "hao123_上网从这里开始")
page.wait_for_timeout(10000)
win.locator('/html/body/div[2]/div/div[3]/div[1]/div[1]/form/div[2]/input').fill("hello")
win.locator('/html/body/div[2]/div/div[3]/div[1]/div[1]/form/div[3]/input').click()
page.close()
context.close()
使用示例2
from playwright.sync_api import sync_playwright
from lxml import etree
with sync_playwright()as p:
bro=p.chromium.launch(headless=False,timeout=1000)
context=bro.new_context()
page=context.new_page()
page.goto("https://www.bilibili.com/")
page.locator('//*[@id="nav-searchform"]/div[1]/input').fill("python")
page.locator('//*[@id="nav-searchform"]/div[2]').click()
page.wait_for_timeout(2000)
page=context.pages[1]
tree=etree.HTML(page.content())
name=tree.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[3]/div/div[1]/div/div[2]/div/div/a/h3/text()')
title=tree.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[3]/div/div[1]/div/div[2]/div/div/p/a/span[1]/text()')
print("".join(name),title[0])
滑动验证
基于opencv实现的免费操作:
- pip install opencv-python
- 可能会存在滑动误差,需要手动调整
from playwright.sync_api import sync_playwright
from lxml import etree
from playwright.sync_api import sync_playwright
import cv2
from urllib import request
# 获取要滑动的距离
def get_distance(background, gap):
# 滑动验证码的整体背景图片
background = cv2.imread(background, 0)
# 缺口图片
gap = cv2.imread(gap, 0)
res = cv2.matchTemplate(background, gap, cv2.TM_CCOEFF_NORMED)
# value就是cv2计算出来的滑动距离的像素值
value = cv2.minMaxLoc(res)[2][0]
# 单位换算
return value * 278 / 360
# 有的检测移动速度的 如果匀速移动会被识别出来,来个简单点的渐进
def get_track(distance): # distance为传入的总距离
# 移动轨迹
track = []
# 当前位移
current = 0
# 减速阈值
mid = distance * 4 / 5
# 计算间隔
t = 0.2
# 初速度
v = 1
while current < distance:
if current < mid:
# 加速度为2
a = 4
else:
# 加速度为-2
a = -3
v0 = v
# 当前速度
v = v0 + a * t
# 移动距离
move = v0 * t + 1 / 2 * a * t * t
# 当前位移
current += move
# 加入轨迹
track.append(round(move))
return track
with sync_playwright() as p:
bro = p.chromium.launch(headless=False, timeout=1000)
context = bro.new_context()
page = context.new_page()
page.goto("https://passport.jd.com/new/login.aspx?ReturnUrl=https%3A%2F%2Fwww.jd.com%2F")
page.locator('//*[@id="loginname"]').fill("13795680749")
page.locator('//*[@id="nloginpwd"]').fill("13795680749jcr")
page.locator('//*[@id="loginsubmit"]').click()
page.wait_for_timeout(2000)
bg_img_src = page.locator('.JDJRV-bigimg > img').get_attribute('src')
small_img_src = page.locator('.JDJRV-smallimg > img').get_attribute('src')
request.urlretrieve(bg_img_src, "bg_img_src.png")
request.urlretrieve(small_img_src, "small_img_src.png")
value = int(get_distance("./bg_img_src.png", "./small_img_src.png"))
tracks=get_track(value)
# 找到滑块在当前页面的坐标(这个会返回一个字典里边四个数字)
# {'x': 858, 'y': 339.9921875, 'width': 55, 'height': 55}
box=page.locator('//*[@id="JDJRV-wrap-loginsubmit"]/div/div/div/div[2]/div[3]').bounding_box()
# 让鼠标移动到滑块标签的中间上
page.mouse.move(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
# 按下鼠标
page.mouse.down()
x = box["x"] + 14 # 加上一个数值调整滑动误差
# 滑动的长度放到轨迹加工一下得到一个轨迹
for track in tracks:
# 循环鼠标按照轨迹移动
page.mouse.move(x + track, 0)
x += track
# 移动结束鼠标释放
page.mouse.up()
page.wait_for_timeout(5000)
page.close()
bro.close()
文字点击验证码
图鉴验证码封装
import base64
import json
import requests
# 一、图片文字类型(默认 3 数英混合):
# 1 : 纯数字
# 1001:纯数字2
# 2 : 纯英文
# 1002:纯英文2
# 3 : 数英混合
# 1003:数英混合2
# 4 : 闪动GIF
# 7 : 无感学习(独家)
# 11 : 计算题
# 1005: 快速计算题
# 16 : 汉字
# 32 : 通用文字识别(证件、单据)
# 66: 问答题
# 49 :recaptcha图片识别
# 二、图片旋转角度类型:
# 29 : 旋转类型
#
# 三、图片坐标点选类型:
# 19 : 1个坐标
# 20 : 3个坐标
# 21 : 3 ~ 5个坐标
# 22 : 5 ~ 8个坐标
# 27 : 1 ~ 4个坐标
# 48 : 轨迹类型
#
# 四、缺口识别
# 18 : 缺口识别(需要2张图 一张目标图一张缺口图)
# 33 : 单缺口识别(返回X轴坐标 只需要1张图)
# 五、拼图识别
# 53:拼图识别
def base64_api(uname, pwd, img, typeid):
with open(img, 'rb') as f:
base64_data = base64.b64encode(f.read())
b64 = base64_data.decode()
data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
if result['success']:
return result["data"]["result"]
else:
# !!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别
return result["message"]
return ""
案例
from playwright.sync_api import sync_playwright
from lxml import etree
from playwright.sync_api import sync_playwright
import cv2
from urllib import request
from demo import base64_api
with sync_playwright() as p:
bro = p.chromium.launch(headless=False, timeout=1000)
context = bro.new_context()
page = context.new_page()
page.goto("https://passport.bilibili.com/login")
# 登录输入
page.locator('//*[@id="app"]/div[2]/div[2]/div[3]/div[2]/div[1]/div[1]/input').fill("13795680749")
page.locator('//*[@id="app"]/div[2]/div[2]/div[3]/div[2]/div[1]/div[3]/input').fill("13795680749")
page.locator('//*[@id="app"]/div[2]/div[2]/div[3]/div[2]/div[2]/div[2]').click()
ele=page.locator('//div[@class="geetest_widget"]')
ele.screenshot(path="./code.png")
# 获取ele的当前坐标
box=ele.bounding_box()
result = base64_api(uname='amxr', pwd='Admin123', img="./code.png", typeid=27)
# result = '154,251|145,167'
result_list = result.split('|')
# 6.根据识别出验证码的结果进行处理
for pos in result_list:
x = int(pos.split(',')[0])
y = int(pos.split(',')[1])
# page.mouse.move(box['x']+x,box['y']+y)
# page.mouse.down()
# page.mouse.up() #或者直接使用click方式
page.mouse.click(box['x'] + x, box['y'] + y)
page.wait_for_timeout(500)
# 找到确认按钮
submit = page.locator('//div[@class="geetest_commit_tip"]')
submit.click()
page.wait_for_timeout(5000)
page.close()
bro.close()
规避检测
如果网站有对Playwright采取监测机制的话,比如正常情况下我们用浏览器访问淘宝等网站的 window.navigator.webdriver的值为 undefined或者为false。而使用Playwright访问则该值为true。
- 查看taobao是否检测Playwright
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
bro = p.chromium.launch(headless=False)
page = bro.new_page()
page.goto('https://www.taobao.com/')
ret = page.evaluate('window.navigator.webdriver')
print(ret)
page.wait_for_timeout(30000)
page.close()
bro.close()
- 实现js注入,绕过检测
stealth.min.js 下载:
stealth.min.js下载
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
bro = p.chromium.launch(headless=False)
context = bro.new_context()
#加载该js文件目的是给Playwright的浏览器模拟真实的浏览器环境
context.add_init_script(path='./stealth.min.js')
page = context.new_page()
page.goto('https://www.taobao.com/')
ret = page.evaluate('window.navigator.webdriver')
print(ret)
page.wait_for_timeout(30000)
page.close()
bro.close()
浏览器接管
window版本
- 首先右键 Chrome 浏览器桌面图标,找到 chrome.exe 的安装路径,然后将其添加到环境变量Path中
添加环境变量
在任意目录下新建一个空白文件夹(playwright_chrome_data):用于保存接管的浏览器的运行数据。
打开cmd输入命令启动chrome浏览器:
chrome.exe --remote-debugging-port=8899 --user-data-dir="E:\playwright_chrome_data"
–remote-debugging-port 是指定浏览器运行端口,只要没被占用就行
–user-data-dir 指定运行浏览器的运行数据,新建一个干净目录,不影响系统原来的数据
- 执行后会启动chrome浏览器
- 可以在浏览器中手动访问百度。
- 在你已经打开的浏览器页面,手工操作登录,登录成功后,让playwright 继续操作。
- Playwright接管浏览器
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp('http://localhost:8899/')
# 获取page对象
page = browser.contexts[0].pages[0]
#该操作会直接作用在接管的浏览器中
page.locator('//*[@id="kw"]').fill('haha')
print(page.url)
print(page.title())
JS注入
url:https://www.ciweimao.com/chapter/109936632
分析:
点击下一章,简单分析可知小说内容是动态加载的数据。查看数据包的fetch/xhr选项卡,会不会到ajax_get_session_code和get_book_chapter_detail_info两个数据包。根据抓包工具分析,get_book_chapter_detail_info数据包中存在chapter_content为小说章节内容,但是数据为加密后的形式。
全局搜索chapter_content关键字,定位到了数据解密环节的ajax代码。然后在ajax代码的首位各自打上两个断点进行调试。最终发现如下代码就是解密操作:其中messageInfo就是密文数据
$.myDecrypt({ content: messageInfo, keys: keys, accessKey: HB.config.chapterAccessKey })
因此可以在Playwright中基于补充浏览器环境和js注入的机制进行解密js代码测试:
from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=True) context = browser.new_context() #规避检测,伪造真实浏览器环境 context.add_init_script(path='stealth.min.js') page = context.new_page() page.goto("https://www.ciweimao.com/chapter/109936632") page.wait_for_timeout(3000) # #进行js注入,执行解密js代码 encrypt_data = page.evaluate('$.myDecrypt({content: messageInfo,keys: keys,accessKey: HB.config.chapterAccessKey})') print(encrypt_data)