十:自动化脚本——用 Python 干杂活

一、回顾与本篇目标

前面九篇,你从零开始学了 Python 的语法、容器、函数、模块、文件读写、爬虫、数据分析。这些技能单独拿出来都能用,但更常见的场景是——把它们组合起来,写一个能自动完成某项任务的脚本

比如:下载了一个文件夹的图片,文件名全是乱的,想批量重命名;每周要发一份同样的邮件给不同的人;每个月要把 Excel 表格里的数据整理成固定格式。这些事情手动做很烦,写个小脚本却能几十秒搞定。

这一篇,我们不引入新概念,而是用你已经学过的知识,写出几个真正实用的自动化脚本。每个脚本你改几个参数就能用到自己的实际场景中。

本篇的目标:

  1. 学会用 Python 批量操作文件
  2. 学会用 Python 自动发送邮件
  3. 学会用 Python 读写 Excel 文件

二、批量文件操作

这是日常工作中最常见的自动化需求——文件太多,手动处理太累。Python 处理文件的优势在于:一次写好脚本,反复使用。

2.1 批量重命名文件

假设你有一个文件夹,里面全是图片,文件名格式很乱:IMG_20230101.jpgIMG_20230102.jpg……你想把它们统一改成 旅行照片_001.jpg旅行照片_002.jpg……

import os

# 目标文件夹路径(改成你自己的)
folder = './photos'

# 新文件名的前缀
prefix = '旅行照片'

# 获取文件夹里所有文件
files = os.listdir(folder)

# 只处理图片文件
image_extensions = ('.jpg', '.jpeg', '.png', '.gif', '.bmp')
image_files = [f for f in files if f.lower().endswith(image_extensions)]

# 按文件名排序(保证顺序一致)
image_files.sort()

# 逐个重命名
for index, old_name in enumerate(image_files, start=1):
    # 获取文件扩展名
    ext = os.path.splitext(old_name)[1]
    # 新文件名:前缀_编号.扩展名
    new_name = f'{prefix}_{index:03d}{ext}'

    old_path = os.path.join(folder, old_name)
    new_path = os.path.join(folder, new_name)

    os.rename(old_path, new_path)
    print(f'{old_name} → {new_name}')

print(f'完成!共重命名 {len(image_files)} 个文件')

代码解释:

  • os.listdir(folder):获取文件夹下所有文件的文件名列表。
  • f.lower().endswith(image_extensions):判断文件扩展名是不是图片格式。.lower() 转小写保证大小写不敏感。
  • enumerate(image_files, start=1):同时获取索引和文件名,start=1 让编号从 1 开始。
  • os.path.splitext(old_name):把文件名拆成“主名”和“扩展名”两部分。
  • {index:03d}:把数字格式化为三位数(001、002、……、099、100)。
  • os.rename(old_path, new_path):重命名文件。

使用前先测试:在正式重命名之前,可以先把 os.rename 那行注释掉,只打印新旧文件名,确认无误后再真正执行。

2.2 按文件类型分类整理

下载文件夹里什么都有——图片、文档、压缩包、安装程序,全混在一起。写个脚本自动分类:

import os
import shutil

folder = './downloads'

# 文件类型与对应的文件夹名
type_mapping = {
    '图片': ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg'),
    '文档': ('.pdf', '.doc', '.docx', '.txt', '.xlsx', '.pptx'),
    '压缩包': ('.zip', '.rar', '.7z', '.tar', '.gz'),
    '视频': ('.mp4', '.avi', '.mkv', '.mov'),
    '音乐': ('.mp3', '.wav', '.flac'),
}

# 先创建分类文件夹
for folder_name in type_mapping.keys():
    os.makedirs(os.path.join(folder, folder_name), exist_ok=True)

# 创建“其他”文件夹
os.makedirs(os.path.join(folder, '其他'), exist_ok=True)

# 遍历文件并归类
for filename in os.listdir(folder):
    filepath = os.path.join(folder, filename)

    # 跳过文件夹
    if os.path.isdir(filepath):
        continue

    # 获取扩展名
    ext = os.path.splitext(filename)[1].lower()

    # 查找匹配的类型
    moved = False
    for category, extensions in type_mapping.items():
        if ext in extensions:
            shutil.move(filepath, os.path.join(folder, category, filename))
            print(f'{filename} → {category}/')
            moved = True
            break

    # 没匹配到的移到“其他”
    if not moved:
        shutil.move(filepath, os.path.join(folder, '其他', filename))
        print(f'{filename} → 其他/')

print('整理完成!')

新出现的 shutil.move():它用来移动文件(或文件夹)。和 os.rename() 类似,但 shutil.move() 可以跨磁盘分区移动,更通用。

2.3 查找并删除重复文件

有时候文件夹里存了多个相同内容的文件,白白占空间。可以通过比较文件的 MD5 哈希值来判断是否重复:

import os
import hashlib

def get_file_md5(filepath):
    """计算文件的 MD5 哈希值"""
    with open(filepath, 'rb') as file:
        content = file.read()
        return hashlib.md5(content).hexdigest()

def find_duplicates(folder):
    """查找重复文件"""
    hash_map = {}  # 哈希值 → 文件路径列表

    for root, dirs, files in os.walk(folder):
        for filename in files:
            filepath = os.path.join(root, filename)
            file_hash = get_file_md5(filepath)

            if file_hash in hash_map:
                hash_map[file_hash].append(filepath)
            else:
                hash_map[file_hash] = [filepath]

    # 找出所有重复组
    duplicates = {h: paths for h, paths in hash_map.items() if len(paths) > 1}
    return duplicates

# 使用
folder = './my_files'
duplicates = find_duplicates(folder)

if duplicates:
    print(f'找到 {len(duplicates)} 组重复文件:')
    for file_hash, paths in duplicates.items():
        print(f'\n  哈希值:{file_hash[:16]}...')
        print(f'  原始文件:{paths[0]}')
        for path in paths[1:]:
            print(f'  重复文件:{path}')
else:
    print('没有找到重复文件')

代码解释:

  • hashlib.md5(content).hexdigest():计算文件的 MD5 哈希值。相同内容的文件哈希值相同。
  • os.walk(folder):递归遍历文件夹下所有子文件夹和文件。root 是当前目录路径,dirs 是当前目录的子目录列表,files 是当前目录的文件列表。

三、自动发送邮件

Python 内置了 smtplibemail 模块,可以自动发送邮件——发送测试报告、定时提醒、批量通知。

3.1 发送简单文本邮件

import smtplib
from email.mime.text import MIMEText
from email.header import Header

# === 配置信息(改成你自己的) ===
smtp_server = 'smtp.qq.com'           # SMTP 服务器地址
smtp_port = 587                       # QQ邮箱用587,163邮箱用25
sender_email = '你的邮箱@qq.com'
sender_password = '你的授权码'         # 不是邮箱密码,是 SMTP 授权码
receiver_email = '收件人@example.com'
# =============================

# 构建邮件内容
subject = '测试邮件——来自 Python'
content = '''
你好!

这是一封由 Python 脚本自动发送的测试邮件。

如果你收到了,说明脚本运行成功。

致礼
Python 自动化脚本
'''

# 创建邮件对象
message = MIMEText(content, 'plain', 'utf-8')
message['From'] = Header(sender_email)
message['To'] = Header(receiver_email)
message['Subject'] = Header(subject)

# 发送邮件
try:
    server = smtplib.SMTP(smtp_server, smtp_port)
    server.starttls()                    # 开启 TLS 加密
    server.login(sender_email, sender_password)
    server.sendmail(sender_email, receiver_email, message.as_string())
    print('邮件发送成功!')
except smtplib.SMTPException as e:
    print(f'邮件发送失败:{e}')
finally:
    server.quit()

关键配置说明:

  • SMTP 服务器地址:QQ邮箱是 smtp.qq.com,163邮箱是 smtp.163.com,Gmail 是 smtp.gmail.com
  • 授权码:不是你的邮箱登录密码。需要在邮箱设置里开启 SMTP 服务后获取。QQ邮箱:设置 → 账户 → POP3/SMTP 服务 → 开启 → 获取授权码。163邮箱:设置 → POP3/SMTP/IMAP → 开启 → 新增授权码。
  • server.starttls():开启 TLS 加密,保护邮件内容在传输过程中不被窃听。
  • server.quit() 放在 finally 块中,确保无论是否发送成功都会关闭连接。

3.2 发送带附件的邮件

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from email.header import Header

def send_email_with_attachment(sender, password, receiver, subject, body, filepath):
    """发送带附件的邮件"""
    # 创建带附件的邮件对象
    message = MIMEMultipart()
    message['From'] = Header(sender)
    message['To'] = Header(receiver)
    message['Subject'] = Header(subject)

    # 添加邮件正文
    message.attach(MIMEText(body, 'plain', 'utf-8'))

    # 添加附件
    with open(filepath, 'rb') as file:
        attachment = MIMEBase('application', 'octet-stream')
        attachment.set_payload(file.read())
        encoders.encode_base64(attachment)
        filename = os.path.basename(filepath)
        attachment.add_header(
            'Content-Disposition',
            'attachment',
            filename=('utf-8', '', filename)
        )
        message.attach(attachment)

    # 发送
    server = smtplib.SMTP('smtp.qq.com', 587)
    server.starttls()
    server.login(sender, password)
    server.sendmail(sender, receiver, message.as_string())
    server.quit()
    print('邮件发送成功!')

四、操作 Excel 文件

上一篇文章讲了用 Pandas 处理表格数据。但有时候需要的不只是分析数据,而是在现有 Excel 模板的基础上自动填写内容,或者设置格式、添加图表。这时候需要 openpyxl 库。

4.1 安装 openpyxl

pip install openpyxl

4.2 创建 Excel 文件并写入数据

from openpyxl import Workbook

# 创建一个工作簿
wb = Workbook()

# 选择默认的工作表
ws = wb.active
ws.title = '学生成绩'

# 写入表头
ws['A1'] = '姓名'
ws['B1'] = '班级'
ws['C1'] = '成绩'

# 写入数据
students = [
    ('张三', '一班', 85),
    ('李四', '二班', 92),
    ('王五', '一班', 78),
    ('赵六', '三班', 95),
    ('孙七', '二班', 88),
]

for row_index, (name, class_name, score) in enumerate(students, start=2):
    ws.cell(row=row_index, column=1, value=name)
    ws.cell(row=row_index, column=2, value=class_name)
    ws.cell(row=row_index, column=3, value=score)

# 保存
wb.save('学生成绩.xlsx')
print('Excel 文件已创建')

两种写入方式

  • ws['A1'] = '姓名':像字典一样用单元格地址写入。
  • ws.cell(row=2, column=1, value='张三'):用行号和列号写入,循环填充时更方便。

4.3 读取 Excel 文件并修改

from openpyxl import load_workbook

# 打开已有的 Excel 文件
wb = load_workbook('学生成绩.xlsx')
ws = wb.active

# 读取所有数据
print('当前数据:')
for row in ws.iter_rows(values_only=True):
    print(row)

# 新增一列:等级
ws['D1'] = '等级'

# 根据成绩填充等级
for row in range(2, ws.max_row + 1):
    score = ws.cell(row=row, column=3).value
    if score >= 90:
        grade = '优秀'
    elif score >= 80:
        grade = '良好'
    elif score >= 60:
        grade = '及格'
    else:
        grade = '不及格'
    ws.cell(row=row, column=4, value=grade)

# 保存
wb.save('学生成绩_带等级.xlsx')
print('等级已添加,保存为新文件')

代码解释:

  • load_workbook():打开已有 Excel 文件。
  • ws.iter_rows(values_only=True):遍历所有行,values_only=True 表示只返回单元格的值而不是单元格对象。
  • ws.max_row:获取工作表中有数据的最大行号。

4.4 设置单元格样式

from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side

wb = Workbook()
ws = wb.active

# 写入数据
ws['A1'] = '产品'
ws['B1'] = '销量'
ws['C1'] = '销售额'

products = [
    ('手机', 120, 359880),
    ('电脑', 45, 269955),
    ('平板', 80, 159920),
]

for row_index, (name, count, total) in enumerate(products, start=2):
    ws.cell(row=row_index, column=1, value=name)
    ws.cell(row=row_index, column=2, value=count)
    ws.cell(row=row_index, column=3, value=total)

# 设置表头样式
header_font = Font(name='微软雅黑', bold=True, size=12, color='FFFFFF')
header_fill = PatternFill(start_color='4A90D9', end_color='4A90D9', fill_type='solid')
header_alignment = Alignment(horizontal='center', vertical='center')

for cell in ws[1]:  # 第一行所有单元格
    cell.font = header_font
    cell.fill = header_fill
    cell.alignment = header_alignment

# 设置边框
thin_border = Border(
    left=Side(style='thin'),
    right=Side(style='thin'),
    top=Side(style='thin'),
    bottom=Side(style='thin')
)

for row in ws.iter_rows(min_row=1, max_row=ws.max_row, max_col=ws.max_column):
    for cell in row:
        cell.border = thin_border

# 设置列宽
ws.column_dimensions['A'].width = 15
ws.column_dimensions['B'].width = 10
ws.column_dimensions['C'].width = 15

wb.save('销售报告_带格式.xlsx')
print('带格式的 Excel 文件已创建')

样式说明:

  • Font():设置字体、大小、加粗、颜色。
  • PatternFill():设置单元格背景色。
  • Alignment():设置水平和垂直对齐方式。
  • Border() + Side():设置单元格边框。
  • ws.column_dimensions['A'].width:设置列宽。

五、综合演示:周报自动生成器

下面这个脚本综合运用了文件读写、Pandas 数据处理、openpyxl 操作 Excel,模拟一个实用的场景——把 CSV 格式的销售数据自动汇总成带格式的 Excel 周报:

import pandas as pd
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from datetime import datetime

def generate_weekly_report(csv_file, output_file):
    """从 CSV 数据生成 Excel 周报"""

    # 1. 读取数据
    df = pd.read_csv(csv_file)

    # 2. 数据处理:分组汇总
    summary = df.groupby('产品').agg(
        总销量=('销量', 'sum'),
        总销售额=('销售额', 'sum'),
        平均单价=('单价', 'mean')
    ).sort_values('总销售额', ascending=False).reset_index()

    # 3. 创建 Excel
    wb = Workbook()
    ws = wb.active
    ws.title = '周报'

    # 4. 标题
    ws.merge_cells('A1:D1')
    title_cell = ws['A1']
    title_cell.value = f'销售周报 - {datetime.now().strftime("%Y年%m月%d日")}'
    title_cell.font = Font(name='微软雅黑', bold=True, size=16, color='333333')
    title_cell.alignment = Alignment(horizontal='center', vertical='center')
    ws.row_dimensions[1].height = 40

    # 5. 表头
    headers = ['产品', '总销量', '总销售额', '平均单价']
    for col, header in enumerate(headers, start=1):
        cell = ws.cell(row=3, column=col, value=header)
        cell.font = Font(name='微软雅黑', bold=True, size=11, color='FFFFFF')
        cell.fill = PatternFill(start_color='4A90D9', end_color='4A90D9', fill_type='solid')
        cell.alignment = Alignment(horizontal='center', vertical='center')

    # 6. 数据
    for row_index, (_, row) in enumerate(summary.iterrows(), start=4):
        ws.cell(row=row_index, column=1, value=row['产品'])
        ws.cell(row=row_index, column=2, value=int(row['总销量']))
        ws.cell(row=row_index, column=3, value=f"¥{row['总销售额']:,.0f}")
        ws.cell(row=row_index, column=4, value=f"¥{row['平均单价']:,.0f}")

    # 7. 汇总行
    total_row = len(summary) + 5
    ws.cell(row=total_row, column=1, value='合计').font = Font(bold=True)
    ws.cell(row=total_row, column=2, value=int(summary['总销量'].sum()))
    ws.cell(row=total_row, column=3, value=f"¥{summary['总销售额'].sum():,.0f}")

    # 8. 边框和列宽
    thin_border = Border(
        left=Side(style='thin'), right=Side(style='thin'),
        top=Side(style='thin'), bottom=Side(style='thin')
    )
    for row in ws.iter_rows(min_row=3, max_row=total_row, max_col=4):
        for cell in row:
            cell.border = thin_border

    ws.column_dimensions['A'].width = 12
    ws.column_dimensions['B'].width = 12
    ws.column_dimensions['C'].width = 16
    ws.column_dimensions['D'].width = 14

    # 9. 保存
    wb.save(output_file)
    print(f'周报已生成:{output_file}')

# 先创建一个示例 CSV
sample_data = '''日期,产品,地区,销量,单价,销售额
2024-01-05,手机,华东,120,2999,359880
2024-01-12,电脑,华北,45,5999,269955
2024-01-18,手机,华东,150,2999,449850
2024-02-03,平板,华南,80,1999,159920
2024-02-15,电脑,华北,55,5999,329945
'''

with open('sample_data.csv', 'w', encoding='utf-8') as f:
    f.write(sample_data)

# 生成周报
generate_weekly_report('sample_data.csv', '销售周报.xlsx')

代码逻辑

  • 第一步:用 Pandas 读取 CSV 数据并进行分组汇总。
  • 第二步:用 openpyxl 创建 Excel 文件,填入汇总结果。
  • 第三步:设置标题、表头、边框、列宽等格式。
  • 第四步:保存文件。

这个脚本在实际工作中稍作修改就能用——把 CSV 路径换成你实际的数据源,调整汇总字段,周报就生成了。

六、定时执行脚本

脚本写好了,但你不想每次手动运行它。让操作系统帮你定时执行:

  • Windows:用“任务计划程序”。创建一个基本任务,设置触发器(如每天上午 9 点),操作为“启动程序”,程序填 python,参数填脚本路径。
  • Mac / Linux:用 crontab。终端执行 crontab -e,添加一行:0 9 * * * /usr/bin/python3 /path/to/your/script.py,表示每天上午 9 点运行脚本。

七、本篇动手练习

练习 1:文件批量重命名

新建一个文件夹,放一些测试文件。写一个脚本,把所有 .txt 文件统一命名格式为 笔记_001.txt笔记_002.txt……

练习 2:给自己发一封邮件

配置邮箱的 SMTP 授权码,写一个脚本给自己发一封测试邮件。成功后,改写成可以批量发送不同内容的邮件(收件人列表 + 模板)。

练习 3:Excel 成绩单

用 openpyxl 创建一个学生成绩表,包含:表头、10 名学生的数据、根据成绩自动填充“优秀/良好/及格/不及格”等级、带格式(边框、表头颜色、居中)。

八、本篇小结

这一篇你把前面九篇学到的知识用在了解决实际问题上:

  • 批量文件操作os.listdir() 遍历文件,os.rename() 重命名,shutil.move() 移动文件,os.walk() 递归遍历子文件夹。
  • 自动发送邮件:用 smtplibemail 模块构建并发送邮件。需要邮箱开启 SMTP 服务并获取授权码。
  • 操作 Excelopenpyxl 创建和读取 Excel 文件、写入数据、设置样式、保存。和 Pandas 分工:Pandas 做数据分析,openpyxl 做格式美化。
  • 定时执行:Windows 用任务计划程序,Mac/Linux 用 crontab。

自动化的核心思想很简单:把重复的、有规律的操作,用代码描述出来,交给计算机去执行。越是烦琐的工作,越值得花时间写个脚本把它自动化——你可能花半小时写脚本,但以后每次省下五分钟,一个月就赚回来了。

下一篇预告

下一篇——《用 Flask 写后端——Python 版的 Express》:用 Python 写后端 API。如果你之前跟着《后端零基础入门》学过 Express,这一篇会让你发现——Python 写后端和 Node.js 写后端的思路完全一样,只是语法不同。你会用 Flask 把之前的留言板项目用 Python 重写一遍。

Python 零基础入门,每周更新。

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容