1000 张图插入 Excel 要过夜?CodeBuddy 双模式批量处理,喝杯咖啡的功夫全入表!

简介: CodeBuddy 生成的这工具就不一样了,双模式批量处理的设计挺巧妙。不管是文件夹批量导入,还是指定图片路径,都能快速把大量图片塞进 Excel 里,而且在格式适配和资源占用控制上也有一手,还没什么操作门槛。


关于Excel图片批量插入的痛点

单张插入的重复性劳动
传统 Excel 仅支持 逐张插入图片(通过「插入图片」功能或复制粘贴),插入 1000 张图需重复操作千次,即便熟练用户每分钟插入 10 张,也需 16 小时以上,严重影响工作效率。
就好比我们老师在批量登记我们学生的成绩,脑子突然抽抽下信息就填错了

缺乏原生批量导入功能
Excel 本身未提供「批量选择文件夹内所有图片」的入口,用户需借助第三方插件或 VBA 代码实现批量插入,对非技术用户门槛极高。
常见问题:图片遮挡数据、单元格高度混乱、跨页显示不全等,需反复调试。

图片位置与对齐失控
手动拖动图片时,难以精准对齐单元格网格线,尤其在多列多行间操作,易出现「图片错位、行列间距不均」等问题,影响表格美观度。

嵌入图片导致文件爆炸式增大
每张高清图片嵌入 Excel 后,文件体积可能从几十 KB 飙升至数百 MB(如 1000 张 500KB 的图片直接使文件超过 500MB),导致 Excel 打开缓慢、保存卡顿,甚至触发「内存不足」报错。

传统 Excel 痛点

CodeBuddy 双模式的解决方案

手动插入耗时、批量功能缺失

「双模式」(文件夹批量导入 + 指定图片路径)一键处理,1000 张图分钟级完成

图片格式与单元格适配困难

自动匹配单元格尺寸、锁定位置,支持批量设置格式(如缩放比例、对齐方式)

文件体积大、性能卡顿

提供「链接模式」(轻量)与「嵌入模式」(安全),按需选择,减少资源占用

操作门槛高(需 VBA 或插件)

可视化界面操作,无需代码基础,新手可快速上手

随着CodeBuddy的爆火,我是否想能否生成一款属于我自己的批量化Excel文件图片的插入操作应用呢?
想法存在,那么我们立刻开始实操

使用CodeBuddy实现一个强大的Excel图片批量插入工具

我们打开vscode进入到拓展中直接搜索CodeBuddy,点击安装这个插件就行了

我们点击左侧的插件图标就能进行使用了
我这里准备了一张关于这款应用简单介绍的README文件,我们让CodeBuddy帮我们优化下

他这里介绍的很详细

那么我们直接再次@这个README文件,让他依据这个文件进行开发操作
这里也是很快的将应用代码生成出来了

我们在终端输入命令
code main.py运行试试
这里我们直接将报错信息复制给CodeBuddy让它处理下
这里他开始进行了快速分析以及代码生成修改

代码修改好了之后,我们继续进行测试
这里我们成功打开了应用,看着风格还是不错的

我们选择一个图片文件夹

选中我们想插入的Excel文件
我们这里也是成功的将图片进行了插入操作了
因为我们这里没有将框框拉开,所以显现的有点狭窄了,但是整体的实现效果还是不错的,大家也可以去试试


示例代码如下:

import os
import re
import tkinter as tk
import webbrowser
from tkinter import filedialog, messagebox, ttk
from typing import Dict, Optional, Tuple
  
import xlwings as xw
  
  
class ExcelImageMatcherPro:
    def __init__(self, master):
        self.master = master
        master.title("Excel批量匹配插图工具")
        master.geometry("600x700")
        # 应用主题和配色方案
        self.setup_theme()
        # 初始化变量
        self.col_image_map: Dict[str, str] = {}
        self.row_image_map: Dict[str, str] = {}
        self.topmost_var = tk.BooleanVar(value=True)
        # 创建主界面
        self.create_main_interface()
        # 窗口居中
        self.center_window(master)
        # 初始化帮助系统
        self._create_help_tags()
        self.show_help_guide()
        # 绑定事件
        self.notebook.bind("<<NotebookTabChanged>>", self.on_tab_changed)
        master.attributes('-topmost', self.topmost_var.get())
  
    def setup_theme(self):
        """设置应用主题和配色方案"""
        style = ttk.Style()
        # 主色调 - 紫色系
        primary_color = "#7B1FA2"
        secondary_color = "#9C27B0"
        accent_color = "#E1BEE7"
        # 文本颜色
        text_color = "#333333"
        light_text = "#FFFFFF"
        # 状态颜色
        success_color = "#4CAF50"
        warning_color = "#FFC107"
        error_color = "#F44336"
        info_color = "#2196F3"
        # 配置主题
        style.theme_create("custom_theme", parent="clam", settings={
            "TFrame": {"configure": {"background": "#F5F5F5"}},
            "TLabel": {"configure": {"foreground": text_color, "background": "#F5F5F5", "font": ('Microsoft YaHei', 9)}},
            "TButton": {
                "configure": {
                    "foreground": light_text,
                    "background": primary_color,
                    "font": ('Microsoft YaHei', 9),
                    "padding": 5,
                    "borderwidth": 1,
                    "relief": "raised"
                },
                "map": {
                    "background": [("active", secondary_color), ("disabled", "#CCCCCC")],
                    "foreground": [("disabled", "#999999")]
                }
            },
            "TEntry": {
                "configure": {
                    "fieldbackground": "white",
                    "foreground": text_color,
                    "insertcolor": text_color,
                    "font": ('Microsoft YaHei', 9)
                }
            },
            "TCombobox": {
                "configure": {
                    "fieldbackground": "white",
                    "foreground": text_color,
                    "selectbackground": accent_color,
                    "font": ('Microsoft YaHei', 9)
                }
            },
            "TNotebook": {
                "configure": {
                    "background": "#F5F5F5",
                    "tabmargins": [2, 5, 2, 0]
                }
            },
            "TNotebook.Tab": {
                "configure": {
                    "background": "#E0E0E0",
                    "foreground": text_color,
                    "padding": [10, 5],
                    "font": ('Microsoft YaHei', 9, 'bold')
                },
                "map": {
                    "background": [("selected", "#FFFFFF"), ("active", "#EEEEEE")],
                    "expand": [("selected", [1, 1, 1, 0])]
                }
            },
            "TScrollbar": {
                "configure": {
                    "background": "#E0E0E0",
                    "troughcolor": "#F5F5F5",
                    "arrowcolor": text_color
                }
            },
            "Horizontal.TProgressbar": {
                "configure": {
                    "background": primary_color,
                    "troughcolor": "#E0E0E0",
                    "borderwidth": 0,
                    "lightcolor": primary_color,
                    "darkcolor": primary_color
                }
            }
        })
        style.theme_use("custom_theme")
  
    def create_main_interface(self):
        """创建主界面组件"""
        # 主容器
        main_frame = ttk.Frame(self.master)
        main_frame.pack(fill="both", expand=True, padx=10, pady=10)
        # 标题栏
        title_frame = ttk.Frame(main_frame)
        title_frame.pack(fill="x", pady=(0, 10))
        title_label = ttk.Label(
            title_frame,
            text="Excel批量匹配插图工具",
            font=('Microsoft YaHei', 12, 'bold'),
            foreground="#7B1FA2"
        )
        title_label.pack(side="left")
        # 标签页控件
        self.notebook = ttk.Notebook(main_frame)
        self.notebook.pack(fill="both", expand=True)
        # 创建两个标签页
        self.create_column_tab()
        self.create_row_tab()
        # 状态栏
        self.create_status_bar()
  
    def create_column_tab(self):
        """创建列匹配模式标签页"""
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="列匹配模式")
        # 描述区域
        desc_frame = ttk.LabelFrame(
            tab,
            text="说明",
            padding=10,
            style="Custom.TLabelframe"
        )
        desc_frame.pack(fill="x", padx=5, pady=5)
        ttk.Label(
            desc_frame,
            text="列匹配模式:按垂直方向匹配插入,适合单列数据匹配。\n图片名称需与指定列中的单元格内容完全匹配。",
            foreground="#616161",
            font=('Microsoft YaHei', 9)
        ).pack(anchor="w")
        # Excel文件选择区域
        excel_frame = ttk.LabelFrame(tab, text="Excel文件设置", padding=10)
        excel_frame.pack(fill="x", padx=5, pady=5)
        self.col_excel_var = tk.StringVar(value="使用当前活动工作簿")
        ttk.Label(excel_frame, text="Excel文件:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
        excel_entry = ttk.Entry(
            excel_frame,
            textvariable=self.col_excel_var,
            width=40,
            state="readonly",
            style="Custom.TEntry"
        )
        excel_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        btn_frame = ttk.Frame(excel_frame)
        btn_frame.grid(row=0, column=2, padx=5, pady=5, sticky="e")
        ttk.Button(
            btn_frame,
            text="浏览...",
            command=lambda: self.select_excel_file(self.col_excel_var),
            style="Accent.TButton"
        ).pack(side="left", padx=2)
        ttk.Button(
            btn_frame,
            text="清除",
            command=lambda: self.col_excel_var.set("使用当前活动工作簿")
        ).pack(side="left", padx=2)
        # 参数设置区域
        param_frame = ttk.LabelFrame(tab, text="匹配参数设置", padding=10)
        param_frame.pack(fill="x", padx=5, pady=5)
        # 第一行参数
        row1_frame = ttk.Frame(param_frame)
        row1_frame.pack(fill="x", pady=5)
        ttk.Label(row1_frame, text="起始行号:").pack(side="left", padx=5)
        self.col_start_row = ttk.Entry(row1_frame, width=8)
        self.col_start_row.pack(side="left", padx=5)
        self.col_start_row.insert(0, "2")
        ttk.Label(row1_frame, text="匹配列:").pack(side="left", padx=5)
        self.col_match = ttk.Entry(row1_frame, width=8)
        self.col_match.pack(side="left", padx=5)
        self.col_match.insert(0, "A")
        ttk.Label(row1_frame, text="插入列:").pack(side="left", padx=5)
        self.col_insert = ttk.Entry(row1_frame, width=8)
        self.col_insert.pack(side="left", padx=5)
        self.col_insert.insert(0, "B")
        # 第二行参数
        row2_frame = ttk.Frame(param_frame)
        row2_frame.pack(fill="x", pady=5)
        ttk.Label(row2_frame, text="边距:").pack(side="left", padx=5)
        self.col_margin = ttk.Entry(row2_frame, width=8)
        self.col_margin.pack(side="left", padx=5)
        self.col_margin.insert(0, "2")
        # 图片文件夹选择
        folder_frame = ttk.Frame(param_frame)
        folder_frame.pack(fill="x", pady=10)
        self.col_folder_var = tk.StringVar()
        ttk.Label(folder_frame, text="图片文件夹:").pack(side="left", padx=5)
        folder_entry = ttk.Entry(
            folder_frame,
            textvariable=self.col_folder_var,
            width=40,
            state="readonly"
        )
        folder_entry.pack(side="left", padx=5, expand=True, fill="x")
        ttk.Button(
            folder_frame,
            text="浏览...",
            command=lambda: self.select_folder(self.col_folder_var, mode="column"),
            style="Accent.TButton"
        ).pack(side="left", padx=5)
        # 执行按钮
        btn_frame = ttk.Frame(tab)
        btn_frame.pack(fill="x", padx=5, pady=10)
        ttk.Button(
            btn_frame,
            text="执行列匹配插入",
            command=self.run_column_match,
            style="Primary.TButton"
        ).pack(fill="x", expand=True)
        # 日志区域
        log_frame = ttk.LabelFrame(tab, text="操作日志", padding=10)
        log_frame.pack(fill="both", expand=True, padx=5, pady=5)
        self.col_log = tk.Text(
            log_frame,
            wrap=tk.WORD,
            height=10,
            state="disabled",
            font=('Microsoft YaHei', 9),
            bg="white",
            fg="#333333",
            padx=5,
            pady=5
        )
        scroll = ttk.Scrollbar(log_frame, command=self.col_log.yview)
        self.col_log.configure(yscrollcommand=scroll.set)
        self.col_log.pack(side="left", fill="both", expand=True)
        scroll.pack(side="right", fill="y")
  
    def create_row_tab(self):
        """创建行匹配模式标签页"""
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="行匹配模式")
        # 描述区域
        desc_frame = ttk.LabelFrame(tab, text="说明", padding=10)
        desc_frame.pack(fill="x", padx=5, pady=5)
        ttk.Label(
            desc_frame,
            text="行匹配模式:按水平方向匹配插入,适合单行数据匹配。\n图片名称需与指定行中的单元格内容完全匹配。",
            foreground="#616161",
            font=('Microsoft YaHei', 9)
        ).pack(anchor="w")
        # Excel文件选择区域
        excel_frame = ttk.LabelFrame(tab, text="Excel文件设置", padding=10)
        excel_frame.pack(fill="x", padx=5, pady=5)
        self.row_excel_var = tk.StringVar(value="使用当前活动工作簿")
        ttk.Label(excel_frame, text="Excel文件:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
        excel_entry = ttk.Entry(
            excel_frame,
            textvariable=self.row_excel_var,
            width=40,
            state="readonly"
        )
        excel_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        btn_frame = ttk.Frame(excel_frame)
        btn_frame.grid(row=0, column=2, padx=5, pady=5, sticky="e")
        ttk.Button(
            btn_frame,
            text="浏览...",
            command=lambda: self.select_excel_file(self.row_excel_var),
            style="Accent.TButton"
        ).pack(side="left", padx=2)
        ttk.Button(
            btn_frame,
            text="清除",
            command=lambda: self.row_excel_var.set("使用当前活动工作簿")
        ).pack(side="left", padx=2)
        # 参数设置区域
        param_frame = ttk.LabelFrame(tab, text="匹配参数设置", padding=10)
        param_frame.pack(fill="x", padx=5, pady=5)
        # 参数行
        row_frame = ttk.Frame(param_frame)
        row_frame.pack(fill="x", pady=10)
        ttk.Label(row_frame, text="匹配行:").pack(side="left", padx=5)
        self.row_match = ttk.Entry(row_frame, width=8)
        self.row_match.pack(side="left", padx=5)
        self.row_match.insert(0, "1")
        ttk.Label(row_frame, text="插入行:").pack(side="left", padx=5)
        self.row_insert = ttk.Entry(row_frame, width=8)
        self.row_insert.pack(side="left", padx=5)
        self.row_insert.insert(0, "2")
        ttk.Label(row_frame, text="边距:").pack(side="left", padx=5)
        self.row_margin = ttk.Entry(row_frame, width=8)
        self.row_margin.pack(side="left", padx=5)
        self.row_margin.insert(0, "2")
        # 图片文件夹选择
        folder_frame = ttk.Frame(param_frame)
        folder_frame.pack(fill="x", pady=10)
        self.row_folder_var = tk.StringVar()
        ttk.Label(folder_frame, text="图片文件夹:").pack(side="left", padx=5)
        folder_entry = ttk.Entry(
            folder_frame,
            textvariable=self.row_folder_var,
            width=40,
            state="readonly"
        )
        folder_entry.pack(side="left", padx=5, expand=True, fill="x")
        ttk.Button(
            folder_frame,
            text="浏览...",
            command=lambda: self.select_folder(self.row_folder_var, mode="row"),
            style="Accent.TButton"
        ).pack(side="left", padx=5)
        # 执行按钮
        btn_frame = ttk.Frame(tab)
        btn_frame.pack(fill="x", padx=5, pady=10)
        ttk.Button(
            btn_frame,
            text="执行行匹配插入",
            command=self.run_row_match,
            style="Primary.TButton"
        ).pack(fill="x", expand=True)
        # 日志区域
        log_frame = ttk.LabelFrame(tab, text="操作日志", padding=10)
        log_frame.pack(fill="both", expand=True, padx=5, pady=5)
        self.row_log = tk.Text(
            log_frame,
            wrap=tk.WORD,
            height=10,
            state="disabled",
            font=('Microsoft YaHei', 9),
            bg="white",
            fg="#333333",
            padx=5,
            pady=5
        )
        scroll = ttk.Scrollbar(log_frame, command=self.row_log.yview)
        self.row_log.configure(yscrollcommand=scroll.set)
        self.row_log.pack(side="left", fill="both", expand=True)
        scroll.pack(side="right", fill="y")
  
    def create_status_bar(self):
        """创建状态栏"""
        status_frame = ttk.Frame(self.master, padding=(10, 5))
        status_frame.pack(side="bottom", fill="x")
        # 窗口置顶按钮
        ttk.Checkbutton(
            status_frame,
            text="窗口置顶",
            variable=self.topmost_var,
            command=lambda: self.master.attributes('-topmost', self.topmost_var.get())
        ).pack(side="left", padx=(0, 10))
        # 帮助按钮
        ttk.Button(
            status_frame,
            text="帮助",
            width=8,
            command=self.show_help_guide
        ).pack(side="left", padx=(0, 10))
        # 版本信息
        version_label = ttk.Label(
            status_frame,
            text="版本: 1.0.0",
            foreground="gray"
        )
        version_label.pack(side="left", padx=(0, 10))
        # 作者信息
        author_label = tk.Label(
            status_frame,
            text="By 创客白泽",
            fg="gray",
            cursor="hand2",
            font=('Microsoft YaHei', 9)
        )
        author_label.bind("<Enter>", lambda e: author_label.config(fg="#7B1FA2"))
        author_label.bind("<Leave>", lambda e: author_label.config(fg="gray"))
        author_label.bind(
            "<Button-1>",
            lambda e: webbrowser.open("https://www.52pojie.cn/thread-2030255-1-1.html")
        )
        author_label.pack(side="right")
  
    def _create_help_tags(self):
        """创建日志文本标签样式"""
        for log in [self.col_log, self.row_log]:
            log.tag_config("title", foreground="#7B1FA2", font=('Microsoft YaHei', 10, 'bold'))
            log.tag_config("success", foreground="#4CAF50")
            log.tag_config("warning", foreground="#FF9800")
            log.tag_config("error", foreground="#F44336")
            log.tag_config("info", foreground="#2196F3")
            log.tag_config("preview", foreground="#616161")
            log.tag_config("highlight", background="#E1BEE7")
  
    def center_window(self, window):
        """窗口居中显示"""
        window.update_idletasks()
        width = window.winfo_width()
        height = window.winfo_height()
        screen_width = window.winfo_screenwidth()
        screen_height = window.winfo_screenheight()
        x = (screen_width - width) // 2
        y = (screen_height - height) // 2
        window.geometry(f"{width}x{height}+{x}+{y}")
  
    def show_help_guide(self, target_log=None):
        """显示帮助指南"""
        help_text = """【新手操作指南 - 点下方"帮助"按钮可再次显示】
  
1. 准备工作:
    - 选择Excel文件或使用当前活动工作簿
    - 准备图片文件夹(支持jpg/png/webp/bmp格式)
  
2. 参数设置:
    - Excel文件:选择要操作的工作簿(可选)
    - 起始行号:从哪一行开始匹配(默认为2)
    - 匹配列/行:包含名称的列或行(如A列或1行)
    - 插入列/行:图片要插入的位置(如B列或2行)
    - 边距:图片与单元格边界的距离(推荐2,0表示撑满)
  
3. 执行步骤:
    (1) 选择Excel文件(可选)
    (2) 选择图片文件夹
    (3) 点击"执行匹配插入"按钮
  
★ 注意事项:
    - 图片名称需与单元格内容完全一致(不区分大小写)
    - 示例:单元格"产品A" → 图片"产品A.jpg"
    - 插入过程中请不要操作Excel
    """
  
        if target_log is None:
            current_tab = self.notebook.index("current")
            target_log = self.col_log if current_tab == 0 else self.row_log
  
        self.log_message(help_text, target_log, append=False, tags="info")
  
    def select_excel_file(self, var_tk_stringvar):
        """选择Excel文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[("Excel文件", "*.xls *.xlsx *.xlsm"), ("所有文件", "*.*")])
        if file_path:
            var_tk_stringvar.set(file_path)
  
    def select_folder(self, var_tk_stringvar, mode):
        """选择图片文件夹"""
        folder_path = filedialog.askdirectory()
        if folder_path:
            var_tk_stringvar.set(folder_path)
            log_widget = self.col_log if mode == "column" else self.row_log
  
            self.log_message("开始加载图片...", log_widget, append=False)
  
            current_image_map = self.build_image_map_from_folder(folder_path)
  
            if mode == "column":
                self.col_image_map = current_image_map
            elif mode == "row":
                self.row_image_map = current_image_map
  
            if len(current_image_map) > 0:
                self.log_message(
                    f"加载完成:找到 {len(current_image_map)} 张支持的图片。",
                    log_widget,
                    tags="success"
                )
            else:
                self.log_message("警告: 未找到任何支持的图片文件。", log_widget, tags="warning")
  
            self.preview_insert_positions(mode)
  
    def build_image_map_from_folder(self, folder_path: str) -> Dict[str, str]:
        """从文件夹构建图片名称到路径的映射"""
        image_map: Dict[str, str] = {}
        extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.webp')
        try:
            for root, _, files in os.walk(folder_path):
                for file in files:
                    if file.lower().endswith(extensions):
                        name_without_ext = os.path.splitext(file)[0].strip().lower()
                        image_map[name_without_ext] = os.path.abspath(os.path.join(root, file))
        except Exception as e:
            self.log_message(
                f"构建图片映射出错: {e}",
                self.col_log if 'col' in self._current_mode else self.row_log,
                tags="error"
            )
        return image_map
  
    def validate_column_params(self) -> Dict:
        """验证列匹配参数"""
        params = {
            'match_col': self.col_match.get().upper(),
            'insert_col': self.col_insert.get().upper(),
            'start_row': self.col_start_row.get(),
            'margin': self.col_margin.get(),
            'excel_file': self.col_excel_var.get()
        }
        if not re.match(r'^[A-Z]{1,3}$', params['match_col']):
            raise ValueError("匹配列格式错误 (例如: A, B, AA)")
        if not re.match(r'^[A-Z]{1,3}$', params['insert_col']):
            raise ValueError("插入列格式错误 (例如: A, B, AA)")
        if not params['start_row'].isdigit() or int(params['start_row']) < 1:
            raise ValueError("起始行号必须是大于0的数字")
        if not params['margin'].isdigit() or int(params['margin']) < 0:
            raise ValueError("边距必须是非负数字")
        return {
            'match_col': params['match_col'],
            'insert_col': params['insert_col'],
            'start_row': int(params['start_row']),
            'margin': int(params['margin']),
            'excel_file': params['excel_file']
        }
  
    def validate_row_params(self) -> Dict:
        """验证行匹配参数"""
        params = {
            'match_row': self.row_match.get(),
            'insert_row': self.row_insert.get(),
            'margin': self.row_margin.get(),
            'excel_file': self.row_excel_var.get()
        }
        if not params['match_row'].isdigit() or int(params['match_row']) < 1:
            raise ValueError("匹配行必须是大于0的数字")
        if not params['insert_row'].isdigit() or int(params['insert_row']) < 1:
            raise ValueError("插入行必须是大于0的数字")
        if not params['margin'].isdigit() or int(params['margin']) < 0:
            raise ValueError("边距必须是非负数字")
        return {
            'match_row': int(params['match_row']),
            'insert_row': int(params['insert_row']),
            'margin': int(params['margin']),
            'excel_file': params['excel_file']
        }
  
    def get_excel_app(self) -> xw.App:
        """获取Excel应用实例,优先使用已打开的实例"""
        try:
            # 尝试获取已打开的Excel应用
            app = xw.apps.active
            if app is not None:
                return app
            # 如果没有打开的Excel,尝试获取第一个Excel实例
            if len(xw.apps) > 0:
                return xw.apps[0]
            # 如果都没有,则创建新实例
            return xw.App(visible=True)
        except Exception as e:
            raise Exception(f"无法获取Excel应用实例: {str(e)}")
  
    def excel_operation(self, excel_file: Optional[str] = None) -> Tuple[xw.Book, xw.Sheet]:
        """Excel操作上下文管理器,正确处理已打开的工作簿"""
        app = self.get_excel_app()
        try:
            if excel_file and excel_file != "使用当前活动工作簿":
                # 检查工作簿是否已经打开
                for book in app.books:
                    if book.fullname.lower() == os.path.abspath(excel_file).lower():
                        wb = book
                        break
                else:
                    wb = app.books.open(excel_file)
            else:
                # 使用活动工作簿或第一个工作簿
                wb = app.books.active
                if wb is None and len(app.books) > 0:
                    wb = app.books[0]
                if wb is None:
                    raise Exception("没有可用的工作簿,请先打开或创建一个工作簿")
            ws = wb.sheets.active
            if ws is None:
                raise Exception("工作簿中没有活动的工作表")
            return wb, ws
        except Exception as e:
            raise Exception(f"Excel操作错误: {str(e)}")
  
    def insert_image(self, ws, image_path, cell_addr, margin, log_widget):
        """在Excel中插入图片"""
        try:
            abs_image_path = os.path.abspath(image_path)
            if not os.path.exists(abs_image_path):
                self.log_message(f"错误: 图片文件不存在 - {abs_image_path}", log_widget, tags="error")
                return False
  
            target_cell = ws.range(cell_addr)
  
            left = target_cell.left + margin
            top = target_cell.top + margin
            width = target_cell.width - 2 * margin
            height = target_cell.height - 2 * margin
  
            ws.pictures.add(
                abs_image_path,
                left=left,
                top=top,
                width=width,
                height=height
            )
            return True
  
        except Exception as e:
            error_msg = f"插入图片失败: {str(e)}"
            self.log_message(error_msg, log_widget, tags="error")
            return False
  
    def run_column_match(self):
        """执行列匹配插入"""
        self.log_message("开始列匹配处理...", self.col_log, append=False, tags="title")
        try:
            if not self.col_folder_var.get():
                messagebox.showwarning("提示", "请先选择图片文件夹!")
                self.log_message("错误: 未选择图片文件夹。", self.col_log, tags="error")
                return
  
            params = self.validate_column_params()
            wb, ws = self.excel_operation(params['excel_file'])
            max_row = ws.used_range.last_cell.row
            success_inserts = 0
            processed_excel_rows = 0
            non_empty_match_cells = 0
  
            self.log_message(
                f"将在列 {params['match_col']} 中查找名称,图片插入到列 {params['insert_col']},从行 {params['start_row']} 开始。",
                self.col_log,
                tags="info"
            )
  
            for row_num_excel in range(params['start_row'], max_row + 1):
                processed_excel_rows += 1
                match_cell_addr = f"{params['match_col']}{row_num_excel}"
  
                cell_value = ws.range(match_cell_addr).value
                name_to_match = str(cell_value).strip() if cell_value is not None else ""
  
                if not name_to_match:
                    continue
  
                non_empty_match_cells += 1
                insert_cell_addr = f"{params['insert_col']}{row_num_excel}"
                lower_name_to_match = name_to_match.lower()
  
                if lower_name_to_match in self.col_image_map:
                    image_file_path = os.path.abspath(self.col_image_map[lower_name_to_match])
                    if not os.path.exists(image_file_path):
                        self.log_message(
                            f"错误: 图片文件不存在 - {image_file_path}",
                            self.col_log,
                            tags="error"
                        )
                        continue
  
                    if self.insert_image(ws, image_file_path, insert_cell_addr, params['margin'], self.col_log):
                        success_inserts += 1
                        self.log_message(
                            f"{match_cell_addr}:{name_to_match} → {insert_cell_addr}:{os.path.basename(image_file_path)}",
                            self.col_log,
                            tags="success"
                        )
                else:
                    self.log_message(
                        f"{match_cell_addr}:{name_to_match} → 未找到匹配图片",
                        self.col_log,
                        tags="warning"
                    )
  
            summary = f"\n共处理 {processed_excel_rows} 行数据,成功插入 {success_inserts} 张图片。"
            self.log_message(summary, self.col_log, tags="info")
  
        except Exception as e:
            error_msg = f"列匹配错误: {str(e)}"
            self.log_message(error_msg, self.col_log, tags="error")
            messagebox.showerror("错误", error_msg)
  
    def run_row_match(self):
        """执行行匹配插入"""
        self.log_message("开始行匹配处理...", self.row_log, append=False, tags="title")
        try:
            if not self.row_folder_var.get():
                messagebox.showwarning("提示", "请先选择图片文件夹!")
                self.log_message("错误: 未选择图片文件夹。", self.row_log, tags="error")
                return
  
            params = self.validate_row_params()
            wb, ws = self.excel_operation(params['excel_file'])
            max_col = ws.used_range.last_cell.column
            success_inserts = 0
            processed_excel_cols = 0
            non_empty_match_cells = 0
  
            self.log_message(
                f"将在行 {params['match_row']} 中查找名称,图片插入到行 {params['insert_row']}。",
                self.row_log,
                tags="info"
            )
  
            for col_num_excel in range(1, max_col + 1):
                processed_excel_cols += 1
                match_cell_addr = f"{xw.utils.col_name(col_num_excel)}{params['match_row']}"
  
                cell_value = ws.range(match_cell_addr).value
                name_to_match = str(cell_value).strip() if cell_value is not None else ""
  
                if not name_to_match:
                    continue
  
                non_empty_match_cells += 1
                insert_cell_addr = f"{xw.utils.col_name(col_num_excel)}{params['insert_row']}"
                lower_name_to_match = name_to_match.lower()
  
                if lower_name_to_match in self.row_image_map:
                    image_file_path = os.path.abspath(self.row_image_map[lower_name_to_match])
                    if not os.path.exists(image_file_path):
                        self.log_message(
                            f"错误: 图片文件不存在 - {image_file_path}",
                            self.row_log,
                            tags="error"
                        )
                        continue
  
                    if self.insert_image(ws, image_file_path, insert_cell_addr, params['margin'], self.row_log):
                        success_inserts += 1
                        self.log_message(
                            f"{match_cell_addr}:{name_to_match} → {insert_cell_addr}:{os.path.basename(image_file_path)}",
                            self.row_log,
                            tags="success"
                        )
                else:
                    self.log_message(
                        f"{match_cell_addr}:{name_to_match} → 未找到匹配图片",
                        self.row_log,
                        tags="warning"
                    )
  
            summary = f"\n共处理 {processed_excel_cols} 列数据,成功插入 {success_inserts} 张图片。"
            self.log_message(summary, self.row_log, tags="info")
  
        except Exception as e:
            error_msg = f"行匹配错误: {str(e)}"
            self.log_message(error_msg, self.row_log, tags="error")
            messagebox.showerror("错误", error_msg)
  
    def preview_insert_positions(self, mode):
        """预览插入位置"""
        image_map = self.col_image_map if mode == "column" else self.row_image_map
        log_widget = self.col_log if mode == "column" else self.row_log
  
        if not image_map:
            self.log_message("没有图片可供预览。请先选择图片文件夹。", log_widget, tags="warning")
            return
  
        self.log_message("【插入位置预览】", log_widget, tags="title")
        for name, path in image_map.items():
            self.log_message(
                f"{name} -> {os.path.basename(path)}",
                log_widget,
                tags="preview"
            )
  
    def log_message(self, message, log_widget, append=True, tags=None, clear=False):
        """记录日志消息"""
        log_widget.config(state="normal")
        if clear:
            log_widget.delete(1.0, tk.END)
        if not append:
            log_widget.delete(1.0, tk.END)
        log_widget.insert(tk.END, message + "\n", tags)
        log_widget.see(tk.END)
        log_widget.config(state="disabled")
  
    def on_tab_changed(self, event):
        """标签页切换事件处理"""
        self.show_help_guide()
  
  
if __name__ == "__main__":
    root = tk.Tk()
    app = ExcelImageMatcherPro(root)
    root.mainloop()

总结

我们搞编程的都清楚,在数据处理这块,效率就是王道。Excel 大家都用得多,但它原生的图片插入功能,碰到大量图片时就拉胯了。一张一张插,重复操作累死人不说,格式调整也麻烦,还特别占性能,处理起来真糟心。

CodeBuddy 生成的这工具就不一样了,双模式批量处理的设计挺巧妙。不管是文件夹批量导入,还是指定图片路径,都能快速把大量图片塞进 Excel 里,而且在格式适配和资源占用控制上也有一手,还没什么操作门槛。

从我们的专业视角看,它给了咱新思路,怎么用简洁的代码实现高效功能,降低用户使用难度。真心希望 CodeBuddy 能不断优化,也盼着以后能有更多这种实用工具冒出来,让咱搞数据处理轻松些,代码少写点弯路,工作更顺溜!

目录
打赏
0
2
2
0
159
分享
相关文章
全网跪求的抢票神器!用 CodeBuddy 联动魔搭 MCP,我把 12306 抢票系统玩明白了
CodeBuddy 作为智能编程领域的佼佼者,无需用户具备深厚的编程知识,就能依据用户需求迅速生成高效代码。在票务信息获取方面,它巧妙地构建起与两款 MCP 顺畅交互的桥梁。通过简洁直观的指令输入,CodeBuddy 将用户对票务信息的需求精准转化为机器可理解的语言,为后续获取信息的流程奠定坚实基础。
154 2
面对MCP"工具投毒",我们该如何应对
本文探讨了MCP(Model Context Protocol)的安全风险与防护措施。MCP作为AI系统与外部工具交互的标准框架,虽提升了插件兼容性,但也带来了“工具投毒”等安全威胁。攻击者可通过篡改工具描述,诱导模型执行非授权操作,如读取敏感文件。文章详细分析了攻击原理,并通过复刻实验展示了如何利用MCP客户端/服务器代码实现此类攻击。为应对风险,提出了基于大模型智能评估和eBPF技术的两种安全可观测方案:前者通过内置评估模板检测潜在威胁,后者实时监控系统运行时行为,结合两者可有效提升MCP系统的安全性。
991 92
面对MCP"工具投毒",我们该如何应对
回归开源,两位 Java 和 Go 程序员分享的开源贡献指引
Higress是一个基于Istio和Envoy的云原生API网关,支持AI功能扩展。它通过Go/Rust/JS编写的Wasm插件提供可扩展架构,并包含Node和Java的console模块。Higress起源于阿里巴巴,解决了Tengine配置重载及gRPC/Dubbo负载均衡问题,现已成为阿里云API网关的基础。本文介绍Higress的基本架构、功能(如AI网关、API管理、Ingress流量网关等)、部署方式以及如何参与开源贡献。此外,还提供了有效的开源贡献指南和社区交流信息。
403 33
Airflow vs Argo Workflows:分布式任务调度系统的“华山论剑”
本文对比了Apache Airflow与Argo Workflows两大分布式任务调度系统。两者均支持复杂的DAG任务编排、社区支持及任务调度功能,且具备优秀的用户界面。Airflow以Python为核心语言,适合数据科学家使用,拥有丰富的Operator库和云服务集成能力;而Argo Workflows基于Kubernetes设计,支持YAML和Python双语定义工作流,具备轻量化、高性能并发调度的优势,并通过Kubernetes的RBAC机制实现多用户隔离。在大数据和AI场景中,Airflow擅长结合云厂商服务,Argo则更适配Kubernetes生态下的深度集成。
359 34
惊现编程「降维打击」:CodeBuddy 把录屏录音工具开发玩成「搭积木」
CodeBuddy 的代码审查功能也为我开发录屏录音工具保驾护航。在完成部分代码编写后,利用其代码审查模式,它能够自动检查代码中潜在的语法错误、逻辑缺陷以及代码规范问题。像我在编写录制文件存储路径相关代码时,CodeBuddy 及时发现了我对文件路径处理不当可能导致的文件存储失败问题,并给出了详细的优化建议,帮助我提升了代码质量,确保整个录屏录音工具的稳定性和可靠性。
74 12
“最近我给有代码洁癖的同事墙裂安利了通义灵码”
通义灵码2.5.0版本现已全面支持Qwen3,采用混合专家架构,参数量仅为DeepSeek-R1的1/3,是国内首个“混合推理模型”。它在性能评测中超越了DeepSeek-R1、OpenAI-o1等国际主流模型,并全面支持MCP能力,集成国内最大MCP中文社区。作为程序员体验后发现,通义灵码可通过简单指令生成完整项目代码,包括前后端、接口调用等,大幅降低开发门槛。文中通过两个Demo展示了其强大功能:一是聚合多平台热榜数据并推送微信通知;二是基于高德和12306 MCP生成旅游攻略HTML页面。整个过程无需手动编写代码,推荐开发者尝试。
225 47
阿里云 SLS 多云日志接入最佳实践:链路、成本与高可用性优化
本文探讨了如何高效、经济且可靠地将海外应用与基础设施日志统一采集至阿里云日志服务(SLS),解决全球化业务扩展中的关键挑战。重点介绍了高性能日志采集Agent(iLogtail/LoongCollector)在海外场景的应用,推荐使用LoongCollector以获得更优的稳定性和网络容错能力。同时分析了多种网络接入方案,包括公网直连、全球加速优化、阿里云内网及专线/CEN/VPN接入等,并提供了成本优化策略和多目标发送配置指导,帮助企业构建稳定、低成本、高可用的全球日志系统。
468 54
AI 场景深度优化!K8s 集群 OSSFS 2.0 存储卷全面升级,高效访问 OSS 数据
OSSFS 2.0通过轻量化协议设计、协程化技术及FUSE3低级API重构,实现大文件顺序读写与小文件高并发加载的显著提升,在实际测试中表现出高达数十倍的吞吐量增长。适用于机器学习训练、推理等对高带宽低延迟要求严苛的场景,同时支持静态和动态挂载方式,方便用户在ACK集群中部署使用。
321 34
自媒体创作场景实践|通义千问3 + MCP=一切皆有可能
本文介绍了通过MCP(Model Context Protocol)结合通义千问大模型实现跨平台、跨服务的自动化任务处理方案。使用Qwen3-235B-A22B模型,配合ComfyUI生成图像,并通过小红书等社交媒体发布内容,展示了如何打破AI云服务的数据孤岛。具体实践包括接入FileSystem、ComfyUI和第三方媒体Server,完成从本地文件读取到生成图像再到发布的全流程。 方案优势在于高可扩展性和易用性,但也存在大模型智能化不足、MCP Server开发难度较大及安全风险等问题。未来需进一步提升模型能力、丰富应用场景并解决安全挑战,推动MCP在更多领域落地。
782 27
自媒体创作场景实践|通义千问3 + MCP=一切皆有可能
登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问