如何使用 Python 读取包含扩展字体的 Excel 文件? (openpyxl 错误:最大值为 14)

Posted

技术标签:

【中文标题】如何使用 Python 读取包含扩展字体的 Excel 文件? (openpyxl 错误:最大值为 14)【英文标题】:How to use Python to read Excel files that contain extended fonts? (openpyxl error: Max value is 14) 【发布时间】:2022-01-23 21:21:48 【问题描述】:

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

我一直在尝试几个可用的 Python 模块来执行此操作(在此示例中为 pandas),但我遇到了大多数依赖于 openpyxl 的问题。

这是我当前的代码:

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 建议修改原始文件并删除格式,但这不是一个选项,因为我不想以任何方式修改文件。

还有没有这种格式限制的另一种方法?

【问题讨论】:

请上传导致此问题的电子表格。 文件是否在 Microsoft Excel 中打开(没有 excel 更改文件)?您可以制作文件的副本并从副本中删除格式...其格式不应影响单元格中的数据,除非格式与您的代码相关。根本问题是文件格式错误(不符合 excel 规范)。 【参考方案1】:

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

【讨论】:

我看不出值超过 14 的电子表格会如何导致失败。但是电子表格中使用的字体一个值。从我引用的另一个线程来看,openpyxl 似乎除了最基本的字体外没有处理任何东西。为什么,我不知道。但是如果 openpyxl 不能用于包含大于 14 的值的 Excel 文件,那将使它完全无用,不是吗? @Zephyr 当然没用。您是否尝试降低字体大小并再次加载工作簿?如果是这样,它有效吗?顺便说一句,您的参考链接似乎已损坏。【参考方案2】:

问题在于您的文件不符合 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>

如果家庭值超过 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 复制。在补丁之前,这将无法加载。补丁后,加载无误。

【讨论】:

哇,很有见地。奇怪的是,例如,没有简单的选择可以退回到openpyxl.styles.fonts.DEFAULT_FONT。与其发射嘲弄大炮,不如增加手动可能性:openpyxl.styles.fonts.Font.family = openpyxl.descriptors.nested.NestedMinMax(min=0, max=100) @JonasHörsch 是的,这对于 openpyxl 来说似乎是一个很好的功能。我确实有这个想法,但我不清楚如何实现这一点(通过模拟或其他方式)——但 OP 表示他们不想以任何方式修改他们的文件。【参考方案3】:

以下是为我解决此错误的方法。我编辑了 lib\site-packages\openpyxl\descriptors\base.py 并在 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。 我所做的只是将raises 错误的行注释掉。 将代码更改为:

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,请尝试first answer。

# 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 之前。

【讨论】:

【参考方案4】:

如果我是对的,您想从目录中的文件中获取所有 xlsx 工作表名称,以便您可以这样做:

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 文件的数据,但是如果您在同一文件夹中具有不同扩展名的文件(例如在同一文件夹中您有 .csv 文件),则会出现错误。因此,您需要先过滤所有文件名,或者您可以使用try except 语句跳过非 excel 文件。 如果您的 .py 文件与您的文件夹目标路径不同,只需更改 dirpath,例如:'D:/changeYour Folder Path/Example/Target/'

注意:需要安装openpyxl

【讨论】:

【参考方案5】:

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

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

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

此时我们可以使用字符串替换来更改该条目以说

      <family val="3"/>

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

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

稍后编辑 我不会声称要重新发明 Pythonic Wheel,而只是说从https://***.com/a/69360331/10802527 (由现在离开的用户)这应该适用于许多其他感兴趣的用户。备份您的文件并进行相应修改。

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() 的调用仅仅是为了检查修改后的电子表格的有效性

【讨论】:

以上是关于如何使用 Python 读取包含扩展字体的 Excel 文件? (openpyxl 错误:最大值为 14)的主要内容,如果未能解决你的问题,请参考以下文章

Selenium2+Python3.6实战:读取Excel文件

python如何获取电脑已安装字体列表

PHP:读取字体文件的 TrueType/OpenType 元数据

EXC_BAD_ACCESS NSArray of ManagedObjects(核心数据)

如何记录python异常? [复制]

Swift 扩展异常执行被中断,原因:EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)