[Python] 自動化辦公 批量生成多份合同

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

本例可以學到的知識點:

  1. openpyxl模塊的綜合運用
  2. Word文檔的兩種遍歷邏輯

練習數據:鏈接:https://pan.baidu.com/s/1QwtuUZwW5kShqqymKY0xNg 提取碼:npxd

一、需求描述:

你是乙方建筑公司,手上有一份 空白合同的Word文件,如下圖:

另外還有一份Excel表格合同信息表,其中是所有甲方(發包人)在合同中需要填寫的內容:

可見一行為一個公司的全部信息,現在需要把相應公司的信息填入空白合同模板中,生成各公司的合同

最終結果如下:

這個需求的實現可以幫助我們解決很多問題,讓我們一起來分析一下

二、步驟分析

原本我們需要將Excel匯總表中每一行的信息填進word模板中,生成相應的合同。現在我們需要交給python來實現,就引出了一個問題:程序如何知道要將某個信息填到哪個下劃線?為了解決這個問題,我們需要對模板進行修改,即將下劃線改成某種標識,讓程序可以“看到標識就明白此處應該放什么信息”

這里采取的策略是:將需要填寫的下劃線改成匯總表中的列名,即

這樣程序就可以識別需要填寫什么內容了。所謂的識別在這里可以換一個特別簡單的詞,即文本替換,只要檢索到#xxxx#(excel中的列名),把這個替換成具體的信息就可以了。出于這種策略,列名就需要用#xxxx#的格式,否則正常的無關文本中的信息也會被替換,就破壞了原有的需求了

最后模板被修改成如下:

通過Excel表我們可以看到,一行為一個公司的信息,而每一列的列名就存在于模板中,用各個公司的實際信息替換到模板中的列名(程序識別和文本替換的依據)就可以完成這個需求。整個大需求的實現可以按照下面的步驟:

分析后的步驟:

  1. 將 空白合同 調整成 合同模板,需要填寫的下劃線改成專屬的列名
  2. 打開Excel表,按行循環,然后按單元格逐個循環各個信息,每個信息都找到模板中存在的對應列名并將其替換(如果不理解下文還有解釋)
  3. 每次循環完一行的全部單元格后保存合同,生存各個公司單獨的合同

分析清楚后邏輯就非常簡單了,但有一個隱含的知識點沒有提到,讓我們邊寫代碼邊說

三、代碼實現

  • 導入模塊,設置路徑,建立文件夾

本例中涉及Excel表的打開和Word的創建,因此需要從openpyxl導入load_workbook,而Word無論打開還是創建,用docx模塊的Document均可

from docx import Document
from openpyxl import load_workbook
# 利用os模塊建立文件夾,用于存放生成的合同
import os

# 給定合同模板和匯總表所在的文件夾路徑,方便復用
path = r'C:\Users\chenx\Desktop\合同'

# 結合路徑判斷生成文件夾,規避程序報錯而終止的風險
if not os.path.exists(path + '/' + '全部合同'):
    os.mkdir(path + '/' + '全部合同')
  • 打開Excel文件
workbook = load_workbook(path + '/' + '合同信息表.xlsx')
sheet = workbook.active
  • 遍歷Excel,生成合同

前面也反復提到,Excel的每一行是一份特定合同的信息,因此docx針對Word文件的實例化和保存一定是在循環體里的,而不像Excel的實例化是在循環體外面

# 有效信息行是從第二行開始的,第二行是表頭,包含列名,也是文本替換的依據
for table_row in range(2, sheet.max_row + 1):
    # 每循環一行實例化一個新的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循環到某個數值時,實際的單元格和列名就確定了
        new_text = str(sheet.cell(row=table_row, column=table_col).value)
        # 加上這個判斷是因為日期信息讀進程序是“日期 時間”格式的,如果要保留日期信息可以用字符串方法或者用time/datetime模塊處理
        if ' ' in new_text:
            new_text = new_text.split()[0]

通過下圖進一步理解這個替換:

例如程序已經進入第3個循環(循環到第3個公司),針對單元格的循環進入第4個循環,那么此時獲取的實際值是“建設C公園”,對應的列名是“#工程內容#”,此時就明確了需要被替換的內容了,那么只要在模板中找到“#工程內容#”把它替換為“建設C公園”即可

了解了這個替換后,下一步要做的就是遍歷Word模板,找到對應列名替換

熟悉docx模塊的讀者應該知道,Word文本存在文檔Document - 段落Paragraph - 文字塊Run的三級結構,需要遍歷文本可以用以下代碼:

all_paragraphs = wordfile.paragraphs
for paragraph in all_paragraphs:
    print(paragraph.text)
    for run in paragraph.runs:
        print(run.text)

針對段落和文字塊均可用.text獲取到文字信息

本需求隱含的陷阱就在這里,注意一下合同最后需要填寫的內容:

這部分內容如果用上述代碼是遍歷不到的。為什么?因為這是Word文檔中的表格,遍歷表格需要有專門的遍歷邏輯:文檔Document - 表格Table - 行Row/列Column - 單元格Cell

遍歷表格中文本的代碼如下:

all_tables = wordfile.tables
for table in all_tables:
    # 也可按列遍歷
    for row in table.rows:
        for cell in row.cells:
            print(cell.text)

有了這些補充的知識我們就可以完成最核心的代碼了:

for table_row in range(2, sheet.max_row + 1):
    wordfile = Document(path + '/' + '合同模板.docx')
    for table_col in range(1, sheet.max_column + 1):
        old_text = str(sheet.cell(row=1, column=table_col).value)
        new_text = str(sheet.cell(row=table_row, column=table_col).value)
        if ' ' in new_text:
            new_text = new_text.split()[0]
        
        # 文檔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)

        # 文檔Document - 表格Table - 行Row/列Column - 單元格Cell
        all_tables = wordfile.tables
        for table in all_tables:
            for row in table.rows:
                for cell in row.cells:
                    cell.text = cell.text.replace(old_text, new_text)

    # 獲取公司名用以生成合同的名稱
    company = str(sheet.cell(row=table_row, column=1).value)
    wordfile.save(path + '/' + f'全部合同/{company}合同.docx')

寫在最后

本次的需求可以延伸成為:將一份信息匯總表Excel中的每一個單獨信息(每一行或者每一列為個人、公司或者其他的信息)填寫到指定的模板Eord中,生成單獨的文檔,因此需要理解內涵
如果你有正在煩惱的辦公需求,歡迎在 早起Python 公眾號后臺留言

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