批量提取 Word 文档里的表格到 Excel/CSV(含 4 个实跑大坑)

销售月报、验收单、项目周报……几十个 docx,每个里面嵌着几张表,领导要的是一张汇总表。手动复制粘贴是体力刑具。下面这份脚本本次完整实跑过:2 个多表 docx,4 张表 12 行数据全部正确抽出,带「源文件 + 表序号」溯源列,重跑幂等。全程只用 Python 标准库,目标机器不用 pip install 任何东西。

原理:docx 就是个 zip

.docx 本质是 zip 包,表格全在 word/document.xml 里。解压读 XML,遍历 <w:tbl> 即可——所以不依赖 python-docx,服务器、旧电脑都能跑。

核心脚本

import glob, os, csv, zipfile
from xml.etree import ElementTree as ET
W = lambda t: '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}' + t

def cell_text(tc):
    # 坑1:只取单元格「直接子级」的 w:p,防止嵌套表格的文字混进来
    parts = [''.join(t.text or '' for t in p.iter(W('t')))
             for p in tc if p.tag == W('p')]
    return '\n'.join(parts)

all_rows = []
for f in sorted(glob.glob('*.docx')):
    with zipfile.ZipFile(f) as z:
        root = ET.fromstring(z.read('word/document.xml'))
    tbl_idx = [0]
    def rec(el):
        for ch in el:
            if ch.tag == W('tbl'):
                tbl_idx[0] += 1
                for tr in ch.findall(W('tr')):      # 坑1:findall 而非 iter
                    cells = []
                    for tc in tr.findall(W('tc')):
                        gs = tc.find(f'{W("tcPr")}/{W("gridSpan")}')
                        span = int(gs.get(W('val'))) if gs is not None else 1
                        cells.append(cell_text(tc))
                        cells.extend([''] * (span - 1))   # 坑3:见下文
                    if any(c.strip() for c in cells):
                        all_rows.append([os.path.basename(f), tbl_idx[0]] + cells)
            rec(ch)                                  # 坑1:递归抓嵌套表
    rec(root)

with open('word_tables_all.csv', 'w', newline='', encoding='utf-8-sig') as fo:
    w = csv.writer(fo)
    w.writerow(['源文件', '表序号', '列1', '列2', '列3'])
    w.writerows(all_rows)
print(f'{len(all_rows)} rows')

实跑结果

构造 2 个 docx(华东月报含 2 张表:主表 + 备注表,备注表有空单元格;华南月报含主表 + 事项表),共 4 张表 12 行,全部正确提取:

销售月报-华东.docx, 1, 月份, 销售额, 负责人
销售月报-华东.docx, 1, 1月, 152,000, 张三
销售月报-华东.docx, 1, 2月, 183,500, 李四
销售月报-华东.docx, 1, 2月, 97,200, 张三
销售月报-华东.docx, 2, 备注区(空单元格正确输出为空串)...
销售月报-华南.docx, 1, 月份, 销售额, 负责人
销售月报-华南.docx, 1, 1月, 210,300, 王五
销售月报-华南.docx, 2, 日期, 事项 ...
... 共 12 行,输出 word_tables_all.csv(utf-8-sig)

输出到独立 CSV、源文件只读,重跑幂等。

4 个实跑大坑

坑 1:嵌套表格会把你抓串。Word 表格单元格里还能再套一张表。用 tc.iter(w:tbl 内的 w:t)iter('w:tbl') 重复提取一遍(同一行数据出两次)。修法:抓 w:tr/w:tc 一律用 findall(只看直接子级),单元格文字只取直接子级 w:p,然后递归遍历让嵌套表自己作为独立表出现。
坑 2:空单元格的 text 是 None 不是空串。t.text or '' 必须加,否则 ''.join 直接 TypeError。看起来低级,但报错点离真正的坑很远,第一眼容易怀疑编码。
坑 3:合并单元格(gridSpan)导致横向错位。横向合并过的行,w:tc 数量比表头少,直接按位置拼进 CSV 会整行左移。读 w:tcPr/w:gridSpan 的 w:val,用空串补齐到正确列数。纵向合并(vMerge)同理,被合并的行会缺单元格,按同样思路补。
坑 4:输出用 utf-8-sig。纯 utf-8 的 CSV 在 Windows 上双击用 Excel 打开中文全是乱码;带 BOM(utf-8-sig)才能被 Excel 正确识别。给非程序员的交付物,这一条是底线。

为什么不用 python-docx?

python-docx 确实更省事(table.rows[i].cells[j].text),但它对嵌套表格和 gridSpan 的处理同样要手写补丁,且目标机器必须装包。对「在别人电脑上跑」的场景(详见 脚本在别人电脑上报错怎么办),纯 stdlib 是更稳的交付形态。要输出真正的 .xlsx 而不是 CSV,拿到汇总 CSV 后用 openpyxl 一次性转格式即可。

没时间自己改?这是 喵窝资源铺 的常用脚本场景之一:9.9 元起,把你 Word/表格发来,AI 写脚本+人工核验结果后交付,虚拟物品不退款但免费重做到结果正确。更多实跑验证过的脚本见 脚本合集。