天天看點

Python+Excel+Word一秒制作百份合同

前言

大家好,又到了Python辦公自動化系列。

今天我們繼續分享一個真實的辦公自動化需求:如何使

Python+Excel+Word

批量生成指定格式内容的合同。

主要涉及的知識點有:

openpyxl

子產品的綜合運用與Word文檔的兩種周遊邏輯。

需求描述

你是乙方建築公司,手上有一份空白合同模闆的

Word

檔案,如下圖:

Python+Excel+Word一秒制作百份合同
Python+Excel+Word一秒制作百份合同

另外還有一份

Excel

合同資訊表,其中是所有甲方(發包人)在合同中需要填寫的内容

Python+Excel+Word一秒制作百份合同
Python+Excel+Word一秒制作百份合同

可見一行為一個公司的全部資訊,現在需要把Excel中每一個公司的資訊填入空白Word合同模闆中,生成各公司的合同,最終結果如下

Python+Excel+Word一秒制作百份合同
Python+Excel+Word一秒制作百份合同

步驟分析

原本我們需要将

Excel

彙總表中每一行的資訊填進word模闆中,生成相應的合同。

現在我們需要交給

Python

來實作,就引出了一個問題:程式如何知道要将某個資訊填到哪個下劃線? 為了解決這個問題,我們需要對模闆進行修改。

即将下劃線改成某種辨別,讓程式可以看到辨別就明白此處應該放什麼資訊,這裡采取的政策是:将需要填寫的下劃線改成彙總表中的列名,即下圖所示

Python+Excel+Word一秒制作百份合同

這樣程式就可以識别需要填寫什麼内容了。所謂的識别在這裡可以換一個特别簡單的詞,即文本替換。隻要檢索到

#xxxx#

(excel中的列名),把這個替換成具體的資訊就可以了。

出于這種政策,列名就需要用

#xxxx#

的格式,否則正常的無關文本中的資訊也會被替換,就破壞了原有的需求,最後模闆被修改成如下:

Python+Excel+Word一秒制作百份合同

通過

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]
           

複制

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

Python+Excel+Word一秒制作百份合同

例如程式已經進入第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

擷取到文字資訊。本需求隐含的陷阱就在這裡,注意一下合同最後需要填寫的内容:

Python+Excel+Word一秒制作百份合同

這部分内容如果用上述代碼是周遊不到的。為什麼?因為這是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中的每一個單獨資訊(每一行或者每一列為個人、公司或者其他的資訊)填寫到指定的模闆Word中,生成單獨的文檔,不過在寫自動化腳本之前也要先拆分任務,明确思路再進行!