)
簡介這份資源面向需要批量處理Word文檔的Python學(xué)習(xí)者與辦公自動化從業(yè)者聚焦用python-docx庫把圖片和表格高效寫入.docx文件解決重復(fù)手工排版、逐份生成文檔的痛點適合具備Python基礎(chǔ)、希望提升文檔處理效率的初中級開發(fā)者。壓縮包共12個文件約367KB包含10個docx示例文檔、1個py腳本和1個jpg圖片docx用于展示批量生成的工資調(diào)整通知效果py腳本承載圖片遍歷與表格填充的核心邏輯jpg則作為插入圖片的素材。目前已有181人學(xué)習(xí)下載。讀者可從中獲得可直接運行的批量處理腳本理解Document創(chuàng)建、add_picture插圖、add_table建表與cell填充、doc.save保存等關(guān)鍵用法并借助os庫遍歷目錄的思路把同一套流程遷移到通知、報表、臺賬等場景快速搭建屬于自己的文檔自動化方案。1. 批量把圖片和表格塞進 Word一份能直接跑的 python-docx 腳本包手里攢了三十幾份巡檢記錄每份都要把現(xiàn)場照片和檢測數(shù)據(jù)表貼進對應(yīng)的 Word 模板里手動復(fù)制粘貼到第三份的時候我就知道這事不能這么干。這份資源就是沖著這個場景來的一個基于 python-docx 的批量寫入腳本包把散落在文件夾里的圖片和表格按規(guī)則灌進 .docx省掉重復(fù)勞動。它適合兩類人——一類是天天跟報表、臺賬、檢測記錄打交道的辦公崗另一類是想拿 python-docx 練手但不知道從哪下手的開發(fā)者。核心邏輯不復(fù)雜難的是圖片尺寸、表格樣式、中文字體這些細(xì)節(jié)腳本包里都做了預(yù)設(shè)改幾個路徑就能跑。2. 拆開腳本包python-docx 寫入圖片和表格的底層邏輯2.1 為什么選 python-docx 而不是直接操作 XMLWord 的 .docx 本質(zhì)是個 zip 包里面是一堆 XML。理論上你可以用 zipfile 解壓、改 document.xml、再壓回去但這條路走一次就不想走第二次——命名空間、關(guān)系映射、rId 分配任何一處對不上Word 打開就報“內(nèi)容有問題”。python-docx 把這層封裝掉了它把 document.xml 里的段落、表格、圖片關(guān)系都抽象成 Python 對象你調(diào) add_picture、add_table 就行。常見做法是用 python-docx 做主體寫入遇到它不支持的冷門樣式比如某些域代碼、復(fù)雜頁眉再退回 lxml 操作底層 XML。這份腳本包走的就是這個路線主體用 python-docx個別地方用 OxmlElement 補了邊框和底紋。選型上還有一個考慮是依賴輕。python-docx 只依賴 lxml 和 typing-extensionspip 裝完就能用不需要裝 Office也不需要 COM 接口。這意味著它能在 Linux 服務(wù)器上跑也能塞進定時任務(wù)里。如果你之前用 win32com 操作 Word應(yīng)該體會過那種“必須開著 Word 進程、偶爾卡死、報錯信息看不懂”的滋味python-docx 在這點上干凈得多。2.2 圖片寫入尺寸、對齊和路徑的三個參數(shù)先看圖片寫入的核心代碼。腳本包里把這段封裝成了insert_images(doc, img_dir, width_cm12)from docx import Document from docx.shared import Cm from docx.enum.text import WD_ALIGN_PARAGRAPH import os def insert_images(doc, img_dir, width_cm12): 把 img_dir 下的圖片按文件名排序后逐張插入 doc width_cm: 圖片寬度單位厘米高度按比例自動縮放 # 只取常見圖片格式避免把縮略圖緩存也讀進來 exts (.png, .jpg, .jpeg, .bmp) files sorted( f for f in os.listdir(img_dir) if f.lower().endswith(exts) ) for fname in files: path os.path.join(img_dir, fname) # 先加一個空段落再往段落里加圖片方便控制對齊 para doc.add_paragraph() para.alignment WD_ALIGN_PARAGRAPH.CENTER run para.add_run() # 只給寬度高度自動等比縮放避免圖片被拉變形 run.add_picture(path, widthCm(width_cm)) # 圖片下方加一行文件名做標(biāo)注不需要可刪 cap doc.add_paragraph(fname) cap.alignment WD_ALIGN_PARAGRAPH.CENTER邏輯說明add_picture只傳 width 的時候python-docx 會按圖片原始寬高比自動算高度這是最穩(wěn)的做法。如果你同時傳 width 和 height圖片會被強行拉伸人像和表格截圖會變形。參數(shù)width_cm默認(rèn) 12 厘米對應(yīng) A4 紙正文寬度約 15.9 厘米留出邊距后的常見值你可以按模板調(diào)整。路徑處理上有個細(xì)節(jié)os.listdir返回的順序在不同系統(tǒng)上不一致所以腳本里加了sorted。如果你的圖片文件名是img1.jpg、img2.jpg、img10.jpg這種字符串排序會把 img10 排到 img2 前面這時候需要改成按數(shù)字排序import re files.sort(keylambda x: int(re.search(r\d, x).group()))這個坑我在第一次批量插巡檢照片的時候就踩了十張照片順序全亂后來加了自然排序才對上。2.3 表格寫入從列表數(shù)據(jù)到帶樣式的 Word 表格表格寫入比圖片多一層結(jié)構(gòu)。腳本包里insert_table(doc, data, headersNone, styleTable Grid)的實現(xiàn)def insert_table(doc, data, headersNone, styleTable Grid): data: 二維列表如 [[A, 1], [B, 2]] headers: 表頭列表為 None 時不生成表頭行 style: Word 內(nèi)置表格樣式名Table Grid 帶全邊框 rows len(data) (1 if headers else 0) cols len(headers) if headers else len(data[0]) table doc.add_table(rowsrows, colscols) table.style style if headers: for j, h in enumerate(headers): cell table.cell(0, j) cell.text str(h) # 表頭加粗 for p in cell.paragraphs: for r in p.runs: r.bold True offset 1 if headers else 0 for i, row in enumerate(data): for j, val in enumerate(row): table.cell(i offset, j).text str(val) return table邏輯說明add_table創(chuàng)建的是空表格行列數(shù)必須提前算好。styleTable Grid是 Word 內(nèi)置樣式名帶全邊框最常用。如果你想要三線表得手動設(shè)邊框python-docx 沒有直接的三線表樣式需要用 OxmlElement 改 tblBorders腳本包里附了這段代碼。參數(shù)上要注意data的每一行長度必須一致否則len(data[0])算出來的列數(shù)會對不上寫入時索引越界。我一般會在調(diào)用前加一句校驗assert len(set(len(r) for r in data)) 1, 每行列數(shù)不一致中文字體是另一個高頻問題。python-docx 默認(rèn)字體是 Calibri中文會回退到宋體但如果你在模板里指定了字體寫入的表格可能不繼承。穩(wěn)妥做法是顯式設(shè)置from docx.oxml.ns import qn style doc.styles[Normal] style.font.name Times New Roman style.element.rPr.rFonts.set(qn(w:eastAsia), 宋體)這段要放在創(chuàng)建文檔之后、寫入內(nèi)容之前否則已經(jīng)寫入的段落不會更新。2.4 批量循環(huán)一份數(shù)據(jù)對應(yīng)一份文檔的目錄約定腳本包的主入口是batch_generate(template_path, data_root, out_dir)目錄約定是這樣的data_root/ record_001/ images/ # 該記錄的圖片 table.csv # 該記錄的表格數(shù)據(jù) record_002/ ...主循環(huán)代碼import csv from docx import Document def batch_generate(template_path, data_root, out_dir): os.makedirs(out_dir, exist_okTrue) for case in sorted(os.listdir(data_root)): case_dir os.path.join(data_root, case) if not os.path.isdir(case_dir): continue # 每份文檔都從模板復(fù)制一份保留頁眉頁腳和樣式 doc Document(template_path) img_dir os.path.join(case_dir, images) if os.path.isdir(img_dir): insert_images(doc, img_dir) csv_path os.path.join(case_dir, table.csv) if os.path.exists(csv_path): with open(csv_path, encodingutf-8-sig) as f: reader list(csv.reader(f)) insert_table(doc, reader[1:], headersreader[0]) doc.save(os.path.join(out_dir, f{case}.docx))邏輯說明Document(template_path)是打開模板而不是新建空文檔這樣模板里的頁眉、頁腳、樣式、頁面設(shè)置都會保留。encodingutf-8-sig是為了兼容 Excel 導(dǎo)出的 CSV 帶 BOM 頭的情況不加這個參數(shù)第一列會多一個不可見字符。參數(shù)out_dir建議和data_root分開避免輸出文件被下一輪循環(huán)當(dāng)成輸入。如果你要覆蓋原文件記得先備份。3. 避坑排查圖片不顯示、表格錯位、字體亂掉的五個血淚經(jīng)驗3.1 圖片插入后 Word 顯示紅叉或空白現(xiàn)象腳本跑完沒報錯打開 docx 圖片位置是紅叉或空白框。原因python-docx 插入圖片時是把圖片二進制寫進 docx 包里的正常不會丟。出現(xiàn)紅叉通常是圖片本身損壞或者擴展名和實際格式不符——比如把 webp 改成 jpg 后綴python-docx 按 jpg 解析失敗但沒拋異常。解決插入前用 PIL 校驗一遍from PIL import Image try: Image.open(path).verify() except Exception as e: print(f跳過損壞圖片 {path}: {e}) continue3.2 表格列寬在 Word 里被自動調(diào)整現(xiàn)象腳本里沒設(shè)列寬Word 打開后表格列寬忽寬忽窄跟預(yù)期不符。原因python-docx 創(chuàng)建表格時如果不設(shè)列寬Word 會按“自動調(diào)整”渲染列寬取決于內(nèi)容長度。table.autofit False只是關(guān)掉自動調(diào)整還需要顯式設(shè)每列寬度。解決創(chuàng)建表格后逐列設(shè)置table.autofit False for j, w in enumerate([Cm(3), Cm(5), Cm(4)]): for cell in table.columns[j].cells: cell.width w注意cell.width要每個單元格都設(shè)只設(shè)column.width在某些 Word 版本上不生效。3.3 中文字體在表格里變成宋體標(biāo)題字體沒繼承現(xiàn)象正文段落字體正常表格里的中文全變成宋體和模板不一致。原因表格單元格的段落用的是 Normal 樣式但 python-docx 寫入時沒有顯式指定 eastAsia 字體Word 回退到默認(rèn)中文字體。解決寫入表格后遍歷單元格統(tǒng)一設(shè)字體for row in table.rows: for cell in row.cells: for p in cell.paragraphs: for r in p.runs: r.font.name 微軟雅黑 r.element.rPr.rFonts.set(qn(w:eastAsia), 微軟雅黑)3.4 批量跑完發(fā)現(xiàn)文檔數(shù)量對不上現(xiàn)象data_root 下有 30 個文件夾out_dir 只生成了 28 個 docx。原因通常是文件夾名排序后中間某個文件夾里沒有 images 也沒有 table.csv腳本跳過了或者文件夾名含特殊字符導(dǎo)致os.path.join路徑異常。解決在循環(huán)里加日志把跳過的文件夾打出來else: print(f跳過空目錄: {case_dir})另外檢查文件夾名有沒有前后空格Windows 下os.listdir會保留空格os.path.isdir可能判斷失敗。3.5 模板里的頁眉頁腳在輸出文檔里丟了現(xiàn)象模板有頁眉頁腳生成的文檔頁眉頁腳是空的。原因Document(template_path)會保留頁眉頁腳但如果模板的頁眉頁腳是“鏈接到前一節(jié)”且第一節(jié)被修改過可能出現(xiàn)異常。更常見的原因是模板本身是用舊版 Word 保存的python-docx 讀取時 section 信息不完整。解決確認(rèn)模板是 .docx 格式不是 .doc 改后綴用 Word 重新另存為 .docx。如果還不行在腳本里顯式復(fù)制 sectionfrom copy import deepcopy for section in doc.sections: for hf in [section.header, section.footer]: for p in hf.paragraphs: pass # 頁眉頁腳內(nèi)容已在模板中通常不需要額外操作這個問題的根因多半在模板不在腳本換一個干凈的 .docx 模板通常就好了。4. 進階技巧用配置表驅(qū)動不同模板和字段映射腳本包默認(rèn)是“一個模板對應(yīng)一種數(shù)據(jù)布局”但實際辦公場景里經(jīng)常是“同一批數(shù)據(jù)要套不同模板”或者“不同文件夾的表格列順序不一樣”。這時候硬編碼就不夠了我一般會加一層 YAML 配置# config.yaml templates: - name: inspection template: templates/inspection.docx image_width_cm: 10 table_style: Table Grid column_map: [0, 2, 1] # 把 CSV 的第0/2/1列映射到表格第0/1/2列 - name: summary template: templates/summary.docx image_width_cm: 14 table_style: Light Shading column_map: null # null 表示不重排讀取配置的代碼import yaml def load_config(path): with open(path, encodingutf-8) as f: return yaml.safe_load(f) def apply_column_map(row, mapping): if mapping is None: return row return [row[i] for i in mapping]這樣改模板和列映射不用動 Python 代碼交給辦公崗?fù)赂?YAML 就行。column_map的索引從 0 開始寫的時候注意 CSV 里有沒有序號列有的話要跳過。另一個進階用法是給圖片加統(tǒng)一水印。python-docx 本身不支持水印但可以在插入圖片前用 PIL 把水印燒進圖片from PIL import Image, ImageDraw def add_watermark(src, dst, text內(nèi)部資料): img Image.open(src).convert(RGBA) overlay Image.new(RGBA, img.size, (0, 0, 0, 0)) draw ImageDraw.Draw(overlay) draw.text((10, 10), text, fill(255, 0, 0, 128)) out Image.alpha_composite(img, overlay).convert(RGB) out.save(dst, quality85)這個操作會重新編碼圖片畫質(zhì)有輕微損失如果原圖是掃描件建議 quality 設(shè) 95 以上。驗證方法上我習(xí)慣跑完腳本后隨機抽三份文檔打開檢查圖片順序、表格邊框、字體三處。批量場景下肉眼全檢不現(xiàn)實但抽檢能攔住大部分低級錯誤。另外可以在腳本末尾加一句統(tǒng)計print(f生成 {len(os.listdir(out_dir))} 份輸入 {len(os.listdir(data_root))} 份)數(shù)字對不上就回頭查 3.4 那條。從那以后我每次改完腳本都強制先拿兩份樣本數(shù)據(jù)跑一遍確認(rèn)輸出沒問題再上全量。批量操作最怕的就是跑完一百份才發(fā)現(xiàn)模板選錯了重跑的時間成本比驗證高得多。希望幫到你。本文還有配套的精品資源點擊獲取