你有没有遇到过这种崩溃时刻:下载文件夹里堆了两千个文件,一份合同放进去就再也找不出来;月底要把五个不同版本的 Excel 粘成一张总表,复制粘贴到怀疑人生;明明只是给文件重命名,却在几十个“新建文档(23).docx”里来回筛选。我全经历过。真正让我下决心写 Python 自动化脚本 的,是有一次手动备份项目资料,重复劳动了快两个小时,最后还是漏掉了两张关键截图。第二天我花了一个周末把这些零碎需求整理成脚本,其中有 10 个到现在都在我的电脑上稳定运行。
这篇文章不打算讲高深理论,就从我自己踩过的坑出发,把这 10 个脚本分别解决什么问题、核心代码怎么搭、实际运行中会遇到哪些问题,全部拆开给你看。无论你是刚学 Python 不久的新手,还是已经写过一点小脚本、想系统提升效率的开发者,读完都应该能直接改出一套属于自己的自动化工具箱。
1. 先把框架搭明白:这10个脚本是怎么选出来的
1.1 判断要不要自动化,只看三个标准
很多朋友问我,为什么偏偏是这 10 个脚本,而不是别的?我说一个总原则:判断一件事该不该用脚本替代,不看它技术含量高不高,只看它是不是同时满足三个特征——高频、重复、有规律。
高频,指的是你一周至少会做一次;重复,指的是每次操作步骤几乎一模一样;有规律,指的是可以明确写成 if/else 或 for 循环。举个例子,“把下载文件夹里的图片挪到图片目录”这种事,完全符合这三条。而“帮老板设计一张年会海报”,虽然也烦,但你没办法用一个固定模板解决,就不适合硬塞给脚本。
当初我给自己列过一个“手动劳动清单”,把每天、每周、每月重复做的事全写在纸上,然后用这三个标准筛选了一遍,最终挑出 10 个最值得写的方向。下面这张表就是我当时的选题清单:
| 序号 | 脚本方向 | 主要解决什么问题 |
|---|---|---|
| 1 | 下载目录自动整理 | 文件堆成山,找不到素材 |
| 2 | 批量重命名 | 文件名混乱,无法排序归档 |
| 3 | PDF 合并与水印 | 多份扫描件需要合成一份 |
| 4 | 商品价格监控 | 手动刷新网页看价格变动 |
| 5 | 每日天气播报 | 给家里群发一条天气提醒 |
| 6 | 网页正文提取 | 把长文保存为本地笔记 |
| 7 | Excel 多表汇总 | 多个报表合成总表 |
| 8 | 定时邮件发送 | 每周自动汇报进度 |
| 9 | 合同批量生成 | 模板套用大量重复文件 |
| 10 | 磁盘清理与巡检 | 释放空间,预防磁盘爆满 |
不夸张地说,第一版脚本写得还很粗糙,但随着我持续迭代,每个都能稳定省下十几分钟到半小时。一个月累积下来,多出来的时间相当可观。
1.2 环境准备:把依赖一次性装齐
动手之前,先建一个独立虚拟环境非常有必要。虚拟环境可以避免你之后用 pip 装不同版本库时互相打架,也能让脚本运行环境更干净。Windows 和 macOS/Linux 通用操作如下:
python -m venv auto_env # Windows 激活 auto_env\Scripts\activate # macOS/Linux 激活 source auto_env/bin/activate激活之后,再安装本文涉及的第三方库:
pip install pandas openpyxl pypdf requests beautifulsoup4 schedule send2trash python-docx docxtpl这几个库的含义和分工,我先简单说一遍:pandas和openpyxl用来处理 Excel 表格;pypdf负责 PDF 合并拆分;requests加上beautifulsoup4用来抓网页信息;schedule是轻量级的定时任务工具;send2trash可以把文件移动到回收站而不是永久删除;python-docx和docxtpl用于操作 Word 和渲染合同模板。标准库里的os、shutil、pathlib、smtplib、email、logging等不需要额外安装。
第一次接触的同学不要被这么多名字吓到,你不需要全部记住,用到哪个再回来看就行。
1.3 写脚本前先做一件事:把人工操作流程写出来
我踩过最大的坑,就是拿到需求直接写代码。后来发现,越是赶时间,越应该先用手写一遍操作流程。
比如“整理下载文件夹”,人工操作其实是这样几件事:打开文件夹、按文件类型分堆、把图片放到图片目录、把文档放到文档目录、遇到同名文件要重命名不能覆盖。这些步骤翻译成代码时,就对应着“遍历目录、判断后缀名、创建目录、移动文件、检查重名”。你看,只要把人的流程写清楚了,代码只是翻译工作。
如果你现在也准备给自己的重复劳动写脚本,建议先花五分钟手记操作步骤,再开始敲键盘。这个习惯帮我省了无数调试时间。
2. 文件与文档处理:最容易被解救的三件事
2.1 下载目录自动整理(脚本一:文件分类器)
电脑里的下载文件夹,大概是现代人“数字杂物间”最形象的比喻。我之前有一次要找一份 PDF 合同,硬是从上千个文件里翻了一刻钟,最后发现它的文件名就叫“文档 (12).pdf”,差点崩溃。后来我用 Python 写了这个文件分类器,每次下载文件夹变得混乱,只需运行一次,几秒钟就能把所有文件按类型归位。
核心逻辑并不复杂:遍历目标目录下的每个文件,通过后缀名判断它属于哪一类,然后移动到对应的分类文件夹。
from pathlib import Path import shutil DOWNLOAD_DIR = Path.home() / "Downloads" RULES = { "图片素材": [".jpg", ".jpeg", ".png", ".gif", ".webp"], "办公文档": [".docx", ".doc", ".xlsx", ".xls", ".pptx", ".pdf", ".txt"], "压缩包": [".zip", ".rar", ".7z", ".tar", ".gz"], "安装程序": [".exe", ".msi", ".dmg", ".deb", ".rpm"], } def classify(file_path: Path): suffix = file_path.suffix.lower() for folder, exts in RULES.items(): if suffix in exts: return folder return "其他文件" def organize(): for item in DOWNLOAD_DIR.iterdir(): if item.is_file(): category = classify(item) target_dir = DOWNLOAD_DIR / category target_dir.mkdir(exist_ok=True) shutil.move(str(item), str(target_dir / item.name)) print(f"移动:{item.name} → {category}") if __name__ == "__main__": organize()运行之后,下载目录里会自动多出几个分类文件夹,所有文件按扩展名归位。我自己用的时候,发现一个大坑:如果目标文件夹里已经有同名文件,shutil.move会直接覆盖,非常危险。
我的改进方案是,当目标文件已存在时,给新文件加上当前时间戳再移动,这样既能保留两份文件,又不会互相覆盖。你可以在移动前加一个判断:
target = target_dir / item.name if target.exists(): timestamp = time.strftime("%Y%m%d%H%M%S") target = target_dir / f"{item.stem}_{timestamp}{item.suffix}"这个脚本我基本每周跑一次,配合系统定时任务之后,下载目录长期保持清爽,搜索文件的效率高了很多。
2.2 批量重命名工具(脚本二:加序号、加日期)
给文件重命名这件事,听起来不起眼,但真碰到几百张照片、几十份扫描件时,手动改名能把手点到抽筋。比如我有个习惯,每周把项目截图按“项目名_日期_序号”归档,最开始纯手动,后来写了个脚本,秒级完成。
这个脚本做得挺直接,遍历文件夹里的所有文件,按照你指定的前缀、日期和序号重新生成文件名。
from pathlib import Path from datetime import date folder = Path("D:/待整理文件") prefix = "项目资料" for i, file in enumerate(folder.iterdir(), start=1): if file.is_file(): new_name = f"{prefix}_{date.today().isoformat()}_{i:03d}{file.suffix}" dest = file.parent / new_name if not dest.exists(): file.rename(dest) print(f"重命名:{file.name} → {new_name}")这里的i:03d把序号格式化成三位数,比如 001、002,方便排序。日期用date.today().isoformat()输出,能直接排进文件名体系里。
用过几轮之后,我发现最值得留意的是特殊字符。Windows 上文件名不能包含\ / : * ? " < > |这些符号,如果原文件名里混有它们,rename会直接报错。我自己会在重命名前做一个过滤函数,把所有非法字符替换成下划线,避免跑一半中断。另外,重命名前最好先打印一份“原文件名→新文件名”的对照表,确认无误再真正执行,尤其是批量处理重要文件时,这个预览动作能救你一次。
2.3 PDF合并与水印(脚本三)
PDF 合并是我给朋友处理最多的一类需求。比如把纸质合同扫描成多页单页 PDF,需要合成一份完整文档;或者把若干份电子发票合并成一个 PDF 文件,方便归档报销。
现在我已经不用手动在工具软件里一张张贴了,直接用pypdf写一个合并脚本:
from pypdf import PdfWriter, PdfReader from pathlib import Path pdf_dir = Path("D:/PDF资料") output = PdfWriter() for pdf_path in sorted(pdf_dir.glob("*.pdf")): reader = PdfReader(pdf_path) for page in reader.pages: output.add_page(page) print(f"已加入:{pdf_path.name}") output.write("合并结果.pdf") print("合并完成")这个脚本把目录下所有 PDF 按文件名排序后逐页追加,最后输出一份新的合并文件。实际过程中我有两个额外需求:其一,有些 PDF 扫描件方向不对,需要自动旋转;其二,需要在每页里加上“仅用于归档”之类的水印。
pypdf里的page.rotate(90)可以直接旋转页面。水印则稍微绕一点,你需要先用一个单独的 PDF 作为水印模板,再把每一页合并上去。这里我建议你把水印内容用独立工具生成好,脚本只负责“叠印”,这样维护水印内容时不用改 Python 代码。
处理大量 PDF 时还有一个容易被忽略的点:如果某些 PDF 带有密码保护,读取时直接报错,代码里要捕获pypdf.errors.FileNotDecryptedError,并提示用户单独处理。我的做法是在遍历时先 t 5 次解密,解密失败就跳过并把文件名记录到日志里。
3. 网页与数据采集:让脚本替你“蹲守”消息
3.1 商品价格监控提醒(脚本四)
不知道你有没有过这种经历:心仪的商品加入购物车后,每天打开网页看价格,看了半个月也没见降价,某天终于降了,却因为没及时下单又错过了。我以前想买个显示器,就是这种状态。后来写了一个价格监控脚本,让它每隔几小时帮我查一次,价格低于预期就让脚本发邮件通知我。
这里的核心是两个动作:先从网页里解析出价格,再跟目标价比较,符合条件就触发通知。
import os import requests from bs4 import BeautifulSoup import smtplib from email.message import EmailMessage url = "商品页面URL" target_price = 1999.0 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } resp = requests.get(url, headers=headers, timeout=10) resp.encoding = "utf-8" soup = BeautifulSoup(resp.text, "html.parser") price_node = soup.select_one(".current-price") price_text = price_node.text.strip().replace("¥", "").replace(",", "") price = float(price_text) if price <= target_price: sender = "sender@example.com" receiver = "receiver@example.com" password = os.environ.get("SMTP_PASS") msg = EmailMessage() msg["Subject"] = "价格监控提醒" msg["From"] = sender msg["To"] = receiver msg.set_content(f"当前价格 {price} 元,已达到你的预期。") with smtplib.SMTP_SSL("smtp.example.com", 465) as smtp: smtp.login(sender, password) smtp.send_message(msg) print("已发送提醒邮件") else: print(f"价格未达预期,当前 {price} 元")网页解析这一步,核心是找到价格所在的 HTML 元素。每个网站结构不一样,你需要先用浏览器开发者工具查看元素,再用select_one定位。如果只依赖某个特定的 CSS 类名,网站一旦改版就可能失效,所以我的建议是把选择器单独抽成一个配置项,方便随时改,而不用动主逻辑。
爬取别人网站时要有基本的礼貌。我给自己定的规矩是:请求间隔至少 30 分钟,并且只在页面公开可访问的前提下做监控。这样既满足个人需求,也不会给目标服务器造成压力。
3.2 每日天气播报(脚本五)
给家里老人或者工作群自动推送天气,是个既贴心又能省事的脚本。我最早是因为每天早晨都要手动查天气再转发到家庭群,持续了两个月,实在受不了,于是让脚本代劳。
思路不复杂,选择一个免费天气 API,注册后拿到一个 key,然后请求指定城市的天气数据,再通过企业微信群机器人或者短信平台推出去。下面这个示例用到了最通用的流程:
import os import requests city = "某城市" api_key = os.environ.get("WEATHER_API_KEY") url = f"https://api.example.com/weather?city={city}&key={api_key}" resp = requests.get(url, timeout=5) data = resp.json() temperature = data["current"]["temperature"] weather_desc = data["current"]["description"] tip = "出门记得带伞" if "雨" in weather_desc else "天气不错,适合出行" message = f"{city}今日天气:{weather_desc},气温 {temperature}°C。{tip}" print(message)天气 API 的选择有几个注意点:注册后要看清免费额度,比如每天请求次数上限和付费规则;返回的 JSON 结构各平台不同,不要照着我的代码硬套,先打印一次data看看实际字段名。
我自己的习惯是,把这个脚本挂在系统定时任务里,每天早晨七点半跑一次,让推送消息自动出现在群聊里。定时任务怎么挂,我在后面的章节统一说。
3.3 网页正文提取,把长文变成本地笔记(脚本六)
有时候你刷到一篇好文章,想收藏到本地做笔记,但网页排版复杂,复制粘贴又容易把广告和无关内容一起带过来。我在学习技术资料的时候经常遇到这件事,后来写了这个“网页正文提取”脚本,只需要给一个 URL,就能把标题和正文保存成 txt 文件。
import requests from bs4 import BeautifulSoup url = "文章链接" resp = requests.get(url, timeout=10) resp.encoding = resp.apparent_encoding soup = BeautifulSoup(resp.text, "html.parser") title = soup.title.get_text(strip=True) body = soup.select_one("article") if not body: body = soup.body lines = [line.strip() for line in body.get_text("\n", strip=True).splitlines() if line.strip()] content = "\n".join(lines) with open(f"{title[:30]}.txt", "w", encoding="utf-8") as f: f.write(content) print(f"已保存文章:{title}")这段代码最关键的坑在编码。很多网页不是 UTF-8,requests默认猜的编码不一定对,中文很容易乱码。我一般先设置resp.encoding = resp.apparent_encoding,让requests自己从 HTML 的 meta 信息里去猜,比写死一个编码更可靠。
另外,不同的网站正文结构不同,有的用<article>,有的用<div class="content">。第一次使用某个网站时,最好先用开发者工具看一眼正文所在节点,然后把选择器配置到脚本开头,而不是每次临时改代码。我保留过一个几十行的配置文件,把常见网站的选择器都收集起来,更新很方便。
4. 办公与沟通自动化:让脚本替你发消息
4.1 多个Excel汇总成一张报表(脚本七)
做运营和财务的朋友应该很有共鸣:月底要从十几个门店发来的 Excel 里汇总一张总表,格式还不完全一样,有的带标题行,有的不带。我以前处理这种表格,直接使用最笨的复制粘贴,后来用pandas一行代码就把活接住了。
import pandas as pd from pathlib import Path folder = Path("D:/报表") frames = [] for file in folder.glob("*.xlsx"): df = pd.read_excel(file) print(f"导入:{file.name},形状:{df.shape}") frames.append(df) merged = pd.concat(frames, ignore_index=True) merged.to_excel("汇总报表.xlsx", index=False) print(f"合并完成,共 {len(merged)} 行")这个脚本帮我把“几十个文件逐一打开、复制、粘贴”的流程压缩成了“双击运行、等两秒、完成”。
不过,用pandas读 Excel 有几个隐藏细节。第一,文件后缀是.xls老格式时,需要额外安装xlrd;第二,如果每个分表的第一行不是真正的表头,要加header参数从指定行开始读取;第三,如果列名在不同表中不完全一致,pd.concat会按列名自动对齐,缺失部分会补 NaN,你后续要决定是删掉还是填充。我认为最好的实践是先让所有分表使用同一套标准模板,源头一致,汇总时才不会产生各种奇奇怪怪的脏数据。
4.2 定时发送邮件报告(脚本八)
每周五下午给团队发工作周报,是我坚持了好几年的习惯。以前都是手动写邮件、查附件、点发送,直到有一次忘了发,被追问了才发现。后来我把这套流程脚本化,每周自动运行,稳定得让人放心。
SMTP 发邮件的原理不复杂:通过你所用邮箱提供的 SMTP 服务器,用账户密码登录,再构造一封邮件发送出去。Python 标准库里的smtplib和email就够用了。
import os import smtplib from email.message import EmailMessage sender = "sender@example.com" receiver = "receiver@example.com" password = os.environ.get("SMTP_PASS") msg = EmailMessage() msg["Subject"] = "每周工作汇报" msg["From"] = sender msg["To"] = receiver msg.set_content("这是本周的自动汇报邮件,请查收附件。") with open("周报.xlsx", "rb") as f: msg.add_attachment(f.read(), maintype="application", subtype="vnd.openxmlformats-officedocument.spreadsheetml.sheet", filename="周报.xlsx") with smtplib.SMTP_SSL("smtp.example.com", 465) as smtp: smtp.login(sender, password) smtp.send_message(msg) print("邮件已发送")有两个经验必须分享。第一,密码不要硬编码在代码里,我通常把它放在环境变量中,脚本里用os.environ.get("SMTP_PASS")读取。如果别人拿到你的脚本文件,也不至于直接看到邮箱密码。第二,用免费邮箱时,不要直接写邮箱本身的主密码,而是去账户设置里开通 SMTP 服务,生成独立授权码,用授权码登录。
如果你要发送的邮件频率很高,建议在代码里加一个异常捕获,发送失败时把错误信息写入日志,再配合系统任务的重跑机制,比每次都盯着屏幕更可靠。
4.3 合同批量生成(脚本九)
需要批量生成几十份内容相似、只有少数字段不同的合同时,手动复制粘贴替换文字往往要花费很长时间,还容易漏改。我借助docxtpl,用 Word 模板配合一份数据表,就能批量生成格式统一的文档。
首先,你在 Word 模板里用{{ 字段名 }}的形式挖好坑,比如:
甲方:{{ party_a }} 乙方:{{ party_b }} 项目名称:{{ project_name }} 签订日期:{{ sign_date }}然后在 Python 中准备一个字典,字段名对应模板里的占位符,执行渲染后保存成新文件:
from docxtpl import DocxTemplate template_path = "合同模板.docx" data = { "party_a": "某公司", "party_b": "某乙方代表", "project_name": "模拟项目X", "sign_date": "2025-01-15", } tpl = DocxTemplate(template_path) tpl.render(data) tpl.save("合同_某乙方代表.docx") print("已生成合同文件")大规模批量生成时,我一般会用一个文本文件存放所有合同的数据,每行一条记录,然后循环读取模板并渲染。比如同时生成 50 份实习协议,每份都以员工姓名命名,整个过程不到一分钟。
需要注意,docxtpl使用的是 Jinja2 语法,如果模板里某个字段循环取值,也可以写{% for item in items %},功能比单纯替换占位符更灵活。只是模板文件里的语法一旦写错,渲染时会直接报错,所以我会先拿一条数据做试渲染,验证样式和内容无误后再批量跑。另外,生成的文档建议存放在独立目录,避免和模板源文件混在一起。
5. 系统维护与任务调度:把电脑当成你的员工
5.1 磁盘空间巡检与过期临时文件清理(脚本十)
电脑磁盘空间越来越小,是很多人没意识到的隐患。我之前有台工作机,C 盘常年标红,直到某次编译大项目时直接报磁盘不足,才被迫开始清理。排查完发现,大部分空间都被临时文件和缓存占着。
这个巡检脚本,会扫描几个常见的临时目录,找出超过指定天数没有改动的文件或文件夹,然后送到回收站,而不是永久删除。
from pathlib import Path import time import send2trash CACHE_DIRS = [ Path.home() / ".cache", Path.home() / "AppData/Local/Temp", ] DAYS = 7 SECONDS = DAYS * 24 * 3600 for folder in CACHE_DIRS: if not folder.exists(): continue for item in folder.iterdir(): try: if item.stat().st_mtime < time.time() - SECONDS: send2trash.send2trash(str(item)) print(f"已移入回收站:{item}") except PermissionError: continue这个脚本有两点我要重点强调。一是路径路径,不同系统缓存目录差异很大,macOS 的缓存目录和 Windows 完全不同,我上面的示例是 Windows 风格的路径,macOS 用户需要改成~/Library/Caches等目录。二是权限,部分临时文件正在被系统或程序占用,直接删除会失败,代码里要捕获异常,跳过这些文件,不能因为单个文件报错就中断整个流程。
使用send2trash而不是shutil.rmtree,是我坚持的安全底线。文件先进回收站,万一误删还有后悔药可吃。跑完脚本后,我都会看一眼回收站,确认没有误伤正常文件,再清空。
5.2 把这10个脚本统一交给定时调度器
单个脚本写完,还只是第一步。真正让它们发挥价值的是定时运行。我在本地用的是 Python 的schedule库,把所有任务统一放到一个调度文件里,后台持续运行:
import schedule import time schedule.every().day.at("09:30").do(organize_downloads) schedule.every().day.at("07:30").do(send_weather) schedule.every().monday.at("10:00").do(send_report) while True: schedule.run_pending() time.sleep(60)schedule库最大的优点就是简单,几乎没学习成本。但它只能在你打开这个 Python 进程的时候运行,如果你退出终端、重启电脑,它就停了。所以生产环境或者说“长期可靠”的环境,我建议把脚本注册到系统任务计划程序里面,比如 Windows 的“任务计划程序”或者 macOS/Linux 的cron。我自己是双重方案:开发期用schedule做快速测试,稳定之后改用系统任务调度。
不管用哪种方式,都要在计划里指定完整的 Python 解释器路径和脚本绝对路径。只写“python xxx.py”这种相对路径,在计划任务里经常因为当前工作目录不同而找不到文件。
5.3 安全底线:自动脚本必须设好“刹车”
自动化脚本最大的风险不是写不出来,而是“跑起来之后不受控”。我在迭代过程中给自己立了几条规矩,也建议把它们写进你的脚本习惯里。
第一,删除类操作默认走回收站,绝不直接rm -rf。第二,批量修改文件之前,先做一次试运行,只打印将要执行的操作名称,不真正动手。第三,脚本中涉及的重要操作都写日志,保留至少最近 30 天的记录,方便排查。第四,对文件做移动、重命名、覆盖前,先判断目标是否存在,存在时优先重命名而不是直接覆盖。这些规则能让你在脚本出错时不至于手忙脚乱,也能避免因为一个小失误造成大范围文件丢失。
6. 常见问题与排查技巧实录
6.1 脚本运行没反应,先检查这三处
遇到脚本明明执行了却没达到预期效果,我一般按顺序排查三个点。首先是“用哪个 Python 环境”,如果你在终端里输入python进入的环境不是安装依赖的那个虚拟环境,脚本可能因为缺第三方库直接报错。其次是“当前目录是否正确”,脚本如果用了相对路径,比如Path("D:/待整理文件"),那么执行时工作目录不在预设位置,路径就会失效。第三是“异常有没有被吞掉”,很多脚本只写了业务逻辑,没做错误处理,一旦某一步抛异常,整个流程就静默退出,你根本不知道发生了什么。
我建议在每个脚本的入口加上一段异常兜底:
import logging logging.basicConfig(filename="auto_script.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") try: main() except Exception as e: logging.exception("脚本执行失败")这样至少失败时能看到日志,而不是干瞪眼。
6.2 中文乱码问题
Python 脚本处理中文时,常见的乱码有三种来源:源码文件本身的编码、读取外部文件时的编码、输出到终端时的编码。我的统一做法是:所有.py文件用 UTF-8 保存;读取外部文本文件时指定encoding="utf-8";Windows 终端如果显示乱码,执行前在脚本开头加sys.stdout.reconfigure(encoding="utf-8")或者chcp 65001切换代码页。
处理网页数据时,我前面提过用resp.encoding = resp.apparent_encoding来猜编码,这个方法比硬编码utf-8更能适配不同站点。处理 Excel 时,pandas读出来的中文一般不会乱,但如果你写 CSV 而不是 Excel,记得用encoding="utf-8-sig",这样文件用 Excel 打开时就不会出现乱码。
6.3 路径跨平台问题
初学者最容易踩的坑是在代码里硬编码路径分隔符,比如"D:\\数据\\xxx"。这种写法写死了 Windows 风格,迁移到 macOS 就崩。我从一开始就改用pathlib.Path,路径拼接用/运算符,例如Path.home() / "Downloads"。它会在各个平台自动转换为正确的分隔符,而且代码读起来也更清晰。
另外,脚本里尽量不要依赖“当前工作目录”。我现在的规范是,每个脚本开头都定义好基础路径常量,用绝对路径或基于Path.home()的路径。这样不管是手动执行还是定时任务执行,都不会因为工作目录不同而出错。
6.4 给脚本加日志和异常兜底,等于买了保险
脚本刚开始运行时,可能只有你自己盯着终端看。但当它挂到定时任务里后,你很少主动盯输出,这时候日志就变成了唯一的诊断依据。我推荐在脚本里统一初始化logging,日志文件按天滚动,避免越积越大。
import logging from logging.handlers import TimedRotatingFileHandler handler = TimedRotatingFileHandler( "auto_script.log", when="midnight", backupCount=30 ) logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[handler], )这样做的好处是:脚本没跑,可以先看日志确认时间对不上;跑失败了,看最后一条日志能定位到具体是哪一步异常。我自己调试价格监控脚本的时候,就是靠日志发现请求被目标网站限制,后来才决定把请求频率调到更保守。
7. 写在最后:自动化最大的阻力不是技术,是习惯
说了这么多脚本,最后讲一点个人体会。很多人学自动化脚本的时候,总想着做一套“大而全”的东西,结果写了一半就放弃了。我第一版文件分类器写得很烂,代码只有几十行,但已经够用,后来才逐步加规则、加重名保护、加日志。如果你也想用 Python 解放双手,我的建议很简单:别想着一次性自动化所有事情,先从你下周已经确定会重复做的那件事开始。
可以是整理一次下载文件夹,也可以是生成一次周报邮件。等你尝到“脚本代替手动”的甜头之后,自然会上瘾,后面每遇到一件重复劳动,你都会条件反射地思考“这能不能脚本化”。我就是这样,从一个手动整理文件都要花一小时的人,变成了现在每天早晨看一眼日志就放心开始工作的人。自动化的价值,不在于一次省了多少时间,而在于你终于不用再为那些不值得占用脑力的事情消耗精力了。
最后再送一个小技巧:给所有脚本统一加上--dry-run参数,让它只打印将来要做什么,而不真正执行。我因为加了这一步,至少避免了两三次“批量误操作”的惨案。写代码慢一点没关系,放心运行才更重要。