使用实现一个浏览器自动化的脚本
更新的时间是, 二零二四年四月二十九日, 上午十点三十四分二十二秒, 作者是进阶的小叮当。
最近, 工作里存在这样的需求, 客户反馈, 于浏览器操作进程中, 重复流程的操作太过频繁, 有无可能让浏览器自动将操作予以完成, 所以, 本文为大家介绍了怎样运用来实现一个浏览器自动化的脚本, 感兴趣的朋友能够参考一下。
背景
近期于工作里存在这般一个需求, 客户反馈, 于浏览器操作这一过程当中, 重复性流程操作过于频繁, 可否让浏览器依靠自动执行来达成操作的完成呢?
于我的认知来讲, 浏览器乃是一种用以浏览页面的工具, 所以第一反应便是"绝无可能!绝对毫无可能!"。领导询问, 你是否了解过RPA? 会后特意去查阅了相关资料, 依我的理解, RPA是一个定制化机器人(脚本流程), 借助控制脚本去操作显示器, 进而模拟用户操作的过程。
开始
刚一开始之际, 我于那上面寻觅了好些开源的项目, 像PyRPA、RPA-、TagUI等等, 然而鉴于定制化程度颇高, 往后不知会演变成何种状况, 所以做的时候得灵活一点儿。不过在这些开源项目当中, 给出了一部分的api, 可在某些地方并非特别容易操作, 感觉后续没准会不受控制。
可是在我的观察里察觉到, 绝大多数的达成都是借着达成来搞定的, 身为一名前端人员, 最初的反应便是, 为何不把node跟js搭配起来呢, 到后来才回过神来, 运用能操控整部PC, 然而要是运用node或者js的话, 只能说操控浏览器会更便利一点了(实际上跟差不多), 只是在运用第三方库之际, 会存有一定程度的延迟。
所以, 我作出决定, 将现有的RPA项目予以放弃, 自行去实现一个自动化的脚本。
语言选择
关于我所选择的作为语言的缘由, 实际上在之前已然讲过了, 身为一名前端开发人员, 其实最为熟悉的莫过于, 再额外增添一些, 那么何以我未曾运用呢? node在前端开发进程当中, 常常充当着一种环境的角色, 虽说也能够作为一种后端语言去编写出一些接口亦或是服务(依赖于第三方库), 然而甚少会应用到DOM的操作, 对于操作浏览器的DOM而言, 我们通常来讲, 皆是运用js直接进行操作, 亦或是借助等第三方工具。要是再次直接去拦截系统的某些操作, js便不太可行了。
于是, 选取了, 这样一门热度始终居高不下的语言。核心思路在于, 对图片进行对照, 从而获取点位, 进而模拟用户鼠标、键盘等各类操作。
浏览器的启动
本来, 开启浏览器这一行为之初, 我仅仅就只是简简单单地把浏览器给打开而已, 然而随后却察觉到, 每一天首次开启之时, 速率都会相当迟缓, 去查阅材料也未曾寻觅到缘由, 似乎是缓存被清除掉了, 在网络上寻觅了海量的相关材料资料, 最终寻觅到了一个办法:于加载之际启动指定的驱动程序(浏览器版本跟驱动程序得相匹配), 新版驱动进行下载, 114版本之前的驱动进行下载, 有一部分页面得借助爬墙上网才能够访问以及下载, 建议大伙爬墙过去上网。
一个开启浏览器的示例, 在此处的我于后续便不再作重复的界定了, 当即予以运用了, 就像:
brush:py;
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
driver = webdriver.Chrome(service=Service(r"C:\\tb2Lot\\driver\\chromedriver.exe"))
driver.maximize_window()
driver.execute_script("document.charset='utf-8';")
driver.get("http://www.baidu.com")
在这个时候,我们已然能够将浏览器打开, 并且对浏览器展开操作, 借此将百度的页面调取出来, 那么, 倘若我们想要于百度的页面当中输入以及搜索特定的内容, 该如何去做? 在这个时候, 我们便需要获取页面的元素节点, 随后进行输入了, 接下来, 我们一同来看一下怎样在浏览器进行搜索!
在百度进行搜索的方案
首先我们先列举一下目前能够想到的两个方案:
对于简单操作而言, 这两种方案是可行的, 可一旦操作变得复杂些, 就将会出现若干问题了, 于是让我们先去查看一下这两种方案各自是怎样进行操作的, 随后再来剖析一下会遭遇怎样一系列的问题吧!
页面抓取元素,直接点击:

brush:py;
import pyautogui
import pyperclip
import time
from selenium.webdriver.common.by import By
def cvEnter(msg):
pyperclip.copy(msg)
time.sleep(1)
pyautogui.hotkey('ctrl','v')
time.sleep(1)
pyautogui.hotkey('enter') #回车搜索
#driver的定义在本文最上面,这里没有重复写
#...
cvEnter("稀土掘金")
driver.find_element(By.XPATH,r'//*[@id="su"]').click() #通过浏览器的xpath触发点击事件
time.sleep(2)
driver.find_element(By.XPATH,r'//*[@id="kw"]').clear() #清除输入框
driver.find_element(By.XPATH,r'//*[@id="kw"]').send_keys('稀土掘金官网') #输入框输入数据
pyautogui.hotkey('enter')
time.sleep(5)
在上面,其实我们已经使用了两种输入与搜索的方式:
经由回车以及点击来实行的输入搜索, 借助图片之间的对比, 去获取点位而后予以点击。
brush:py;
import pyautogui
import cv2
def getPosiXY(img):
pyautogui.screenshot('./pics/screen.png') #显示完成之后,截图
screen = cv2.imread('./pics/screen.png') #加载全屏截图
current = cv2.imread(f'./tb2lot_pic/{img}') #加载比对的图片
result = cv2.matchTemplate(screen, current, cv2.TM_CCOEFF_NORMED) #在screen中寻找current的点位
pos_start = cv2.minMaxLoc(result)[3]
x = int(pos_start[0]) + int(current.shape[1]/2)
y = int(pos_start[1]) + int(current.shape[0]/2)
return x,y
pointX, pointY = getPosiXY("query_btn.png")
pyautogui.click(pointX, pointY)
在上面那场演示当中, 我们凭借cv2这个库, 达成了一项内容为在一张尺寸比较大的图形里寻觅一张尺寸相较于其小的另一张图形的功能事项, 这里面存在这样一种情况,先是自动截取屏幕画面, 并且把截得的画面保存在指定的目录范围之内, 将尺寸较小那张图形的名称当作参数, 传至被包裹打包起来的方法里面, 从中抓取点位方面的信息, 而后点击获取到的坐标处所位置。鉴于我们所抓取到的坐标点情形, 所以要是点击那般的输入框, 在输入框里粘贴数据, 那就得径直借助粘贴板去实施粘贴操作才行进行粘贴活动了。
为什么我们前面说抓取元素会有问题呢?
起先, 我们最为理想的元素抓取情形便是, 全都处于一个html里, 不存在动态的元素, 不存在切换界面的状况, 不存在切换弹窗之类的情况, 然而在实际的应用进程当中, 通常都是颇为复杂的, 比如:
以上这些问题, 在直接着手去抓取元素xpath然后直接进行调取之际, 会出现报错情况, 难道说我们真的就绝对没办法运用xpath来开展操作了吗? 答案显然是否定的, 然而这也并非是完全如此, 举例来说, 在其中, 我们能够进行句柄的切换, 以此来达成代码引用模块的替换, 就像这样:
brush:py;
#driver的定义在本文最上面,这里没有重复写
#...
iframeSrc = driver.find_element(By.XPATH,r'/html/body/div/iframe')
driver.switch_to.frame(iframeSrc) # 切换句柄到iframe
#driver.switch_to.default_content() #切换到默认的句柄
driver.find_element(By.XPATH,r'/html/body/form/div/input').click()
这般看来, 我们亦是能够抓取到元素的, 然而得要等待页面渲染完毕之后再去进行抓取, 不然仍然会报错的。其余的那两个, 鉴于元素是动态的, 要是仅仅是id的问题的话, 我们能够运用full xpath来进行抓取, 可是要是元素嵌套皆存在可能变化的情况的话, 那就只得采用截图比对的方式了。
此外, 我们还要再增添一段代码, 比如说, 当开启浏览器两个标签页之际如何处理, 而后标签页之间进行切换又该怎么处理:
brush:py;
driver.get("http://www.baidu.com") #打开标签1
driver.execute_script("window.open('http://www.baidu.com');") #打开标签2
driver.switch_to.window(driver.window_handles[0]) # 手动切换到标签1
最后
实际上, 到了这个地方, 那种简单的浏览器控制已然是结束了, 实际上, 图片的对比可不就限定于浏览器, 它是那种能够截取PC屏幕所有内容的设定情况, 所以呢也是能够去模拟APP操作状况的, 然而, 咱在这调取的进程当中, 常常会出现报错的情形, 可是, 要是给打包成.exe文件之后, 咱却全然不知到底报的是啥错误, 因而, 咱也能够在最外层添加上try -这样子的设置, 如果检测到出现了报错, 咱能够试着将报错的具体内容记在txt文档里面, 进而模拟日志呈现出来的那种效果, 举个例子说哈:
brush:py;
from datetime import datetime, timedelta
try:
# some codes
except Exception as e:
now = datetime.now()
date_time = now.strftime("%Y-%m-%d %H:%M:%S")
data_to_append = f"{date_time}\n{e}\n\n"
with open('C:\\logs\\error.txt', 'a') as file:
file.write(data_to_append)
print("Error!", e)
driver.quit()
raise ValueError("Error!")
最后打开预览报错信息,例如:

代码
brush:py;
import pyautogui
import pyperclip
import time
from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from datetime import datetime, timedelta
def cvEnter(msg):
pyperclip.copy(msg)
time.sleep(1)
pyautogui.hotkey('ctrl','v')
time.sleep(1)
pyautogui.hotkey('enter')
try:
driver = webdriver.Chrome(service=Service(r"C:\\pyExe\\driver\\chromedriver.exe")) #驱动写在最上面
driver.maximize_window()
driver.execute_script("document.charset='utf-8';")
driver.get("http://www.baidu.com")
driver.implicitly_wait(10)
cvEnter("稀土掘金")
driver.find_element(By.XPATH,r'//*[@id="su"]').click() #通过浏览器的xpath触发点击事件、
time.sleep(2)
driver.find_element(By.XPATH,r'//*[@id="kw"]').clear()
driver.find_element(By.XPATH,r'//*[@id="kw"]').send_keys('稀土掘金官网')
pyautogui.hotkey('enter')
time.sleep(5)
except Exception as e:
now = datetime.now()
date_time = now.strftime("%Y-%m-%d %H:%M:%S")
data_to_append = f"{date_time}\n{e}\n\n"
with open('C:\\pyExe\\logs\\error.txt', 'a') as file:
file.write(data_to_append)
print("Error!", e)
driver.quit()
raise ValueError("Error!")
代码打包
brush:bash;
pyinstaller --onefile --noconsole --icon ./favorite.ico ./index.py
# --noconsole 隐藏黑窗口
# --icon ./favorite.ico 打包后的应用图标(路径选对,仅支持ico)
# ./index.py 需要打包的py文件路径
些许时候打完备图标变未变, 作默认图标, 能查看属性, 属性里理应已有, 为此系应用缓存缘故, 刷新下应用程序便可。
重中之重!!!
完成打包之后, 把打包后的dist/index.exe这个文件, 移动到之前index.py处于的同级目录之下, 原因是要是涉及到图片的比对, 就会致使图片的位置访问出现失败的情况。