How to use Python to read Excel files that contain extended fonts? (openpyxl error: Max value is 14)

How to use Python to read Excel files that contain extended fonts? (openpyxl error: Max value is 14)

作为 Python 的学习项目,我正在尝试读取目录中的所有 Excel 文件并提取所有工作表的名称。

我一直在尝试几个可用的 Python 模块来执行此操作(在此示例中为 pandas),但是 运行 遇到了一个问题,其中大多数取决于 [=12] =].

这是我当前的代码:

import os
import pandas

directory_root = 'D:\testFiles'

# Dict to hold all files, stats
all_files = {}

for _current_path, _dirs_in_path, _files_in_path in os.walk(directory_root):

    # Add all files to this `all_files`
    for _file in _files_in_path:
        # Extract filesystem stats from the file
        _stats = os.stat(os.path.join(_current_path, _file))

        # Add the full file path and its stats to the `all_files` dict.
        all_files[os.path.join(_current_path, _file)] = _stats

# Loop through all found files to extract the sheet names
for _file in all_files:

    # Open the workbook
    xls = pandas.ExcelFile(_file)

    # Loop through all sheets in the workbook
    for _sheet in xls.sheet_names():
        print(_sheet)

这会在调用 pandas.ExcelFile() 时从 openpyxl 引发错误:ValueError: Max value is 14

根据我在网上找到的信息,这是因为该文件包含 14 以上的字体系列。如何在忽略任何现有格式的情况下读取 Excel (xlsx) 文件?

我能找到的 only potential solution 建议修改原始文件并删除格式,但这不是一个选项,因为我不想以任何方式修改文件。

是否有另一种没有此格式限制的方法?

这很可能不是因为字体大小或字体系列,因为它给出了 ValueError。我从 this page and this page 看到的,似乎 excel 文件中的浮点值之一不能超过 14。这就是它给出错误 ValueError: Max value is 14 的原因。您可以深入文件并搜索大于 14 的值,然后通过操纵该值来尝试您的代码。

问题是您的文件不符合 Open Office 规范。只允许使用某些字体系列。一旦 openpyxl 遇到超出规格的字体,它就会抛出此错误,因为 OpenPyxl only allows spec-conforming excel files.

一些 Excel 读者可能对此没有问题,并且更灵活地处理不符合 OpenOffice 规范的文件,但 openpyxl 仅实现 Apache Open Office 规范。

正在解析的 xml 将包含有关字体的信息,如下所示:

<font>
  <b/>
  <sz val="11"/>
  <color rgb="FF000000"/>
  <name val="Century Gothic"/>
  <family val="34"/>
</font>

如果 family 值超过 14,openpyxl 将抛出此 ValueError。 Open Office 中有一个底层描述符来控制它。

当其他阅读器,例如 Microsoft Office 365 Excel 遇到此问题时,它会在加载文件时更改字体系列以兼容字体(默认字体 Calibri)。

作为解决方法,如果您不想更改该值(如 Microsoft Excel 那样),您可以对描述符进行猴子修补以允许更大的最大字体系列。

# IMPORTANT, you must do this before importing openpyxl
from unittest import mock
# Set max font family value to 100
p = mock.patch('openpyxl.styles.fonts.Font.family.max', new=100)
p.start()
import openpyxl
openpyxl.open('my-bugged-worksheet.xlsx') # this works now!

这可以使用 this excel workbook 进行复制。在补丁之前,这将无法加载。补丁后加载无误

以下是为我修复此错误的方法。我编辑了 lib\site-packages\openpyxl\descriptors\base.py 并在 class Max 中的 86 行之后添加了打印语句,如下所示:

def __set__(self, instance, value):
    if ((self.allow_none and value is not None)
        or not self.allow_none):
        value = _convert(self.expected_type, value)
        if value > self.max:
            print(f"value is {value}")
            raise ValueError('Max value is {0}'.format(self.max))
    super(Max, self).__set__(instance, value)

这样打印出来的34的值明显高于最大值14。
我所做的只是注释掉 raise 错误的行。
将代码更改为:

def __set__(self, instance, value):
    if ((self.allow_none and value is not None)
        or not self.allow_none):
        value = _convert(self.expected_type, value)
        if value > self.max:
            self.max = value
            # print(f"value is {value}")
            # raise ValueError('Max value is {0}'.format(self.max))
    super(Max, self).__set__(instance, value)

这解决了我的问题。
或者,如果您需要分发文件并且必须使用原始库代码 THEN,请尝试 .

# IMPORTANT, you must do this before importing openpyxl
from unittest import mock
# Set max font family value to 100
p = mock.patch('openpyxl.styles.fonts.Font.family.max', new=100)
p.start()
import openpyxl
openpyxl.open('my-bugged-worksheet.xlsx') # this works now!

在导入 openpyxl 之前。

如果我没看错,您想从目录中的文件中获取所有 xlsx sheet 名称,这样您就可以这样做:

import pandas as pd
import os
dirpth = './Target Folder/'
for dirpath, dirnames, filenames in os.walk(dirpth):
    file_names = filenames
file_names = [dirpth+file_names[i] for i in range(len(file_names))]
data = []
sheet_names = []
for names in file_names:
    df = pd.ExcelFile(names,engine = 'openpyxl')
    data_sheet = []
    sheet_temp = []
    for name in df.sheet_names:
        data_sheet.append(df.parse(nama,index_col = [0]))
        sheet_temp.append(name)
    data.append(data_sheet)
    sheet_names.append(sheet_temp)

这样,对于每个 excel 文件,您将自动从每个 sheet 中获取数据,但是如果您在同一文件夹中有不同扩展名的文件(例如在与 .csv 文件相同的文件夹)。所以你需要先过滤所有文件名或者你可以使用 try except 语句跳过非 excel 文件。 如果您的 .py 文件与您的文件夹目标路径不同,只需更改 dirpath,例如:'D:/changeYour Folder Path/Example/Target/'

注意:您需要安装openpyxl

通过简单的 unzip|find 在 windows 中或在其他人中使用 grep,很容易检测到 family 值何时超出范围。因此,您可以根据这些值过滤掉文件。在这里,我们在坏男孩示例中看到它们是可接受的 2 和不可接受的 34

然而,由于所有平台(包括 win 10)都有 TAR,最简单的方法是首先将 file.xlsx 作为一个集合展开,然后在本机 OS 中使用按文件查找(或python) 然后确保您确切知道哪个文件需要调整。

所以我们现在知道它是 styles.xml(这并不奇怪,因为字体值应该在那里)

此时我们可以使用字符串替换将该条目更改为 say

      <family val="3"/>

如果这对您的目的更有用。

然后重新打包调整后的 xlsx(注意:- 最好只使用一种工具来“更新”一个 style.xls 文件以保持 zip 的相对顺序)并且它的行为应该与standard.xlsx有标准的1-14字体,假设作者没有引入其他错误。

稍后编辑 我不会声称要重新发明一个 Pythonic Wheel,而只是说 (来自一个现已离职的用户)这应该适用于许多其他感兴趣的用户。备份您的文件并进行相应修改。

import tempfile
from openpyxl import load_workbook
import os
import shutil
from lxml import etree


EXCELFILE = '~/Book1.xlsx'
STYLES = 'xl/styles.xml'
FORMAT = 'zip'


with tempfile.TemporaryDirectory() as tdir:
    os.chdir(tdir)
    shutil.unpack_archive(filename=EXCELFILE, format=FORMAT)
    with open(STYLES, 'r') as styles:
        tree = etree.parse(styles)
        for family in tree.xpath('//*[local-name()="fonts"]//*[local-name()="font"]//*[local-name()="family"]'):
            try:
                if int(family.attrib['val']) > 14:
                    family.set('val', '2')
            except Exception:
                pass
    with open(STYLES, 'wb') as styles:
        tree.write(styles)
    shutil.make_archive(base_name=EXCELFILE, format=FORMAT)
    shutil.move(f'{EXCELFILE}.{FORMAT}', EXCELFILE)

load_workbook(EXCELFILE)

调用 load_workbook() 只是为了检查修改后的电子表格的有效性

这个问题可以通过 完全清理 xlsx 样式 来解决,这是我的代码如何使用 pandas 虽然 openpyxl