first commit
This commit is contained in:
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Virtual environment
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# RPA Framework output
|
||||
output/
|
||||
results/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Data (sensitive)
|
||||
data/*.xlsx
|
||||
data/*.csv
|
||||
data/*.json
|
||||
!data/.gitkeep
|
||||
output
|
||||
49
README.md
Normal file
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# RPA-robot
|
||||
|
||||
基于 [RPA Framework](https://rpaframework.org/) 的机器人流程自动化项目。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
RPA-robot/
|
||||
├── tasks/ # Robot 任务文件
|
||||
│ ├── main.robot # 主任务入口
|
||||
│ └── example_task.robot # 示例任务
|
||||
├── resources/ # 可复用的资源文件
|
||||
│ ├── common.resource # 通用关键字
|
||||
│ └── variables.py # 变量定义
|
||||
├── libraries/ # 自定义 Python 库
|
||||
│ └── __init__.py
|
||||
├── data/ # 测试/业务数据
|
||||
├── output/ # 运行输出(日志、报告)
|
||||
├── requirements.txt # Python 依赖
|
||||
└── README.md
|
||||
└── main.py #运行主文件
|
||||
└──config.py # 配置主文件
|
||||
```
|
||||
|
||||
## 环境准备
|
||||
|
||||
```bash
|
||||
# 创建虚拟环境
|
||||
conda env create -f environment.yaml
|
||||
conda activate rpa-robot
|
||||
rfbrowser init
|
||||
|
||||
## 运行任务
|
||||
|
||||
```bash
|
||||
python .\main.py feishu-chat
|
||||
```
|
||||
|
||||
## 输出说明
|
||||
|
||||
运行后会在 `output/` 目录生成:
|
||||
- `report.html` - 测试报告
|
||||
- `log.html` - 详细日志
|
||||
- `output.xml` - 结构化输出
|
||||
|
||||
## 配置说明
|
||||
```bash
|
||||
config.py 配置文件中有 FEISHU_GROUPS = [] #配置飞书群名
|
||||
```
|
||||
24
config.py
Normal file
24
config.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
项目全局配置
|
||||
"""
|
||||
from dataclasses import Field
|
||||
import os
|
||||
|
||||
# ============ 飞书配置 ============
|
||||
FEISHU_DESKTOP_PATH = r"C:\Users\songy\AppData\Local\Feishu\app\Feishu.exe" # 飞书客户端路径(按实际修改)
|
||||
FEISHU_WEB_URL = "https://www.feishu.cn/messenger/" # 飞书网页版
|
||||
FEISHU_GROUPS = ["资产管理组", "鲜生活信息支持沟通群(仅系统研发)","信息支持中台沟通群","参盘与总部人力费用沟通群"]
|
||||
|
||||
# ============ 微信配置 ============
|
||||
WECHAT_DESKTOP_PATH = r"C:\Program Files (x86)\Tencent\WeChat\WeChat.exe" # 微信客户端路径(按实际修改)
|
||||
|
||||
# ============ 常用网页 ============
|
||||
WEB_LINKS = {
|
||||
"百度": "https://www.baidu.com",
|
||||
"GitHub": "https://github.com",
|
||||
"Google": "https://www.google.com",
|
||||
}
|
||||
|
||||
# ============ 输出配置 ============
|
||||
OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "output")
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
15
environment.yaml
Normal file
15
environment.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
name: rpa-robot
|
||||
|
||||
channels:
|
||||
- conda-forge
|
||||
- defaults
|
||||
|
||||
dependencies:
|
||||
- python=3.10
|
||||
- pip
|
||||
- pip:
|
||||
- rpaframework
|
||||
- robotframework-browser
|
||||
- pywinauto
|
||||
- pyautogui
|
||||
- pygetwindow
|
||||
78
main.py
Normal file
78
main.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
RPA-robot 主入口
|
||||
|
||||
统一调度所有自动化任务
|
||||
"""
|
||||
import argparse
|
||||
from asyncio import Task
|
||||
import sys
|
||||
|
||||
from tasks import open_feishu_chat
|
||||
from utils import get_logger, show_warning_countdown
|
||||
|
||||
log = get_logger("main")
|
||||
|
||||
# 任务注册表
|
||||
TASKS = {
|
||||
"feishu-chat": {
|
||||
"desc": "打开飞书并找到指定群聊截图(默认:资产管理组)",
|
||||
"func": open_feishu_chat.feishu_groups_list,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def list_tasks():
|
||||
"""列出所有可用任务"""
|
||||
print("\n可用任务:")
|
||||
print("-" * 40)
|
||||
for name, info in TASKS.items():
|
||||
print(f" {name:20s} {info['desc']}")
|
||||
print()
|
||||
|
||||
|
||||
def run_task(task_name: str, *args):
|
||||
"""执行指定任务"""
|
||||
task = TASKS.get(task_name)
|
||||
show_warning_countdown(message="即将开始自动截图", seconds=5)
|
||||
if not task:
|
||||
log.error("未知任务: %s", task_name)
|
||||
list_tasks()
|
||||
for task in TASKS:
|
||||
task["func"](*args)
|
||||
|
||||
log.info("===== 开始执行任务: %s =====", task_name)
|
||||
try:
|
||||
if args:
|
||||
task["func"](*args)
|
||||
else:
|
||||
task["func"]()
|
||||
log.info("===== 任务完成: %s =====", task_name)
|
||||
except Exception as e:
|
||||
log.error("任务执行失败 [%s]: %s", task_name, e)
|
||||
sys.exit(1)
|
||||
show_warning_countdown(message="任务完成", seconds=3)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="RPA-robot 自动化任务调度")
|
||||
parser.add_argument(
|
||||
"task",
|
||||
nargs="?",
|
||||
help="要执行的任务名称(不传则列出所有任务)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"args",
|
||||
nargs="*",
|
||||
help="任务参数(如 web 任务的网页名称)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.task:
|
||||
list_tasks()
|
||||
return
|
||||
|
||||
|
||||
run_task(args.task, *args.args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
rpaframework
|
||||
229
tasks/open_feishu_chat.py
Normal file
229
tasks/open_feishu_chat.py
Normal file
@@ -0,0 +1,229 @@
|
||||
"""
|
||||
任务:打开飞书客户端,找到"资产管理组"群聊并截图
|
||||
|
||||
流程:
|
||||
1. 启动飞书桌面客户端(或连接已运行的实例)
|
||||
2. 等待窗口加载并激活
|
||||
3. 使用搜索功能找到"资产管理组"群聊
|
||||
4. 点击进入群聊
|
||||
5. 截取群聊页面
|
||||
6. 保存今日全部聊天记录
|
||||
"""
|
||||
from ast import main
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import pygetwindow as gw
|
||||
from pywinauto import Application
|
||||
|
||||
from config import FEISHU_DESKTOP_PATH, OUTPUT_DIR, FEISHU_GROUPS
|
||||
from utils import get_logger
|
||||
from utils.screenshot import take_window_screenshot
|
||||
|
||||
log = get_logger("feishu_chat")
|
||||
|
||||
# 飞书窗口标题可能的关键词
|
||||
FEISHU_WINDOW_TITLES = ["飞书", "Lark"]
|
||||
|
||||
|
||||
def _find_feishu_window():
|
||||
"""查找已运行的飞书窗口,返回窗口标题"""
|
||||
for keyword in FEISHU_WINDOW_TITLES:
|
||||
windows = gw.getWindowsWithTitle(keyword)
|
||||
if windows:
|
||||
return windows[0].title
|
||||
return None
|
||||
|
||||
|
||||
def _connect_feishu(max_retries: int = 6, interval: int = 5):
|
||||
"""
|
||||
连接飞书窗口,带重试机制
|
||||
|
||||
Args:
|
||||
max_retries: 最大重试次数
|
||||
interval: 每次重试间隔(秒)
|
||||
"""
|
||||
for attempt in range(1, max_retries + 1):
|
||||
# 先检查飞书窗口是否已出现
|
||||
win_title = _find_feishu_window()
|
||||
if win_title:
|
||||
log.info("检测到飞书窗口: '%s'(第 %d 次尝试)", win_title, attempt)
|
||||
app = Application(backend="uia").connect(
|
||||
title=win_title, timeout=10
|
||||
)
|
||||
main_win = app.window(title=win_title)
|
||||
main_win.wait("visible", timeout=10)
|
||||
main_win.set_focus()
|
||||
# 全屏显示
|
||||
main_win.maximize()
|
||||
# // 全屏显示
|
||||
main_win.fullscreen = True
|
||||
return app, main_win
|
||||
|
||||
log.info("等待飞书窗口出现...(第 %d/%d 次)", attempt, max_retries)
|
||||
time.sleep(interval)
|
||||
|
||||
raise TimeoutError(
|
||||
f"等待飞书窗口超时({max_retries * interval}秒),"
|
||||
"请确认飞书客户端已正确安装且路径正确: " + FEISHU_DESKTOP_PATH
|
||||
)
|
||||
|
||||
|
||||
def save_today_chat_history(app, chat_name: str) -> str:
|
||||
"""
|
||||
通过滚动截图保存今日全部聊天记录
|
||||
|
||||
从当前聊天位置向上滚动,逐屏截图,
|
||||
直到检测到今日日期分隔线或达到最大滚动次数。
|
||||
|
||||
Args:
|
||||
app: pywinauto Application 对象
|
||||
chat_name: 群聊名称
|
||||
|
||||
Returns:
|
||||
截图保存目录路径
|
||||
"""
|
||||
today_str = datetime.now().strftime("%Y-%m-%d")
|
||||
# 按日期创建子目录,如 output/20260826/
|
||||
output_dir = os.path.join(OUTPUT_DIR, datetime.now().strftime("%Y%m%d"))
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
import pyautogui
|
||||
|
||||
try:
|
||||
# 直接通过 pygetwindow 查找聊天窗口(避免 pywinauto 多窗口匹配问题)
|
||||
candidates = (
|
||||
gw.getWindowsWithTitle(chat_name)
|
||||
or gw.getWindowsWithTitle("飞书")
|
||||
or gw.getWindowsWithTitle("Lark")
|
||||
)
|
||||
if not candidates:
|
||||
raise ValueError(f"未找到飞书或群聊窗口: {chat_name}")
|
||||
|
||||
win = candidates[0]
|
||||
# 激活窗口到前台(最小化时先还原)
|
||||
try:
|
||||
if win.isMinimized:
|
||||
win.restore()
|
||||
win.activate()
|
||||
except Exception:
|
||||
log.warning("窗口激活失败,尝试继续截图")
|
||||
time.sleep(0.5)
|
||||
win_bbox = (win.left, win.top, win.width, win.height)
|
||||
|
||||
max_scrolls = 30 # 最大滚动次数
|
||||
scroll_pause = 0.8 # 每次滚动后等待时间
|
||||
shot_index = 1
|
||||
|
||||
log.info("开始滚动截图,最多 %d 屏...", max_scrolls)
|
||||
log.info("先滚动到聊天框最底部...")
|
||||
scroll_to_bottom_times = 30
|
||||
for _ in range(scroll_to_bottom_times):
|
||||
center_x = win.left + win.width - 100 # 聊天区域右侧中间位置
|
||||
center_y = win.top + win.height // 2
|
||||
pyautogui.moveTo(center_x, center_y)
|
||||
pyautogui.scroll(-500000) # 向下滚动(负数表示向下,显示最新消息)
|
||||
time.sleep(scroll_pause)
|
||||
|
||||
for i in range(max_scrolls):
|
||||
# 截图
|
||||
shot_name = f"{chat_name}_{today_str}_{shot_index:03d}"
|
||||
shot_path = os.path.join(output_dir, f"{shot_name}.png")
|
||||
screenshot = pyautogui.screenshot(region=win_bbox)
|
||||
screenshot.save(shot_path)
|
||||
log.info("截图 %d/%d: %s", shot_index, max_scrolls, shot_path)
|
||||
shot_index += 1
|
||||
|
||||
# 向上滚动(鼠标滚轮向上滚动加载历史消息)
|
||||
# 将鼠标移到聊天区域右侧
|
||||
center_x = win.left + win.width - 100
|
||||
center_y = win.top + win.height // 2
|
||||
# // 滚动
|
||||
pyautogui.moveTo(center_x, center_y)
|
||||
pyautogui.scroll(500) # 向上滚动
|
||||
time.sleep(scroll_pause)
|
||||
|
||||
# 检测是否已到达顶部(通过检查是否有日期分隔线)
|
||||
# 简单策略:如果连续滚动后内容不再变化则停止
|
||||
# 这里用固定次数,实际可根据图像相似度判断
|
||||
|
||||
log.info("滚动截图完成,共 %d 张,保存在: %s", shot_index - 1, output_dir)
|
||||
return output_dir
|
||||
|
||||
except Exception as e:
|
||||
log.error("滚动截图失败: %s", e)
|
||||
return output_dir
|
||||
|
||||
def feishu_groups_list():
|
||||
log.info("获取飞书群组")
|
||||
# 任务开始前弹出桌面警告倒计时
|
||||
for chat_name in FEISHU_GROUPS:
|
||||
open_feishu_and_find_chat(chat_name=chat_name)
|
||||
log.info(f"获取群组列表完成{chat_name}")
|
||||
def open_feishu_and_find_chat(
|
||||
chat_name: str = "资产管理组",
|
||||
screenshot_name: str = None,
|
||||
save_chat_history: bool = True,
|
||||
):
|
||||
"""
|
||||
打开飞书客户端,找到指定群聊并截图
|
||||
|
||||
Args:
|
||||
chat_name: 群聊名称,默认"资产管理组"
|
||||
screenshot_name: 截图文件名(不含扩展名),默认为时间戳
|
||||
save_chat_history: 是否保存今日聊天记录
|
||||
"""
|
||||
log.info("===== 开始任务:打开飞书并查找群聊 '%s' =====", chat_name)
|
||||
|
||||
# 1. 检查飞书是否已在运行
|
||||
existing_title = _find_feishu_window()
|
||||
if existing_title:
|
||||
log.info("步骤 1: 飞书已在运行,直接连接")
|
||||
else:
|
||||
log.info("步骤 1: 启动飞书客户端")
|
||||
try:
|
||||
subprocess.Popen(FEISHU_DESKTOP_PATH)
|
||||
except FileNotFoundError:
|
||||
log.error("未找到飞书客户端,请检查路径: %s", FEISHU_DESKTOP_PATH)
|
||||
raise
|
||||
|
||||
# 2. 连接飞书窗口(带重试)
|
||||
log.info("步骤 2: 等待飞书窗口加载...")
|
||||
app, main_win = _connect_feishu()
|
||||
log.info("飞书窗口已激活")
|
||||
|
||||
# 3. 打开搜索(飞书支持 Ctrl+K 快捷键打开搜索)
|
||||
log.info("步骤 3: 打开搜索框")
|
||||
main_win.type_keys("^k") # Ctrl+K
|
||||
time.sleep(1.5)
|
||||
|
||||
# 4. 输入群聊名称
|
||||
log.info("步骤 4: 搜索 '%s'", chat_name)
|
||||
# 先清空搜索框
|
||||
main_win.type_keys("^a")
|
||||
time.sleep(0.3)
|
||||
main_win.type_keys(chat_name, with_spaces=True)
|
||||
time.sleep(2) # 等待搜索结果
|
||||
|
||||
# 5. 按回车选中第一个搜索结果
|
||||
log.info("步骤 5: 选中搜索结果")
|
||||
main_win.type_keys("{ENTER}")
|
||||
time.sleep(2) # 等待群聊页面加载
|
||||
|
||||
# 6. 保存今日聊天记录
|
||||
if save_chat_history:
|
||||
log.info("步骤 6: 保存今日聊天记录")
|
||||
chat_file = save_today_chat_history(app, chat_name)
|
||||
log.info("聊天记录已保存: %s", chat_file)
|
||||
|
||||
log.info("===== 任务完成 =====")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 任务开始前弹出桌面警告倒计时
|
||||
from utils import show_warning_countdown
|
||||
|
||||
show_warning_countdown(message="即将开始自动截图", seconds=5)
|
||||
open_feishu_and_find_chat()
|
||||
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