再也不用重复捕获图像!如何用影刀RPA导出其全部应用的图像库,供其他应用或分享其他账号使用?

0 阅读13分钟

影刀哪哪都好,但是提起”图像库”,还是让不少铁铁抓狂!我们先来看一组影刀社区用户对于此功能的一些反馈~

图片

这些讨论全部围绕影刀RPA「图像库的可复用性与同步管理」展开,

核心痛点在于图像库功能目前在易用性和跨场景兼容性存在不足。具体如下:

1、复用困难。图像被锁死在单个应用里,用户无法在不同应用间直接调用已捕获过的图像,只能重新截图或重新导入/命名,导致大量重复工作(辛苦了,请再截一遍),降低了开发效率。 

2、管理混乱。应用内图像库与本地image文件夹不同步,并且图像库中显示的文件名与实际保存的文件名不一致(对不上号,鬼知道谁是谁!),使得用户难以有效管理和查找图像。

3、数据安全隐患。元素库可以上云,图像库却不行,且没有“导出图像库”功能,导致重装系统或换电脑就丢图(喜提“空库”,全部重截)

有的铁铁看着,血压已经上来了有木有🤣?

其实早在去年我也发了类似建议帖,但似乎因为需求的优先级不够高,目前官方还没有动静。

图片

无法掌控的咱就不多提了,不过,我们也不能干等着官方更新。本期就来分享,我花了小半天功夫琢磨出来的"曲线救国"方案:

借助一个py脚本,3-5秒内把指定影刀账户下所有应用的图像集中拷贝到一个新建的&带分类的”公共图像库文件夹”中,同时生成一个Excel汇总文件,记录所有提取的图像及其相关信息!

以后再也不用重复截图,所有应用都能快速从这里面找到要调用的图像(你看到的会是”有意义的文件名”,而不是一串数字UUID),并导入图像库使用。想共享给其他人使用,直接分享该文件夹即可。

图片

下面具体来看看ta到底强在哪,我又做了哪方面的细节处理~~

一、应用/功能介绍

这个应用主要就是依托于一段Python代码,代码的核心目标是将分散在多个影刀项目中的图像库整合为一个可复用的公共图像库。

目前支持的具体功能点:

  • 批量提取图像资源。遍历&解析影刀账户所有应用文件夹下的xbot_robot/imagesV2.xml文件图像配置,从中获取图像名称、路径、分组等信息,并拷贝图像文件到公共库;

  • 结构化管理图像。按 “分组(group)” 对图像进行分类存储(公共库中以分组名称创建文件夹),保持与原项目中图像的分组逻辑一致,方便后续查找和复用;

  • 去重与冲突处理。通过 MD5 值校验图像内容,避免相同图像被重复拷贝(即使名称不同,内容一致也会被识别为重复);若名称相同但内容不同(MD5 不同),自动生成带序号的副本(如image.png冲突时生成image_1.png),避免文件覆盖;

  • 清理文件名非法字符。支持图像库分组名称和图像名称中非法字符清理,仅保留字母数字字符、空格、连字符、下划线;

  • 生成图像汇总信息。创建 Excel 表格记录所有图像的关键信息,包括:所属项目名称、分组名称、原始图像名称、在原项目中的路径、识别相似度、是否灰度匹配等,方便用户快速定位和管理图像。

二、效果演示

示例一共325张图像,耗时3秒就运行结束了,也没有录视频的必要了,直接看结果吧。

下面第一张图是我运行RPA前指定目录创建的"我的影刀公共图像库"文件夹。来自不同应用但属于同一"group"的图像将全部集中到该"group"对应的文件夹中。

图片

下面图示是"应用图像汇总Excel表",你可以快速查看每个应用使用了哪些图像,以及图像的识别参数。

图片

三、全流程指令代码

1. 主流程

图片

2. Python代码

这次代码生成也是用到了之前分享提到的"影刀RPA代码生成专家提示词",简单跟着AI的引导一步步明确、细化、调整需求即可,相当丝滑~

图片

图片

ShadowBotImageLibraryExtractor.py


# 使用此指令前,请确保安装必要的Python库: 
# pip install openpyxl
# 由公众号:掌心向暖 开发搭建 :PD-1104

import os
import json
import xml.etree.ElementTree as ET
import shutil
import hashlib # 导入 hashlib 库用于MD5计算
from openpyxl import Workbook
from openpyxl.styles import PatternFill, Font, Alignment
from typing import *
try:
    from xbot.app.logging import trace as print
except:
    from xbot import print

def calculate_md5(filepath: str) -> str:
    """计算文件的MD5值"""
    if not os.path.exists(filepath):
        return ""
    hash_md5 = hashlib.md5()
    with open(filepath, "rb"as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

def extract_all_images_to_library(account_folder_path: str, output_dir: str) -> str:
    """
    title: 提取账户下所有机器人项目图像并创建公共图像库
    description: 遍历影刀RPA账户文件夹下的所有机器人项目,提取所有图像文件到公共图像库,并生成汇总Excel文件
    inputs: 
        - account_folder_path (str): 影刀RPA账户文件夹路径,eg: "C:/Users/xxx/AppData/Local/ShadowBot/users/xxx/apps"
        - output_dir (str): 输出目录路径,eg: "E:/桌面"
    outputs: 
        - library_folder_path (str): 创建的公共图像库文件夹路径,eg: "E:/桌面/我的影刀公共图像库"
    """
    
    print(f"开始处理账户文件夹: {account_folder_path}")
    print(f"输出目录: {output_dir}")
    
    # 1. 检查账户文件夹是否存在
    if not os.path.exists(account_folder_path):
        print(f"错误:账户文件夹不存在: {account_folder_path}")
        return ""
    
    # 2. 创建公共图像库根文件夹
    library_folder_name = "我的影刀公共图像库"
    library_folder_path = os.path.join(output_dir, library_folder_name)
    
    try:
        if not os.path.exists(library_folder_path):
            os.makedirs(library_folder_path)
            print(f"创建公共图像库文件夹: {library_folder_path}")
        else:
            print(f"公共图像库文件夹已存在: {library_folder_path}")
    except Exception as e:
        print(f"创建公共图像库文件夹失败: {e}")
        return ""
    
    # 用于记录已拷贝文件的MD5值,避免重复拷贝内容相同的图像
    # key: (safe_group_name, safe_image_name) -> value: 实际拷贝到的文件路径
    copied_image_md5s = {} 
    
    # 3. 遍历账户文件夹下的所有子文件夹(即应用/项目文件夹)
    all_image_elements = []
    processed_projects = 0
    total_images_copied = 0
    
    for item in os.listdir(account_folder_path):
        item_path = os.path.join(account_folder_path, item)
        
        # 跳过非文件夹
        if not os.path.isdir(item_path):
            continue
            
        print(f"\n检查应用/项目文件夹: {item}")
        
        # 4. 检查是否为有效的机器人项目(通过检查xbot_robot/imagesV2.xml存在)
        xml_file_path = os.path.join(item_path, "xbot_robot""imagesV2.xml")
        package_json_path = os.path.join(item_path, "xbot_robot""package.json")
        
        if not os.path.exists(xml_file_path):
            print(f"跳过:未找到XML文件 {xml_file_path},这不是一个影刀RPA项目。")
            continue
        
        # 5. 获取项目名称
        project_name = item  # 默认使用文件夹名
        if os.path.exists(package_json_path):
            try:
                with open(package_json_path, 'r', encoding='utf-8'as f:
                    package_data = json.load(f)
                    project_name = package_data.get("name", item)
            except Exception as e:
                print(f"读取package.json失败: {e}")
        
        # 清理项目名称中的非法字符
        safe_project_name = "".join(c for c in project_name if c.isalnum() or c in (' ''-''_')).rstrip()
        if not safe_project_name:
            safe_project_name = f"project_{processed_projects + 1}"
            
        print(f"处理项目: {safe_project_name}")
        
        # 6. 解析XML文件
        try:
            tree = ET.parse(xml_file_path)
            root = tree.getroot()
        except Exception as e:
            print(f"解析XML文件失败: {e}")
            continue
        
        # 7. 提取该项目的所有图像元素并拷贝
        project_copied_count = 0
        for group in root.findall('group'):
            group_name = group.get('name''默认分组')
            
            # 清理group name中的非法字符,并创建group文件夹
            safe_group_name = "".join(c for c in group_name if c.isalnum() or c in (' ''-''_')).rstrip()
            if not safe_group_name:
                safe_group_name = "default_group" # 确保有一个默认的group名称
                
            group_target_path = os.path.join(library_folder_path, safe_group_name)
            try:
                if not os.path.exists(group_target_path):
                    os.makedirs(group_target_path)
                    print(f"创建Group文件夹: {group_target_path}")
            except Exception as e:
                print(f"创建Group文件夹失败: {e}")
                continue # 跳过该group的图像处理
            
            for imageelement in group.findall('imageelement'):
                element_image_name = imageelement.get('name''')
                element_filepath = imageelement.get('filepath''')
                
                if not element_image_name or not element_filepath:
                    continue
                    
                # 构建源文件完整路径
                source_image_path = os.path.join(item_path, "xbot_robot", element_filepath)
                
                # 检查源文件是否存在
                if not os.path.exists(source_image_path):
                    print(f"源图像文件不存在: {source_image_path}")
                    continue
                
                # 清理图像名称中的非法字符
                safe_image_name = "".join(c for c in element_image_name if c.isalnum() or c in (' ''-''_''.')).rstrip()
                if not safe_image_name:
                    safe_image_name = f"image_{total_images_copied + 1}" # fallback name
                
                # 确保文件名以.png结尾,如果没有则添加,如果有其他扩展名则替换为.png
                if not safe_image_name.lower().endswith('.png'):
                    name_parts = os.path.splitext(safe_image_name)
                    safe_image_name = name_parts[0] + '.png'
                
                # 构建目标文件名 (仅仅是图像名.png)
                target_base_name = safe_image_name
                target_image_path_initial = os.path.join(group_target_path, target_base_name)
                
                current_target_image_path = target_image_path_initial
                
                # MD5 相同文件处理逻辑
                # 计算源文件的MD5值
                source_md5 = calculate_md5(source_image_path)
                if not source_md5: # 如果MD5计算失败,跳过此文件
                    print(f"无法计算源文件MD5: {source_image_path}, 跳过拷贝。")
                    continue
                
                found_duplicate_by_md5 = False
                # 检查当前group下是否有MD5值相同的图片已经拷贝
                for key_tuple, existing_path in copied_image_md5s.items():
                    # 仅检查相同group且文件名相同的基础情况
                    if key_tuple[0] == safe_group_name and key_tuple[1] == target_base_name:
                        existing_md5 = calculate_md5(existing_path)
                        if existing_md5 == source_md5:
                            print(f"检测到重复图像 (MD5一致), 跳过拷贝: {element_image_name} (原路径: {source_image_path}) -> 已存在于 {existing_path}")
                            found_duplicate_by_md5 = True
                            # 更新到汇总列表,指向已存在的那个文件路径
                            all_image_elements.append({
                                '项目名称': safe_project_name,
                                '分组名称': group_name,
                                '图像名称': element_image_name,
                                '图像文件路径': element_filepath, # 仅记录原始项目内路径
                                '识别相似度': imageelement.get('similarity'''),
                                '是否灰度匹配': imageelement.get('graying''')
                            })
                            break
                
                if found_duplicate_by_md5:
                    continue # 跳过当前文件的拷贝
                
                # 处理文件名冲突 (不覆盖,以副本形式共存) - 仅当MD5不一致时才进行重命名拷贝
                counter = 1
                while os.path.exists(current_target_image_path):
                    # 检查已存在文件的MD5,如果一致则不再拷贝
                    existing_file_md5 = calculate_md5(current_target_image_path)
                    if existing_file_md5 == source_md5:
                        print(f"检测到重复图像 (MD5一致), 跳过拷贝: {element_image_name} (原路径: {source_image_path}) -> 已存在于 {current_target_image_path}")
                        found_duplicate_by_md5 = True
                        # 更新到汇总列表,指向已存在的那个文件路径
                        all_image_elements.append({
                            '项目名称': safe_project_name,
                            '分组名称': group_name,
                            '图像名称': element_image_name,
                            '图像文件路径': element_filepath, # 仅记录原始项目内路径
                            '识别相似度': imageelement.get('similarity'''),
                            '是否灰度匹配': imageelement.get('graying''')
                        })
                        break # 跳出while循环,不进行拷贝
                    
                    # 如果MD5不一致,则生成新的文件名
                    name_without_ext = os.path.splitext(target_base_name)[0]
                    extension = os.path.splitext(target_base_name)[1]
                    current_target_image_path = os.path.join(group_target_path, f"{name_without_ext}_{counter}{extension}")
                    counter += 1
                
                if found_duplicate_by_md5: # 再次检查是否因MD5重复而跳过
                    continue
                
                # 拷贝图像文件
                try:
                    shutil.copy2(source_image_path, current_target_image_path)
                    project_copied_count += 1
                    total_images_copied += 1
                    print(f"拷贝图像: {element_image_name} -> {os.path.relpath(current_target_image_path, library_folder_path)}")
                    
                    # 记录拷贝成功的文件MD5,用于后续检测重复
                    copied_image_md5s[(safe_group_name, target_base_name)] = current_target_image_path
                    
                    # 添加到汇总列表
                    all_image_elements.append({
                        '项目名称': safe_project_name,
                        '分组名称': group_name,
                        '图像名称': element_image_name,
                        '图像文件路径': element_filepath, # 仅记录原始项目内路径
                        '识别相似度': imageelement.get('similarity'''),
                        '是否灰度匹配': imageelement.get('graying''')
                    })
                except Exception as e:
                    print(f"拷贝图像失败: {element_image_name} - {e}")
        
        print(f"项目 {safe_project_name} 完成,拷贝了 {project_copied_count} 个图像文件")
        processed_projects += 1
            
    # 8. 检查是否有处理的项目
    if processed_projects == 0:
        print("未找到任何有效的机器人项目")
        return ""
        
    print(f"\n=== 项目处理完成 ===")
    print(f"处理项目数量: {processed_projects}")
    print(f"总图像数量: {len(all_image_elements)}")
    print(f"成功拷贝: {total_images_copied}")
    
    # 9. 创建汇总Excel文件
    wb = Workbook()
    ws = wb.active
    ws.title = "公共图像库汇总"
    
    # 10. 设置表头
    headers = ['项目名称''分组名称''图像名称''图像文件路径''识别相似度''是否灰度匹配']
    ws.append(headers)
    
    # 11. 设置表头样式
    yellow_fill = PatternFill(start_color='FFFF00', end_color='FFFF00', fill_type='solid')
    black_font_bold = Font(color='000000', bold=True# 加粗字体
    center_alignment = Alignment(horizontal='center', vertical='center')
    
    for cell in ws[1]:
        cell.fill = yellow_fill
        cell.font = black_font_bold # 应用加粗字体
        cell.alignment = center_alignment
    
    # 12. 添加数据行
    for element in all_image_elements:
        ws.append([
            element['项目名称'],
            element['分组名称'],
            element['图像名称'],
            element['图像文件路径'], # 仅记录原始项目内路径
            element['识别相似度'],
            element['是否灰度匹配']
        ])
    
    # 13. 设置列宽
    column_widths = {
        'A'25,  # 项目名称
        'B'20,  # 分组名称
        'C'40,  # 图像名称
        'D'50,  # 图像文件路径 (原始路径)
        'E'15,  # 识别相似度
        'F'15   # 是否灰度匹配
    }
    
    for col_idx, (column_letter, width) in enumerate(column_widths.items(), 1):
        ws.column_dimensions[column_letter].width = width
    
    # 14. 设置所有单元格居中对齐
    for row in ws.iter_rows():
        for cell in row:
            cell.alignment = center_alignment
    
    # 15. 保存Excel文件到公共图像库文件夹
    excel_filename = "公共图像库汇总.xlsx"
    excel_file_path = os.path.join(library_folder_path, excel_filename)
    
    try:
        if os.path.exists(excel_file_path):
            os.remove(excel_file_path)
            
        wb.save(excel_file_path)
        print(f"Excel汇总文件保存成功: {excel_file_path}")
        
        if os.path.exists(excel_file_path):
            file_size = os.path.getsize(excel_file_path)
            print(f"Excel文件大小: {file_size} 字节")
            
    except Exception as e:
        print(f"保存Excel文件失败: {e}")
    
    # 16. 输出最终统计
    print(f"\n=== 最终统计 ===")
    print(f"公共图像库路径: {library_folder_path}")
    print(f"处理的项目数量: {processed_projects}")
    print(f"拷贝的图像文件数量: {total_images_copied}")
    print(f"Excel汇总文件: {excel_file_path}")
    # 确保文件存在再计算总文件数量,因为Excel文件保存可能失败
    total_files_in_library = total_images_copied
    if os.path.exists(excel_file_path):
        total_files_in_library += 1 
    print(f"公共图像库总文件数量: {total_files_in_library}")
    
    return library_folder_path

四、注意事项

1、如何使用:点击"运行",在弹出的对话框中依次输入:apps文件夹路径和"公共库"指定存储目录,回车或点击"确定",等待几秒即可 ;

图片

图片

*apps文件夹路径怎么找?

图片

图片

图片

2、联动管理:图像库日常使用中即进行"分组"命名管理(推荐“平台_客户端”格式,如“小红书_手机端”、“抖音_网页端”等),这样我们得到的公共库也是严格按照这个分组分类的,方便我们在其他应用调用时快速从中找到目标图像。

3、运行准备:该应用基于影刀RPA开发,请确保电脑端已正确下载影刀RPA应用并登录账号;作者使用设备及测试环境有限,建议你正式运行前先拷贝"1个apps文件夹和几个应用文件夹"来做测试,避免影响原账户文件夹图像。


-END-

• 爱练字的ISTJ型互联网人/信息整合怪/工具人/影刀高级认证工程师。 • 专注分享:RPA&AI自动化场景提效方案、效率软件安利、实用技能。"所有的生产要素都可以被构建,只有认知是壁垒",欢迎関注 @掌心向暖

推荐阅读:

• 拒绝品牌碰瓷!如何通过影刀RPA为品牌IP搭建一套高效的“内容合规治理”工作流? • 那些拥有上千浏览器书签/收藏夹的电脑用户,是怎么管理书签的? • 不会编程的我开发了一款近900行指令的自动化RPA应用,完美解决98%以上复制受限的飞书文档!! • 飞书文档附件文件下载RPA方案2.0来了!不仅是PDF,Word、PPT、Excel、视频都能批量导出了,还都是源文件