资讯动态

Python脚本搞定办公自动化:文件归档与Excel汇总实战

发布时间:2026/10/14 17:33:21 来源:尧图企业网站定制
每天一到公司先花半小时把昨天散落在各个目录下的报表、日志、临时文件翻出来手动改成规范文件名再复制到对应文件夹里每到周五还要把组里七八个人发来的进度文档汇总成一张总表对着Excel来回复制粘贴一通操作下来半个上午就没了。这种场景我太熟悉了因为我自己就是从这条路走过来的。后来我花了一个下午写了第一个真正派上用场的Python脚本把这两件事合并成了一条命令从此每天省下至少四十分钟。这篇文章想跟你聊的就是这个脚本从想法到落地的完整过程。不是贴一段万能代码就完事而是把一个真实的需求怎么拆解、技术方案怎么选、代码怎么写、踩了哪些坑从头到尾捋一遍。适合那些刚学Python不久、想拿学的东西解决点实际问题的人也适合已经写了一阵子脚本但总觉得自己做得不够规范的朋友。你会看到一个能稳定跑几个月的自动化脚本并不是什么高深的东西它靠的是对需求的准确拆解和对细节的敬畏。1. 定需求先搞清楚哪些环节值得自动化1.1 从一件让人烦躁的小事说起当时让我下定决心写脚本的是每周都躲不掉的周报汇总。我们业务组每周五下午要交一份项目进度汇总十几个人各自维护自己的进度文档我负责把它们合并成一张总表发给上级。每周五中午我就要开始在聊天软件里挨个催文档发一下然后等收齐之后打开每一个文件找到那一列数据复制、粘贴、核对格式。这个工作本身不复杂但特别讨厌。它属于那种不费脑子但费手指的活儿大量重复操作而且极其容易出错。有一次我漏看了一行数据没复制进去上级指出后我对着屏幕硬生生盯了十分钟才找到问题。后来我就想这种纯体力活应该可以用代码来解决吧。1.2 怎么判断一项工作适不适合用脚本自动化我总结了一套自己的判断标准不一定适用于所有人但对普通办公室日常工作来说参考价值很大。一项工作适不适合自动化主要看四个特征。第一频率高不高。每周、每天都要做哪怕一次只花20分钟自动化之后省下来的时间都是可观的。那种一年只做一次的事除非一次要花八小时否则写脚本的成本可能比手动干还高。第二规则清不清楚。操作步骤能不能用如果A就B否则就C来描述。比如把文件名包含确认的文件移走提取第2列第5行到第10行的数据这种规则越明确、越死板越适合用代码来跑。反过来如果一件事需要大量主观判断比如看看这份文档的情绪倾向评估这条表达的力度那脚本就很难帮上忙至少现阶段很难。第三出错成本高不高。人工复制粘贴最大的问题是偶尔会看花眼、漏一格、错一行。如果这个操作出错会导致严重后果那就更应该让机器来做——机器是确定性执行同样一段逻辑跑一百遍和跑一遍结果完全一致。第四是否涉及多个文件、多个系统。人从一个文件切到另一个文件大脑需要不断切换上下文疲惫感会急剧上升。而Python脚本处理批量文件是天生优势几千个文件和一两个文件对脚本来说没有本质区别。我建议你也拿这三条标准去对照自己手头的工作。凡是高频率规则明确出错成本高多文件批量基本就在自动化射程之内。我的这个需求四个条件全部命中所以没有任何犹豫直接开干。2. 技术选型为什么是Python而不是别的2.1 从Windows批处理到Python的理由一开始我其实想过用Windows批处理脚本就是那种.bat文件来做文件归类因为它不需要安装任何东西双击就能跑。但仔细分析需求之后我发现两个棘手点。第一汇总这个动作绕不开Excel文件的读写。批处理对Excel内部数据的处理能力几乎为零最多操作文件名字符串没法直接读取单元格内容再合并。第二这个脚本需要具备一定的容错能力某个文件格式不对、某些单元格是空的批处理里这些逻辑写起来非常痛苦几乎等同于用翻译腔写长篇小说。Python则完全不同。它是一门通用的编程语言安装之后除了标准库之外还有极其丰富的第三方库可以处理各种脏活累活读写Excel有openpyxl和pandas处理文件路径有pathlib正则表达式有re记录日志有logging。相当于它像一个工具箱里装着各种专用工具头的瑞士军刀而批处理脚本只是里面那个小指甲刀。所以我的结论是安装Python的微小成本换来的是一套可以无限扩展的处理能力。这个投资对于任何一个稍微有点重复性办公需求的人来说都是划算的。更何况Python安装本身就是下一步操作真跑起来你会发现引入第三方库几乎是零成本的事情。2.2 核心库的选择与分工在动手写代码之前我先花时间把选型想明白了。一个稳的脚本不是靠某个库大包大揽而是让每个库做自己最擅长的事。脚本需要处理五类事情我分别做了这样的分工pathlib负责文件路径解析、目录遍历。它比老式的os.path好用太多能用路径对象直接做拼接、判断存在性代码读起来非常自然。re正则表达式负责从文件名里提取日期、编号、类型这些结构信息。比如文件名周报-2025-03-27-张三.xlsx一行正则就能把日期和人名分别抓出来。openpyxl / pandas负责读原始Excel、写汇总表。我统一用pandas读取因为它在处理二维表结构时几乎是碾压级的方便最后输出汇总结果也用pandas直接写回Excel。logging负责记日志追踪脚本每一步干了什么。这个库看起来不起眼但实际调试和排查问题时是救命稻草。smtplib可选负责把执行结果发送成邮件通知。第一阶段我没引入但第二版加上了后面扩容时再说。有人可能会问既然pandas这么万能为什么还要openpyxl其实我直接用pandas的read_excel底层就是调openpyxl或xlrd所以我只需要安装pandas就能同时享受这两个库的能力。日常使用中直接依赖pandas就足够了但在需要精细控制单元格格式的场景下我会单独引入openpyxl来做最终写盘。这个思路本质上就是用最合适的工具处理最擅长的环节。3. 动手实现从第一行代码到完整脚本3.1 脚本的整体结构与骨架代码很多新手拿到一个需求就直接从第一行开始写代码写着写着发现前面没考虑周全推倒重来。我这次学乖了先在纸上把整个脚本的逻辑画成了一个树状结构。这个脚本要完成的事情可以分为四大模块入口模块接收参数决定今天执行什么任务归档还是汇总。扫描模块遍历指定目录找出所有符合条件的文件。处理模块对文件进行分类、重命名、读取内容。输出模块把处理结果写入汇总Excel同时记录日志和异常。这个拆分不是拍脑袋拍的它对应的是职责单一原则。每个函数只干一件事将来改动某个功能时不需要翻完整段代码。下面是骨架代码的结构# work_report.py import re import logging from pathlib import Path import pandas as pd # 日志配置 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, filenamework_report.log, encodingutf-8) def scan_files(root_dir, patternr.*\.(xlsx|xls|csv)$): 扫描目录返回符合要求的文件路径列表 ... def extract_info_from_filename(filename): 从文件名中解析出日期、提交人等信息 ... def classify_file(file_path): 根据规则决定移动目标目录 ... def summarize_to_excel(file_list, output_path): 读取多个Excel中的关键内容汇总写入一张表 ... def main(): ... logging.info(脚本执行完成共处理 %d 个文件, len(file_list)) if __name__ __main__: main()初看这个骨架你可能觉得什么干货都还没有。但其实对我来说这步已经把整个问题解决了一半。因为接下来要做的就一件事往骨架的每个空函数里填肉。3.2 关键函数拆解与实现细节真正写代码的时候我按照骨架顺序一个一个函数来填每填一个就想清楚它的输入输出。先说scan_files。这个函数接收一个根目录用Path.rglob遍历所有子目录用正则表达式过滤出Excel文件。rglob比glob强在可以自动递归子文件夹非常省事。值得注意的是我把文件后缀的判断写进了正则里而不是用文件扩展名硬判断这样后续如果还想支持.pdf、.txt只需要改一处配置。def scan_files(root_dir, patternr.*\.(xlsx|xls|csv)$): root Path(root_dir) if not root.exists(): logging.warning(目录不存在: %s, root_dir) return [] file_regex re.compile(pattern, re.IGNORECASE) files [p for p in root.rglob(*) if p.is_file() and file_regex.search(p.name)] logging.info(扫描到 %d 个文件, len(files)) return files再说extract_info_from_filename。这个函数是整段代码里最需要动脑子的地方因为每个人的命名习惯都不一样。我们当时的文件命名大致是周报-20250327-张三.xlsx但偶尔也有人写成周报-3.27-zhangsan.xlsx。为了兼容这种混乱我写了两套正则优先匹配规范格式匹配不上再退到宽松模式仍然匹配不上的文件就丢进待人工处理目录绝不让整个脚本因为这些异常中断。def extract_info_from_filename(filename): # 优先匹配: 周报-20250327-张三 m re.search(r周报[-_]?(\d{8})[-_]?([\u4e00-\u9fa5]), filename) if m: date_str m.group(1) person m.group(2) return date_str, person # 宽松匹配: 周报-3.27-zhangsan m re.search(r周报[-_]?(\d{1,2})\.(\d{1,2})[-_]?([a-zA-Z]), filename) if m: month, day, person m.groups() date_str f2025{int(month):02d}{int(day):02d} return date_str, person return None, None这里有个细节日期的格式化我用了f-string补零比如3月5日会变成20250305。为什么要这样因为最终汇总表的日期列需要统一成8位字符串如果有的文件是202534、有的是3.27、有的是2025-03-27后面排序、筛选的时候全是坑。这一步就像把各种鞋码的鞋统一换算成毫米后面怎么比都不会乱。3.3 汇总逻辑把十几个Excel合成一张表汇总模块是整个脚本里复用价值最高的部分。我们当时每个人提交的文件结构是固定的A列是任务编号B列是任务描述C列是进度百分比。我的思路很简单把所有文件读进来提取C列最后一行即当前进度然后拼接到一个总的DataFrame里。下面的代码展示了核心逻辑def summarize_to_excel(file_list, output_path): rows [] for file_path in file_list: try: df pd.read_excel(file_path, header0) task_id df.iloc[0, 0] # 任务编号 task_desc df.iloc[0, 1] # 任务描述 progress df[完成度].iloc[-1] # 最新进度 rows.append({ 文件名: file_path.name, 任务编号: task_id, 任务描述: task_desc, 最新进度: progress }) except Exception as e: logging.error(读取文件失败 %s: %s, file_path, e) continue summary_df pd.DataFrame(rows) summary_df.sort_values(by[任务编号], inplaceTrue) summary_df.to_excel(output_path, indexFalse) logging.info(汇总完成共写入 %d 条记录, len(summary_df)) return summary_df注意我把读取单个文件的逻辑包围在一个try-except里这样某个文件如果是坏的、缺列了、密码保护了都不会连累整个脚本崩溃。这个习惯极其重要因为真实世界里你永远不知道同事们会交上来什么神奇格式的文件。另一个细节是最新进度我取的是完成度列的最后一个非空值这个逻辑我是在代码注释里写清楚了的。为什么这么选因为每个人在更新周报时都是追加一行最后一行就是最新状态。如果你用汇总函数、求和或者平均值那反而得不出需要的结论。这个最后一行的思路是反复读了几个人文件之后总结出来的规律。3.4 文件归档给散落各地的文件安个家处理完汇总还有一类日常最常见的需求是文件归类整理。这个模块我封装成了一个独立函数逻辑非常直白每扫描到一个文件从文件名提取出日期然后移动到归档/2025-03/这样的目录下。如果发现目标目录里已经有同名文件我会在文件名后面加时间戳后缀绝不覆盖旧文件。def archive_file(file_path, archive_root): date_str, person extract_info_from_filename(file_path.name) if date_str is None: # 无法识别的文件移入待处理目录 target_dir Path(archive_root _unhandled) target_dir.mkdir(parentsTrue, exist_okTrue) target_path target_dir.joinpath(file_path.name) else: month_dir f{date_str[:4]}-{date_str[4:6]} target_dir Path(archive_root).joinpath(month_dir) target_dir.mkdir(parentsTrue, exist_okTrue) target_path target_dir.joinpath(file_path.name) if target_path.exists(): stamp datetime.now().strftime(%H%M%S%f) target_path target_path.with_stem(target_path.stem f_{stamp}) shutil.move(str(file_path), str(target_path)) logging.info(文件 %s 已归档到 %s, file_path.name, target_path)这段代码用了pathlib的with_stem方法是在Python 3.9之后才有的特性可以把文件名主体部分安全地替换掉不会动后缀。很多人在这个场景喜欢用字符串slice但那样遇到后缀比较长的时候容易截错。这类小技巧都是在实际踩过坑之后才学到的。4. 打磨与部署脚本要能挂机跑才算成功4.1 日志、重试和异常兜底的设计一个脚本写完第一版能跑通只算完成了60%。剩下40%在于它能不能在你不在场的情况下稳定运行。我第一次跑这个脚本的时候就遇到过问题扫描过程中突然遇到一个文件正在被别的人打开编辑Excel文件在写入状态下被读取直接抛出了PermissionError。当时我的代码没有兜底整个脚本当场中断等于白跑。后来我把所有文件级操作都做了异常捕获并且加了一套重试机制对读取失败的文件先记录到待处理清单等主流程结束后再重新尝试一次如果二次仍然失败就在日志里留下明显警告同时发送一个提示通知。这套机制听上去复杂其实核心代码只有十几行def safe_read_excel(file_path, retries2): for attempt in range(retries): try: return pd.read_excel(file_path) except Exception as e: logging.warning(第 %d 次读取失败: %s - %s, attempt1, file_path, e) time.sleep(2) raise RuntimeError(f读取文件最终失败: {file_path})retries次数设成2等待2秒是为了给其他进程释放文件锁留出时间。这是我在自己台式机上试了几次之后调出来的平衡值等太久会拖慢整体速度不等又大概率失败。4.2 用任务计划程序定时执行脚本本体写好了下一步是让它在每天的固定时间自动跑。因为我用的是Windows系统环境所以最简单的方案是使用任务计划程序它是系统自带的不需要装额外的调度软件。创建计划任务其实不复杂我之前也怕这玩意麻烦但真正点开之后发现是现代界面几步就能配好。主要注意的有几个点程序填的是python.exe的完整路径参数填脚本路径。比如python.exe 后面跟 D:\tools\work_report.py。起始于目录填脚本所在目录否则脚本里如果有相对路径会在奇怪的位置找不到文件。使用不管用户是否登录都要运行选项这样电脑不用一直挂着登录状态。触发器设置成每天某个时间比如中午12:30执行一次即可。这里有一个我踩过的坑如果用py脚本直接关联python解释器用双击的方式运行那只是测试用的真正做计划任务时必须把python.exe全路径写清楚否则计划任务可能因为环境变量混乱而报找不到程序。另外脚本里的日志文件路径和归档目录也尽量写绝对路径因为计划任务的工作目录默认是system32跟你在命令行里执行时的当前目录完全不是一回事。4.3 从脚本到工具的升级思路第一版脚本上线后稳定跑了两周我又动起了别的心思。既然脚本每天都能自动跑了那么能不能让它把执行结果主动发给我比如把汇总表生成后自动发到团队共享位置或者给相关负责人发一封带附件的通知邮件。于是我在main函数里增加了一个通知钩子。如果脚本生成了新的汇总文件就调用smptlib发送邮件发完在日志里记录一条。这个功能本来我以为会写不少代码结果用标准库的email和smtplib组合也就几十行。而且我把它封装成了notify.py跟主脚本分离开来主脚本只是调用了一下send_email函数。这一步让脚本从一个本地工具变成了一个半自动助手。每天不用去翻有没有生成汇总表而是等邮件到达就行。如果某天的邮件没有准时到那可能是数据出了问题我再主动去查日志。这种人和程序的分工比盯着控制台等结果舒服得多。5. 常见问题与排查技巧实录5.1 路径分隔符和中文编码的坑跨平台开发者可能不觉得这是什么问题但对于只在自己电脑上跑脚本的人来说路径分隔符和编码问题真的是最容易反复踩的。第一次跑归档功能时我用的路径拼接方式还是Path(D:/work).joinpath(new_folder)语法上完全没错但生成的路径在旧代码里被用字符串方式传递给某些函数时偶尔会出现反斜杠和正斜杠混用的情况。后来我强制要求自己统一用pathlib所有路径都作为Path对象传递坚决不用手拼字符串路径。中文编码的问题更隐蔽。Windows下某些版本的Excel文件编码不是常规UTF-8尤其是老版本导出的csv文件用pandas直接读取可能得到一堆乱码。我的经验是读取csv时显式指定encoding参数优先尝试utf-8-sig如果失败就回退到gbk再失败就是cp936。这个顺序是我对着不同机器上的多份文件实测出来的。def read_csv_with_fallback(file_path): for encoding in [utf-8-sig, gbk, cp936]: try: return pd.read_csv(file_path, encodingencoding) except UnicodeDecodeError: continue raise RuntimeError(f所有编码尝试均失败: {file_path})5.2 文件占用和路径过长的问题文件占用的问题前面已经提到过一次这里再补充一个特殊场景当脚本尝试移动一个正被Excel打开的文件时在Windows上会直接报错而在别的操作系统上可能不会这导致让我一度以为代码没问题结果上线第一天就被打了脸。另外还有一个坑Windows的文件路径长度限制。如果归档目录层级很深比如D:\archive\2025-03\projectA\release\report...再加上文件名本身很长就可能超出系统默认的260字符限制。这个问题的处理方案有两个一是控制目录层级不要随意加深二是如果避不开深层目录可以在注册表里启用长路径支持或者干脆把归档根目录放到磁盘更浅的位置。我当时的处理是调整归档目录结构把月份作为一级目录而不是二级目录问题就自然消失了。5.3 脚本越跑越慢的排查思路脚本在最初几天跑得都很快几秒就结束。但到了第二周我发现执行时间突然变成了三四十秒。排查了一圈发现是归档目录里文件越来越多而scan_files每次都扫描全部历史文件重复处理它们。解决方案是在扫描时加了一个只看今天有没有新文件的逻辑只处理修改日期在今天或者昨天之后且文件状态正常的文件历史文件则跳过。这一点对于长期挂机跑的脚本非常重要因为如果不做增量处理脚本永远在重复劳动时间久了性能一定会退化。def scan_files(root_dir, since_dateNone): ... for p in root.rglob(*): if p.is_file() and regex.match(p.name): mtime datetime.fromtimestamp(p.stat().st_mtime) if since_date and mtime.date() since_date: continue files.append(p)一个小细节p.stat().st_mtime取的是文件最后修改时间不是我创建时间。做增量处理时用修改时间判断比创建时间更合理因为文件归档后可能会被人移动过而创建时间在复制时可能会变。5.4 Docker与脚本化扩展视野如果你所在的团队有一定技术基础还可以把这类自动化脚本打个Docker镜像用定时任务容器来跑。不过这个方案的维护成本要更高一些Windows计划任务能解决的问题没必要为了听起来先进而引入更多复杂度。我建议普通场景就用系统自带的调度器等团队有了统一的调度平台再考虑把脚本上传上去让自动化在更大的范围里生效。脚本先本地跑通再考虑容器化这个顺序永远不要颠倒。6. 几个给初学者的建议踩坑换来的写了这么久的自动化脚本我最想对刚入门的朋友说的几句话是第一不要一上来就追求完美架构。第一版能跑就行哪怕代码写得像一坨乱麻也没关系。我自己的第一个版本就只有一个main函数里面平铺了三百行代码丑得我自己都不敢回头看。但它解决了问题这就已经赢了一半。后面再慢慢重构不会晚。第二日志一定要从一开始就加。不要觉得写日志是浪费时间。出了问题时日志是你唯一的破案线索。我在设计脚本时就养成了一个习惯关键节点必须打日志而且要打出足够的上下文信息比如文件X处理成功用时Y秒而不是一个孤立的消息。日志写得好排查问题的速度能快五倍。第三能用库就别自己造轮子。读Excel用pandas处理路径用pathlib做定时任务优先用系统调度。很多问题的标准解法都已经封装在库里了你只要学会调用就行。第四是长期主义的想法你可以把工作中的自动化需求积累成一个脚本工具箱。这次写文件整理下次写数据清洗再下次写定时报告每完成一个就沉淀一个脚本慢慢你就有了一座小型的自动化工厂。你会发现重复的劳动在减少做的事情越来越靠近分析、判断和优化这种真正有价值的部分。我个人在这条路上最大的体会是写脚本这件事情的陡峭期真的很短只要撑过第一次完整的需求分析-编码-测试-部署循环后面就会顺很多。而第一次跑通脚本看到控制台刷出整齐日志的那一刻那种成就感比手动处理一百次文件都爽。希望这篇文章能帮你少绕一点弯路早点体会到这种愉快。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑