☰
10个Python自动化脚本:文件整理、报表处理、PDF合并一次搞定
2026/9/28 18:08:38 网站建设 项目流程

很多人的电脑桌面,常年躺着一堆“新建文件夹 (3)”。问起来就是“先放着,哪天整理”。这个“哪天”我一般等不到,所以我选择用 Python 写自动化脚本,让程序替我做归档文件、合并表格、压缩图片、发日报这些重复劳动。今天这篇就把我最近在用的 10 个脚本整理出来,不追求炫技,突出一个“能跑、能改、能救急”。

适合谁?一是每天和文件夹、Excel、PDF 打交道的办公党,二是刚学完 Python 基础、想找点真实练手项目的人,三是单纯想偷懒但又怕把电脑搞乱的朋友。我会按场景分组讲,每个脚本都会说清楚它解决了什么问题、依赖什么库、运行前要注意什么。默认你已经装好 Python 3.8+,还没装的直接去 python.org 下载,安装时勾选 Add Python to PATH;第三方库用 pip install 补齐。另外我建议给这些脚本单独建一个虚拟环境,免得和系统里其他项目互相影响。

1. 先收拾桌面:文件归档与批量重命名的两个脚本

文件整理这事的难点从来不是“知道该分类”,而是“坚持分类”。所以我建议把这件事交给脚本。这里两个脚本拆开用:一个管归档,一个管重命名。拆开的原因很简单,出问题的时候你只需要调试一个函数,而不是在一坨代码里翻半天。

1.1 按扩展名自动归档下载文件夹

import shutil import time from pathlib import Path SOURCE = Path.home() / "Downloads" TARGET = SOURCE / "sorted" EXT_MAP = { ".jpg": "图片", ".jpeg": "图片", ".png": "图片", ".gif": "图片", ".mp4": "视频", ".mkv": "视频", ".avi": "视频", ".mp3": "音频", ".wav": "音频", ".flac": "音频", ".pdf": "文档", ".docx": "文档", ".xlsx": "表格", ".pptx": "演示", ".py": "代码", ".js": "代码", ".html": "代码", ".css": "代码", ".zip": "压缩包", ".rar": "压缩包", ".7z": "压缩包", } files = [item for item in SOURCE.iterdir() if item.is_file()] for item in files: folder = EXT_MAP.get(item.suffix.lower(), "其他") dest_dir = TARGET / folder dest_dir.mkdir(parents=True, exist_ok=True) dest = dest_dir / item.name if dest.exists(): dest = dest_dir / f"{item.stem}_{int(time.time())}{item.suffix}" shutil.move(str(item), str(dest)) print(f"归档完成,共处理 {len(files)} 个文件")

这个脚本的思路很直接:把下载目录里每一个文件的后缀名取出来,对照字典找到它应该去哪个子目录,然后移动过去。我用pathlib而不是os.path拼接路径,代码看起来更干净;shutil.move负责移动,同一个磁盘下是 rename 操作,速度极快,跨盘才会复制后删除。

有一点要注意:如果你把目标目录放在源目录里面,移动文件会改变目录结构,所以先通过files = [...]把文件快照收集好再循环。不要在遍历目录的同时移动文件,否则可能跳过或重复处理。另一个容易忽略的坑是文件重名,如果目标文件夹里已经有同名文件,shutil.move会直接覆盖掉,你可能毫无察觉。所以代码里先判断目标是否存在,存在就在文件名后加时间戳。第一次跑的时候,我建议先别急着真移动,把前面的shutil.move替换成print,预览一下每个文件会落到哪里,确认规则没问题再放开跑。

提示:shutil.move在目标文件已存在时会覆盖。上面的脚本用时间戳重命名来规避,实际操作时先跑一遍 print 预览更安心。

1.2 批量重命名:给交付文件加统一前缀和序号

from pathlib import Path import re folder = Path(r"D:\交付文件") prefix = "项目A" # 去掉 Windows 文件名非法字符 base = re.sub(r'[\\/:*?"<>|]', "", prefix) for idx, file in enumerate(sorted(folder.iterdir()), 1): if not file.is_file(): continue new_name = f"{base}_第{idx:02d}{file.suffix}" dest = folder / new_name if not dest.exists(): file.rename(dest) else: print(f"已存在,跳过:{dest}")

这个脚本适合给一批文件加统一前缀和序号。比如给设计交付的 PNG 加上“项目A_第01期”这样的名字,给比赛照片加上日期前缀,批量加序号能保证顺序不乱,而且是可预期的。sorted(folder.iterdir())是为了让文件按文件名排序后再编序号,否则os.listdir的顺序在不同系统上可能不一样,最后编号顺序会很随机。

重命名是不可逆操作,改完再想找回原文件名特别麻烦。我习惯在脚本里加一个 dry-run 开关:先把file.rename(dest)换成print(new_name),跑一遍看清单,再真正执行。Windows 下文件名不能包含\ / : * ? " < > |这些字符,如果前缀是从外部传来的,记得用re.sub清洗;另外也不要让新文件名太长,Windows 的路径长度限制在 260 个字符左右。

2. 报表人的救赎:表格合并与日报生成的自动化

见客户、交周报、拉数据,很多时候时间都耗在打开 Excel、复制粘贴、删重复行这些重复操作上。用 pandas 处理数据会快得多。这一节两个脚本,一个处理 Excel 合并,一个处理日志统计和消息推送,核心都是“读进来 -> 处理 -> 输出”三步。

2.1 多表合并与去重,顺手清理脏数据

import pandas as pd files = ["sales_1.xlsx", "sales_2.xlsx", "sales_3.xlsx"] frames = [] for f in files: df = pd.read_excel(f, dtype=str) # 清洗列名:去掉首尾空格和换行 df.columns = [str(c).strip().replace("\n", "") for c in df.columns] # 关键字段去掉空格,避免同一订单因为空格被当成两条 if "订单号" in df.columns: df["订单号"] = df["订单号"].astype(str).str.strip() frames.append(df) merged = pd.concat(frames, ignore_index=True) if "订单号" in merged.columns: merged = merged.drop_duplicates(subset=["订单号"], keep="first") merged.to_excel("sales_all.xlsx", index=False) print(f"合并完成,共 {len(merged)} 条")

dtype=str是我强烈建议保留的参数。订单号、身份证号、银行卡号只要超过 15 位,Excel 就会偷偷转成科学计数法,pandas 默认也可能推断成数字类型,最后丢尾数。强制按字符串读入,问题直接消失。列名的坑也很常见,不同人交上来的表列名可能带空格、带换行,合并前先统一处理,否则concat之后会出现“订单号”和“订单号 ”两列。

drop_duplicates用subset参数指定按订单号去重,keep='first'表示保留第一个出现的记录,相当于默认保留最靠前的文件里的数据。如果你希望后面的表覆盖前面的,把keep='last'就行。还有一个实用小操作:在concat之前给每张表加一列来源,比如df['来源'] = f,合并后就能知道每条数据是从哪个文件来的,排查问题方便很多。

2.2 日志统计自动生成日报,推送到企业微信群

import requests from datetime import date log_file = "app.log" error_keywords = ["ERROR", "Exception", "Traceback"] total = 0 errors = [] with open(log_file, encoding="utf-8") as f: for line in f: total += 1 if any(k in line for k in error_keywords): errors.append(line.strip()) text = f"{date.today()} 日报:日志总量 {total} 条,异常 {len(errors)} 条" if errors: text += "\n最近异常:\n" + "\n".join(errors[:5]) webhook = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY" resp = requests.post( webhook, json={"msgtype": "text", "text": {"content": text}}, timeout=10, ) print(resp.status_code)

这个脚本很多人都需要:每天下班前看一眼日志有没有异常。与其打开终端翻半天,不如让脚本把统计结果直接推到群里。企业微信机器人只需要一个 webhook URL,不用配账号密码,是最轻量的方案;如果你用钉钉或飞书,换个接口地址和消息格式就行。记得在群里给机器人添加关键词校验,比如内容必须包含“日报”,否则消息会被拒绝,这是官方安全机制。

读取日志我用的是逐行for line in f,不是一次性f.read()。日志文件动辄几百 MB,一次性全读到内存里会卡,逐行处理更稳。如果日志文件是 logs 目录下多个文件,可以用glob.glob('logs/*.log')套一层循环。还有一点:webhook key 等同于往你群里发消息的权限,千万别把它写进代码后推到公开仓库,用环境变量或本地配置文件存。

3. 图片与 PDF 处理:把素材格式化的体力活交给脚本

图片压缩、PDF 合并、提文字,这些事在线工具都能做,但只要文件一多,上传下载比手动处理还累。而且有些文件不适合传到第三方网站,本地脚本更让人放心。我最早学自动化就是因为要处理一百多张现场照片,一个个改尺寸实在受不了。

3.1 批量压缩图片并转换格式,顺手解决透明底变黑

from PIL import Image from pathlib import Path src = Path("photos") dst = Path("photos_compressed") dst.mkdir(exist_ok=True) max_width = 1920 for p in src.glob("*.png"): img = Image.open(p) if img.width > max_width: ratio = max_width / img.width img = img.resize((max_width, int(img.height * ratio)), Image.LANCZOS) if img.mode != "RGB": rgba = img.convert("RGBA") background = Image.new("RGB", img.size, (255, 255, 255)) background.paste(rgba, mask=rgba.split()[-1]) img = background img.save(dst / f"{p.stem}.jpg", quality=85) print("压缩完成")

先缩放到最大宽度,再把 PNG 转成 JPG 并压缩,是图片处理任务的常见组合。Pillow 的Image.LANCZOS在 Pillow 10+ 会提示改用Image.Resampling.LANCZOS,如果你不想看到 DeprecationWarning,替换一下即可。quality=85是体积和画质比较平衡的点,网上很多教程默认quality=80,实际对照片来说 85 更稳,肉眼几乎看不出差别,体积能小不少。

透明底变黑是转 JPG 最常见的翻车点。PNG 的透明通道在 JPG 里没有对应存储,如果不处理,透明区域会直接变成黑色。我这里的做法是:先转成 RGBA,然后贴到一张白色背景上,再把 alpha 通道作为 mask 合过去。实测下来,处理带透明的 logo、截图都非常稳。批量处理前先抽两三张试一下,确定参数没问题再全量跑,不然一百张图压完才发现颜色不对,返工成本很高。

提示:Pillow 10+ 中Image.LANCZOS会显示弃用警告,可以换成Image.Resampling.LANCZOS。

3.2 PDF 合并、拆分与文本提取,一条脚本全搞定

import fitz # PyMuPDF from pathlib import Path def merge_pdfs(pdf_list, output="merged.pdf"): result = fitz.open() for pdf in pdf_list: with fitz.open(pdf) as doc: result.insert_pdf(doc) result.save(output) result.close() def split_pages(pdf_path, out_dir="split"): doc = fitz.open(pdf_path) Path(out_dir).mkdir(exist_ok=True) for i in range(len(doc)): new = fitz.open() new.insert_pdf(doc, from_page=i, to_page=i) new.save(f"{out_dir}/page_{i+1}.pdf") new.close() def extract_text(pdf_path): doc = fitz.open(pdf_path) return "\n".join(page.get_text() for page in doc) if __name__ == "__main__": merge_pdfs(["a.pdf", "b.pdf"])

PyMuPDF 的包名是PyMuPDF,但 import 的名字却是fitz,这个错位让很多人第一次装完就懵了。合并 PDF 的核心是insert_pdf,它可以一次插入多个页面;拆分的时候from_page和to_page设成同一个页码,就能只取出那一页。如果只需要合并和拆分,pypdf也可以,但页数多、文件多的时候 PyMuPDF 明显更快。

get_text()提取的是 PDF 的文字层,对扫描版、拍照版 PDF 没用,那种 PDF 本质上是一张张图片,必须先 OCR。如果你经常处理扫描件,可以配合 Tesseract 做 OCR 流程,但那是另一个话题了。有一点建议:生成的拆分文件放到单独目录,避免和原 PDF 混在一起,等下想整体删除都不好删。

4. 盯梢与提醒:网页监控和定时邮件的基本姿势

有些自动化任务是“立刻执行”,有些是“盯着变化”。网页上什么时候出现某个内容、系统报表什么时候该发出去,这些更适合让脚本盯着,有变化再通知人。这一节就用两个最常见场景:页面监控和定时发信。

4.1 页面内容变化监控:检测到目标内容就发提醒

import time import requests import smtplib from email.mime.text import MIMEText url = "https://example.com/some/page" monitor_text = "立即购买" check_interval = 300 smtp_config = { "server": "smtp.qq.com", "port": 465, "user": "you@qq.com", "password": "授权码", "to": ["me@example.com"], } def send_alert(content): msg = MIMEText(content, "plain", "utf-8") msg["Subject"] = "监控提醒" msg["From"] = smtp_config["user"] msg["To"] = ", ".join(smtp_config["to"]) with smtplib.SMTP_SSL(smtp_config["server"], smtp_config["port"]) as server: server.login(smtp_config["user"], smtp_config["password"]) server.sendmail(smtp_config["user"], smtp_config["to"], msg.as_string()) while True: try: resp = requests.get(url, timeout=10, headers={"User-Agent": "Mozilla/5.0"}) if monitor_text in resp.text: send_alert(f"页面出现目标内容:{url}") break except Exception as e: print("请求异常", e) time.sleep(check_interval)

这个脚本应用场景很多:盯商品上架、盯官网公告、盯某个页面有没有出现你想看到的关键词。实现上就是一个请求循环,每 5 分钟访问一次公开页面,检查关键词有没有出现在返回文本里,出现就发邮件并退出。注意两个底线:一是轮询间隔不要太短,5 分钟起步;二是只碰公开的、允许访问的页面,别拿它去高频请求小网站,更不要为了破解访问限制去写绕过脚本。

requests默认的 User-Agent 是python-requests,很多服务器会拦,所以 headers 里带一个常规浏览器的 User-Agent 能降低请求被拒的概率。try/except不能省,网络抖动是常态,一次失败就退出的话,半夜监控就断了。如果你需要监控登录后的页面,就带上 Cookie,但 Cookie 属于敏感信息,放进环境变量比写死在代码里安全。

4.2 SMTP 定时发信:日报、附件、临时通知一网打尽

import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication def send_email(subject, content, attach_path=None): smtp_cfg = { "server": "smtp.qq.com", "port": 465, "user": "you@qq.com", "password": "授权码", "to": "boss@example.com", } msg = MIMEMultipart() msg["From"] = smtp_cfg["user"] msg["To"] = smtp_cfg["to"] msg["Subject"] = subject msg.attach(MIMEText(content, "plain", "utf-8")) if attach_path: with open(attach_path, "rb") as f: part = MIMEApplication(f.read()) part.add_header( "Content-Disposition", "attachment", filename=attach_path.rsplit("/", 1)[-1], ) msg.attach(part) with smtplib.SMTP_SSL(smtp_cfg["server"], smtp_cfg["port"]) as server: server.login(smtp_cfg["user"], smtp_cfg["password"]) server.sendmail(smtp_cfg["user"], [smtp_cfg["to"]], msg.as_string()) send_email("每日备份报告", "今天的备份已完成", "backup.zip")

发送邮件最常用的方式是SMTP_SSL,465 端口。企业邮箱、QQ 邮箱、163 都能用,但密码必须填授权码而不是登录密码,授权码在邮箱设置里开启 SMTP 服务后生成。如果直接把登录密码写在脚本里,一旦脚本泄露,等于把整个邮箱交出去;用授权码还有机会单独撤回权限。如果你想定时跑,可以把函数和schedule库结合,比如每天早上 9 点调用一次。

附件部分我用的是MIMEApplication,支持任意文件类型,但要确认发送的附件路径存在,否则open会抛异常。中文附件名在部分客户端上会出现显示乱码,稳妥做法是用email.header.Header对文件名编码;我日常图省事直接用英文文件名,等真有需求再去处理。最后,在公司网络环境下可能还需要配置邮件服务器白名单,总之先在本地测通一次再上定时任务。

5. 懒人维护术:按天打包备份和剪贴板历史

这一组的共同点是“平时没存在感,关键时刻救命”。备份脚本是提前布防,剪贴板历史是随时查证。两个都不复杂,但能极大减少“当时没留底,现在找不回”的焦虑。

5.1 按天全量打包关键目录,保留 7 天自动清理

import os import zipfile import datetime src = r"D:\work" backup_root = r"D:\backup" keep_days = 7 os.makedirs(backup_root, exist_ok=True) today = datetime.date.today() zip_path = os.path.join(backup_root, f"backup_{today.strftime('%Y%m%d')}.zip") with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: for root, dirs, files in os.walk(src): # 跳过可再生成的临时目录 dirs[:] = [d for d in dirs if d not in (".git", "__pycache__", "node_modules")] for f in files: file_path = os.path.join(root, f) arcname = os.path.relpath(file_path, src) zf.write(file_path, arcname) now = datetime.datetime.now() for name in os.listdir(backup_root): if name.startswith("backup_") and name.endswith(".zip"): fpath = os.path.join(backup_root, name) if os.path.getmtime(fpath) < (now - datetime.timedelta(days=keep_days)).timestamp(): os.remove(fpath) print(f"备份完成:{zip_path}")

我的备份策略很简单:每天全量打一个 zip,文件名带日期,保留 7 天,超过就删。和真正的增量备份相比,全量 zip 占空间更多,但恢复非常简单,解压就是全部。对于大多数人重要的文档、项目代码、素材目录,这种粗放策略反而最不容易出问题。zipfile是标准库,几乎不引入新依赖。注意跳过.git、__pycache__、node_modules这类临时或可再生成的目录,压缩体积能小很多,速度也快。

两个容易被忽略的点:备份千万别放在和源目录同一个物理磁盘上,硬盘坏了那叫陪葬;备份目录也不要放在被备份的源目录里,否则下次备份会把上次的压缩包一起打进去,体积会越来越离谱。清理旧备份时用 mtime 而不是文件名日期,更稳。如果源目录特别大,建议改用增量方案,先记录每个文件的修改时间或哈希,只复制变化的文件,但那是进阶玩法。日常使用,先把这个按天全量跑起来,比任何完美的备份方案都强。

5.2 剪贴板历史记录:让 Ctrl+C 不再被覆盖

import pyperclip import time from datetime import datetime log_file = "clip_history.txt" last_text = "" last_time = 0 while True: now = datetime.now() text = pyperclip.paste() if text and text != last_text and (now.timestamp() - last_time) > 1: with open(log_file, "a", encoding="utf-8") as f: f.write(f"\n[{now:%Y-%m-%d %H:%M:%S}]\n{text}\n{'-' * 40}\n") last_text = text last_time = now.timestamp() time.sleep(0.5)

这个脚本本质上是每 0.5 秒读一次剪贴板,发现内容变了就追加到一个文本文件。pyperclip在 Windows 和 macOS 上很省心,在 Linux 上需要先装xclip或xsel,否则取不到剪贴板内容。很多人复制完一段话,转头复制别的,上一段就找不回来了;有了这个历史文件,至少能翻回来。改起来也很灵活,想只记录网址就加一个判断,想按天滚动日志就按日期生成文件名。

必须提醒一个隐私问题:剪贴板里可能出现密码、验证码、身份证号,如果这个脚本常驻,这些内容都会被明文记录下来。所以我的习惯是只在自己信任的个人电脑上开,而且日志文件不要放桌面,不给别人看的机会。它也有局限性:只能抓文本,复制图片是抓不到的,想做完整的剪贴板管理器需要配合 GUI 和图片处理,但核心逻辑就是这段轮询。

提示:剪贴板历史会明文记录密码等敏感内容,别在共用机器上常驻运行。

我自己实用的体会是,自动化脚本起步阶段最忌讳贪大求全。很多人想做一个“一键全自动”的终极脚本,结果写了三天还没跑通。不如像我这样,每个脚本只解决一个痛点,先让一个脚本稳定跑起来,再慢慢加下一个。10 个脚本看着多,拆开其实每个都在 30 行以内,你完全可以照着改出属于自己的一套。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询