first commit
This commit is contained in:
29
utils/__init__.py
Normal file
29
utils/__init__.py
Normal 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
99
utils/notify.py
Normal 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
207
utils/screenshot.py
Normal 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("未找到微信窗口,跳过窗口截图示例")
|
||||
Reference in New Issue
Block a user