first commit

This commit is contained in:
2026-08-26 16:14:47 +08:00
commit 4bdbf31b3e
10 changed files with 763 additions and 0 deletions

29
utils/__init__.py Normal file
View File

@@ -0,0 +1,29 @@
"""
通用工具模块
"""
import logging
from datetime import datetime
def get_logger(name: str) -> logging.Logger:
"""创建仅输出到控制台的 logger"""
logger = logging.getLogger(name)
if not logger.handlers:
logger.setLevel(logging.INFO)
fmt = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s - %(message)s")
sh = logging.StreamHandler()
sh.setFormatter(fmt)
logger.addHandler(sh)
return logger
def now_str(fmt: str = "%Y%m%d%H%M") -> str:
"""返回当前时间字符串,用于文件命名"""
return datetime.now().strftime(fmt)
# 注意:该导入必须放在 get_logger/now_str 定义之后——
# notify 模块内部通过 from . import get_logger 引用本包,
# 若放在定义之前会形成循环导入utils → notify → utils
from .notify import show_warning_countdown

99
utils/notify.py Normal file
View File

@@ -0,0 +1,99 @@
"""
桌面提醒工具模块
在任务开始前于桌面弹出置顶警告窗口并倒计时,
倒计时结束后窗口自动关闭,程序继续执行
"""
import tkinter as tk
from . import get_logger
log = get_logger("notify")
def show_warning_countdown(
message: str = "即将开始自动截图",
seconds: int = 5,
title: str = "警告",
):
"""
在桌面弹出置顶警告窗口并倒计时,结束后自动关闭
倒计时期间函数阻塞,结束后窗口销毁并返回,任务继续执行。
Args:
message: 警告正文
seconds: 倒计时秒数(<=0 时直接返回)
title: 警告标题
"""
if seconds <= 0:
return
try:
root = tk.Tk()
except Exception as e:
# tkinter 初始化失败(如无显示环境)时仅记录日志,不阻塞任务
log.warning("无法弹出桌面警告窗口: %s", e)
return
root.title(title)
root.attributes("-topmost", True) # 始终置顶,确保用户能看到
root.resizable(False, False)
bg = "#c62828" # 红色警示背景
root.configure(bg=bg)
# 标题
tk.Label(
root,
text=f"—— {title} ——",
font=("Microsoft YaHei UI", 32, "bold"),
fg="white",
bg=bg,
).pack(padx=80, pady=(30, 10))
# 正文
tk.Label(
root,
text=message,
font=("Microsoft YaHei UI", 20),
fg="white",
bg=bg,
).pack(padx=80, pady=5)
# 倒计时(每秒刷新)
countdown = tk.Label(
root,
text="",
font=("Microsoft YaHei UI", 48, "bold"),
fg="#ffeb3b",
bg=bg,
)
countdown.pack(padx=80, pady=15)
# 底部提示
tk.Label(
root,
text="倒计时结束后任务自动开始,请勿操作鼠标键盘",
font=("Microsoft YaHei UI", 12),
fg="#ffebee",
bg=bg,
).pack(padx=80, pady=(0, 30))
# 窗口居中显示
root.update_idletasks()
w, h = root.winfo_width(), root.winfo_height()
sw, sh = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry(f"+{(sw - w) // 2}+{(sh - h) // 2}")
def _tick(remaining: int):
if remaining > 0:
countdown.config(text=f"{remaining} 秒后开始")
root.after(1000, _tick, remaining - 1)
else:
root.destroy()
log.info("桌面警告已弹出: '%s%s',倒计时 %d", title, message, seconds)
root.after(100, _tick, seconds)
root.mainloop()
log.info("倒计时结束,窗口已关闭,任务继续")

207
utils/screenshot.py Normal file
View File

@@ -0,0 +1,207 @@
"""
截图工具模块
提供多种截图方式:
1. rpaframework 内置截图(浏览器/桌面)
2. pyautogui 全屏/区域截图
3. pillow 窗口截图
"""
import os
from typing import Optional, Tuple
from RPA.Browser import Playwright
from config import OUTPUT_DIR
from utils import get_logger, now_str
log = get_logger("screenshot")
try:
import pyautogui
import pygetwindow as gw
except ImportError:
pyautogui = None
gw = None
def take_screenshot(
name: Optional[str] = None,
full_page: bool = False,
region: Optional[Tuple[int, int, int, int]] = None,
) -> str:
"""
截图工具
Args:
name: 截图文件名(不含扩展名),默认为时间戳
full_page: 是否截取整个页面(仅浏览器有效)
region: 区域截图 (left, top, width, height)None为全屏
Returns:
截图文件完整路径
"""
if name is None:
name = now_str()
# 确保输出目录存在
os.makedirs(OUTPUT_DIR, exist_ok=True)
filepath = os.path.join(OUTPUT_DIR, f"{name}.png")
# 优先尝试 rpaframework 浏览器截图
try:
browser = Playwright()
if browser.get_browser_count() > 0:
# 如果有浏览器实例,则截取当前页面
if full_page:
browser.take_screenshot(filepath, selector="body")
else:
browser.take_screenshot(filepath)
log.info("已使用 rpaframework 截取浏览器页面: %s", filepath)
return filepath
except:
# 没有浏览器实例,继续尝试其他方式
pass
# 尝试 pyautogui 截图
if pyautogui:
try:
if region:
# 区域截图
screenshot = pyautogui.screenshot(region=region)
else:
# 全屏截图
screenshot = pyautogui.screenshot()
screenshot.save(filepath)
log.info("已使用 pyautogui 截取屏幕: %s", filepath)
return filepath
except Exception as e:
log.warning("pyautogui 截图失败: %s", e)
# 尝试截取活动窗口
if gw:
try:
active_window = gw.getActiveWindow()
if active_window:
bbox = (
active_window.left,
active_window.top,
active_window.width,
active_window.height,
)
screenshot = pyautogui.screenshot(region=bbox)
filepath = os.path.join(OUTPUT_DIR, f"{name}_window.png")
screenshot.save(filepath)
log.info("已截取活动窗口: %s", filepath)
return filepath
except Exception as e:
log.warning("窗口截图失败: %s", e)
# 最后尝试 Pillow 截图(需要安装 Pillow
try:
from PIL import ImageGrab
if region:
bbox = (region[0], region[1], region[0] + region[2], region[1] + region[3])
img = ImageGrab.grab(bbox=bbox)
else:
img = ImageGrab.grab()
img.save(filepath)
log.info("已使用 Pillow 截取屏幕: %s", filepath)
return filepath
except ImportError:
log.error("未安装 Pillow无法进行屏幕截图")
raise
except Exception as e:
log.error("所有截图方式均失败: %s", e)
raise
def take_element_screenshot(selector: str, name: Optional[str] = None) -> str:
"""
截取浏览器中特定元素
Args:
selector: CSS 选择器或 XPath
name: 截图文件名
Returns:
截图文件完整路径
"""
if name is None:
name = f"element_{now_str()}"
filepath = os.path.join(OUTPUT_DIR, f"{name}.png")
try:
browser = Playwright()
if browser.get_browser_count() == 0:
raise RuntimeError("没有活动的浏览器实例")
browser.take_screenshot(filepath, selector=selector)
log.info("已截取元素 '%s': %s", selector, filepath)
return filepath
except Exception as e:
log.error("元素截图失败: %s", e)
raise
def take_window_screenshot(window_title: str, name: Optional[str] = None) -> str:
"""
截取指定窗口
Args:
window_title: 窗口标题(模糊匹配)
name: 截图文件名
Returns:
截图文件完整路径
"""
if name is None:
name = f"window_{now_str()}"
if not gw:
raise RuntimeError("未安装 pygetwindow无法截取窗口")
try:
# 查找匹配的窗口
windows = gw.getWindowsWithTitle(window_title)
if not windows:
raise ValueError(f"未找到标题包含 '{window_title}' 的窗口")
# 使用第一个匹配的窗口
target_window = windows[0]
bbox = (
target_window.left,
target_window.top,
target_window.width,
target_window.height,
)
# 确保输出目录存在
os.makedirs(OUTPUT_DIR, exist_ok=True)
filepath = os.path.join(OUTPUT_DIR, f"{name}.png")
# 截取窗口
screenshot = pyautogui.screenshot(region=bbox)
screenshot.save(filepath)
log.info("已截取窗口 '%s': %s", window_title, filepath)
return filepath
except Exception as e:
log.error("窗口截图失败: %s", e)
raise
if __name__ == "__main__":
# 示例:全屏截图
take_screenshot("full_screen_demo")
# 示例:区域截图 (left, top, width, height)
# take_screenshot("region_demo", region=(100, 100, 500, 400))
# 示例:窗口截图
# try:
# take_window_screenshot("微信", "wechat_window_demo")
# except:
# print("未找到微信窗口,跳过窗口截图示例")