别再手动操作了!用Python自动化搞定一切重复工作,效率提升10倍
你是不是经常遇到这些情况?
- 批量录入数据,眼睛一行行盯着Excel核对 如果你点头了,那这篇文章就是为你准备的。
今天分享一个我用了5年的Python库—— PyAutoGUI ,它能帮你自动控制鼠标和键盘,把那些机械重复的操作全部交给程序去做。
一、PyAutoGUI 简介
PyAutoGUI 是一个用于自动化控制鼠标和键盘的 Python 库,可以模拟用户的操作,实现 GUI 自动化任务。它支持 Windows、macOS 和 Linux 系统,是自动化测试、重复性任务处理的绝佳工具。
安装方法
pip install pyautogui
可选依赖:
opencv-python:支持 confidence 参数进行图像识别pillow:图像处理(PyAutoGUI 会自动安装)
基础配置
import pyautogui
# 设置操作间隔(秒),防止操作过快
pyautogui.PAUSE = 1
# 设置失败安全功能,移动鼠标到左上角可中断脚本(默认开启)
pyautogui.FAILSAFE = True
# 获取屏幕分辨率(仅主显示器)
screenWidth, screenHeight = pyautogui.size()
print(f"屏幕分辨率: {screenWidth} x {screenHeight}")
二、入门教程
2.1 鼠标控制
import pyautogui
# 获取当前鼠标位置
x, y = pyautogui.position()
print(f"当前鼠标位置: ({x}, {y})")
# 移动鼠标到指定位置(绝对坐标)
pyautogui.moveTo(100, 200, duration=0.5)
# 相对移动(从当前位置偏移)
pyautogui.move(50, -30, duration=0.3)
# 单击
pyautogui.click()
# 双击
pyautogui.doubleClick()
# 右键单击
pyautogui.rightClick()
# 鼠标拖拽(绝对坐标)
pyautogui.dragTo(300, 400, duration=0.5, button='left')
# 鼠标拖拽(相对坐标)
pyautogui.drag(50, 0, duration=0.2, button='left')
# 滚轮滚动(正数向上,负数向下)
pyautogui.scroll(10) # 向上滚动
pyautogui.scroll(-10) # 向下滚动
注意:鼠标移动的 duration 如果小于 pyautogui.MINIMUM_DURATION(默认 0.1 秒),移动会是瞬时的。
2.2 键盘控制
import pyautogui
# 输入文本(支持 interval 参数设置按键间隔)
pyautogui.write('Hello, PyAutoGUI!')
pyautogui.write('Hello', interval=0.25) # 每个按键间隔 0.25 秒
# 按下和释放键
pyautogui.keyDown('shift')
pyautogui.press('a')
pyautogui.keyUp('shift')
# 组合键(hotkey 会自动处理按下和释放顺序)
pyautogui.hotkey('ctrl', 'c') # 复制
pyautogui.hotkey('ctrl', 'v') # 粘贴
# 特殊按键
pyautogui.press('enter')
pyautogui.press('tab')
pyautogui.press('backspace')
pyautogui.press('escape')
# 按住某个键执行多个操作
with pyautogui.hold('shift'):
pyautogui.press(['left', 'left', 'left']) # 选中左侧3个字符
说明:pyautogui.typewrite() 是旧版函数名,新版本推荐使用 pyautogui.write(),两者功能相同。
2.3 屏幕截图
import pyautogui
# 截取整个屏幕,返回 PIL Image 对象
screenshot = pyautogui.screenshot()
screenshot.save('full_screen.png')
# 截取指定区域 (left, top, width, height)
screenshot = pyautogui.screenshot(region=(0, 0, 300, 400))
screenshot.save('region.png')
# 截取并同时保存到文件
pyautogui.screenshot('my_screenshot.png')
# 查找图像位置
# 注意:PyAutoGUI 0.9.41+ 版本中,找不到图像会抛出 ImageNotFoundException
try:
location = pyautogui.locateOnScreen('button.png')
x, y = pyautogui.center(location)
pyautogui.click(x, y)
except pyautogui.ImageNotFoundException:
print("未找到目标图像")
# 使用置信度匹配(需要安装 opencv-python)
# confidence 越低,匹配越宽松
# 注意:未安装 OpenCV 时使用 confidence 会抛出 NotImplementedError
try:
location = pyautogui.locateOnScreen('button.png', confidence=0.8)
print(f"找到图像,位置: {location}")
except pyautogui.ImageNotFoundException:
print("未找到目标图像")
except NotImplementedError:
print("未安装 OpenCV,confidence 参数不可用")
print("请运行: pip install opencv-python")
三、有趣案例
案例1:自动画画
import pyautogui
import time
# 打开画图软件(Windows)
pyautogui.hotkey('win', 'r')
pyautogui.write('mspaint')
pyautogui.press('enter')
time.sleep(2)
# 点击画布区域准备绘画
pyautogui.click(300, 300)
# 画一个正方形螺旋
distance = 200
while distance > 0:
pyautogui.drag(distance, 0, duration=0.2) # 向右
distance -= 5
pyautogui.drag(0, distance, duration=0.2) # 向下
pyautogui.drag(-distance, 0, duration=0.2) # 向左
distance -= 5
pyautogui.drag(0, -distance, duration=0.2) # 向上
案例2:鼠标追逐游戏
import pyautogui
import random
import time
screenWidth, screenHeight = pyautogui.size()
print("移动鼠标追逐目标!按 Ctrl+C 退出")
try:
whileTrue:
# 随机生成目标位置
targetX = random.randint(50, screenWidth - 50)
targetY = random.randint(50, screenHeight - 50)
# 获取当前鼠标位置
currentX, currentY = pyautogui.position()
# 计算距离
distance = ((targetX - currentX) ** 2 + (targetY - currentY) ** 2) ** 0.5
if distance < 30:
print(f"抓到了!目标位置: ({targetX}, {targetY})")
time.sleep(1)
else:
print(f"距离目标: {int(distance)} 像素", end='\r')
time.sleep(0.1)
except KeyboardInterrupt:
print("\n游戏结束")
案例3:自动玩Flappy Bird
import pyautogui
import time
print("3秒后开始...请切换到游戏窗口")
time.sleep(3)
# 游戏主循环
try:
whileTrue:
# 截取游戏区域(根据实际游戏位置调整坐标)
# 只截取需要检测的区域,提高效率
screenshot = pyautogui.screenshot(region=(400, 300, 100, 150))
# 检测小鸟前方是否有障碍物
found_obstacle = False
width, height = screenshot.size
for x in range(width):
for y in range(height):
# 检测深色像素(障碍物,根据实际游戏颜色调整)
pixel = screenshot.getpixel((x, y))
# 判断是否为深色
if pixel[0] < 50and pixel[1] < 50and pixel[2] < 50:
found_obstacle = True
break
if found_obstacle:
break
if found_obstacle:
pyautogui.press('space') # 跳跃
time.sleep(0.3) # 跳跃后等待一段时间
time.sleep(0.05)
except KeyboardInterrupt:
print("游戏结束")
说明:需要根据实际游戏窗口位置调整 region 参数和颜色检测阈值。
四、实用案例
案例1:自动填写表单
import pyautogui
import time
deffill_form(data):
# 点击姓名输入框
pyautogui.click(200, 150)
pyautogui.write(data['name'])
# 按 Tab 切换到邮箱输入框
pyautogui.press('tab')
pyautogui.write(data['email'])
# 按 Tab 切换到电话输入框
pyautogui.press('tab')
pyautogui.write(data['phone'])
# 按 Tab 切换到提交按钮,回车提交
pyautogui.press('tab')
pyautogui.press('enter')
# 示例数据
form_data = {
'name': '张三',
'email': 'zhangsan@example.com',
'phone': '13800138000'
}
print("请在3秒内切换到表单页面...")
time.sleep(3)
fill_form(form_data)
提示:对于中文输入,建议使用 pyperclip 复制粘贴,因为 write() 只支持英文输入。
案例2:定时自动截图工具
自动定时截取屏幕,按日期整理保存到文件夹,可用于记录工作过程、监控屏幕变化等。
import pyautogui
import os
import time
from datetime import datetime
classAutoScreenshot:
"""定时自动截图工具"""
def__init__(self, save_dir='~/Pictures/AutoScreenshots', interval=60, max_screenshots=None):
"""
初始化截图工具
:param save_dir: 截图保存目录
:param interval: 截图间隔(秒)
:param max_screenshots: 最大截图数量,None 表示不限制
"""
self.save_dir = os.path.expanduser(save_dir)
self.interval = interval
self.max_screenshots = max_screenshots
self.screenshot_count = 0
self.running = False
# 创建保存目录
os.makedirs(self.save_dir, exist_ok=True)
defget_today_folder(self):
"""获取当天的文件夹路径,不存在则创建"""
today = datetime.now().strftime('%Y-%m-%d')
folder = os.path.join(self.save_dir, today)
os.makedirs(folder, exist_ok=True)
return folder
deftake_screenshot(self, region=None):
"""
截取屏幕并保存
:param region: 截图区域 (left, top, width, height),None 表示全屏
:return: 保存的文件路径
"""
# 生成文件名:时分秒.png
timestamp = datetime.now().strftime('%H-%M-%S')
folder = self.get_today_folder()
filename = f"{timestamp}.png"
filepath = os.path.join(folder, filename)
# 截取屏幕
if region:
screenshot = pyautogui.screenshot(region=region)
else:
screenshot = pyautogui.screenshot()
# 保存图片
screenshot.save(filepath)
self.screenshot_count += 1
print(f"[{self.screenshot_count}] 已保存: {filepath}")
return filepath
defcompare_screenshots(self, img1_path, img2_path, threshold=10):
"""
比较两张截图的差异
:param img1_path: 第一张图片路径
:param img2_path: 第二张图片路径
:param threshold: 差异阈值(0-100,值越小越敏感)
:return: 是否有显著差异
"""
try:
from PIL import Image
import numpy as np
img1 = Image.open(img1_path).convert('L')
img2 = Image.open(img2_path).convert('L')
# 调整为相同大小
img2 = img2.resize(img1.size)
arr1 = np.array(img1)
arr2 = np.array(img2)
# 计算差异百分比
diff = np.abs(arr1 - arr2)
diff_percent = (diff > 30).mean() * 100
return diff_percent > threshold
except ImportError:
print("提示:安装 numpy 和 Pillow 可启用差异检测")
returnTrue
defstart(self, region=None, detect_change=False):
"""
开始定时截图
:param region: 截图区域,None 表示全屏
:param detect_change: 是否只在屏幕变化时才保存(需要 numpy + Pillow)
"""
print(f"自动截图工具已启动")
print(f"保存目录: {self.save_dir}")
print(f"截图间隔: {self.interval} 秒")
if self.max_screenshots:
print(f"最大截图数: {self.max_screenshots}")
print(f"按 Ctrl+C 停止\n")
self.running = True
last_screenshot_path = None
try:
while self.running:
# 检查是否达到最大截图数量
if self.max_screenshots and self.screenshot_count >= self.max_screenshots:
print(f"已达到最大截图数量 {self.max_screenshots},停止")
break
# 截图
current_path = self.take_screenshot(region=region)
# 如果开启了变化检测且不是第一张
if detect_change and last_screenshot_path:
has_changed = self.compare_screenshots(
last_screenshot_path, current_path
)
ifnot has_changed:
# 没有变化就删除当前截图
os.remove(current_path)
self.screenshot_count -= 1
print("屏幕无变化,跳过保存")
else:
last_screenshot_path = current_path
else:
last_screenshot_path = current_path
# 等待下一次截图
time.sleep(self.interval)
except KeyboardInterrupt:
print(f"\n用户停止,共保存 {self.screenshot_count} 张截图")
finally:
self.running = False
# 使用示例
if __name__ == '__main__':
# 创建截图工具:每60秒截一次,最多截100张
screener = AutoScreenshot(
save_dir='~/Pictures/AutoScreenshots',
interval=60,
max_screenshots=100
)
# 开始截图(全屏)
screener.start()
# 只截取指定区域
# screener.start(region=(0, 0, 800, 600))
# 只在屏幕变化时保存(节省空间)
# screener.start(detect_change=True)
使用场景:
案例3:自动化数据批量录入工具
从 Excel/CSV 表格读取数据,自动填入表单系统(如OA、CRM、内部管理系统等)。
import pyautogui
import pandas as pd
import time
import os
from datetime import datetime
classDataEntryAutomation:
"""自动化数据批量录入工具"""
def__init__(self, data_file, form_config):
"""
初始化数据录入工具
:param data_file: 数据源文件路径(Excel或CSV)
:param form_config: 表单配置字典
"""
self.data_file = data_file
self.form_config = form_config
self.data = None
self.success_count = 0
self.fail_count = 0
# 检测 OpenCV 支持
try:
import cv2
self.has_opencv = True
except ImportError:
self.has_opencv = False
print("提示:未安装 OpenCV,confidence 参数不可用")
# 加载数据
self._load_data()
def_load_data(self):
"""加载数据文件"""
ifnot os.path.exists(self.data_file):
raise FileNotFoundError(f"数据文件不存在: {self.data_file}")
ext = os.path.splitext(self.data_file)[1].lower()
if ext == '.csv':
self.data = pd.read_csv(self.data_file)
elif ext in ['.xlsx', '.xls']:
self.data = pd.read_excel(self.data_file)
else:
raise ValueError(f"不支持的文件格式: {ext}")
print(f"成功加载 {len(self.data)} 条数据")
def_safe_click(self, x, y, duration=0.5):
"""安全点击,带失败重试"""
pyautogui.moveTo(x, y, duration=duration)
pyautogui.click()
def_safe_input(self, text, interval=0.05):
"""安全输入文本"""
# 清除现有内容
pyautogui.hotkey('ctrl', 'a')
pyautogui.write(str(text), interval=interval)
def_find_and_click_image(self, image_path, confidence=0.8, timeout=10):
"""
查找图像并点击(带超时和降级处理)
:param image_path: 图像路径
:param confidence: 匹配置信度
:param timeout: 超时时间(秒)
:return: 是否成功
"""
start_time = time.time()
while time.time() - start_time < timeout:
try:
if self.has_opencv and confidence:
location = pyautogui.locateOnScreen(image_path, confidence=confidence)
else:
location = pyautogui.locateOnScreen(image_path)
if location:
x, y = pyautogui.center(location)
pyautogui.click(x, y)
returnTrue
except pyautogui.ImageNotFoundException:
time.sleep(0.5)
except NotImplementedError:
# OpenCV 不可用,降级为精确匹配
try:
location = pyautogui.locateOnScreen(image_path)
if location:
x, y = pyautogui.center(location)
pyautogui.click(x, y)
returnTrue
except pyautogui.ImageNotFoundException:
time.sleep(0.5)
returnFalse
def_wait_for_element(self, image_path, timeout=30):
"""等待元素出现"""
return self._find_and_click_image(image_path, timeout=timeout)
def_fill_single_record(self, row):
"""
填写单条记录
:param row: pandas Series 行数据
:return: 是否成功
"""
try:
# 遍历表单配置,填写每个字段
for field_name, config in self.form_config.items():
field_value = row.get(field_name, '')
if config['type'] == 'input':
# 输入框
x, y = config['position']
self._safe_click(x, y)
time.sleep(0.2)
self._safe_input(field_value, interval=0.05)
time.sleep(0.1)
elif config['type'] == 'dropdown':
# 下拉选择
x, y = config['position']
self._safe_click(x, y)
time.sleep(0.3)
# 查找选项
option_index = config['options'].get(str(field_value), 0)
# 根据实际情况调整按键
for _ in range(option_index):
pyautogui.press('down')
pyautogui.press('enter')
time.sleep(0.1)
elif config['type'] == 'checkbox':
# 复选框
if str(field_value).lower() in ['true', '1', 'yes', '是']:
x, y = config['position']
self._safe_click(x, y)
time.sleep(0.1)
elif config['type'] == 'button':
# 按钮点击
if config.get('before_fill'):
# 点击按钮打开输入框
self._find_and_click_image(config['image'])
time.sleep(0.5)
# 提交按钮
if'submit'in self.form_config:
time.sleep(0.5)
submit_config = self.form_config['submit']
if submit_config.get('type') == 'image':
self._find_and_click_image(submit_config['image'])
else:
x, y = submit_config['position']
self._safe_click(x, y)
time.sleep(1)
returnTrue
except Exception as e:
print(f"填写失败: {e}")
returnFalse
defrun(self, start_row=0, end_row=None, pause_between=2):
"""
执行批量录入
:param start_row: 起始行(0索引)
:param end_row: 结束行,None 表示到最后
:param pause_between: 每条记录间隔(秒)
"""
total = len(self.data) if end_row isNoneelse min(end_row, len(self.data))
print(f"\n开始批量录入,第 {start_row + 1} - {total} 条记录")
print("按 Ctrl+C 可随时停止\n")
try:
for i in range(start_row, total):
row = self.data.iloc[i]
print(f"[{i + 1}/{total}] 正在录入: {row.get('name', row.to_dict())}")
if self._fill_single_record(row):
self.success_count += 1
print(f" ✓ 成功")
else:
self.fail_count += 1
print(f" ✗ 失败")
if i < total - 1: # 最后一条不需要等待
time.sleep(pause_between)
except KeyboardInterrupt:
print("\n用户中断录入")
print(f"\n录入完成!成功: {self.success_count}, 失败: {self.fail_count}")
return self.success_count, self.fail_count
# ==================== 使用示例 ====================
if __name__ == '__main__':
# 方式1:使用图像识别定位元素
form_config_with_image = {
'username': {
'type': 'input',
'image': 'username_field.png', # 输入框截图
'position': None# 使用图像定位时不需要 position
},
'department': {
'type': 'dropdown',
'image': 'dept_dropdown.png',
'options': {'销售部': 0, '技术部': 1, '财务部': 2}
},
'submit': {
'type': 'image',
'image': 'submit_button.png'
}
}
# 方式2:使用坐标定位元素(更快但需要先测量坐标)
form_config_with_coords = {
'name': {
'type': 'input',
'position': (400, 200) # (x, y) 坐标
},
'phone': {
'type': 'input',
'position': (400, 250)
},
'email': {
'type': 'input',
'position': (400, 300)
},
'agree_terms': {
'type': 'checkbox',
'position': (400, 350) # 复选框坐标
},
'submit': {
'type': 'input',
'position': (500, 400) # 提交按钮坐标
}
}
# 创建自动化工具
print("=" * 50)
print("自动化数据批量录入工具")
print("=" * 50)
# 数据文件路径
data_file = '员工信息表.xlsx'# 替换为实际路径
# 表单配置
form_config = form_config_with_coords # 或使用 form_config_with_image
try:
automation = DataEntryAutomation(data_file, form_config)
# 执行录入:跳过前3行,从第4行开始,录入10条
# automation.run(start_row=3, end_row=13, pause_between=3)
# 或录入全部
automation.run(pause_between=2)
except FileNotFoundError as e:
print(f"错误: {e}")
print("\n请确保:")
print("1. 数据文件存在")
print("2. 已准备好要填写的表单页面")
print("3. 根据实际情况调整 form_config 中的坐标或图像")
使用流程:
配置说明:
type: 字段类型(input/dropdown/checkbox/button)position: 元素坐标 (x, y),使用 pyautogui.position() 获取options: 下拉选项映射(显示文本 -> 按键次数)
五、进阶案例
案例1:自动化测试框架
一个完整的自动化测试框架,支持测试用例管理、报告生成、图像识别定位和错误处理。
import pyautogui
import unittest
import time
import os
import json
import logging
from datetime import datetime
from pathlib import Path
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('auto_test.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
classAutoTestFramework:
"""自动化测试框架"""
def__init__(self, config_file='test_config.json'):
self.config = self._load_config(config_file)
self.results = []
self.screenshot_dir = Path('test_screenshots')
self.screenshot_dir.mkdir(exist_ok=True)
# 检测 OpenCV 支持
try:
import cv2
self.has_opencv = True
logger.info("OpenCV 已安装,支持 confidence 参数")
except ImportError:
self.has_opencv = False
logger.warning("OpenCV 未安装,confidence 参数不可用")
def_load_config(self, config_file):
"""加载测试配置文件"""
if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as f:
return json.load(f)
return {
'app_path': '',
'test_cases': [],
'default_timeout': 30,
'screenshot_on_error': True
}
def_find_element(self, locator, timeout=10):
"""
查找元素(支持坐标和图像两种方式)
:param locator: 定位器,格式:{'type': 'coords/image', 'value': (x,y)/path}
:param timeout: 超时时间
:return: 元素位置或 None
"""
start_time = time.time()
if locator['type'] == 'coords':
return locator['value'] # 直接返回坐标
# 图像识别定位
image_path = locator['value']
confidence = locator.get('confidence', 0.8)
while time.time() - start_time < timeout:
try:
if self.has_opencv:
location = pyautogui.locateOnScreen(
image_path,
confidence=confidence,
grayscale=locator.get('grayscale', True)
)
else:
location = pyautogui.locateOnScreen(image_path)
if location:
return pyautogui.center(location)
except pyautogui.ImageNotFoundException:
time.sleep(0.5)
except NotImplementedError:
# 降级到精确匹配
try:
location = pyautogui.locateOnScreen(image_path)
if location:
return pyautogui.center(location)
except pyautogui.ImageNotFoundException:
time.sleep(0.5)
returnNone
def_click(self, locator, timeout=10):
"""点击元素"""
pos = self._find_element(locator, timeout)
if pos:
pyautogui.click(pos[0], pos[1])
logger.debug(f"点击成功: {locator}")
returnTrue
logger.error(f"未找到元素: {locator}")
returnFalse
def_type(self, locator, text, interval=0.05, timeout=10):
"""输入文本"""
if self._click(locator, timeout):
time.sleep(0.2)
pyautogui.hotkey('ctrl', 'a')
pyautogui.write(str(text), interval=interval)
logger.debug(f"输入成功: {text}")
returnTrue
returnFalse
def_assert_image_exists(self, image_path, timeout=10, confidence=0.8):
"""断言图像存在"""
locator = {
'type': 'image',
'value': image_path,
'confidence': confidence
}
return self._find_element(locator, timeout) isnotNone
def_take_screenshot(self, name):
"""截取测试截图"""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f"{timestamp}_{name}.png"
filepath = self.screenshot_dir / filename
pyautogui.screenshot(str(filepath))
return str(filepath)
defrun_test_case(self, test_case):
"""
运行单个测试用例
:param test_case: 测试用例字典
:return: {'name': str, 'status': 'PASS/FAIL', 'error': str, 'screenshot': str}
"""
result = {
'name': test_case['name'],
'status': 'PASS',
'error': '',
'screenshot': '',
'start_time': datetime.now().strftime('%H:%M:%S')
}
logger.info(f"开始测试: {test_case['name']}")
try:
# 执行测试步骤
for step in test_case['steps']:
action = step['action']
if action == 'click':
ifnot self._click(step['locator'], step.get('timeout', 10)):
raise Exception(f"点击失败: {step['locator']}")
elif action == 'type':
ifnot self._type(
step['locator'],
step['text'],
step.get('interval', 0.05),
step.get('timeout', 10)
):
raise Exception(f"输入失败: {step['locator']}")
elif action == 'wait':
time.sleep(step.get('seconds', 1))
elif action == 'hotkey':
pyautogui.hotkey(*step['keys'])
elif action == 'assert_image':
ifnot self._assert_image_exists(
step['image'],
step.get('timeout', 10),
step.get('confidence', 0.8)
):
raise Exception(f"断言失败: 未找到图像 {step['image']}")
elif action == 'assert_position':
x, y = pyautogui.position()
expected_x, expected_y = step['position']
tolerance = step.get('tolerance', 10)
if abs(x - expected_x) > tolerance or abs(y - expected_y) > tolerance:
raise Exception(f"位置断言失败: 期望 ({expected_x},{expected_y}), 实际 ({x},{y})")
# 步骤间等待
time.sleep(step.get('delay', 0.3))
logger.info(f"测试通过: {test_case['name']}")
except Exception as e:
result['status'] = 'FAIL'
result['error'] = str(e)
if self.config.get('screenshot_on_error', True):
result['screenshot'] = self._take_screenshot(test_case['name'])
logger.error(f"测试失败: {test_case['name']} - {e}")
result['end_time'] = datetime.now().strftime('%H:%M:%S')
self.results.append(result)
return result
defrun_all_tests(self):
"""运行所有测试用例"""
logger.info("=" * 60)
logger.info("开始执行自动化测试套件")
logger.info("=" * 60)
start_time = datetime.now()
for test_case in self.config.get('test_cases', []):
self.run_test_case(test_case)
end_time = datetime.now()
duration = (end_time - start_time).total_seconds()
self.generate_report(duration)
return self.results
defgenerate_report(self, duration):
"""生成测试报告"""
report = {
'summary': {
'total': len(self.results),
'passed': sum(1for r in self.results if r['status'] == 'PASS'),
'failed': sum(1for r in self.results if r['status'] == 'FAIL'),
'duration': f"{duration:.2f}秒",
'run_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
},
'details': self.results
}
# 生成 JSON 报告
report_file = f"test_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
with open(report_file, 'w', encoding='utf-8') as f:
json.dump(report, f, ensure_ascii=False, indent=2)
# 生成文本报告
text_report = f"""
测试报告
========
执行时间: {report['summary']['run_time']}
总用时: {report['summary']['duration']}
测试统计
--------
总用例: {report['summary']['total']}
通过: {report['summary']['passed']}
失败: {report['summary']['failed']}
通过率: {report['summary']['passed']/report['summary']['total']*100:.1f}%
测试详情
--------
"""
for idx, result in enumerate(self.results, 1):
status_icon = '✓'if result['status'] == 'PASS'else'✗'
text_report += f"{idx}. [{status_icon}] {result['name']}\n"
if result['status'] == 'FAIL':
text_report += f" 错误: {result['error']}\n"
if result['screenshot']:
text_report += f" 截图: {result['screenshot']}\n"
text_report_file = f"test_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
with open(text_report_file, 'w', encoding='utf-8') as f:
f.write(text_report)
logger.info("=" * 60)
logger.info("测试报告已生成")
logger.info(f"JSON 报告: {report_file}")
logger.info(f"文本报告: {text_report_file}")
logger.info("=" * 60)
# 打印摘要
print(text_report)
# ==================== 测试用例配置示例 ====================
TEST_CONFIG = {
'app_path': 'notepad',
'default_timeout': 30,
'screenshot_on_error': True,
'test_cases': [
{
'name': '测试记事本启动',
'steps': [
{'action': 'hotkey', 'keys': ['win', 'r']},
{'action': 'type', 'locator': {'type': 'coords', 'value': (200, 100)}, 'text': 'notepad'},
{'action': 'hotkey', 'keys': ['enter']},
{'action': 'wait', 'seconds': 2},
{'action': 'assert_image', 'image': 'notepad_window.png', 'confidence': 0.7}
]
},
{
'name': '测试文本输入',
'steps': [
{'action': 'type', 'locator': {'type': 'coords', 'value': (100, 100)}, 'text': 'Hello, PyAutoGUI!'},
{'action': 'wait', 'seconds': 0.5},
{'action': 'hotkey', 'keys': ['ctrl', 's']},
{'action': 'wait', 'seconds': 1},
{'action': 'type', 'locator': {'type': 'coords', 'value': (300, 300)}, 'text': 'test_output.txt'},
{'action': 'hotkey', 'keys': ['enter']}
]
},
{
'name': '测试关闭应用',
'steps': [
{'action': 'hotkey', 'keys': ['alt', 'f4']},
{'action': 'wait', 'seconds': 1},
{'action': 'assert_image', 'image': 'notepad_window.png', 'confidence': 0.7, 'timeout': 2}
]
}
]
}
# ==================== 使用示例 ====================
if __name__ == '__main__':
# 保存配置文件
with open('test_config.json', 'w', encoding='utf-8') as f:
json.dump(TEST_CONFIG, f, ensure_ascii=False, indent=2)
# 创建测试框架实例
test_framework = AutoTestFramework('test_config.json')
# 运行所有测试
results = test_framework.run_all_tests()
# 检查结果
passed = sum(1for r in results if r['status'] == 'PASS')
failed = sum(1for r in results if r['status'] == 'FAIL')
print(f"\n测试完成!通过: {passed}, 失败: {failed}")
框架特性:
- 自动降级:OpenCV 不可用时自动降级为精确匹配
测试用例配置说明:
action: 操作类型(click/type/wait/hotkey/assert_image/assert_position)image: 断言图像路径(assert_image操作)
案例2:多显示器控制
注意:PyAutoGUI 官方仅支持主显示器操作。要实现多显示器控制,需要借助第三方库 screeninfo 来获取显示器信息,再结合 PyAutoGUI 的全局坐标进行操作。
import pyautogui
from screeninfo import get_monitors
# 获取所有显示器信息
monitors = get_monitors()
print(f"检测到 {len(monitors)} 个显示器")
for i, monitor in enumerate(monitors):
print(f"显示器 {i+1}: {monitor.name} "
f"位置=({monitor.x}, {monitor.y}) "
f"分辨率={monitor.width}x{monitor.height}")
# 在指定显示器上操作
defclick_on_monitor(monitor_index, x, y):
"""
在指定显示器的相对坐标处点击
:param monitor_index: 显示器索引(从0开始)
:param x: 显示器内的相对X坐标
:param y: 显示器内的相对Y坐标
"""
monitor = monitors[monitor_index]
absolute_x = monitor.x + x
absolute_y = monitor.y + y
pyautogui.click(absolute_x, absolute_y)
# 在第二个显示器的(100, 100)位置点击
if len(monitors) > 1:
click_on_monitor(1, 100, 100)
else:
print("当前只有一个显示器")
# 截取指定显示器的画面
defscreenshot_monitor(monitor_index):
monitor = monitors[monitor_index]
return pyautogui.screenshot(
region=(monitor.x, monitor.y, monitor.width, monitor.height)
)
安装 screeninfo:
pip install screeninfo
案例3:图像识别与自动化
注意:
confidence 参数需要安装 OpenCV 支持:pip install opencv-python- 未安装 OpenCV 时,代码会自动降级为精确匹配模式
import pyautogui
import time
from datetime import datetime
defcheck_in():
# 打开浏览器
pyautogui.hotkey('win', 'r')
pyautogui.write('chrome')
pyautogui.press('enter')
time.sleep(3)
# 访问打卡页面
pyautogui.click(300, 50) # 点击地址栏
pyautogui.write('https://example.com/checkin')
pyautogui.press('enter')
time.sleep(5)
# 点击打卡按钮
try:
# 尝试使用 confidence(需要 OpenCV),失败则降级为精确匹配
try:
checkin_button = pyautogui.locateCenterOnScreen(
'checkin_button.png', confidence=0.8
)
except NotImplementedError:
# 未安装 OpenCV,使用精确匹配
checkin_button = pyautogui.locateCenterOnScreen(
'checkin_button.png'
)
pyautogui.click(checkin_button)
print(f"打卡成功!时间: {datetime.now()}")
except pyautogui.ImageNotFoundException:
print("未找到打卡按钮,请检查页面")
# 设置定时打卡(每天9点)
print("定时打卡程序已启动,每天9:00 自动打卡...")
whileTrue:
now = datetime.now()
if now.hour == 9and now.minute == 0:
check_in()
time.sleep(61) # 避免重复打卡
time.sleep(30) # 每30秒检查一次
案例4:GUI自动化工作流
import pyautogui
import time
classAutomationWorkflow:
def__init__(self):
self.steps = []
defadd_step(self, action, params=None):
self.steps.append({'action': action, 'params': params or {}})
defexecute(self):
for step in self.steps:
action = step['action']
params = step['params']
if action == 'click':
pyautogui.click(**params)
elif action == 'type':
text = params.get('text', '')
interval = params.get('interval', 0.0)
pyautogui.write(text, interval=interval)
elif action == 'hotkey':
keys = params.get('keys', [])
pyautogui.hotkey(*keys)
elif action == 'wait':
seconds = params.get('seconds', 1)
time.sleep(seconds)
elif action == 'move':
pyautogui.moveTo(**params)
elif action == 'drag':
pyautogui.dragTo(**params)
time.sleep(0.5)
# 创建工作流:打开记事本,输入文字,保存文件
workflow = AutomationWorkflow()
workflow.add_step('hotkey', {'keys': ['win', 'r']})
workflow.add_step('type', {'text': 'notepad'})
workflow.add_step('hotkey', {'keys': ['enter']})
workflow.add_step('wait', {'seconds': 2})
workflow.add_step('type', {'text': 'Hello from PyAutoGUI!', 'interval': 0.1})
workflow.add_step('hotkey', {'keys': ['ctrl', 's']})
workflow.add_step('wait', {'seconds': 1})
workflow.add_step('type', {'text': 'automation.txt'})
workflow.add_step('hotkey', {'keys': ['enter']})
# 执行工作流
print("3秒后开始执行工作流...")
time.sleep(3)
workflow.execute()
print("工作流执行完成!")
六、注意事项与最佳实践
6.1 安全设置
import pyautogui
# 启用失败安全(移动鼠标到屏幕左上角可中断脚本,默认 True)
pyautogui.FAILSAFE = True
# 设置全局操作间隔,防止操作过快(秒)
pyautogui.PAUSE = 0.5
# 设置鼠标移动最小持续时间(秒),小于该值则瞬时移动
pyautogui.MINIMUM_DURATION = 0.1
# 捕获失败安全异常
try:
# 你的自动化代码
pyautogui.moveTo(100, 100)
except pyautogui.FailSafeException:
print("脚本被用户中断(鼠标移到了左上角)")
6.2 性能优化
import pyautogui
# 只截取需要的区域,提高截图速度
screenshot = pyautogui.screenshot(region=(0, 0, 800, 600))
# 使用置信度参数平衡速度与准确性(需要 OpenCV)
# confidence 越低,匹配越快但可能误匹配
try:
location = pyautogui.locateOnScreen('image.png', confidence=0.7)
except pyautogui.ImageNotFoundException:
pass
except NotImplementedError:
# 未安装 OpenCV 时回退到精确匹配
try:
location = pyautogui.locateOnScreen('image.png')
except pyautogui.ImageNotFoundException:
pass
# 使用灰度匹配加快速度(需要 OpenCV)
try:
location = pyautogui.locateOnScreen('image.png', grayscale=True)
except pyautogui.ImageNotFoundException:
pass
except NotImplementedError:
pass
# 减少不必要的操作,批量处理
# 避免频繁调用 pyautogui 函数
6.3 常见问题
1. 图像识别失败
- 调整
confidence 参数(需要 OpenCV) - 使用
grayscale=True 提高匹配速度和鲁棒性 - 注意:PyAutoGUI 0.9.41+ 版本找不到图像时抛出
ImageNotFoundException,而非返回 None
2. confidence 参数报错 NotImplementedError
原因:未安装 OpenCV,confidence 和 grayscale 参数需要 OpenCV 支持
解决方法 1:安装 OpenCV
pip install opencv-python
解决方法 2:代码中添加降级处理,不使用 confidence 参数
解决方法 3:使用 try-except NotImplementedError 捕获异常并回退
3. 坐标不准确
- 使用
pyautogui.position() 实时获取精确坐标
4. 权限问题
- macOS 需要在「系统设置→隐私与安全性→辅助功能」中授权
- Linux 可能需要安装
scrot、python3-xlib 等依赖
5. 中文输入问题
pyautogui.write() 仅支持英文输入
中文输入建议使用 pyperclip 复制粘贴:
import pyperclip
import pyautogui
pyperclip.copy('你好,世界') # 复制中文到剪贴板
pyautogui.hotkey('ctrl', 'v') # 粘贴
6. 多显示器问题
七、总结
PyAutoGUI 是一个功能强大的自动化工具,可以帮助我们完成各种重复性任务。从简单的鼠标键盘控制到复杂的图像识别和工作流自动化,PyAutoGUI 都能胜任。
学习路径建议:
- 结合其他库(OpenCV、pygetwindow 等)扩展功能
扩展资源:
- 官方文档:https://pyautogui.readthedocs.io
- 网页自动化:Selenium、Playwright