用Python自动化枯燥的工作 第7篇:办公文档自动化处理
摘要
本文将带你深入了解办公文档自动化处理的核心技术,帮助你掌握使用Python处理Excel、Word、PDF和邮件的能力。你将学到Excel表格的读写与数据操作、Word文档的生成与编辑、PDF文件的合并与处理、邮件自动化发送,以及综合办公自动化的实战应用。
学习目标
阅读完本文后,你将能够:
- Excel处理:能够使用openpyxl读写Excel文件,进行数据操作和格式设置
- Word处理:能够使用python-docx创建和编辑Word文档
- PDF处理:能够使用PyPDF2和ReportLab处理PDF文件
- 邮件自动化:能够使用smtplib和email库发送邮件
- 综合应用:能够构建完整的办公自动化解决方案,提高工作效率
一、Excel自动化:数据处理与报表生成
1.1 Excel文件基础操作
Excel是办公中最常用的数据处理工具,Python可以自动化完成Excel的读写、格式设置、公式计算等任务。
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font, Alignment, PatternFill
# 创建新的Excel工作簿
wb = Workbook()
ws = wb.active
ws.title = "销售数据"
# 写入数据
headers = ["日期", "产品", "数量", "单价", "金额"]
ws.append(headers)
# 写入数据行
data = [
["2024-01-01", "产品A", 10, 100, 1000],
["2024-01-02", "产品B", 5, 200, 1000],
["2024-01-03", "产品C", 8, 150, 1200],
]
for row in data:
ws.append(row)
# 保存文件
wb.save("sales_data.xlsx")1.2 读取Excel数据
from openpyxl import load_workbook
def read_excel(filepath, sheet_name=None):
"""读取Excel文件数据"""
wb = load_workbook(filepath)
# 指定工作表
if sheet_name:
ws = wb[sheet_name]
else:
ws = wb.active
data = []
# 读取所有行
for row in ws.iter_rows(values_only=True):
data.append(row)
return data
# 读取数据
data = read_excel("sales_data.xlsx")
for row in data:
print(row)
# 读取特定范围
wb = load_workbook("sales_data.xlsx")
ws = wb.active
# 读取A1:C3区域
for row in ws['A1':'C3']:
for cell in row:
print(cell.value)1.3 Excel格式设置
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
wb = Workbook()
ws = wb.active
# 添加数据
ws.append(["姓名", "年龄", "部门", "工资"])
ws.append(["张三", 25, "技术部", 8000])
ws.append(["李四", 30, "市场部", 7500])
ws.append(["王五", 28, "销售部", 9000])
# 设置标题行样式
header_font = Font(name='微软雅黑', size=12, bold=True, color='FFFFFF')
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', 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
# 设置列宽
ws.column_dimensions['A'].width = 12
ws.column_dimensions['B'].width = 8
ws.column_dimensions['C'].width = 12
ws.column_dimensions['D'].width = 10
# 添加边框
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=5, min_col=1, max_col=4):
for cell in row:
cell.border = thin_border
# 设置数字格式
ws['D2'].number_format = '¥#,##0.00'
ws['D3'].number_format = '¥#,##0.00'
ws['D4'].number_format = '¥#,##0.00'
wb.save("formatted_table.xlsx")下面的序列图展示了Excel操作的完整流程:
sequenceDiagram participant User as 用户代码 participant WB as Workbook对象 participant WS as Worksheet对象 participant Cell as 单元格 participant File as Excel文件 Note over User,File: 创建新Excel文件 User->>WB: 创建Workbook WB->>WS: 获取活动工作表 User->>WS: 写入标题行 User->>WS: 写入数据行 User->>Cell: 设置单元格格式 Note right of Cell: 字体、颜色、<br/>对齐、边框 User->>WS: 设置列宽 User->>File: 保存文件 Note right of File: sales_data.xlsx Note over User,File: 读取Excel文件 User->>File: 加载Excel文件 File-->>WB: Workbook对象 WB->>WS: 获取工作表 User->>WS: 读取数据 WS-->>User: 返回数据列表
图表讲解:这个序列图展示了Excel文件的创建和读取操作的完整流程,理解这个流程有助于掌握Excel自动化。
创建新Excel文件(上半部分):用户代码首先创建Workbook对象,这是Excel文件的内存表示。Workbook包含一个或多个Worksheet对象,每个工作表包含单元格网格。获取活动工作表后,用户可以向工作表写入数据——通过append()方法添加行,或直接访问单元格赋值。
格式设置是Excel自动化的重要部分(蓝色区域):可以设置单元格的字体(字体名称、大小、粗细、颜色)、填充(背景色)、对齐(水平/垂直)、边框(线条样式)等。这些样式使生成的Excel文件更加专业和易读。还可以设置列宽、行高、数字格式(如货币、百分比)。
最后将Workbook保存到文件(绿色区域),所有数据和格式都被写入磁盘,生成标准的Excel文件,可以在Microsoft Excel或其他电子表格软件中打开。
读取Excel文件(下半部分):从文件加载Workbook对象,包含所有工作表和数据。获取特定工作表后,可以读取数据。读取方式包括:遍历所有行、读取特定区域、访问特定单元格。iter_rows()方法提供按行迭代,values_only=True参数只返回值而不是Cell对象。
理解这个流程,可以构建各种Excel自动化任务,如数据导入导出、报表生成、数据分析等。
1.4 Excel公式和图表
from openpyxl import Workbook
from openpyxl.chart import BarChart, Reference
wb = Workbook()
ws = wb.active
# 准备数据
ws.append(["产品", "Q1", "Q2", "Q3", "Q4", "总计"])
ws.append(["产品A", 1000, 1200, 1100, 1300, "=SUM(B2:E2)"])
ws.append(["产品B", 800, 900, 950, 1100, "=SUM(B3:E3)"])
ws.append(["产品C", 1200, 1150, 1300, 1400, "=SUM(B4:E4)"])
# 添加汇总行
ws.append(["总计", "=SUM(B2:B4)", "=SUM(C2:C4)",
"=SUM(D2:D4)", "=SUM(E2:E4)", "=SUM(F2:F4)"])
# 创建图表
chart = BarChart()
chart.type = "col"
chart.style = 10
chart.title = "季度销售对比"
chart.y_axis.title = '销售额'
chart.x_axis.title = '产品'
# 设置数据源
data = Reference(ws, min_col=2, min_row=1, max_row=5, max_col=5)
cats = Reference(ws, min_col=1, min_row=2, max_row=4)
chart.add_data(data, titles_from_data=True)
chart.set_categories(cats)
ws.add_chart(chart, "H2")
wb.save("chart_example.xlsx")二、Word文档自动化:报告生成与编辑
2.1 Word文档基础操作
Python可以使用python-docx库创建和编辑Word文档,支持文本、表格、图片、样式等元素。
from docx import Document
from docx.shared import Pt, Inches, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
# 创建新文档
doc = Document()
# 添加标题
title = doc.add_heading('项目进度报告', level=0)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
# 添加段落
doc.add_paragraph('本文档汇报了本季度的项目进展情况。')
# 添加副标题
doc.add_heading('一、项目概况', level=1)
# 添加带格式的段落
para = doc.add_paragraph()
run = para.add_run('项目名称:')
run.bold = True
para.add_run('自动化办公系统开发')
para = doc.add_paragraph()
run = para.add_run('项目负责人:')
run.bold = True
para.add_run('张三')
# 保存文档
doc.save('project_report.docx')2.2 添加表格
from docx import Document
from docx.shared import Pt
doc = Document()
# 添加标题
doc.add_heading('员工信息表', level=1)
# 创建表格
table = doc.add_table(rows=1, cols=4)
table.style = 'Light Grid Accent 1'
# 设置表头
header_cells = table.rows[0].cells
header_cells[0].text = '姓名'
header_cells[1].text = '部门'
header_cells[2].text = '职位'
header_cells[3].text = '工资'
# 添加数据行
employees = [
['张三', '技术部', '工程师', '8000'],
['李四', '市场部', '经理', '12000'],
['王五', '销售部', '专员', '6000'],
]
for emp in employees:
row_cells = table.add_row().cells
for i, value in enumerate(emp):
row_cells[i].text = value
doc.save('employee_table.docx')2.3 批量生成文档
from docx import Document
from docx.shared import Pt
from pathlib import Path
def generate_certificate(name, course, date, template_path=None):
"""生成证书文档"""
if template_path:
doc = Document(template_path)
else:
doc = Document()
# 添加标题
title = doc.add_heading('结业证书', level=0)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
# 添加内容
doc.add_paragraph()
para = doc.add_paragraph()
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = para.add_run(f'兹证明 {name} 同志')
run.font.size = Pt(16)
doc.add_paragraph()
para = doc.add_paragraph()
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = para.add_run(f'已完成《{course}》课程学习')
run.font.size = Pt(14)
doc.add_paragraph()
para = doc.add_paragraph()
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = para.add_run(f'颁发日期:{date}')
run.font.size = Pt(12)
return doc
# 批量生成证书
students = [
{'name': '张三', 'course': 'Python编程', 'date': '2024-03-01'},
{'name': '李四', 'course': '数据分析', 'date': '2024-03-01'},
{'name': '王五', 'course': 'Web开发', 'date': '2024-03-01'},
]
output_dir = Path('certificates')
output_dir.mkdir(exist_ok=True)
for student in students:
doc = generate_certificate(**student)
filename = f"{student['name']}_证书.docx"
doc.save(output_dir / filename)
print(f"已生成: {filename}")下面的流程图展示了Word文档生成的完整流程:
flowchart TD A[开始] --> B[创建Document对象] B --> C[添加文档内容] C --> D{添加什么内容?} D -->|标题| E[add_heading] D -->|段落| F[add_paragraph] D -->|表格| G[add_table] D -->|图片| H[add_picture] D -->|分页| I[add_page_break] E --> J[设置格式] F --> J G --> J H --> J I --> J J --> K{格式类型} K -->|字体| L[Font设置] K -->|段落| M[Alignment设置] K -->|表格| N[Table样式] L --> O[还有内容?] M --> O N --> O O -->|是| D O -->|否| P[保存文档] P --> Q[输出.docx文件] style A fill:#e1f5e1 style Q fill:#e1f5ff style D fill:#fff5e1 style K fill:#fff5e1 style O fill:#fff5e1
图表讲解:这个流程图展示了使用python-docx创建Word文档的完整过程。
从创建Document对象开始(绿色区域):这是空白文档的内存表示。然后添加各种内容(黄色决策):标题(add_heading)、段落(add_paragraph)、表格(add_table)、图片(add_picture)、分页符(add_page_break)等。每种内容类型都有对应的方法,可以按需要组合使用。
添加内容后进行格式设置(蓝色区域):格式分为几个层次——字体格式(名称、大小、颜色、粗细、斜体)、段落格式(对齐、缩进、行距)、表格格式(样式、边框、宽度)等。格式设置通过给Run对象(段落中的文本片段)或段落对象赋值属性实现。
检查是否还有更多内容要添加(黄色决策):如果继续添加,返回选择内容类型;如果完成,保存文档(绿色区域)。保存时将内存中的文档结构写入.docx文件,生成可以在Microsoft Word中打开的标准文档。
理解这个流程,可以生成各种类型的Word文档:报告、合同、证书、发票等,实现办公文档的自动化生成。
三、PDF文件处理:文档合并与转换
3.1 读取PDF内容
import PyPDF2
def extract_pdf_text(filepath):
"""提取PDF文本内容"""
text = ""
with open(filepath, 'rb') as file:
reader = PyPDF2.PdfReader(file)
# 获取页数
num_pages = len(reader.pages)
print(f"总页数: {num_pages}")
# 逐页提取文本
for page_num in range(num_pages):
page = reader.pages[page_num]
text += page.extract_text()
text += "\n\n"
return text
# 使用示例
text = extract_pdf_text("document.pdf")
print(text)3.2 合并PDF文件
from PyPDF2 import PdfMerger
from pathlib import Path
def merge_pdfs(input_files, output_file):
"""合并多个PDF文件"""
merger = PdfMerger()
try:
for filepath in input_files:
print(f"添加文件: {filepath}")
merger.append(filepath)
print(f"正在合并...")
merger.write(output_file)
print(f"合并完成: {output_file}")
except Exception as e:
print(f"合并失败: {e}")
finally:
merger.close()
# 使用示例
pdf_files = [
"chapter1.pdf",
"chapter2.pdf",
"chapter3.pdf"
]
merge_pdfs(pdf_files, "complete_book.pdf")3.3 拆分PDF文件
from PyPDF2 import PdfReader, PdfWriter
from pathlib import Path
def split_pdf(input_file, output_dir):
"""拆分PDF文件(每页一个文件)"""
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
reader = PdfReader(input_file)
for page_num in range(len(reader.pages)):
writer = PdfWriter()
writer.add_page(reader.pages[page_num])
output_filename = output_path / f"page_{page_num + 1}.pdf"
with open(output_filename, 'wb') as output_file:
writer.write(output_file)
print(f"已保存: {output_filename}")
def extract_pages(input_file, output_file, page_range):
"""提取指定页面范围"""
reader = PdfReader(input_file)
writer = PdfWriter()
start, end = page_range
for page_num in range(start - 1, min(end, len(reader.pages))):
writer.add_page(reader.pages[page_num])
with open(output_file, 'wb') as output_file:
writer.write(output_file)
print(f"已提取页面 {start}-{end}: {output_file}")
# 使用示例
# split_pdf("large_file.pdf", "split_pages")
# extract_pages("large_file.pdf", "extracted.pdf", (1, 5))3.4 旋转PDF页面
from PyPDF2 import PdfReader, PdfWriter
def rotate_pdf(input_file, output_file, rotation):
"""旋转PDF页面
参数:
rotation: 90, 180, 270
"""
reader = PdfReader(input_file)
writer = PdfWriter()
for page in reader.pages:
page.rotate(rotation)
writer.add_page(page)
with open(output_file, 'wb') as output_file:
writer.write(output_file)
print(f"已旋转PDF {rotation}度: {output_file}")
# 使用示例
rotate_pdf("document.pdf", "rotated.pdf", 90)下面的类图展示了PDF处理的主要组件和操作:
classDiagram class PdfReader { +pages: list +metadata: dict +get_page(number) Page +len() int } class PdfWriter { +pages: list +add_page(page) void +write(file) void } class PdfMerger { +files: list +append(filepath) void +merge(file) void +close() void } class Page { +rotate(angle) void +extract_text() str +merge_page(page) void } class PdfOperations { +extract_text(filepath) str +merge_pdfs(files, output) void +split_pdf(filepath, dir) void +rotate_pdf(filepath, angle) void } PdfReader --> Page: contains PdfWriter --> Page: contains PdfOperations --> PdfReader: uses PdfOperations --> PdfWriter: uses PdfOperations --> PdfMerger: uses
图表讲解:这个类图展示了PDF处理系统的架构设计,帮助理解各组件的职责和关系。
PdfReader类(绿色区域):负责读取PDF文件,提供对PDF内容的访问。包含页面列表、元数据字典,可以获取特定页面对象,查询总页数。PdfReader是输入操作的入口,打开现有PDF文件进行分析和处理。
PdfWriter类(蓝色区域):负责创建新的PDF文件。包含页面列表,可以添加页面(从其他PDF复制或新建),最后将所有页面写入输出文件。PdfWriter是输出操作的终点,用于生成合并、拆分、修改后的PDF。
PdfMerger类(粉色区域):专门用于合并多个PDF文件的高级接口。内部维护文件列表,提供append()方法添加文件,merge()方法执行合并,close()方法释放资源。简化了多文件合并的操作流程。
Page类(紫色区域):代表PDF中的单个页面,提供页面级操作。rotate()旋转页面(90/180/270度),extract_text()提取页面文本内容,merge_page()将另一个页面的内容合并到当前页面。
PdfOperations类(黄色区域):封装了常见的PDF操作功能。包括提取文本、合并PDF、拆分PDF、旋转PDF等实用方法。这些方法内部使用PdfReader、PdfWriter、PdfMerger等基础组件,提供更简洁的API。
理解这个架构,可以根据需要选择合适的组件:简单读取用PdfReader,创建PDF用PdfWriter,合并用PdfMerger,复杂操作组合使用多个组件。
四、邮件自动化:批量发送与通知
4.1 发送纯文本邮件
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def send_email(sender, password, recipient, subject, body, smtp_server='smtp.gmail.com'):
"""发送纯文本邮件"""
# 创建邮件对象
msg = MIMEMultipart()
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = subject
# 添加邮件正文
msg.attach(MIMEText(body, 'plain'))
try:
# 连接SMTP服务器
server = smtplib.SMTP(smtp_server, 587)
server.starttls() # 启用TLS加密
# 登录
server.login(sender, password)
# 发送邮件
server.send_message(msg)
print(f"邮件已发送给: {recipient}")
except Exception as e:
print(f"发送失败: {e}")
finally:
server.quit()
# 使用示例
sender_email = "[email protected]"
sender_password = "your_app_password" # 使用应用专用密码
recipient_email = "[email protected]"
send_email(
sender=sender_email,
password=sender_password,
recipient=recipient_email,
subject="测试邮件",
body="这是一封来自Python的测试邮件。"
)4.2 发送HTML邮件
def send_html_email(sender, password, recipient, subject, html_content, smtp_server='smtp.gmail.com'):
"""发送HTML格式邮件"""
msg = MIMEMultipart('alternative')
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = subject
# 添加纯文本版本(备用)
text_part = MIMEText("请使用HTML邮箱查看此邮件", 'plain')
msg.attach(text_part)
# 添加HTML版本
html_part = MIMEText(html_content, 'html')
msg.attach(html_part)
try:
server = smtplib.SMTP(smtp_server, 587)
server.starttls()
server.login(sender, password)
server.send_message(msg)
print(f"HTML邮件已发送给: {recipient}")
except Exception as e:
print(f"发送失败: {e}")
finally:
server.quit()
# HTML模板示例
html_template = """
<html>
<body>
<h2 style="color: #4472C4;">{title}</h2>
<p>尊敬的 {name}:</p>
<p>{content}</p>
<hr>
<p style="color: gray; font-size: 12px;">
此邮件由系统自动发送,请勿回复。
</p>
</body>
</html>
"""
html_content = html_template.format(
title="月度报告",
name="张三",
content="您的月度报告已生成,请查收附件。"
)
send_html_email(
sender="[email protected]",
password="your_password",
recipient="[email protected]",
subject="月度报告",
html_content=html_content
)4.3 发送带附件的邮件
from email.mime.application import MIMEApplication
from pathlib import Path
def send_email_with_attachment(sender, password, recipient, subject,
body, attachment_path, smtp_server='smtp.gmail.com'):
"""发送带附件的邮件"""
msg = MIMEMultipart()
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = subject
# 添加正文
msg.attach(MIMEText(body, 'plain'))
# 添加附件
attachment_path = Path(attachment_path)
if attachment_path.exists():
with open(attachment_path, 'rb') as f:
part = MIMEApplication(f.read(), Name=attachment_path.name)
part['Content-Disposition'] = f'attachment; filename="{attachment_path.name}"'
msg.attach(part)
print(f"已添加附件: {attachment_path.name}")
else:
print(f"附件不存在: {attachment_path}")
try:
server = smtplib.SMTP(smtp_server, 587)
server.starttls()
server.login(sender, password)
server.send_message(msg)
print(f"邮件已发送给: {recipient}")
except Exception as e:
print(f"发送失败: {e}")
finally:
server.quit()
# 使用示例
send_email_with_attachment(
sender="[email protected]",
password="your_password",
recipient="[email protected]",
subject="报告附件",
body="请查收附件中的报告。",
attachment_path="report.pdf"
)4.4 批量发送邮件
import time
from typing import List, Dict
def batch_send_emails(sender, password, recipients_data, smtp_server='smtp.gmail.com',
delay=2):
"""批量发送邮件
参数:
recipients_data: 包含收件人信息的字典列表
[{'email': '[email protected]', 'name': '张三', 'content': '...'}, ...]
"""
success_count = 0
fail_count = 0
for i, recipient in enumerate(recipients_data, 1):
print(f"\n发送第 {i}/{len(recipients_data)} 封邮件...")
# 个性化内容
subject = f"致 {recipient['name']} 的重要通知"
body = f"""
尊敬的 {recipient['name']}:
{recipient['content']}
祝好!
""".strip()
try:
send_email(
sender=sender,
password=password,
recipient=recipient['email'],
subject=subject,
body=body,
smtp_server=smtp_server
)
success_count += 1
# 添加延迟避免被识别为垃圾邮件
if i < len(recipients_data):
time.sleep(delay)
except Exception as e:
print(f"发送给 {recipient['email']} 失败: {e}")
fail_count += 1
print(f"\n批量发送完成!成功: {success_count}, 失败: {fail_count}")
# 使用示例
recipients = [
{
'email': '[email protected]',
'name': '张三',
'content': '您的账户已激活,请登录查看详细信息。'
},
{
'email': '[email protected]',
'name': '李四',
'content': '您的申请已通过审核。'
}
]
batch_send_emails(
sender="[email protected]",
password="your_password",
recipients_data=recipients
)下面的序列图展示了邮件发送的完整流程:
sequenceDiagram participant Sender as 发送者程序 participant SMTP as SMTP服务器 participant Recipient as 收件人邮箱 Note over Sender,SMTP: 1. 建立连接 Sender->>SMTP: 连接SMTP服务器<br/>(端口587) SMTP-->>Sender: 连接成功 Note over Sender,SMTP: 2. 启动加密 Sender->>SMTP: STARTTLS命令 SMTP-->>Sender: 准备加密 Sender->>SMTP: TLS握手 SMTP-->>Sender: 加密通道建立 Note over Sender,SMTP: 3. 身份认证 Sender->>SMTP: LOGIN命令<br/>(用户名和密码) SMTP-->>Sender: 认证成功 Note over Sender,SMTP: 4. 发送邮件 Sender->>SMTP: MAIL FROM命令<br/>(发件人地址) SMTP-->>Sender: 250 OK Sender->>SMTP: RCPT TO命令<br/>(收件人地址) SMTP-->>Sender: 250 OK Sender->>SMTP: DATA命令<br/>(邮件内容) SMTP-->>Sender: 354 End data with <CR><LF>.<CR><LF> Sender->>SMTP: 邮件头和正文<br/>以.结束 SMTP-->>Sender: 250 OK: 邮件接受 Note over SMTP,Recipient: 5. 投递邮件 SMTP->>Recipient: 投递到收件人邮箱 SMTP-->>Sender: 邮件发送成功 Note over Sender,SMTP: 6. 关闭连接 Sender->>SMTP: QUIT命令 SMTP-->>Sender: 221 Bye
图表讲解:这个序列图展示了使用SMTP协议发送邮件的完整过程,理解这个过程有助于排查邮件发送问题。
建立连接阶段(绿色区域):发送者程序连接到SMTP服务器的587端口(邮件提交端口)。现代邮件服务器要求使用TLS加密,587端口用于带TLS的邮件提交。连接成功后,可以开始发送命令。
启动加密阶段(蓝色区域):发送STARTTLS命令,请求升级为加密连接。服务器响应准备进行TLS加密,然后进行TLS握手,建立加密通道。之后的所有通信都经过加密,保护邮件内容和认证信息的安全。
身份认证阶段(粉色区域):发送LOGIN命令(或其他认证命令),提供用户名和密码。注意这里应该使用应用专用密码而不是账户密码,特别是在使用Gmail等服务时。认证成功后,服务器允许发送邮件。
发送邮件阶段(黄色区域):首先用MAIL FROM命令声明发件人地址,服务器确认后返回250 OK。然后用RCPT TO命令声明收件人地址,可以有多个收件人。接着用DATA命令开始发送邮件内容,服务器响应354表示等待接收数据。发送者发送完整的邮件头和正文,以单独的一行.表示结束。服务器确认收到邮件。
投递邮件阶段(紫色区域):SMTP服务器将邮件投递到收件人的邮箱服务器。这个过程可能涉及多个服务器跳转,但发送者不需要关心。投递成功后,向发送者确认发送完成。
关闭连接阶段(红色区域):发送QUIT命令结束会话,服务器响应221 Bye后关闭连接。
理解这个流程,可以诊断邮件发送失败的原因:连接失败可能是网络问题或服务器地址错误,认证失败可能是密码错误,DATA阶段失败可能是邮件格式问题。根据错误发生在哪个阶段,可以快速定位问题。
五、实战案例:自动化报表生成系统
5.1 项目概述
构建一个完整的办公自动化系统,能够:
- 从数据源读取数据
- 生成Excel报表
- 创建Word分析报告
- 发送邮件通知
5.2 完整实现
import json
from datetime import datetime
from pathlib import Path
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill
from openpyxl.chart import BarChart, Reference
from docx import Document
from docx.shared import Pt
import smtplib
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
class ReportGenerator:
"""自动化报表生成系统"""
def __init__(self, config_file='config.json'):
"""加载配置"""
self.config = self._load_config(config_file)
self.output_dir = Path(self.config.get('output_dir', 'reports'))
self.output_dir.mkdir(exist_ok=True)
self.timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
def _load_config(self, config_file):
"""加载配置文件"""
try:
with open(config_file, 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
return {
'output_dir': 'reports',
'email': {
'sender': '[email protected]',
'password': 'your_password',
'recipients': ['[email protected]']
}
}
def generate_excel_report(self, data):
"""生成Excel报表"""
wb = Workbook()
ws = wb.active
ws.title = "销售数据"
# 写入数据
headers = list(data[0].keys())
ws.append(headers)
for item in data:
ws.append(list(item.values()))
# 设置样式
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
header_font = Font(bold=True, color='FFFFFF')
for cell in ws[1]:
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal='center')
# 调整列宽
for col in ws.columns:
max_length = 0
column = col[0].column_letter
for cell in col:
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass
adjusted_width = min(max_length + 2, 50)
ws.column_dimensions[column].width = adjusted_width
# 保存文件
filepath = self.output_dir / f"sales_report_{self.timestamp}.xlsx"
wb.save(filepath)
return filepath
def generate_word_report(self, data, excel_path):
"""生成Word分析报告"""
doc = Document()
# 标题
title = doc.add_heading('销售数据分析报告', level=0)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
# 概述
doc.add_paragraph(f'报告生成时间:{datetime.now().strftime("%Y-%m-%d %H:%M")}')
# 数据概览
doc.add_heading('一、数据概览', level=1)
total_sales = sum(item['销售额'] for item in data)
total_quantity = sum(item['数量'] for item in data)
doc.add_paragraph(f'总销售额:¥{total_sales:,.2f}')
doc.add_paragraph(f'总销售量:{total_quantity:,}')
doc.add_paragraph(f'产品种类:{len(data)}')
# 详细数据
doc.add_heading('二、详细数据', level=1)
table = doc.add_table(rows=1, cols=len(data[0]) + 1)
table.style = 'Light Grid Accent 1'
# 表头
header_cells = table.rows[0].cells
headers = ['排名'] + list(data[0].keys())
for i, header in enumerate(headers):
header_cells[i].text = header
# 数据行(按销售额排序)
sorted_data = sorted(data, key=lambda x: x['销售额'], reverse=True)
for rank, item in enumerate(sorted_data, 1):
row_cells = table.add_row().cells
row_cells[0].text = str(rank)
for i, value in enumerate(item.values()):
row_cells[i + 1].text = str(value)
# 保存文件
filepath = self.output_dir / f"analysis_report_{self.timestamp}.docx"
doc.save(filepath)
return filepath
def send_report_email(self, excel_path, word_path):
"""发送报表邮件"""
msg = MIMEMultipart()
msg['From'] = self.config['email']['sender']
msg['To'] = ', '.join(self.config['email']['recipients'])
msg['Subject'] = f'销售数据报表 - {datetime.now().strftime("%Y-%m-%d")}'
# 邮件正文
body = f"""
各位领导:
附件是本期的销售数据报表,包含:
1. Excel原始数据文件
2. Word分析报告
请查收。
此邮件由系统自动发送。
""".strip()
msg.attach(MIMEText(body, 'plain'))
# 添加附件
for filepath in [excel_path, word_path]:
with open(filepath, 'rb') as f:
part = MIMEApplication(f.read())
part.add_header('Content-Disposition', 'attachment',
filename=Path(filepath).name)
msg.attach(part)
# 发送邮件
try:
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(self.config['email']['sender'],
self.config['email']['password'])
server.send_message(msg)
print("报表邮件已发送")
return True
except Exception as e:
print(f"邮件发送失败: {e}")
return False
finally:
server.quit()
def generate_full_report(self, data):
"""生成完整报表"""
print("开始生成报表...")
# 生成Excel
print("生成Excel报表...")
excel_path = self.generate_excel_report(data)
# 生成Word
print("生成Word报告...")
word_path = self.generate_word_report(data, excel_path)
# 发送邮件
print("发送邮件通知...")
success = self.send_report_email(excel_path, word_path)
print(f"\n报表生成完成!")
print(f"Excel: {excel_path}")
print(f"Word: {word_path}")
print(f"邮件发送: {'成功' if success else '失败'}")
return {
'excel': str(excel_path),
'word': str(word_path),
'email_sent': success
}
# 使用示例
if __name__ == "__main__":
# 模拟销售数据
sales_data = [
{'产品': '产品A', '数量': 150, '单价': 100, '销售额': 15000},
{'产品': '产品B', '数量': 200, '单价': 80, '销售额': 16000},
{'产品': '产品C', '数量': 120, '单价': 120, '销售额': 14400},
{'产品': '产品D', '数量': 180, '单价': 90, '销售额': 16200},
{'产品': '产品E', '数量': 90, '单价': 150, '销售额': 13500},
]
# 生成报表
generator = ReportGenerator()
result = generator.generate_full_report(sales_data)5.3 定时任务集成
import schedule
import time
def daily_report_job():
"""每日定时生成报表"""
print(f"\n{'='*50}")
print(f"开始执行定时任务: {datetime.now()}")
print(f"{'='*50}\n")
# 这里可以从数据库或API获取实时数据
sales_data = get_sales_data_from_db()
# 生成报表
generator = ReportGenerator()
result = generator.generate_full_report(sales_data)
print(f"\n任务完成: {datetime.now()}")
def get_sales_data_from_db():
"""从数据库获取销售数据(示例)"""
# 实际应用中连接数据库获取数据
return [
{'产品': '产品A', '数量': 150, '单价': 100, '销售额': 15000},
{'产品': '产品B', '数量': 200, '单价': 80, '销售额': 16000},
]
# 设置定时任务
schedule.every().day.at("09:00").do(daily_report_job)
# 或者每X小时执行一次
# schedule.every(6).hours.do(daily_report_job)
# 运行定时任务
if __name__ == "__main__":
print("定时任务调度器已启动...")
print("按Ctrl+C退出")
try:
while True:
schedule.run_pending()
time.sleep(60) # 每分钟检查一次
except KeyboardInterrupt:
print("\n调度器已停止")下面的流程图展示了自动化报表系统的完整工作流程:
flowchart TD A[定时任务触发] --> B[获取数据源] B --> C{数据来源?} C -->|数据库| D[查询数据库] C -->|API| E[调用API接口] C -->|文件| F[读取数据文件] D --> G[数据验证] E --> G F --> G G --> H{数据有效?} H -->|否| I[记录错误] H -->|是| J[生成Excel报表] I --> Z[任务结束] J --> K[设置格式和公式] K --> L[创建图表] L --> M[保存Excel文件] M --> N[生成Word报告] N --> O[写入概述] O --> P[添加数据表格] P --> Q[添加分析内容] Q --> R[保存Word文件] R --> S[准备邮件] S --> T[添加附件] T --> U[发送邮件] U --> V{发送成功?} V -->|是| W[记录成功] V -->|否| X[记录失败] W --> Y[清理临时文件] X --> Y Y --> Z style A fill:#e1f5e1 style Z fill:#ffe1e1 style H fill:#fff5e1 style V fill:#fff5e1 style J fill:#e1f5ff style N fill:#e1f5ff style U fill:#e1f5ff
图表讲解:这个流程图展示了自动化报表系统的端到端流程,包含数据获取、报表生成、邮件发送等完整环节。
系统由定时任务触发(绿色区域):使用schedule库或其他调度工具,在指定时间(如每天早上9点)启动报表生成流程。
数据获取阶段(黄色决策):从不同来源获取数据——数据库(SQL查询)、API接口(HTTP请求)、文件(CSV/Excel读取)。无论来源如何,都需要进行数据验证(黄色决策),检查数据完整性、格式正确性、值域合理性等。如果数据无效,记录错误并结束(红色区域)。
生成Excel报表(蓝色区域):这是第一部分输出。创建工作簿,写入数据,设置格式(标题、列宽、颜色、边框),添加公式和图表,最后保存Excel文件。Excel适合展示原始数据和计算结果。
生成Word报告(绿色区域):这是第二部分输出。创建文档,添加标题、概述章节、详细数据表格、分析内容。Word适合展示文字分析、解释说明、结论建议等。
准备和发送邮件(粉色区域):创建邮件对象,设置收件人、主题、正文,添加Excel和Word文件作为附件。然后通过SMTP服务器发送邮件。发送可能成功或失败(黄色决策),记录结果。
最后(蓝色区域):清理临时文件(如果有),释放资源,任务结束(红色区域)。
这个流程展示了办公自动化的完整模式:数据获取→处理→生成输出→通知发送。理解这个模式,可以构建各种自动化系统,如财务报表、销售分析、项目报告等,将重复性劳动转化为自动化任务。
六、核心概念总结
| 概念 | 定义 | 应用场景 | 注意事项 |
|---|---|---|---|
| openpyxl | Excel操作库 | 读写.xlsx文件 | 不支持.xls旧格式 |
| python-docx | Word操作库 | 生成.docx文档 | 不支持.doc旧格式 |
| PyPDF2 | PDF操作库 | 合并、拆分PDF | 文本提取可能不完整 |
| smtplib | 邮件发送库 | SMTP邮件发送 | 需要应用专用密码 |
| MIME | 邮件格式标准 | 多媒体邮件内容 | HTML和纯文本都要提供 |
| schedule | 任务调度库 | 定时任务执行 | 程序需持续运行 |
常见问题解答
Q1:如何处理大型Excel文件,避免内存不足?
答:处理大型Excel文件需要使用流式读取或分批处理的方式。
openpyxl默认会加载整个文件到内存,对于大文件(如几十MB)可能导致内存不足。解决方案是使用read_only模式进行只读访问,或使用write_only模式进行写入操作。
from openpyxl import load_workbook
# 只读模式(节省内存)
wb = load_workbook('large_file.xlsx', read_only=True)
ws = wb.active
# 逐行读取
for row in ws.iter_rows(values_only=True):
# 处理每一行
process_row(row)
wb.close()
# 写入大型文件
from openpyxl import Workbook
wb = Workbook(write_only=True)
ws = wb.create_sheet()
# 逐行写入
for data in large_data_source:
ws.append(data)
wb.save('large_output.xlsx')对于更大的文件,考虑使用pandas的chunksize参数或专门的库如xlrd(只读)和xlsxwriter(只写)。这些库针对大文件进行了优化,内存效率更高。
另一个策略是数据分片:将大文件拆分为多个小文件分别处理,最后合并结果。这虽然增加了处理步骤,但可以避免内存问题。
Q2:如何设置复杂的Word文档样式?
答:复杂Word样式需要理解Run和Paragraph对象的样式层次结构。
Word文档的样式分几个层次:段落样式影响整个段落,字符样式影响文本片段。在python-docx中,段落是Paragraph对象,文本片段是Run对象,每个Run可以有独立的字体样式。
from docx import Document
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
doc = Document()
# 创建段落并添加多个Run(不同样式)
para = doc.add_paragraph()
# Run 1: 粗体标题
run1 = para.add_run('重要通知:')
run1.bold = True
run1.font.size = Pt(14)
run1.font.color.rgb = RGBColor(255, 0, 0)
# Run 2: 普通文本
run2 = para.add_run('这是一条重要信息。')
run2.font.size = Pt(12)
# Run 3: 斜体强调
run3 = para.add_run('请注意!')
run3.italic = True
run3.font.underline = True
# 段落整体样式
para.alignment = WD_ALIGN_PARAGRAPH.LEFT
para.paragraph_format.first_line_indent = Inches(0.5)
para.paragraph_format.line_spacing = 1.5更复杂的样式可以通过定义Style对象实现,然后在多个段落中复用。也可以使用模板文档,预先定义好样式,Python只需填充内容。
Q3:PDF文本提取不完整怎么办?有什么替代方案?
答:PyPDF2的文本提取功能有限,对复杂PDF效果不佳。
PyPDF2的extract_text()方法适用于简单文本PDF,但对于扫描版PDF、图像PDF、复杂布局的PDF效果很差。替代方案包括:
PDFMiner是更强大的PDF解析库,能处理复杂布局和编码:
from pdfminer.high_level import extract_text
text = extract_text('complex.pdf')
print(text)对于扫描版PDF(图像),需要OCR技术。使用pytesseract调用Tesseract引擎:
import pytesseract
from PIL import Image
from pdf2image import convert_from_path
# PDF转图像
pages = convert_from_path('scanned.pdf')
# OCR识别
for i, page in enumerate(pages):
text = pytesseract.image_to_string(page)
print(f"Page {i+1}: {text}")商业PDF库(如pdftron、Aspose)提供更强的功能,但需要付费。
最佳实践是根据PDF类型选择工具:文本PDF用PDFMiner,扫描PDF用OCR,如果预算充足考虑商业库。
Q4:邮件发送被拒绝或进入垃圾邮件怎么办?
答:邮件发送失败通常涉及认证、内容、发送频率等问题。
常见问题及解决方案:
-
认证失败:使用应用专用密码而不是账户密码。Gmail等服务需要生成应用专用密码,用于第三方程序登录。
-
被识别为垃圾邮件:避免触发垃圾邮件过滤规则。使用合法的发件人地址,提供真实的退订链接,控制发送频率,避免重复发送相同内容。
-
连接超时:检查SMTP服务器地址和端口。不同服务商的配置不同,如Gmail是
smtp.gmail.com:587,QQ邮箱是smtp.qq.com:587。 -
内容被拒:避免使用推销性语言、过多的感叹号和链接。HTML邮件应该同时提供纯文本版本。
# 提高邮件送达率的技巧
msg = MIMEMultipart('alternative')
msg['From'] = '[email protected]' # 使用正式域名
msg['Reply-To'] = '[email protected]' # 提供回复地址
# 添加纯文本版本
text_part = MIMEText('纯文本内容', 'plain')
html_part = MIMEText('<p>HTML内容</p>', 'html')
msg.attach(text_part)
msg.attach(html_part)
# 添加取消订阅链接
unsubscribe_link = "https://yourdomain.com/unsubscribe"
html_content += f'<br><a href="{unsubscribe_link}">取消订阅</a>'大量邮件发送建议使用专业邮件服务(SendGrid、Mailgun),它们有更好的送达率和反垃圾邮件机制。
Q5:如何安全地存储和管理邮件密码等敏感信息?
答:敏感信息绝不能硬编码在代码中,应该使用安全的存储方案。
最基本的方法是使用环境变量:
import os
from dotenv import load_dotenv
# 加载.env文件
load_dotenv()
EMAIL_PASSWORD = os.getenv('EMAIL_PASSWORD')更好的方案是使用密钥管理系统:
import keyring
# 存储密码(只需运行一次)
keyring.set_password('email_system', '[email protected]', 'app_password')
# 读取密码
password = keyring.get_password('email_system', '[email protected]')对于生产环境,使用云密钥管理服务(AWS KMS、Azure Key Vault、Google Secret Manager)。这些服务提供加密存储、访问控制、审计日志等企业级功能。
配置文件应该加密存储,使用时解密:
import cryptography.fernet
def encrypt_config(config, key):
"""加密配置"""
f = cryptography.fernet.Fernet(key)
encrypted = f.encrypt(json.dumps(config).encode())
return encrypted
def decrypt_config(encrypted, key):
"""解密配置"""
f = cryptography.fernet.Fernet(key)
decrypted = f.decrypt(encrypted)
return json.loads(decrypted.decode())最佳实践:永远不要将密码提交到版本控制系统(添加到.gitignore),使用不同的开发和生产环境配置,定期轮换密钥,限制密钥的访问权限。
总结
本文全面介绍了办公文档自动化处理的核心技术。我们学习了Excel文件的读写和格式设置、Word文档的创建和编辑、PDF文件的合并与拆分、邮件的批量发送,以及构建完整的自动化报表系统。
办公自动化是提高工作效率的有效手段,将重复性的文档处理工作转化为自动化任务,可以节省大量时间。掌握这些技术,可以自动化生成报表、批量处理文档、定时发送通知,让办公工作更加高效。
下篇预告
下一篇是本系列的最后一篇,我们将深入探讨定时任务与系统自动化,带你了解任务调度、进程控制、图像识别、键盘鼠标自动化等高级技术。你将学会构建完整的自动化系统,真正实现解放重复性劳动的目标。