如何从 Excel 文档中提取文字内容?
初始 LLM 接受的输入通常只包含文本信息,而不具备多模态理解的能力。如果需要LLM接受单元格数据并进行处理,我们通常需要先将其文本化以与 LLM 的输入接口对齐。
Excel 文件(.xls / .xlsx)在本质上是一种富结构的二进制或压缩 XML 文件,其内容不仅包括了基本的表格数据(即单元格中的文字和数值),还可能包含:
- 合并单元格
- 图表、图像、批注
- 单元格格式(颜色、字体、对齐方式)
- 数学公式(如 =SUM(A1:A5))
- 宏脚本(如 VBA)
上述内容对于传统的文本 LLM 来说是不可直接解析的非文本模态,因此我们需要一个合理的文本化方法,既能保留足够信息,又不过度引入 token 噪声。
首先被提出的方法是将 .xls/.xlsx 文件转换为.csv文件。.csv 文件是一种纯文本格式的结构化数据文件,用于以 逗号分隔的方式 存储表格数据。一个简单的 example.csv 文件内容如下:
Name,Age,Occupation
Alice,30,Engineer
Bob,25,Designer
Charlie,28,Teacher这表示一个表格,含有三列:姓名、年龄、职业,每行是一条记录。
.csv 格式的优点在于:纯文本格式,适合 LLM 处理,且其易于读取与生成,兼容广泛(Pandas, Excel, SQL)。
但.csv 格式的主要缺点之一是它无法表达 Excel 等高级表格中的结构性信息,比如合并单元格、单元格样式、公式、注释、图表等。因此其仅适用于结构规整、样式不重要的表格处理,如财务数据、名册、日志导出等,但在对于信息保留较为严格的应用场景下并非最佳适用。
另一个可能的方法是将 .xls/.xlsx 文件转换为 .htm l或 .xml 的标记语言格式。Excel 内部实际就是由嵌套的 XML 标签描述的,因此可以将其转换为 .html 或 .xml 格式进行呈现,在浏览器中保留完整排版、结构和嵌套逻辑。与 .csv 相比,这种方式最大程度上保留了一切结构化和多模态的信息,可以在浏览器中被充分渲染和加载。然而,对于针对 LLM 训练为导向的应用场景,引入大量冗余 tag 和嵌套结构会导致 token 数量激增,同时对 LLM 来说,HTML tag 不是自然语言的一部分,干扰模型理解。这种方式适用于结构保留要求极高的任务(如结构重建、格式恢复、文档重构),但不适合直接作为LLM输入使用。
基于这两种方式的优缺点,一个折中的方案被提出:如果我们每一个 Excel 文档都视为一个矩阵,那么每一个有效单元格都是矩阵中的一个元素。可以通过基于统计的方法来进行文档的文本化。将每个 Excel 表格视为一个二维矩阵,提取出有效内容单元格的位置和值,并可选地补充合并区域或样式元数据,以结构化标记方式保存。
我们统计每个特定元素出现的位置,并进行表示。
[
{"cell": "A1", "value": "Name"},
{"cell": "B1", "value": "Age"},
{"cell": "C1", "value": "Occupation"},
{"cell": "A2", "value": "Alice"},
{"cell": "B2", "value": "30"},
{"cell": "C2", "value": "Engineer"},
{"cell": "A3", "value": "Bob"}
]对于合并单元格的情况,这种方式也可以处理:
{
"value": "Test",
"range": "A1:A3",
}这样,在文本化的同时,我们仍然保留了单元格的空间位置、合并关系、有效值等核心信息。这种形式也易于转换为 Markdown 表格、JSON 或简化 HTML 片段,以供不同类型 LLM 消化。
具体的实现代码如下:
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
wb = load_workbook("SWE1.xlsx", data_only=True)
ws = wb.active # or wb['SheetName']
merged_ranges = list(ws.merged_cells.ranges)
used_cells = set()
cell_contents = []
# 处理合并单元格
for mr in merged_ranges:
top_left_cell = ws.cell(row=mr.min_row, column=mr.min_col)
value = top_left_cell.value
cell_range = f"({get_column_letter(mr.min_col)}{mr.min_row}:{get_column_letter(mr.max_col)}{mr.max_row})"
for r in range(mr.min_row, mr.max_row + 1):
for c in range(mr.min_col, mr.max_col + 1):
used_cells.add((r, c))
cell_contents.append(((mr.min_row, mr.min_col), cell_range, value))
# 处理非合并单元格
for row in ws.iter_rows():
for cell in row:
coord = (cell.row, cell.column)
if coord not in used_cells and cell.value is not None:
col_letter = get_column_letter(cell.column)
cell_range = f"({col_letter}{cell.row}:{col_letter}{cell.row})"
cell_contents.append((coord, cell_range, cell.value))
# 按照行列排序
cell_contents.sort(key=lambda x: (x[0][0], x[0][1]))
# 最终打印
print(f"Sheet: {ws.title}")
for _, cell_range, value in cell_contents:
print(f"{cell_range}{value}")另外,对于 xls 此类旧式文件的情况,可以先使用 xls2xlsx 进行处理:
import os
from xlrd import open_workbook
from openpyxl import Workbook
xls_file_dir = './test_xls_path'
for xls_file in os.listdir(xls_file_dir):
if xls_file.endswith('.xls'):
xls_file_path = os.path.join(xls_file_dir, xls_file)
# 读取 xls 文件
workbook = open_workbook(xls_file_path, formatting_info=True) # 保留格式信息
sheet = workbook.sheet_by_index(0)
# 创建 xlsx 文件
new_workbook = Workbook()
new_sheet = new_workbook.active
# 复制数据
for row_idx in range(sheet.nrows):
for col_idx in range(sheet.ncols):
new_sheet.cell(row=row_idx + 1, column=col_idx + 1, value=sheet.cell_value(row_idx, col_idx))
# 处理合并单元格
if sheet.merged_cells:
for (rlow, rhigh, clow, chigh) in sheet.merged_cells:
cell_range = f"{new_sheet.cell(row=rlow + 1, column=clow + 1).coordinate}:{new_sheet.cell(row=rhigh, column=chigh).coordinate}"
new_sheet.merge_cells(cell_range)
# 保存路径
new_xlsx_file_name = os.path.splitext(xls_file)[0] + '.xlsx'
new_xlsx_file_path = os.path.join(xls_file_dir, new_xlsx_file_name)
# 保存文件
new_workbook.save(new_xlsx_file_path)
print(f"Converted {xls_file} to {new_xlsx_file_name}")