乐于分享
好东西不私藏

《告别繁琐的复制粘贴,python实现word文档数据自动渲染填充,效率拉满!》

《告别繁琐的复制粘贴,python实现word文档数据自动渲染填充,效率拉满!》

“带你横跨办公自动化的数据江海”

@摸鱼

闻道有先后,术业有专攻。

各位大佬好!

~我依旧是你们的老朋友摸鱼~

♥ 1

在职场摸爬滚打的这十多年里,我用Python悄悄干了不少“正事”——不知不觉攒下了一整套办公自动化的实用项目技巧。去年10月初创立了公众号 「码海听潮」 ,初衷很简单:把重复的劳动交给代码,把摸鱼的时间留给生活。

目前已经吭哧吭哧更新了90多篇原创文章,每一篇都是实操干货,不讲虚的,只聊怎么用代码真正解放双手,帮大家早点下班、准点摸鱼

♥ 2

♥ 3好叻,多了不说,少了不唠,咱直接上干货。

办公需求场景

从崩溃到优雅的进化

有一个神秘的word文档模板,文档里的一些空白位置需填写的数据和图片,数据来源于一个excel表格,图片则来源与一个图片文件夹,现在的需求是把excel里的数据和图片填写到word对应的地方,要是这种类似的需求你的Big Boss安排你去完成,请问阁下该如何应对?

  • 需求的word模板如下图:

  • 需求的excel文档和图片文件列表

办公痛点分析

01

痛点一: 重复劳动量大

    • 每填写一个 Word 文档,都需要:看 Excel → 记住数据 → 切换到 Word → 找到位置 → 粘贴 → 切换到文件夹 → 找图片 → 插入 → 调整大小。如果 Excel 有 100 行数据,这些动作就要重复 100 次,太可怕…..

    02

    痛点二:频繁切换窗口打断思路

      • 需要在 Excel、Word、文件夹三个界面之间来回切换,每次切换都会打断操作节奏,降低专注度

      由此可见若下载成百上千个word文档的话整个操作流程繁琐且耗时,高频次的鼠标点击和键盘输入使操作者手指疲劳,堪称”键盘敲冒烟”式的体力劳动,加上人工疲劳操作极易导致遗漏文件夹。于是乎这时候,按以往的 “解题套路”,Python 的专属 BGM 该响起来了 ——go~ go~ go~,救苦救难的大救星这不就来了!!

      @摸鱼

      问题拆解思路

      1.数据预处理,获取excel表格数据

      2.把模板字符串与数据列,图片名进行映射

      3.自动渲染保存到word文档里

      下面,我就用python代码让excel见识一下,什么叫”传统文化遇上赛博效率”(仅展示部分代码,非完整代码,需完整代码看文章末尾说明~)

      import tkinter as tkfrom tkinter import filedialog, messagebox, ttkclass QualityFeedbackApp:    def __init__(self, root):        self.root = root        self.root.title("Word模板渲染工具(欢迎关注微信公众号:码海听潮)")        self.root.geometry("900x750")        # 存储文件路径的变量        self.template_path = tk.StringVar()        self.excel_path = tk.StringVar()        self.output_path = tk.StringVar(value="result.docx")        # 存储动态图片路径的列表        self.image_paths = []        self.image_frames = []        # 存储动态列映射的列表        self.column_mappings = []  # 存储 (模板字段名, Excel列名, 框架)        self.column_frames = []        # 存储Excel列名        self.available_columns = []        # 设置默认输出路径        self.output_path.set(os.path.join(os.getcwd(), "result.docx"))        # 创建界面        self.create_widgets()    def create_widgets(self):        # 创建主框架        main_frame = tk.Frame(self.root)        main_frame.pack(pady=10, padx=20, fill=tk.BOTH, expand=True)        # 基础文件选择框架        base_frame = tk.LabelFrame(main_frame, text="基础文件", padx=10, pady=5)        base_frame.pack(fill=tk.X, pady=(0,10))        # 模板文件选择        self.create_file_row(base_frame, "模板文件 (*.docx):"                            self.template_path, self.select_template, 0)        # Excel数据文件选择        self.create_file_row(base_frame, "Excel数据文件 (*.xlsx):"                            self.excel_path, self.select_excel, 1)        # 输出文件        self.create_file_row(base_frame, "输出文件:"self.output_path,                             self.select_output, 2)        # Excel列映射框架        columns_frame = tk.LabelFrame(main_frame, text="Excel列映射(动态添加)", padx=10, pady=5)        columns_frame.pack(fill=tk.X, pady=(0,10))        # 列映射按钮框架        columns_btn_frame = tk.Frame(columns_frame)        columns_btn_frame.pack(pady=(0,10), fill=tk.X)        add_column_btn = tk.Button(columns_btn_frame, text="+ 添加列映射"                                  command=self.add_column_row,                                  bg="#2196F3", fg="white",                                  font=("Arial"10"bold"),                                  width=12)        add_column_btn.pack(side=tk.LEFT, padx=5)        remove_all_columns_btn = tk.Button(columns_btn_frame, text="清空所有映射"                                          command=self.remove_all_columns,                                          bg="#FF5722", fg="white",                                          font=("Arial"10"bold"),                                          width=12)        remove_all_columns_btn.pack(side=tk.LEFT, padx=5)        # 列映射提示标签        columns_tip_label = tk.Label(columns_btn_frame, text="提示:模板字段名对应Word中的变量名"                                    fg="gray", font=("Arial"9))        columns_tip_label.pack(side=tk.RIGHT, padx=5)        # 列映射列表显示区域        columns_list_frame = tk.Frame(columns_frame)        columns_list_frame.pack(fill=tk.BOTH, expand=True)        # 创建Canvas和滚动条        columns_canvas = tk.Canvas(columns_list_frame, height=80)        columns_scrollbar = tk.Scrollbar(columns_list_frame, orient="vertical", command=columns_canvas.yview)        self.columns_scrollable_frame = tk.Frame(columns_canvas)        self.columns_scrollable_frame.bind(            "<Configure>",            lambda e: columns_canvas.configure(scrollregion=columns_canvas.bbox("all"))        )        columns_canvas.create_window((00), window=self.columns_scrollable_frame, anchor="nw")        columns_canvas.configure(yscrollcommand=columns_scrollbar.set)        columns_canvas.pack(side="left", fill="both", expand=True)        columns_scrollbar.pack(side="right", fill="y")        self.columns_container = self.columns_scrollable_frame        # 列状态标签        self.columns_status_label = tk.Label(columns_frame, text="请先选择Excel文件,然后动态添加列映射"                                            fg="gray", font=("Arial"9))        self.columns_status_label.pack(pady=5)        # 图片管理框架        images_frame = tk.LabelFrame(main_frame, text="图片管理", padx=10, pady=5)        images_frame.pack(fill=tk.BOTH, expand=True, pady=(0,10))        # 按钮框架 - 放在图片管理框架顶部        btn_frame = tk.Frame(images_frame)        btn_frame.pack(pady=(0,10), fill=tk.X)        add_image_btn = tk.Button(btn_frame, text="+ 添加图片"                                 command=self.add_image_row,                                  bg="#2196F3", fg="white",                                 font=("Arial"10"bold"),                                 width=12)        add_image_btn.pack(side=tk.LEFT, padx=5)        remove_all_btn = tk.Button(btn_frame, text="清空所有图片"                                  command=self.remove_all_images,                                  bg="#FF5722", fg="white",                                  font=("Arial"10"bold"),                                  width=12)        remove_all_btn.pack(side=tk.LEFT, padx=5)        # 图片提示标签        tip_label = tk.Label(btn_frame, text="提示:可以动态添加/删除图片"                            fg="gray", font=("Arial"9))        tip_label.pack(side=tk.RIGHT, padx=5)        # 图片列表显示区域        images_list_frame = tk.Frame(images_frame)        images_list_frame.pack(fill=tk.BOTH, expand=True)        # 创建Canvas和滚动条        images_canvas = tk.Canvas(images_list_frame, height=80)        images_scrollbar = tk.Scrollbar(images_list_frame, orient="vertical", command=images_canvas.yview)        self.images_scrollable_frame = tk.Frame(images_canvas)        self.images_scrollable_frame.bind(            "<Configure>",            lambda e: images_canvas.configure(scrollregion=images_canvas.bbox("all"))        )        images_canvas.create_window((00), window=self.images_scrollable_frame, anchor="nw")        images_canvas.configure(yscrollcommand=images_scrollbar.set)        images_canvas.pack(side="left", fill="both", expand=True)        images_scrollbar.pack(side="right", fill="y")        self.image_container = self.images_scrollable_frame        # 图片尺寸设置框架        size_frame = tk.LabelFrame(main_frame, text="图片尺寸设置", padx=10, pady=5)        size_frame.pack(fill=tk.X, pady=(0,10))        tk.Label(size_frame, text="宽度(英寸):").pack(side=tk.LEFT, padx=5)        self.image_width = tk.StringVar(value="3")        width_spinbox = tk.Spinbox(size_frame, from_=0.5, to=10, increment=0.5                                   textvariable=self.image_width, width=5)        width_spinbox.pack(side=tk.LEFT, padx=5)        tk.Label(size_frame, text="高度(英寸):").pack(side=tk.LEFT, padx=5)        self.image_height = tk.StringVar(value="3")        height_spinbox = tk.Spinbox(size_frame, from_=0.5, to=10, increment=0.5,                                    textvariable=self.image_height, width=5)        height_spinbox.pack(side=tk.LEFT, padx=5)        # 生成按钮        self.generate_btn = tk.Button(main_frame, text="生成word文档"                                     command=self.generate_report,                                      bg="#4CAF50", fg="white"                                     font=("Arial"10"bold"),                                     height=2, width=16)        self.generate_btn.pack(pady=20)        # 初始化时添加一个默认列映射和默认图片行        self.add_column_row()        self.add_image_row()    def create_file_row(self, parent, label_text, variable, command, row):        """创建文件选择行"""        tk.Label(parent, text=label_text, width=18, anchor=tk.W).grid(            row=row, column=0, sticky=tk.W, pady=5)        entry = tk.Entry(parent, textvariable=variable, width=45)        entry.grid(row=row, column=1, sticky=tk.W+tk.E, pady=5, padx=(0,5))        btn = tk.Button(parent, text="浏览", command=command, width=8)        btn.grid(row=row, column=2, sticky=tk.W, pady=5)        # 设置列的权重        parent.grid_columnconfigure(1, weight=1)    def select_excel(self):        """选择Excel数据文件"""        filename = filedialog.askopenfilename(            title="选择Excel数据文件",            filetypes=[("Excel文件""*.xlsx *.xls"), ("所有文件""*.*")]        )        if filename:            self.excel_path.set(filename)            # 自动刷新列信息            self.refresh_excel_columns()    def refresh_excel_columns(self):        """刷新Excel列信息"""        if not self.excel_path.get() or not os.path.exists(self.excel_path.get()):            self.columns_status_label.config(text="请先选择Excel文件", fg="gray")            self.available_columns = []            # 更新所有列映射的下拉框            self.update_all_column_combos()            return        try:            # 读取Excel列名            df = pd.read_excel(self.excel_path.get(), nrows=0)            self.available_columns = df.columns.tolist()            if not self.available_columns:                self.columns_status_label.config(text="Excel文件中没有列", fg="red")                return            # 更新所有列映射的下拉框            self.update_all_column_combos()            self.columns_status_label.config(text=f"找到 {len(self.available_columns)} 列,可动态添加列映射"                                            fg="green")        except Exception as e:            self.columns_status_label.config(text=f"读取失败: {str(e)}", fg="red")            self.available_columns = []    def update_all_column_combos(self):        """更新所有列映射的下拉框选项"""        for mapping in self.column_mappings:            combo = mapping['combo']            if combo:                combo['values'] = self.available_columns                if self.available_columns:                    combo.config(state="readonly")                else:                    combo.config(state="disabled")    def add_column_row(self):        """添加列映射行"""        frame = tk.Frame(self.columns_container)        frame.pack(fill=tk.X, pady=3)        # 模板字段名        tk.Label(frame, text="模板字段名:", width=12, anchor=tk.W).pack(side=tk.LEFT, padx=5)        field_name_var = tk.StringVar()        field_name_entry = tk.Entry(frame, textvariable=field_name_var, width=20)        field_name_entry.pack(side=tk.LEFT, padx=5)        # Excel列名选择        tk.Label(frame, text="Excel列名:", width=10, anchor=tk.W).pack(side=tk.LEFT, padx=5)        column_var = tk.StringVar()        column_combo = ttk.Combobox(frame, textvariable=column_var, width=25)        if self.available_columns:            column_combo['values'] = self.available_columns            column_combo.config(state="readonly")        else:            column_combo.config(state="disabled")        column_combo.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)        # 删除按钮        delete_btn = tk.Button(frame, text="删除"                              command=lambda: self.remove_column_row(frame, field_name_var, column_var),                              bg="#FF5722", fg="white",                              width=6)        delete_btn.pack(side=tk.LEFT, padx=2)        # 存储映射信息        mapping = {            'frame': frame,            'field_name': field_name_var,            'column': column_var,            'combo': column_combo        }        self.column_mappings.append(mapping)        # 自动滚动到底部        self.scroll_columns_to_bottom()    def scroll_columns_to_bottom(self):        """滚动列映射区域到底部"""        try:            for widget in self.columns_container.master.master.winfo_children():                if isinstance(widget, tk.Canvas):                    widget.yview_moveto(1.0)                    break        except:            pass    def remove_column_row(self, frame, field_name_var, column_var):        """删除列映射行"""        # 从列表中移除        for i, mapping in enumerate(self.column_mappings):            if mapping['field_name'] == field_name_var and mapping['column'] == column_var:                self.column_mappings.pop(i)                break        # 销毁框架        frame.destroy()    def remove_all_columns(self):        """删除所有列映射"""        if self.column_mappings:            result = messagebox.askyesno("确认""确定要清空所有列映射吗?")            if result:                for mapping in self.column_mappings:                    mapping['frame'].destroy()                self.column_mappings.clear()                # 重新添加一个默认列映射                self.add_column_row()    def add_image_row(self):        """添加图片行"""        frame = tk.Frame(self.image_container)        frame.pack(fill=tk.X, pady=3)        # 图片标签        label = tk.Label(frame, text=f"图片 {len(self.image_frames) + 1}:", width=8)        label.pack(side=tk.LEFT, padx=5)        # 图片路径显示        path_var = tk.StringVar()        entry = tk.Entry(frame, textvariable=path_var, width=45)        entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)        # 浏览按钮        browse_btn = tk.Button(frame, text="浏览"                              command=lambda v=path_var: self.select_image(v),                              width=6)        browse_btn.pack(side=tk.LEFT, padx=2)        # 删除按钮        delete_btn = tk.Button(frame, text="删除"                              command=lambda: self.remove_image_row(frame, path_var),                              bg="#FF5722", fg="white",                              width=6)        delete_btn.pack(side=tk.LEFT, padx=2)        self.image_frames.append(frame)        self.image_paths.append(path_var)        # 更新标签文字        self.update_image_labels()        # 自动滚动到底部        self.scroll_images_to_bottom()    def scroll_images_to_bottom(self):        """滚动图片区域到底部"""        try:            for widget in self.image_container.master.master.winfo_children():                if isinstance(widget, tk.Canvas):                    widget.yview_moveto(1.0)                    break        except:            pass    def remove_image_row(self, frame, path_var):        """删除图片行"""        if path_var in self.image_paths:            index = self.image_paths.index(path_var)            self.image_paths.pop(index)            self.image_frames.pop(index)        frame.destroy()        self.update_image_labels()    def remove_all_images(self):        """删除所有图片"""        if self.image_frames:            result = messagebox.askyesno("确认""确定要清空所有图片吗?")            if result:                for frame in self.image_frames:                    frame.destroy()                self.image_frames.clear()                self.image_paths.clear()                self.add_image_row()    def update_image_labels(self):        """更新图片标签文字"""        for i, frame in enumerate(self.image_frames):            for widget in frame.winfo_children():                if isinstance(widget, tk.Labeland "图片" in widget.cget("text"):                    widget.config(text=f"图片 {i + 1}:")                    break    def select_image(self, path_var):        """选择图片文件"""        filename = filedialog.askopenfilename(            title="选择图片文件",            filetypes=[("图片文件""*.jpg *.jpeg *.png *.gif *.bmp"),                       ("所有文件""*.*")]        )        if filename:            path_var.set(filename)    def select_template(self):        """选择模板文件"""        filename = filedialog.askopenfilename(            title="选择Word模板文件",            filetypes=[("Word文档""*.docx"), ("所有文件""*.*")]        )        if filename:            self.template_path.set(filename)    def select_output(self):        """选择输出文件路径"""        filename = filedialog.asksaveasfilename(            title="保存为",            defaultextension=".docx",            filetypes=[("Word文档""*.docx"), ("所有文件""*.*")]        )        if filename:            self.output_path.set(filename)    def generate_report(self):        """生成报告(界面验证部分)"""        # 验证必要文件        if not self.template_path.get():            messagebox.showerror("错误""请选择模板文件!")            return        if not self.excel_path.get():            messagebox.showerror("错误""请选择Excel数据文件!")            return        if not os.path.exists(self.template_path.get()):            messagebox.showerror("错误""模板文件不存在!")            return        if not os.path.exists(self.excel_path.get()):            messagebox.showerror("错误""Excel文件不存在!")            return        # 验证列映射        if not self.column_mappings:            messagebox.showerror("错误""请至少添加一个列映射!")            return        # 验证每个映射都有值        for mapping in self.column_mappings:            if not mapping['field_name'].get():                messagebox.showerror("错误""请填写所有模板字段名!")                return            if not mapping['column'].get():                messagebox.showerror("错误""请选择所有Excel列名!")                return        # 验证图片        valid_images = [p.get() for p in self.image_paths if p.get() and os.path.exists(p.get())]        if not valid_images:            result = messagebox.askyesno("提示""没有添加任何有效图片,是否继续?")            if not result:                return        # 提示用户        messagebox.showinfo("提示""验证通过,准备生成报告...")def main():    root = tk.Tk()    app = QualityFeedbackApp(root)    root.mainloop()if __name__ == "__main__":    main()

      最终将word文档里所需数据渲染成功,实现了之前既定的需求….

      通过上面Python自动化脚本,仅用几分钟的时间就完成原需手动操作数小时甚至数天的工作任务。从最初准备手动人工机械操作的麻木到用python实现高效自动化的畅快,工作效率获得指数级提升,终于实现了不加班熬夜的自由!

      大佬们也可以举一反三,参照上面的代码思路根据自己工作中的实际情况来具体问题具体分析,实现自己定制化的需求。

      结语

      当Python遇见办公,牛马打工人终于笑出了猪叫声

      【职场人必看】每天早上一睁眼,想到又要面对:

      1.📊 堆积如山的Excel表格

      2.📑 机械重复的复制粘贴

      3.✍️ 永远改不完的各类文档

      4.诸如此类的更多……..

      是不是连Ctrl+Alt+Delete的心都有了?

      别慌!别急,摸鱼这位“职场外挂”已经带着Python代码来拯救你了!

      友情提示:考虑到没有python环境的朋友需要打包好的成品exe,摸鱼早已贴心打包好,本篇文章代码打包的exe截图如下:

      另外,《码海听潮》公众号所有文章码和exe程序已打包好上传绿联nas私有云,有需要的大佬扫一扫上面博主的个人微信二维码,需要的大佬需支付9.9元永久拥有公众号资源(写原创干货费时费力,属实不易),邀请您进入社区群获取下载链接!!,群内提供python办公自动化交流问题,解决问题,且码海听潮微信公众号文章发布会第一时间会更新到群里,非诚勿扰哈!

      码海听潮官方社区群如下:

      赶紧微信扫一扫下方二维码添加摸鱼君微信