轉載請注明:陳熹 chenx6542@foxmail.com (簡書號:半為花間酒)
若公眾號內轉載請聯系公眾號:早起Python
這篇文章能學到的主要內容:
imbox
讀取郵件解析附件openpyxl
和python-docx
對文件的交互操作
一、需求描述
你在某三家醫(yī)院的醫(yī)務處工作,之前已經發(fā)通知讓醫(yī)生們申請外派 A 醫(yī)院進修,表格 申請.xlsx
如下:
你需要根據他們的申請表開出相應的介紹信:
每個人會單獨自己填寫好的表格以 “進修申請 xxx” 的郵件標題發(fā)到你的郵箱。申請截止日期到了,你打開郵件發(fā)現有 300 多人申請,而你覺得從郵件中下載附件,打開 Excel 文件并把對應信息填寫到 Word,再修改介紹信文件名為 “xxx 進修介紹信” 實在過于繁瑣,你希望借助 Python 自動化高效完成上述任務
二、邏輯梳理
這次的真實需求實際上和之前的推文 批量生成多份合同:http://www.lxweimin.com/p/3ee47f594d81 非常類似,不同之處在于需要配合郵件相關的工具完成整個需求。本需求同樣繞不開一個問題:程序如何知道要將某個信息填到何處?為了解決這個問題,我們需要對模板 介紹信.docx
進行修改,即將需要填寫的地方改成某種標識,讓程序可以“看到標識就明白此處應該放什么信息”
采取的策略是:將需要填寫的地方改成表中的列名,即:
這樣程序通過文本識別就能夠定位相應信息并完成替換
本需求完整的邏輯包括:
- 遍歷所有郵件,將標題符合要求的郵件附件下載到指定文件夾中
- 遍歷打開文件夾下的所有 Excel 文件
- 獲取每個 Excel 表格中的信息,填寫至 Word 模板中
- 保存文件到新文件夾中
三、代碼實現
1. 解析郵件
首先完成第一部分的工作,讀取全部郵件:
import keyring
from imbox import Imbox
利用 keyring
庫,通過系統密鑰環(huán)將密碼(授權碼)預先在本地存儲好,后面在代碼中調用 keyring
庫的方法,通過賬號把密碼取出來作為變量,降低了密碼(授權碼)泄露的幾率
通過 imbox
庫獲取附件:
password = keyring.get_password("yagmail","xxx@163.com")
with Imbox('imap.163.com', 'xxx@163.com', password) as imbox:
all_inbox_messages = imbox.messages()
for uid,message in all_inbox_messages:
print(message.attachments)
從需求中我們知道,特定的郵件是以 進修申請 四個字開頭的,那么就可以以此為依據作為判斷,獲取特定郵件的附件:
password = keyring.get_password("yagmail","xxx@163.com")
with Imbox('imap.163.com', 'xxx@163.com', password) as imbox:
all_inbox_messages = imbox.messages()
for uid, message in all_inbox_messages:
if message.subject[:4] == '進修申請':
pass
pass
代碼就可以寫附件存儲了。需要把 Excel 文件存儲到指定文件夾中,因此需要先利用 os
庫建立文件夾。郵件部分的代碼如下:
import keyring
from imbox import Imbox
import os
path = r'C:\xxx'
if not os.path.exists(path + r'\申請表文件夾'):
os.mkdir(path + r'\申請表文件夾')
password = keyring.get_password("yagmail","xxx@163.com")
with Imbox('imap.163.com', 'xxx@163.com', password) as imbox:
all_inbox_messages = imbox.messages()
for uid, message in all_inbox_messages:
if message.subject[:4] == '進修申請':
if message.attachments: # 判斷是否存在附件
for attachment in message.attachments:
with open(path + f'\申請表文件夾\\{attachment["filename"]}', 'wb') as file:
file.write(attachment['content'].getvalue())
2. Excel 和 Word 文件交互
接下來的操作涉及 Excel 讀取和 Word 文件的寫入,需要導入相應的模塊。同時建立新文件夾存放最終的介紹信:
from docx import Document
from openpyxl import load_workbook
if not os.path.exists(path + r'\介紹信文件夾'):
os.mkdir(path + r'\介紹信文件夾')
現在 申請表文件夾
中存放 300 多個 Excel 文件,可以利用 glob
庫進行遍歷和讀取:
import glob
for file in glob.glob(path + r'\申請表文件夾\*.xlsx'):
workbook = load_workbook(file)
sheet = workbook.active
有效信息在第二行,列名(文本替換的依據)在第一行。但考慮到有的申請表可能不按常規(guī),填寫了多個人的申請,因此用循環(huán),不局限在第二行:
for file in glob.glob(path + r'\申請表文件夾\*.xlsx'):
workbook = load_workbook(file)
sheet = workbook.active
for table_row in range(2, sheet.max_row + 1): # 考慮到有的申請表可能不按常規(guī),填寫了多個人的申請,因此用循環(huán)
# 每循環(huán)一行實例化一個新的word文件
wordfile = Document(path + r'\新模板.docx')
# 單元格需要逐個遍歷,每一個都包含著有用的信息
for table_col in range(1, sheet.max_column + 1):
# 舊的文本也就是列名,已經在模板里填好了,用于文本替換,將row限定在第一行后就是列名
old_text = '#' + str(sheet.cell(row=1, column=table_col).value) + '#'
# 新的文本就是實際的信息,table_col循環(huán)到某個數值時,實際的單元格和列名就確定了
new_text = str(sheet.cell(row=table_row, column=table_col).value)
獲取到信息以后就可以進行 Word 模板文件的文本替換了,根據其 文檔 Document - 段落 Paragraph - 文字塊 Run的三級結構,在文字塊層面完成替換:
# 文檔Document - 段落Paragraph - 文字塊Run
all_paragraphs = wordfile.paragraphs
for paragraph in all_paragraphs:
for run in paragraph.runs:
run.text = run.text.replace(old_text, new_text)
介紹信的落款日期是當天的日期,可以考慮借助 datetime
庫獲取,并在替換新舊文本時同時判斷 #今天日期#
這個文本是否存在,存在就替換為真實日期:
run.text = run.text.replace(old_text, new_text)
run.text = run.text.replace('#今天日期#', datetime.date.today())
最后保存即可,文件名中的姓名即為當前循環(huán)行的第一個單元格,sheet.cell(row=table_row,column=1).value
完整代碼如下:
import keyring
from imbox import Imbox
from docx import Document
from openpyxl import load_workbook
import os
import glob
import datetime
path = r'C:\xxx'
if not os.path.exists(path + r'\申請表文件夾'):
os.mkdir(path + r'\申請表文件夾')
password = keyring.get_password("yagmail", "xxx@163.com")
with Imbox('imap.163.com', 'xxx@163.com', password) as imbox:
all_inbox_messages = imbox.messages()
for uid, message in all_inbox_messages:
if message.subject[:4] == '進修申請':
if message.attachments:
for attachment in message.attachments:
with open(path + f'\申請表文件夾\\{attachment["filename"]}', 'wb') as file:
file.write(attachment['content'].getvalue())
if not os.path.exists(path + r'\介紹信文件夾'):
os.mkdir(path + r'\介紹信文件夾')
for file in glob.glob(path + r'\申請表文件夾\*.xlsx'):
workbook = load_workbook(file)
sheet = workbook.active
for table_row in range(2, sheet.max_row + 1): # 考慮到有的申請表可能不按常規(guī),填寫了多個人的申請,因此用循環(huán)
# 每循環(huán)一行實例化一個新的word文件
wordfile = Document(path + '\新模板.docx')
# 單元格需要逐個遍歷,每一個都包含著有用的信息
for table_col in range(1, sheet.max_column + 1):
# 舊的文本也就是列名,已經在模板里填好了,用于文本替換,將row限定在第一行后就是列名
old_text = '#' + str(sheet.cell(row=1, column=table_col).value) + '#'
# 新的文本就是實際的信息,table_col循環(huán)到某個數值時,實際的單元格和列名就確定了
new_text = str(sheet.cell(row=table_row, column=table_col).value)
all_paragraphs = wordfile.paragraphs
for paragraph in all_paragraphs:
for run in paragraph.runs:
run.text = run.text.replace(old_text, new_text)
run.text = run.text.replace('#今天日期#', datetime.date.today())
wordfile.save(path + f'\\介紹信文件夾\\{sheet.cell(row=table_row,column=1).value} 進修介紹信.docx')
整個復雜的需求就被瓦解成多個問題而成功解決!