【Python小工具】PrtSc、Win+Shift+S截图自动保存 & 画板“白嫖“抠图,200行代码搞定桌面截图管家!
【Python小工具】PrtSc、Win+Shift+S截图自动保存 & 画板"白嫖"抠图,200行代码搞定桌面截图管家!手把手教会你编写!Python 0.1 基础系列!
声明:本项目为作者原创,采用 MIT 开源协议,欢迎 Star、Fork 和二次开发。
📦 源码仓库:https://gitcode.com/lumileth/auto-screenshot-saver-for-windows
✉️ 作者:Gary | 联系邮箱:gary_beijing@126.com
文章目录
一、为什么要做这个工具?
众所周知,Windows自带一个非常方便的截图工具…
作为一个经常写文档、写博客的人,PrtSc 和 Win + Shift + S 截图是我每天用得最多的快捷键之一。
但每次截完图,我都要手动打开文件夹、粘贴、Ctrl + S、取文件名……一套操作下来,思路都断了。
能不能让截图"自己"保存到指定目录? 截完图弹个窗问我要不要存,点"是"就自动归档,截错了就点"否"当做无事发生?
于是,这个 Windows截图自动保存工具 就诞生了。
更妙的是,我发现了一个 “美丽的Bug”——你在Windows自带的 画图板(mspaint) 里随便框选一个区域然后 Ctrl+C,这个工具也会把它当成截图来处理!这意味着你 不用下载PS、GIMP这些重量级软件,用系统自带的画图板就能轻松完成简单的抠图+保存。这个"特性"我直接保留了下来,后面会详细聊 😎。
二、功能概览
先来看看这个小工具都能干什么:
| 功能 | 说明 |
|---|---|
| 📋 剪贴板监听 | 实时轮询剪贴板,检测新截图 |
| 💬 弹窗确认 | 检测到截图后弹窗询问,避免误保存 |
| 📁 自动归档 | 按 日期+小时 自动创建子目录 |
| 🔢 自动编号 | 文件名自动递增编号,不会覆盖 |
| 📝 日志记录 | 控制台+文件双通道日志 |
| 🎨 画图板兼容 | 画图板框选复制也能触发保存(意外之喜!) |
整体架构非常清晰,就两个核心类:
ClipboardMonitor(监听剪贴板)
↓ 检测到新截图
ScreenshotSaver(弹窗 → 保存)
好,接下来让我们 从零开始,一步步把它写出来!
三、第一步:怎么知道剪贴板有新截图?
🤔 问题来了
要监听剪贴板,第一反应是什么?写个
while True死循环,不停地去读剪贴板内容呗!
没错,轮询是最朴素的思路。但这里有几个问题值得思考:
- 每次都去读剪贴板内容,效率是不是太低了? 如果剪贴板里是一张 4K 截图,每次都要把整张图片读出来比较,CPU不得累死?
- 怎么判断"新"截图? 用户截了两次一模一样的图,要不要保存两次?(当你连续按PrtSc的时候)
💡 解决方案:序列号 + 哈希 双重检测
Windows 提供了一个非常贴心的 API:GetClipboardSequenceNumber()。每次剪贴板内容发生变化,这个序列号就会递增。所以我们可以:
- 第一层过滤:比较序列号,没变就直接跳过(几乎零开销)
- 第二层过滤:序列号变了,再读取内容,计算图片的 MD5 哈希值 来精确比对
先来看看序列号怎么用:
import ctypes
# 调用 Windows API 获取剪贴板序列号
# ctypes 是 Python 内置库,可以直接调用 C 语言风格的系统函数
last_sequence = ctypes.windll.user32.GetClipboardSequenceNumber()
🧠 小知识:
ctypes是 Python 标准库之一,不需要额外安装。windll是 Windows 专属的动态链接库调用接口,user32是 Windows 系统中负责用户界面(窗口、消息、对话框等)的核心 DLL。
有了序列号,我们的轮询逻辑就清晰了。先用一个最简单的版本跑起来:
import ctypes
import time
from PIL import Image, ImageGrab # 需要先 pip install Pillow
# 先获取程序开始运行时的剪切板内容,避免刚运行程序就询问保存截图
last_sequence = ctypes.windll.user32.GetClipboardSequenceNumber()
while True:
# 获取当前序列号
current_sequence = ctypes.windll.user32.GetClipboardSequenceNumber()
if current_sequence != last_sequence:
# 序列号变了!说明剪贴板有更新
last_sequence = current_sequence
clipboard_content = ImageGrab.grabclipboard()
# 判断是不是图片
# isinstance() 可以检查一个对象是不是某个类的实例
if isinstance(clipboard_content, Image.Image):
print("检测到新截图!") # 先用 print 看看效果
time.sleep(0.2) # 每 0.2 秒检查一次
⚠️ 这里
ImageGrab.grabclipboard()来自第三方库 Pillow(需要使用pip下载),它可以读取剪贴板中的图片数据。ImageGrab.Image.Image就是 Pillow 中图片对象的类型。
如果发现pip下载速度过慢,可以使用清华源下载:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple Pillow
🔄 优化:引入哈希去重
上面的代码有个问题:如果用户在画图板里对同一张图做了两次复制(比如先复制,再取消选择,再复制),序列号会变,但内容其实一样。这时候就需要 MD5 哈希 来做精确比对:
import hashlib
from PIL import Image
def compute_hash(image):
"""
计算图片的 MD5 哈希值。
- image.convert("RGB"):统一转成 RGB 格式,避免不同色彩模式导致哈希不同
- .tobytes():把图片像素数据转成字节串
- hashlib.md5(...):计算这段字节的 MD5 摘要
"""
if image is None:
return None
return hashlib.md5(image.convert("RGB").tobytes()).hexdigest()
🧠 什么是 MD5? 简单来说,它能把任意长度的数据"压缩"成一串 32 位的字符。相同的数据一定会得到相同的哈希值,不同的数据(几乎不可能)得到相同的哈希值。所以我们只需要比较这串字符,就知道两张图片是否一模一样。
把哈希去重加进去之后,检测逻辑就更健壮了:
import hashlib
from PIL import Image, ImageGrab
last_sequence = ctypes.windll.user32.GetClipboardSequenceNumber()
last_hash = None
while True:
current_sequence = ctypes.windll.user32.GetClipboardSequenceNumber()
if current_sequence != last_sequence:
last_sequence = current_sequence
# 读取剪贴板内容
clipboard_content = ImageGrab.grabclipboard()
image = clipboard_content if isinstance(clipboard_content, Image.Image) else None
# 计算哈希,与上次比较
current_hash = compute_hash(image)
if current_hash != last_hash:
if image is not None:
print("检测到真正的新截图!")
last_hash = current_hash
time.sleep(0.2)
这样,序列号快速过滤 + MD5精确去重,两层防护让我们的监听既高效又准确 ✅
四、第二步:检测到截图后怎么办?弹窗问用户!
🤔 问题来了
检测到截图就直接保存?那万一是用户不小心截的呢?万一截了个"社死"内容呢?
所以我们需要一个 确认弹窗,让用户自己决定要不要保存。
💡 解决方案:调用 Windows 原生 MessageBox
Python 有很多 GUI 库(Tkinter、PyQt……),但弹个确认框而已,杀鸡焉用牛刀?直接用 Windows 系统自带的 MessageBoxW 就行,还是 ctypes 一行搞定:
import ctypes
# MessageBoxW 的参数:
# 第1个参数 0 → 父窗口句柄,0 表示桌面(即没有父窗口)
# 第2个参数 message → 弹窗正文
# 第3个参数 title → 弹窗标题
# 第4个参数 flags → 控制按钮和图标的标志位
result = ctypes.windll.user32.MessageBoxW(
0,
"检测到新的截图,是否保存为文件?",
"截图保存",
0x4 | 0x20 | 0x40000 # 是/否按钮 + 问号图标 + 窗口置顶
)
# result == 6 表示用户点了"是"
if result == 6:
print("用户选择保存")
else:
print("用户选择取消")
🔄 优化:用 IntEnum 让"魔法数字"变得可读
上面的代码里,0x4、0x20、6 这些数字是什么意思?过一个月再看,你自己都忘了!
Python 的 enum 模块(标准库,不用安装)可以把这些数字变成有名字的常量:
from enum import IntEnum
class MessageBoxFlags(IntEnum):
"""MessageBoxW 的标志位,用名字代替数字"""
YESNO = 0x4 # 显示"是"和"否"两个按钮
ICONQUESTION = 0x20 # 显示问号图标
DEFBUTTON1 = 0x0 # 默认选中第一个按钮("是")
TOPMOST = 0x40000 # 弹窗置顶(不被其他窗口遮挡)
class MessageBoxResult(IntEnum):
"""MessageBoxW 的返回值"""
IDYES = 6 # 用户点了"是"
这样写出来的代码,一眼就能看懂:
def ask_yes_no(title, message):
"""弹出一个置顶的"是/否"确认框,返回 True 表示用户点了"是"。"""
flags = (
MessageBoxFlags.YESNO # 是/否按钮
| MessageBoxFlags.ICONQUESTION # 问号图标
| MessageBoxFlags.DEFBUTTON1 # 默认焦点在"是"
| MessageBoxFlags.TOPMOST # 窗口置顶
)
# "|" 是按位或运算符,用来组合多个标志位,效果和加法类似
result = ctypes.windll.user32.MessageBoxW(0, message, title, flags)
return result == MessageBoxResult.IDYES
🧠 什么是"按位或"(
|)? 每个标志位其实是一个二进制数中不同的位。|运算可以把多个标志"合并"在一起,这是 Windows API 中非常常见的参数传递方式。你不需要深入理解位运算,只需要知道:用|把多个标志连起来就行。
⚡ 进阶问题:弹窗会"卡住"整个程序!
如果你跑一下上面的代码,会发现:弹窗出来的时候,整个程序都停了,剪贴板监听也暂停了。这是因为 MessageBoxW 是一个 同步阻塞 调用——它会一直等到用户点按钮才返回。
这就引出了我们下一个话题:异步编程。
五、第三步:让程序"一心二用"——异步编程入门
🤔 问题来了
监听剪贴板要一直循环检查,弹窗要等用户操作——两件事都是"死等"型任务。如果用传统的同步写法,弹窗的时候监听就停了,监听的时候就没法弹窗。怎么办?开多线程?会不会太复杂了?
多线程当然可以,但 Python 有一个更轻量的方案:asyncio 异步协程。
💡 什么是异步协程?(大白话版)
想象你是一个餐厅服务员(事件循环):
- 同步方式:你给1号桌点完菜,站在厨房等菜做好,端给1号桌,然后才去2号桌。效率极低。
- 异步方式:你给1号桌点完菜,把单子交给厨房,马上去2号桌点菜。厨房做好了会喊你端菜。
在 Python 里,async def 定义的任务就是"可以暂停和恢复"的函数。遇到 await 就暂停自己,让出控制权给其他任务。
import asyncio
# async def 定义一个"异步函数"
async def my_task():
print("开始做某事...")
await asyncio.sleep(1) # "暂停"1秒,期间可以做别的事
print("做完了!")
# asyncio.run() 启动事件循环,运行异步任务
asyncio.run(my_task())
⚠️ 注意:
async和await是 Python 3.5+ 引入的语法,它们需要配合asyncio模块使用。await只能在async def函数内部使用。
🔄 把弹窗放进线程池
MessageBoxW 是阻塞的,没法直接 await。但 asyncio 提供了一个神器:asyncio.to_thread()——它可以把一个阻塞的同步函数丢到后台线程去执行,主线程继续干别的事:
async def _ask_user(self):
"""
asyncio.to_thread() 把阻塞的 ask_yes_no 函数丢到线程池执行,
await 等待结果期间,事件循环可以继续监听剪贴板。
"""
return await asyncio.to_thread(
ask_yes_no,
"截图保存",
"检测到新的截图,是否保存为文件?",
)
这样一来,弹窗和监听互不干扰,完美!🎉
六、第四步:截图保存到哪里?怎么命名?
🤔 问题来了
用户点了"是",截图往哪儿存?文件名叫什么?总不能都叫
screenshot.png互相覆盖吧?
💡 我的解决方案:按时间归档 + 自动编号
我设计的目录结构是这样的:
~/Pictures/Screenshots/
├── 20250630-14/ ← 2025年6月30日 14点
│ ├── 01_2530.png ← 第1张,14:25:30截的
│ ├── 02_3015.png ← 第2张,14:30:15截的
│ └── 03_4500.png
├── 20250630-15/ ← 2025年6月30日 15点
│ └── 01_0230.png
└── ...
好处显而易见:
- 按小时分目录,不会一个文件夹里塞几千张图
- 自动编号,不会覆盖
- 文件名包含分秒信息,方便回溯
💻代码实现
先写保存逻辑,这部分是纯同步的,不需要异步:
from datetime import datetime
from pathlib import Path
# pathlib 是 Python 标准库,用来处理文件路径,比 os.path 好用得多
# Path.home() 返回用户主目录,比如 C:\Users\你的用户名
# 这里可以换成你自己喜欢的位置,但是要用 Path 类型!!
save_dir = Path.home() / "Pictures" / "Screenshots"
def save_image(image):
now = datetime.now()
# strftime() 把时间格式化:%Y年%m月%d日-%H时
# Path 的方便之处:使用 / 进行直观的路径拼接
target_dir = save_dir / now.strftime("%Y%m%d-%H") # 这个是Path类型的变量
# mkdir() 创建目录
# parents=True → 如果父目录不存在也一并创建(类似 mkdir -p)
# exist_ok=True → 目录已存在也不报错
target_dir.mkdir(parents=True, exist_ok=True)
# 生成文件名
file_name = f"截图_{now:%M%S}.png"
file_path = target_dir / file_name
image.save(file_path, "PNG")
print(f"截图已保存: {file_path}")
🧠 如何自定义目录? 更改
save_dir = Path.home() / "Pictures" / "Screenshots"为save_dir = Path(“your_custom_path”)
🔄 优化:加上自动编号
上面的代码虽然能用,但如果同一分钟截了两张图,文件名就会冲突。我们需要扫描目录里已有的文件,取最大编号 +1:
def get_next_sequence(directory):
"""
扫描目录中已有的 .png 文件,找出最大编号,返回下一个可用编号。
文件命名格式:01_2530.png
我们用 "_" 分割,取前面那段数字。
"""
max_seq = 0
for file_path in directory.iterdir(): # iterdir() 遍历目录下所有文件
if file_path.is_file() and file_path.suffix == ".png":
stem = file_path.stem # stem 是不含后缀的文件名,如 "01_2530"
if "_" in stem:
prefix = stem.split("_", 1)[0] # 取 "_" 前面的部分
if prefix.isdigit(): # 检查是不是纯数字
max_seq = max(max_seq, int(prefix))
return max_seq + 1
然后把它整合进保存逻辑:
def save_image(image):
now = datetime.now()
target_dir = save_dir / now.strftime("%Y%m%d-%H")
target_dir.mkdir(parents=True, exist_ok=True)
# 自动编号
seq = get_next_sequence(target_dir)
file_name = f"{seq:02d}_{now:%M%S}.png" # :02d 表示至少2位,不足补零
file_path = target_dir / file_name
image.save(file_path, "PNG")
print(f"截图已保存: {file_path}")
🧠
f"{seq:02d}"是什么? 这是 Python 的格式化字符串语法。:02d表示把整数格式化为至少 2 位的十进制数,不足补零。比如1→"01",12→"12"。
💡补充说明
其实这里也可以使用 正则表达式(Regular Expression) 进行提取。在Python里,我们可以使用re库来便捷的使用它。re库在数据处理中扮演了非常重要的角色。因为这里不是重点,也不方便新手理解,这里大概了解一下即可:
import re
#我们的代码...
def get_next_sequence(directory):
"""新版函数:使用正则表达式,同时匹配开头数字和 .png 后缀。"""
max_seq = 0
for file_path in directory.iterdir():
if file_path.is_file():
# 注意:这里用 .name 获取完整文件名(如 "01_2530.png"),而不是 .stem
full_name = file_path.name
# re.match 从字符串开头开始匹配我们制定的完整规则
match_obj = re.match(r"^(\d+).*\.png$", full_name)
# 如果匹配成功,说明它既是以数字开头,又是以 .png 结尾
if match_obj:
# .group(1) 提取第1个括号(分组)里匹配到的内容,也就是开头的数字
seq_num = int(match_obj.group(1))
max_seq = max(max_seq, seq_num)
return max_seq + 1
🧠 正则小课堂(包教包会):
r"...":前面的r表示“原始字符串”,告诉 Python 不要处理里面的反斜杠,原封不动传给正则引擎。^:表示“必须从字符串的最开头开始匹配”。(\d+):分组(Group)。\d代表数字,+代表1次或多次。括号把它圈起来,方便稍后“提取”。.*:. 代表任意单个字符,*代表0次或多次。连起来就是“匹配中间的任意内容”(比如_2530)。\.:在正则里.是个特殊符号(代表任意字符),所以要在前面加\进行转义,表示“我只想要真正的英文句号”。png$:$表示“必须到字符串的最结尾”。确保文件是以.png真正结束的,而不是.png.bak这种伪装者。
七、第五步:把 print 换成专业日志
🤔 问题来了
前面我们到处用
print()来输出调试信息。但 print 有几个硬伤:
- 分不清"普通信息"和"调试信息"和"错误信息"
- 程序关了就什么都没了,没法事后排查问题
- 想关掉调试输出?得满代码找 print 一个个删?
💡 解决方案:Python 内置 logging 模块
logging 是 Python 标准库,专门用来替代 print 做日志记录。它支持 日志级别、多输出通道、格式化 等功能。
先看最简单的用法:
import logging
# 获取一个日志记录器,给它起个名字
logger = logging.getLogger("ScreenshotSaver")
# 设置最低记录级别:DEBUG(最详细)
logger.setLevel(logging.DEBUG)
# 创建一个"控制台输出通道"
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) # 控制台只显示 INFO 及以上级别
# 设置输出格式
formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] %(message)s", # 时间 [级别] 内容
datefmt="%H:%M:%S" # 时间只显示时分秒
)
console_handler.setFormatter(formatter)
# 把通道加到记录器上
logger.addHandler(console_handler)
# 使用!
logger.debug("这是调试信息,控制台不会显示(因为控制台级别是INFO)")
logger.info("这是普通信息,控制台会显示")
logger.error("这是错误信息!")
🔄 再加上文件日志
控制台日志方便实时看,但程序关了就没了。我们再加一个 文件输出通道,把所有细节都记到文件里:
# 创建一个"文件输出通道"
# 追加模式(不会覆盖之前的日志),指定 UTF-8 编码(防止中文乱码)
file_handler = logging.FileHandler("./running_log.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG) # 文件记录所有级别的日志
file_formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S" # 文件中记录完整时间
)
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
这样,我们就实现了 双通道日志:
| 通道 | 级别 | 用途 |
|---|---|---|
| 控制台 | INFO | 实时查看关键事件 |
| 文件 | DEBUG | 事后排查所有细节 |
🧠 以后你写任何 Python 项目,都可以把这段日志配置直接拷过去用,堪称万能模板!但是别忘了定制哦!
八、第六步:把一切组装起来
🏗️ 面向对象:两个核心类
现在所有零件都有了,我们用两个类把它们组织起来:
ScreenshotSaver —— 负责"保存"的业务逻辑:
class ScreenshotSaver:
"""负责:询问用户、创建目录、生成文件名、保存截图。"""
def __init__(self, save_dir):
self.save_dir = save_dir # 保存根目录
async def process(self, image):
"""异步处理入口:先问用户,用户同意就保存。"""
should_save = await self._ask_user()
if should_save:
self._save_image(image)
async def _ask_user(self):
"""在线程池中弹窗,不阻塞事件循环。"""
return await asyncio.to_thread(
ask_yes_no,
"截图保存",
"检测到新的截图,是否保存为文件?",
)
def _save_image(self, image):
"""保存图片到按时间组织的目录中。"""
now = datetime.now()
target_dir = self.save_dir / now.strftime("%Y%m%d-%H")
target_dir.mkdir(parents=True, exist_ok=True)
seq = self._get_next_sequence(target_dir)
file_name = f"{seq:02d}_{now:%M%S}.png"
file_path = target_dir / file_name
image.save(file_path, "PNG")
logger.info("截图已保存: %s", file_path)
@staticmethod
def _get_next_sequence(directory):
"""扫描目录,返回下一个可用编号。"""
max_seq = 0
for fp in directory.iterdir():
if fp.is_file() and fp.suffix == ".png":
stem = fp.stem
if "_" in stem:
prefix = stem.split("_", 1)[0]
if prefix.isdigit():
max_seq = max(max_seq, int(prefix))
return max_seq + 1
ClipboardMonitor —— 负责"监听"的核心循环:
class ClipboardMonitor:
"""
异步监听剪贴板:
1. 序列号快速过滤
2. MD5哈希精确去重
3. 新截图交给 ScreenshotSaver 处理
"""
def __init__(self, saver, interval=0.2):
self.saver = saver # 注入业务对象
self.interval = interval # 轮询间隔(秒)
self._last_hash = None # 上次图片的哈希
self._last_sequence = ctypes.windll.user32.GetClipboardSequenceNumber()
async def run(self):
"""启动监听循环。"""
logger.info("开始监听剪贴板截图(Win+Shift+S 等),按 Ctrl+C 退出")
try:
while True:
await self._check_clipboard()
await asyncio.sleep(self.interval) # 异步等待,不阻塞
except KeyboardInterrupt:
logger.info("监听已停止")
async def _check_clipboard(self):
"""一次完整的剪贴板检查流程。"""
# ① 序列号没变?跳过!
current_sequence = ctypes.windll.user32.GetClipboardSequenceNumber()
if current_sequence == self._last_sequence:
return
self._last_sequence = current_sequence
logger.debug("剪贴板序列号变化: %d", current_sequence)
# ② 读取剪贴板内容
clipboard_content = ImageGrab.grabclipboard()
image = (
clipboard_content
if isinstance(clipboard_content, Image.Image)
else None
)
# ③ 哈希比对
current_hash = self._compute_hash(image)
if current_hash == self._last_hash:
logger.debug("剪贴板内容与上次相同,忽略")
return
# ④ 新图片 → 交给业务处理
if image is not None:
logger.info("检测到新的截图,正在询问用户...")
await self.saver.process(image)
# ⑤ 更新哈希
self._last_hash = current_hash
@staticmethod
def _compute_hash(image):
"""计算图片的 MD5 哈希值。"""
if image is None:
return None
return hashlib.md5(image.convert("RGB").tobytes()).hexdigest()
🚀 启动入口
最后,写一个异步主函数把两个类串起来:
async def main():
# 1. 创建保存器,指定截图存储目录
saver = ScreenshotSaver(SAVE_DIR)
# 2. 创建监听器,把保存器"注入"进去
monitor = ClipboardMonitor(saver)
# 3. 启动!
await monitor.run()
if __name__ == "__main__":
asyncio.run(main()) # asyncio.run() 是启动异步程序的入口
🧠
if __name__ == "__main__"是什么意思?这是 Python 的经典写法,表示"只有直接运行这个文件时才执行下面的代码",如果这个文件被别的文件import,则不会执行。这是一个好习惯。
九、美丽的Bug:画图板秒变"抠图神器" 🎨
在测试过程中,我发现了一个有趣的现象:
在画图板(mspaint)中框选任意区域,按
Ctrl+C复制,这个工具也会弹窗询问是否保存!
为什么呢?因为画图板的"复制"操作,本质上就是把框选区域的 位图数据 放进了系统剪贴板——这和 Win+Shift+S 截图在剪贴板中存储的数据格式是一模一样的。我们的工具监听的是剪贴板中的 所有图片,并不区分图片来源。
这其实是一个超级实用的"特性"! 想想看:
- 打开系统自带的 画图板(
Win + R→ 输入mspaint→ 回车) - 粘贴一张图片(或者直接打开图片文件)
- 用 选择工具 框选你想要的区域
Ctrl + C复制- 🎉 弹窗自动出现!点"是"就保存了
不需要安装 Photoshop,不需要安装 GIMP,不需要任何第三方截图软件,系统自带工具就能完成"打开图片 → 框选 → 抠图 → 保存"的完整流程!
| 方案 | 安装成本 | 操作步骤 |
|---|---|---|
| Photoshop | 几百元/月 | 打开 → 选区 → 裁剪 → 导出 |
| GIMP | 免费但需下载安装 | 打开 → 选区 → 裁剪 → 导出 |
| 画图板 + 本工具 | 零成本,系统自带 | 框选 → Ctrl+C → 弹窗点"是" ✅ |
💡 使用技巧:如果你只想用画图板抠图而不想触发弹窗,可以先 停止本工具(
Ctrl+C),再在画图板中操作。反之,如果你正好需要快速抠图保存,这个"Bug"简直就是量身定做的功能!
十、完整代码 & 项目结构
完整源码已开源,欢迎 Star ⭐:
📦 GitCode 仓库:https://gitcode.com/lumileth/auto-screenshot-saver-for-windows
运行方式:
# 1. 安装依赖
pip install Pillow
# 使用清华源
#pip install -i https://pypi.tuna.tsinghua.edu.cn/simple Pillow
# 2. 运行程序
python main.py
⚠️ 本工具仅支持 Windows 系统(因为使用了
ctypes.windll和user32.dll)。
十一、知识点回顾
写完这个小工具,我们来盘点一下用到的知识点:
| 知识点 | 模块/库 | 是否标准库 | 用途 |
|---|---|---|---|
| 异步编程 | asyncio |
✅ 标准库 | 监听循环 + 弹窗不互相阻塞 |
| 调用系统API | ctypes |
✅ 标准库 | 获取剪贴板序列号、弹出 MessageBox |
| 日志系统 | logging |
✅ 标准库 | 替代 print,双通道日志 |
| 哈希计算 | hashlib |
✅ 标准库 | MD5 哈希去重 |
| 日期时间 | datetime |
✅ 标准库 | 按时间归档文件名 |
| 文件路径 | pathlib |
✅ 标准库 | 跨平台路径处理 |
| 枚举类型 | enum |
✅ 标准库 | 消除魔法数字 |
| 图片处理 | Pillow |
❌ 第三方库 | 读取剪贴板图片、保存图片 |
你看,除了 Pillow 之外,其他全部是 Python 标准库,不需要安装任何额外的包!
十二、写在最后
这个项目的初衷很简单:让截图保存这件事变得无感。
但在编写过程中,我们涉及了异步编程、Windows API 调用、日志系统设计、面向对象设计模式等多个知识点。希望这篇博客不仅分享了一个实用的小工具,更能帮你理解 如何从零开始构建一个完整的 Python 项目。
如果你觉得这篇文章有帮助,别忘了 点赞 👍 + 收藏 ⭐ + 关注 🔔 三连支持!
有任何问题或建议,欢迎在评论区留言,也欢迎到 GitCode 仓库提 Issue 或 PR!
声明:本文为原创文章,遵循 MIT 协议开源。代码仅供学习交流,请勿用于商业用途。
📦 源码地址:https://gitcode.com/your-username/screenshot-saver
✉️ 作者:Gary| gary_beijing@126.com
更多推荐

所有评论(0)