[Python] 自動化辦公 郵件/Excel/Word交互快速生成介紹信

轉載請注明:陳熹 chenx6542@foxmail.com (簡書號:半為花間酒)
若公眾號內轉載請聯系公眾號:早起Python

這篇文章能學到的主要內容:

  1. imbox 讀取郵件解析附件
  2. openpyxlpython-docx 對文件的交互操作

https://pan.baidu.com/s/1QKXBIPvOEGurpqbe4KiGTA 提取碼:j2xp

一、需求描述

你在某三家醫(yī)院的醫(yī)務處工作,之前已經發(fā)通知讓醫(yī)生們申請外派 A 醫(yī)院進修,表格 申請.xlsx 如下:

你需要根據他們的申請表開出相應的介紹信:

每個人會單獨自己填寫好的表格以 “進修申請 xxx” 的郵件標題發(fā)到你的郵箱。申請截止日期到了,你打開郵件發(fā)現有 300 多人申請,而你覺得從郵件中下載附件,打開 Excel 文件并把對應信息填寫到 Word,再修改介紹信文件名為 “xxx 進修介紹信” 實在過于繁瑣,你希望借助 Python 自動化高效完成上述任務

二、邏輯梳理

這次的真實需求實際上和之前的推文 批量生成多份合同:http://www.lxweimin.com/p/3ee47f594d81 非常類似,不同之處在于需要配合郵件相關的工具完成整個需求。本需求同樣繞不開一個問題:程序如何知道要將某個信息填到何處?為了解決這個問題,我們需要對模板 介紹信.docx 進行修改,即將需要填寫的地方改成某種標識,讓程序可以“看到標識就明白此處應該放什么信息”

采取的策略是:將需要填寫的地方改成表中的列名,即:

這樣程序通過文本識別就能夠定位相應信息并完成替換

本需求完整的邏輯包括:

  1. 遍歷所有郵件,將標題符合要求的郵件附件下載到指定文件夾中
  2. 遍歷打開文件夾下的所有 Excel 文件
  3. 獲取每個 Excel 表格中的信息,填寫至 Word 模板中
  4. 保存文件到新文件夾中

三、代碼實現

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')

整個復雜的需求就被瓦解成多個問題而成功解決!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容