PyAutoGUI 坐标指南:size()、多显示器、DPI 缩放修复
PyAutoGUI 在你的屏幕上会看到什么:
pyautogui.size() 返回屏幕尺寸,pyautogui.position() 返回当前鼠标位置,pyautogui.click(960, 540) 点击 1920×1080 屏幕的中心——但前提是 DPI 缩放已关闭。如果你写了 pyautogui.click(960, 540) 结果点击落在了完全不同的地方,或者 pyautogui.locateOnScreen() 返回了错误的坐标,你遇到了最常见的 PyAutoGUI 坐标 bug。下面讲解 pyautogui.size() 的原理、多显示器坐标范围,以及 DPI 缩放的完整修复方法。
这是 PyAutoGUI 最经典的三个坐标 bug,它们有一个共同的根源:DPI 缩放 + 多显示器虚拟屏幕坐标。下面我们逐个解决——附上可以直接复制到项目里的代码。上方已自动检测了你屏幕的参数。
打开免费屏幕坐标工具PyAutoGUI 的三大坐标 Bug
PyAutoGUI 的坐标问题几乎都可以归入以下三类:
- 高 DPI 显示器上点击偏移:
pyautogui.click(960, 540)没点到预期位置,因为 PyAutoGUI 在用逻辑坐标,而目标应用用的是物理坐标。详见我们的DPI 缩放指南。 - 报告了错误的屏幕尺寸:
pyautogui.size()在 4K 显示器上返回 2560×1440,因为 Windows 给它的是逻辑(缩放后的)分辨率。你的脚本算出的中心点、百分比、所有东西都错了。 - 截图区域错位:
pyautogui.screenshot(region=(x, y, w, h))截取的区域跟你指定的不一样,因为区域坐标是逻辑空间的,但截图用的是物理像素缓冲区。
三个 bug 的根因完全一样:PyAutoGUI 在 Windows 上默认以非 DPI 感知进程运行,操作系统给它的是逻辑坐标。修复方法也始终相同——启用 DPI 感知。
pyautogui.size() 与真实分辨率的差异
pyautogui.size() 在 4K 显示器上实际返回什么?取决于你的缩放设置:
| 物理分辨率 | Windows 缩放 | pyautogui.size() 返回 | 像素差异 |
|---|---|---|---|
| 3840×2160 | 100% | 3840×2160 ✓ | 无 |
| 3840×2160 | 125% | 3072×1728 | 差 768×432 |
| 3840×2160 | 150% | 2560×1440 | 差 1280×720 |
| 3840×2160 | 200% | 1920×1080 | 差 1920×1080 |
| 2560×1440 | 125% | 2048×1152 | 差 512×288 |
| 2560×1440 | 150% | 1707×960 | 差 853×480 |
4K 开 150% 缩放时,PyAutoGUI 以为你的屏幕只有 2560 像素宽。当它点击自以为的中心 (1280, 720) 时,Windows 把这个坐标转换成物理像素 (1920, 1080)——恰好是真实的中心。但任何其他位置都会按缩放比例偏移。
完整修复:DPI 感知的 PyAutoGUI 配置
在脚本最顶部加上这段代码——在导入 PyAutoGUI 之前:
import ctypes
import sys
# 修复 #1:告诉 Windows 我们要用真实的物理像素
if sys.platform == 'win32':
try:
# 逐显示器 DPI 感知(多显示器最佳选择)
ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception:
# 备选方案:系统级 DPI 感知
ctypes.windll.user32.SetProcessDPIAware()
# 现在导入 PyAutoGUI——它会看到真实的像素
import pyautogui
# 验证修复是否生效
w, h = pyautogui.size()
print(f"屏幕尺寸: {w}×{h}")
# 4K 显示器开 150% 缩放时,这里应该输出 3840×2160
为什么用 SetProcessDpiAwareness(2) 而不是 SetProcessDPIAware()?
| 调用 | 级别 | 多显示器 | 适用场景 |
|---|---|---|---|
SetProcessDPIAware() | 系统级 | 所有显示器使用主显示器的 DPI | 仅单显示器 |
SetProcessDpiAwareness(2) | 逐显示器 | 每台显示器报告其真实物理分辨率 | 任何配置(推荐) |
如果你有一块 4K 笔记本屏幕(150% 缩放)外接一块 1080p 显示器(100% 缩放),SetProcessDpiAwareness(2) 能确保 PyAutoGUI 在笔记本屏幕上看到 3840×2160、在外接显示器上看到 1920×1080——每块屏幕的真实参数。关于不同显示器之间坐标如何运作,参见我们的多显示器坐标指南。
修复截图区域偏移
截图区域的问题很隐蔽。没有 DPI 修复的情况下,region 参数用的是逻辑坐标——所以会出现这种情况(以 4K 显示器开 150% 缩放为例;你实际检测到的 DPR 可能不同):
# 没有 DPI 修复(4K 开 150% 缩放):
# 你想截取左上角 960×540 像素
img = pyautogui.screenshot(region=(0, 0, 960, 540))
# 但实际截取的是逻辑空间的左上角 960×540
# 对应物理像素 (0, 0, 1440, 810)——比你想截的大得多
# 加上 SetProcessDpiAwareness(2) 后:
# 现在 region 参数正确使用物理像素
img = pyautogui.screenshot(region=(0, 0, 960, 540))
# 精确截取左上角 960×540 物理像素 ✓
重要提示:启用 DPI 感知后,pyautogui.screenshot() 返回的是完整物理分辨率的图像。4K 显示器上就是 3840×2160 的图像,不是 2560×1440。如果你在修复前有裁剪截图的代码,需要把裁剪坐标更新为物理像素。
多显示器:虚拟屏幕坐标详解
在多显示器环境下,PyAutoGUI 操作的是一个跨越所有显示器的单一虚拟屏幕。主显示器从 (0, 0) 开始,其他显示器从那里延伸:
- 主显示器右边的显示器从 X =
主显示器宽度开始(假设两台显示器使用相同的 DPI 缩放——混合 DPI 的情况更复杂) - 主显示器左边的显示器有负的 X 坐标(例如 −1920 到 −1)
- 主显示器上方的显示器有负的 Y 坐标
# 查看你的虚拟屏幕尺寸
import ctypes
import pyautogui
# DPI 修复之后:
print(pyautogui.size()) # 主显示器尺寸
x, y = pyautogui.position()
print(f"当前鼠标位置: ({x}, {y})")
# 负值表示你在主显示器左边/上方的显示器上
关于计算任意显示器中心点、偏移和边缘位置的完整数学推导,参见我们的多显示器坐标指南。
为什么 locateOnScreen() 返回错误坐标
pyautogui.locateOnScreen('button.png') 是 PyAutoGUI 最实用的功能之一——也是在高 DPI 显示器上最容易出问题的功能。问题出在这里(以 4K 显示器开 150% 缩放为例):
- 你在 4K 显示器上截了一张图(图片是 3840×2160 物理像素)
- 你裁出按钮并保存为
button.png locateOnScreen()内部会截图——但没有 DPI 感知时,Windows 返回的是一张逻辑尺寸的图片(2560×1440),而且被操作系统重新采样和模糊处理过(DPI 虚拟化)。像素不再跟你保存的参考图精确匹配。- 它试图在这张模糊的、缩小过的截图里找你清晰的 4K 按钮——结果找不到,或者返回错误坐标
修复方法:调用 SetProcessDpiAwareness(2) 之后,PyAutoGUI 内部截图会捕获原始物理像素,不经操作系统重采样。你的参考图和搜索截图在同一个分辨率和清晰度下,locateOnScreen() 就能正常工作了。
提示:务必在启用 DPI 感知之后制作参考图。如果参考图是修复前截的,它们是逻辑分辨率的,无法匹配物理像素截图。
可直接使用的完整脚本
复制粘贴到你的项目里即可。自动处理 DPI 感知、多显示器检测,并提供了分辨率无关的点击辅助函数:
"""
PyAutoGUI DPI 感知配置——直接放在任何脚本顶部。
支持 Windows、macOS 和 Linux。多显示器安全。
关键:DPI 感知必须在导入 pyautogui 之前设置,
否则它会在导入时缓存逻辑分辨率。
"""
import ctypes
import sys
# --- 在导入 pyautogui 之前设置 DPI 感知 ---
if sys.platform == 'win32':
try:
ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception:
try:
ctypes.windll.user32.SetProcessDPIAware()
except Exception:
pass
# 现在可以安全导入 pyautogui 了
import pyautogui
def get_screen_info():
"""返回有用的屏幕信息。"""
w, h = pyautogui.size()
return {
'width': w,
'height': h,
'center': (w // 2, h // 2),
'total_pixels': w * h,
}
def click_relative(x_pct, y_pct):
"""按屏幕尺寸百分比点击(0.0 到 1.0)。
不依赖分辨率——在任何屏幕上都有效。"""
w, h = pyautogui.size()
pyautogui.click(int(w * x_pct), int(h * y_pct))
# --- 用法 ---
info = get_screen_info()
print(f"主屏幕: {info['width']}×{info['height']}")
print(f"中心点: {info['center']}")
# 示例:点击屏幕中心
click_relative(0.5, 0.5)
常见问题
这个修复在 macOS 和 Linux 上有效吗?
SetProcessDpiAwareness() 只存在于 Windows。macOS 上 PyAutoGUI 自己就能较好地处理 Retina 缩放。Linux 上取决于你的显示服务器(X11 还是 Wayland)。上面的代码在任何平台上都可以安全调用——只是在非 Windows 系统上什么都不做。
为什么在 IDLE 里能跑,直接运行就不行?
有些 IDE——包括 IDLE——本身已经是 DPI 感知的。在它们里面运行脚本会继承这个上下文。但同样的脚本从命令行或任务计划程序运行时,它就是非 DPI 感知进程。所以无论怎么启动,都要调用 SetProcessDpiAwareness(2)。
可以跟 PyAutoGUI + OpenCV 一起用吗?
可以。启用 DPI 感知后,pyautogui.screenshot() 返回完整分辨率的 PIL 图像。你可以把它转成 NumPy 数组用 OpenCV 处理。cv2.matchTemplate() 返回的坐标会与 PyAutoGUI 的物理像素坐标空间匹配。
SetProcessDpiAwareness(2) 报错了怎么办?
如果你的进程已经被标记为非 DPI 感知(比如 Python 是由非 DPI 感知的父进程启动的),这个调用会失败。备选方案是 SetProcessDPIAware()。如果连这个也失败,你大概率是在继承父进程的 DPI 上下文——检查你的 Python 启动器或终端设置。
如何获取鼠标坐标来验证修复?
调用 setup_dpi_awareness() 之后运行 print(pyautogui.position())。把鼠标移到屏幕右下角——应该接近 (width-1, height-1),其中 width/height 是物理分辨率。或者用我们的在线屏幕坐标工具实时查看鼠标位置。
???? PyAutoGUI ?????????????????????????? PowerShell?xdotool?DevTools ???
????????????????????????????????????????? PyAutoGUI ???