ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python自动化脚本:10个实例,让重复工作一键完成

Python自动化脚本:10个实例,让重复工作一键完成 先说我自己的场景。每天早上打开电脑第一件事不是看邮件而是先面对那个已经乱成一锅粥的下载文件夹——PDF、压缩包、临时截图、安装包全混在一起文件名还是那种新建文档 (23).docx找一份上周的合同得翻五分钟。后来我实在受不了了花了两个下午写了几个 Python 自动化脚本把整理文件、备份数据、处理 Excel、巡检日志这些重复劳动全部交给脚本去跑。结果就是以前需要半小时手工处理的杂事现在每天只需要在终端里敲一条命令或者干脆让它定时自己跑。这篇文章就把我实际在用的 10 个 Python 自动化脚本拿出来每一个都附带完整代码、使用场景和我在踩坑后补充的细节适合有一定 Python 基础、想把日常杂活交给代码的朋友直接参考。不管你是写代码的、做运营的、管数据的还是纯粹不想手动整理文件的人里面总有两三个脚本能立刻用上。1. 为什么我坚持让脚本干这些重复活1.1 自动化不是炫技是给自己留时间很多人一说自动化脚本就想到爬虫、外挂、黑科技其实真正让普通人的效率翻倍的往往是最不起眼的文件操作和数据处理。我自己的经验是凡是你连续手动做过三次以上的操作都值得写个脚本。因为手动操作不但慢而且容易出错——手动重命名文件时手一抖把2024打成2042手动复制粘贴 Excel 时漏掉一行这些错误很难被发现等发现时往往已经造成麻烦。脚本的好处是相同的步骤每次都严格一致跑完还能留日志出了问题能倒查。这套脚本的设计原则也很简单单文件、无复杂依赖、能跑就行。我没有把它们做成一个大型框架因为脚本的价值在于小快灵拆得越碎越好维护。你不需要装什么重型环境一台装了 Python 3 的电脑就够了第三方库能用标准库就尽量用标准库实在需要才装每个脚本的依赖我都会单独说明。1.2 这套脚本适合谁如果你符合下面任何一种情况这篇文章就是为你写的每天要处理大量文件归档、重命名、备份的办公场景经常收到一堆格式不太规整的 Excel 或 CSV需要快速合并清洗负责维护服务器或个人电脑需要定期看磁盘、查日志想学 Python 但不知道练手项目做什么这 10 个脚本本身就是很好的练习题。每个脚本我都会给出完整的思路和代码你不需要全部照搬按自己的需求改一下路径、关键词、阈值就行。下面从最简单也最痛的文件整理开始。2. 文件整理类自动归档下载文件夹2.1 这个脚本要解决的核心痛点下载文件夹是所有电脑的重灾区。浏览器默认下载位置、微信接收的文件、各种安装包全堆在一起时间一长根本找不到东西。手动整理的核心问题是分类规则不统一今天按日期分明天按类型分后天又按项目分最后反而更乱。所以我写脚本时定的规则是先按文件类型分大类再按日期建子文件夹规则固定找文件时逻辑清晰。复杂的情况是文件可能重名比如同一个目录下有两个report.pdf。脚本需要自动处理重名给后来的文件加数字后缀而不是直接覆盖否则丢文件就麻烦了。2.2 完整实现与运行逻辑import os import shutil from pathlib import Path from datetime import datetime DOWNLOAD_DIR Path.home() / Downloads # 定义类型分类规则扩展名 - 目标文件夹 RULES { 图片: [.jpg, .jpeg, .png, .gif, .bmp, .webp, .svg], 文档: [.pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .txt, .md], 压缩包: [.zip, .rar, .7z, .tar, .gz], 安装包: [.exe, .msi, .dmg, .pkg, .deb], 音视频: [.mp3, .mp4, .avi, .mov, .mkv, .wav], 代码: [.py, .js, .java, .html, .css, .json, .ipynb], } OTHERS 其他 def safe_move(src: Path, dest_dir: Path): dest_dir.mkdir(parentsTrue, exist_okTrue) dest dest_dir / src.name if dest.exists(): stem, suffix src.stem, src.suffix i 1 while (dest_dir / f{stem}_{i}{suffix}).exists(): i 1 dest dest_dir / f{stem}_{i}{suffix} shutil.move(str(src), str(dest)) print(f移动: {src.name} - {dest}) def main(): date_str datetime.now().strftime(%Y-%m) for item in DOWNLOAD_DIR.iterdir(): if item.is_dir(): continue # 文件夹不做移动避免误伤 ext item.suffix.lower() category next((name for name, exts in RULES.items() if ext in exts), OTHERS) target DOWNLOAD_DIR / category / date_str safe_move(item, target) if __name__ __main__: main()这段代码的核心逻辑有三块。第一块是RULES字典把扩展名映射到分类目录用next()加生成器的方式匹配比逐个 if-else 清晰得多。第二块是safe_move专门处理重名文件用 while 循环找空的序号保证不会覆盖同名文件。第三块是自动按年-月建子目录这样同一个分类里不会堆太多文件后续想按月份清理也方便。2.3 实测效果与边界情况我实际跑了三个月最大的感受是心理健康水平显著提升。以前找文件靠记忆现在靠路径图片类走Downloads/图片/2025-03文档类走Downloads/文档/2025-03整个检索逻辑是固定的。但有几个边界情况需要注意。第一目录里的文件夹我一律不移动因为有些文件夹是正在用的项目源码移动后会破坏相对路径。第二Downloads路径在 Windows 上如果用Path.home()会定位到用户目录下的Downloads但在有些企业电脑上下载目录可能被改到了 D 盘这时直接改DOWNLOAD_DIR这个变量就行。第三不要把脚本放在下载文件夹里运行否则脚本文件本身可能被自己移动走。把这个脚本配上下面要说的定时调度每周跑一次基本就不用管了。3. 批量重命名与自动备份两个几乎每天都用到的脚本3.1 批量重命名告别新建文档 (23).docx批量重命名是我被同事问过最多的问题。最常见的使用场景是相机导出的照片全是IMG_2314.JPG这种无意义的编号想改成2025-03-20_丽江_01.jpg这种可读的名字或者从系统导出的一批合同文件名带一堆乱码需要统一加前缀、后缀。from pathlib import Path import re TARGET_DIR Path(rC:\Users\you\Pictures\2025-03-20) PREFIX 旅行_ # 把文件名中的日期串提取出来做排序依据 pattern re.compile(rIMG_(\d)) for idx, item in enumerate(sorted(TARGET_DIR.iterdir(), keylambda p: int(pattern.search(p.name).group(1)))): if item.is_dir(): continue ext item.suffix.lower() # 保留原扩展名 new_name f{PREFIX}{idx 1:03d}{ext} new_path TARGET_DIR / new_name if new_path.exists(): print(f跳过重名: {new_path}) continue item.rename(new_path) print(f{item.name} - {new_name})排序那行是关键。我用正则匹配文件名里的IMG_后面的数字把整个文件夹按拍摄顺序排好再按序号重命名。如果不排序文件系统返回的顺序很可能不是拍摄顺序重命名后你会发现照片顺序完全乱了。如果是其他命名规则只要改一下正则表达式就行。3.2 自动备份增量复制比全量复制聪明得多备份脚本最容易犯的错就是每次全量复制。如果目标文件夹有 100G 数据每次备份都复制 100G磁盘寿命和速度都受不了。正确做法是做增量备份只复制源目录中新增和修改过的文件删除的文件也在备份目录里同步删除。import shutil from pathlib import Path import filecmp SRC Path.home() / Documents / work DST Path(rD:\backup\work) def sync_dir(src: Path, dst: Path): dst.mkdir(parentsTrue, exist_okTrue) # 同步文件 for item in src.iterdir(): target dst / item.name if item.is_dir(): sync_dir(item, target) elif item.is_file(): if not target.exists() or not filecmp.cmp(item, target, shallowFalse): shutil.copy2(item, target) print(f更新: {item.name}) # 删除备份目录中源目录已经删掉的文件 for item in dst.iterdir(): if not (src / item.name).exists(): item.unlink() if item.is_file() else shutil.rmtree(item) print(f删除: {item.name}) sync_dir(SRC, DST)这里用filecmp.cmp(..., shallowFalse)做内容级比较而不是只比文件名和时间戳。原因很简单有些软件保存文件时会更新修改时间但内容没变光比时间会造成无效备份。shallowFalse会比较文件内容虽然略慢但准确。shutil.copy2会保留文件的元信息修改时间、权限这对文档类文件很重要。不过这个脚本是单线程的文件数量超过几万个时速度会慢。我在实际使用中加了concurrent.futures.ThreadPoolExecutor做并行复制小文件多的时候速度提升明显。但并行复制有一个隐患文件名相同、内容正在写入的临时文件可能被读到一半所以我通常只在源目录相对稳定的时候启用并行。4. 数据处理类Excel 合并、CSV 清洗与 PDF 批量处理4.1 一键合并多个 Excel 表格做过运营和财务的人应该深有体会每月底要把 12 个部门的报表合并成一张总表手动复制粘贴几百行容易漏格式还经常变。用pandas一行就能解决合并的事但真正决定脚本好不好用的是表头处理和来源标记。import pandas as pd from pathlib import Path INPUT_DIR Path(sales_reports) OUTPUT Path(merged_sales.xlsx) frames [] for f in sorted(INPUT_DIR.glob(*.xlsx)): df pd.read_excel(f) df[来源文件] f.name # 标记每行数据的来源方便追踪 frames.append(df) merged pd.concat(frames, ignore_indexTrue) if frames else pd.DataFrame() # 按时间去重防止同一个人被统计两次 merged merged.drop_duplicates(subset[订单号], keeplast) merged.to_excel(OUTPUT, indexFalse) print(f合并完成共 {len(merged)} 行输出到 {OUTPUT})我合并时会故意保留来源文件这一列。这个习惯帮我排查过不止一次数据问题——当合并后某个数字对不上时能直接定位到是哪一个源文件的数据有问题不用整张表翻。drop_duplicates也很重要部门报表之间经常出现同一笔订单被两个部门同时上报的重复情况按订单号去重是合并前必须做的。4.2 CSV 清洗把脏数据收拾成标准格式CSV 是我觉得比 Excel 更坑的东西因为 CSV 没有格式约束什么妖魔鬼怪都能装进去。同事给的 CSV 常见问题数字存成了字符串、日期格式混乱、有 BOM 头、字段里有逗号但没加引号。清洗脚本的目标就是把这些问题一次性处理干净。import csv from pathlib import Path SRC Path(raw_data.csv) DST Path(clean_data.csv) def clean_value(value: str) - str: value value.strip() if value in (, NULL, null, None, N/A): return return value with SRC.open(r, encodingutf-8-sig, newline) as fin, \ DST.open(w, encodingutf-8, newline) as fout: reader csv.reader(fin) writer csv.writer(fout) header [clean_value(h) for h in next(reader)] writer.writerow(header) for row in reader: writer.writerow([clean_value(c) for c in row]) print(f清洗完成列数: {len(header)})读取时用utf-8-sig编码是一个很多人不知道的细节。Windows 下生成的 CSV 经常带 BOM文件开头有三个不可见字符直接按utf-8读第一列的表头前面会多出\ufeff导致列名对不上。用utf-8-sig读取时 Python 会自动去掉 BOM。写出的文件用utf-8无 BOM这样下游系统比如 Linux 上的服务解析时不会出问题。4.3 PDF 合并与拆分终极办公救星PDF 处理我用的是pypdf旧版叫PyPDF2后面改名的。合并多个 PDF 是最刚需的场景——投标文件、论文附录、合同扫描件全部需要按顺序合在一起。拆分则用于把一个大文件按页抽出来给人审阅。from pypdf import PdfReader, PdfWriter from pathlib import Path PDF_DIR Path(pdf_files) # 合并 writer PdfWriter() for f in sorted(PDF_DIR.glob(*.pdf)): reader PdfReader(str(f)) for page in reader.pages: writer.add_page(page) print(f已读取 {f.name}, 共 {len(reader.pages)} 页) with (Path(merged.pdf)).open(wb) as out: writer.write(out) print(f合并完成总页数: {len(writer.pages)}) # 拆分把第 2~5 页抽出来 reader PdfReader(merged.pdf) sub_writer PdfWriter() for page_num in range(2, 6): # 第 3 页到第 6 页从0开始计数 sub_writer.add_page(reader.pages[page_num]) with Path(extracted.pdf).open(wb) as out: sub_writer.write(out)说两个容易踩的坑。第一PdfReader需要传入的是文件路径字符串或二进制流不能传Path对象在新版本中可能直接报错建议用str(f)转一下。第二writer.write之后一定要在with块内关闭文件否则文件可能没有完全落盘。还有一个性能问题一个几百页的大 PDF 逐页add_page会占用不少内存如果服务器内存紧张改成按文件合并而不是逐页合并会更快。5. 信息获取类网页数据采集与图片批量压缩5.1 网页数据采集给自己写一个信息聚合器我这里说的网页采集是指访问公开页面、遵守网站访问规则、有节制地拉取公开数据比如每天自动看看几个行业网站上有没有发布新公告、某个商品的价格有没有变动。这类脚本的核心不是写爬虫而是怎么做到礼貌且稳定。import requests from bs4 import BeautifulSoup from datetime import datetime URL https://example.com/notices # 替换成你关注的公开页面 HEADERS {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Edg/120.0} def fetch_notices(): resp requests.get(URL, headersHEADERS, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding # 自动识别页面编码 soup BeautifulSoup(resp.text, html.parser) items soup.select(ul.notice-list li a) # 按实际页面结构调整选择器 for a in items[:10]: title a.get_text(stripTrue) link a.get(href) print(f{datetime.now():%H:%M} {title} - {link}) if __name__ __main__: fetch_notices()几个经验之谈。第一requests必须带timeout否则某个页面卡住时脚本会一直挂在那里定时任务也就废了。第二headers里的User-Agent用浏览器的有些网站会拦截默认的python-requestsUA。第三不要一上来就写死选择器很多页面的结构会改脚本报错时的异常处理要写成跳过本次下次再跑而不是崩溃退出。第四最重要的一条尽量看清楚网站的使用条款控制请求频率只在公开页面上采集必要的信息不要对服务器造成压力采集到的数据也不要随意对外发布。5.2 图片批量压缩给硬盘和网页减负图片占空间是所有人都会遇到的问题。手机拍的图一张 5M微信群里存的图更多时间长了硬盘报警。压缩脚本的关键在于保持画质可接受的前提下尽量减小体积我用的是Pillow。from PIL import Image from pathlib import Path IMG_DIR Path(photos) MAX_WIDTH 1920 # 超过这个宽度就等比缩放 QUALITY 85 for f in IMG_DIR.glob(*.jpg): img Image.open(f) if img.width MAX_WIDTH: ratio MAX_WIDTH / img.width new_size (MAX_WIDTH, int(img.height * ratio)) img img.resize(new_size, Image.LANCZOS) img.save(f.with_name(f.stem _compressed.jpg), JPEG, qualityQUALITY, optimizeTrue) print(f压缩完成: {f.name}) # 注意关闭文件句柄 img.close()optimizeTrue这个参数经常被忽略它会让编码器做额外的优化文件更小在 JPEG 场景下基本无损。LANCZOS是 Pillow 里质量最高的重采样算法比起默认的BICUBIC在缩小图片时边缘更锐利。我实际测试过一张 4000x3000 的照片从 6.8M 压到 1.2M肉眼几乎看不出差别。如果是要做缩略图MAX_WIDTH可以设到 800体积能压到 200K 左右。唯一的坑是如果原图本身就是小图resize不会执行但save时仍然会重新编码一次能把原本被手机渲染过的有效率损失的图清理干净。6. 系统巡检类磁盘监控与日志关键字告警6.1 磁盘空间监控在磁盘满之前就报警如果你维护过服务器一定经历过磁盘满了服务全部挂掉的深夜事故。人肉去看磁盘不现实脚本定时检查、超过阈值就发通知才是正解。这里我用shutil.disk_usage获取磁盘信息不需要任何第三方库。import shutil import smtplib from email.mime.text import MIMEText from pathlib import Path CHECK_DIR Path.home() THRESHOLD 90 # 使用率超过90%就告警 def check_disk(): usage shutil.disk_usage(CHECK_DIR) percent usage.used / usage.total * 100 print(f{CHECK_DIR} 使用率: {percent:.1f}% (剩余 {usage.free // 2**30}G)) if percent THRESHOLD: send_alert(f磁盘使用率 {percent:.1f}% 超过阈值 {THRESHOLD}%) def send_alert(message: str): # 这里填入你的邮箱服务配置 msg MIMEText(message, plain, utf-8) msg[Subject] [告警] 磁盘空间不足 msg[From] senderexample.com msg[To] adminexample.com with smtplib.SMTP(smtp.example.com, 587) as server: server.starttls() server.login(senderexample.com, password) server.send_message(msg) check_disk()usage.free // 2**30是把字节数转成 G这个写法比round(usage.free / 1024**3, 2)更直观。告警这块我只写了邮件示例实际生产环境中我建议用更灵活的通道比如企业微信机器人或者普通的消息推送到手机这样不用时刻盯邮箱。脚本本身很简单但发送告警的服务挂了怎么办这个问题要考虑——告警脚本最好部署在另一台机器上或者至少配置一个备用通知通道。6.2 日志关键字巡检让脚本帮你盯着错误日志巡检的价值在于主动发现问题而不是等用户报障。比如服务日志里出现了Traceback、ERROR、Timeout这些词说明有问题正在发生。简单的巡检脚本读文件末尾或者今天的日志匹配关键词就告警能省掉大量人工排查时间。from pathlib import Path from datetime import datetime import re LOG_FILE Path(app.log) KEYWORDS [ERROR, Traceback, Timeout, ConnectionRefused] # 只检查今天新增的日志行日志里每行开头的时间戳 today datetime.now().strftime(%Y-%m-%d) hits [] with LOG_FILE.open(r, encodingutf-8, errorsignore) as f: for line in f: if today not in line: # 非今天的行直接跳过 continue if any(re.search(kw, line) for kw in KEYWORDS): hits.append(line.strip()) if hits: print(f发现 {len(hits)} 条异常日志第一条如下:) for h in hits[:5]: print(h) else: print(今日日志正常)errorsignore是为了防止日志里有无法解析的二进制乱码导致整个脚本崩溃。日志文件通常很大逐行读取比readlines()更省内存而且这里的巡检只需要顺序扫描不需要把所有内容读进内存。如果要提高效率可以记住上次读到哪一行用偏移量存一个状态文件这次从偏移位置继续读避免每次都扫全量文件。re.search而不是re.match是因为关键词不一定出现在行首search只要行里包含就命中。7. 把脚本串起来定时调度与工程化部署7.1 用 schedule 库让脚本自己跑脚本写好了如果每天还要手动运行那等于没自动化。Python 生态里最简单好用的定时调度方案是schedule库它允许你用人类可懂的语言定义运行时间。import schedule import time def job_all(): print(开始执行每日例行任务...) # 这里写你要调度的所有脚本 # 比如 # from scripts import organize_downloads, backup_docs # organize_downloads.main() # backup_docs.sync_dir(...) schedule.every().day.at(09:30).do(job_all) # 每天早上 9:30 schedule.every().sunday.at(22:00).do(job_all) # 每周日晚上 10 点 schedule.every(6).hours.do(lambda: print(每隔6小时检查)) # 每6小时 while True: schedule.run_pending() time.sleep(60) # 不要让循环空转占用 CPU启动方式就是终端执行python scheduler.py让它常驻。如果你的电脑/服务器会重启建议用系统自带的服务机制Windows 的计划任务程序Linux 的 systemd把脚本注册成服务这样重启后自动拉起。time.sleep(60)很关键它让主循环每分钟检查一次有没有到期的任务CPU 占用几乎为零。schedule库的局限是只适合本机、单任务、不依赖跨节点状态的场景如果以后任务多了可以考虑专业调度器但对个人自动化来说它已经足够了。7.2 部署时的几个实用建议把脚本部署上生产之前有四个细节我吃过亏这里直接写出来第一日志输出一定要落盘。很多人只print结果脚本半夜跑了报错你根本不知道。建议每个脚本开头加一行logging.basicConfig(filenameautomation.log, levellogging.INFO)输出到文件配合上面的日志巡检就能形成闭环。第二处理失败要重试 跳过不能遇到第一个错误就停。比如批量下载图片时一张超时不应该让整个脚本退出。可以用 try-except 捕获异常、记录日志后继续跑下一项。第三敏感信息不要硬编码。邮箱密码、数据库地址这些如果直接写在脚本里一旦脚本被分享出去就是事故。建议用环境变量或者单独的config.ini文件代码里只做引用。第四版本管理。这些脚本是会迭代的今天加一个分类规则明天改一个阈值用 Git 管理起来改坏了能回滚。我见过太多人用脚本_v2_final_再改一版.py这种命名管理代码最后自己都不知道哪个是最终版。8. 我把这 10 个脚本用了一年总结出的避坑经验8.1 编码问题永远是第一坑Python 处理中文文件时编码问题是出现频率最高的报错来源。我的经验就三条读取外部文件时优先尝试utf-8报错就换gbk或gb18030再不行就用chardet自动检测打开文件统一用with语句让它自动管理资源不写f.close()写文件时指定encodingutf-8如果目标系统是 Windows 老软件可能要写encodingutf-8-sig带上 BOM 防止别人用记事本打开乱码。很多脚本在我电脑上跑得好好的换个人就报错十有八九就是编码问题。8.2 路径与权限Windows 和 Linux 的差异路径处理我会无条件用pathlib.Path而不是字符串拼接。原因很直接Windows 路径分隔符是反斜杠\Linux 是正斜杠/字符串拼接在跨平台时一定会出问题而Path对象会自动处理。另外 Windows 的路径长度限制260 字符是个隐蔽的坑当文件层级很深时shutil.move可能报错解决办法是缩短路径层级或者启用系统长路径支持。权限方面如果在 Linux 服务器上跑注意脚本用户对目标目录有没有写权限。我遇到过脚本本身没错、但因为没有执行权限导致定时任务一直失败的情况。排错时先手动执行一遍能跑通再交给调度器。8.3 如何把这个脚本库扩展下去这套脚本我还在持续扩展。最近的思路是加一个依赖检查模块每个脚本开头检查需要的第三方库是否安装没有就自动pip install这样换电脑部署时不用手动装依赖。另一个方向是把文件整理脚本和通知脚本联动起来整理完通过消息推送到手机每天一早就能看到今天整理了 23 个文件、释放了 1.2G 空间这样的报告。最后分享一个我自己的习惯每写一个新脚本先花十分钟想清楚这个操作我一个月会做几次。如果一个月做不了三次不值得写脚本如果能做十次以上就算脚本写一小时也值得。自动化是为了省时间不是为了写代码而写代码想清楚这一点你才能把有限的精力放在真正有价值的事情上。
返回列表