Openpyxl:如何解决打开工作簿的问题

时间:2018-01-26 22:25:25

标签: python excel openpyxl

TL; DR: 在尝试加载工作簿时,找出挂起openpyxl的内容有什么好的第一步?

长版: 所以,我发现了一些'为什么它没有工作'关于openpyxl的SO问题,但在发现/解决问题的实际尝试方面没有多大帮助。

我刚刚开始查看openpyxl,看起来非常有前景,但是,刚开始时,我遇到了一个问题:我有各种各样的工作簿非常复杂。我想做一个至少从他们那里读取数据的好尝试。我使用的工作簿不是巨大的(~750kB),但它确实有很多:条件格式,数据验证,命名范围,vba内容等。当我尝试打开工作簿,我得到一个关于数据验证的警告(好的,没什么大不了的)但是它在CPU上启动并且没有为时间完成任何事情 - 我不知道它是否是将永远完成,因为不可避免地,我需要继续前进,所以我放弃了。无论如何,如果装载完成,加载速度太慢而无法使用。

所以,我喜欢它,如果有人可以建议一些坚实的第一步来确定阻塞是什么,所以我可以尝试通过从工作簿中删除违规内容或理想地通过做某事来完成这项工作在python方面更顺利地处理事情。

为了清楚起见,这里是我开始使用的两行代码:

from openpyxl import Workbook, load_workbook
wb = load_workbook('book.xlsm')

1 个答案:

答案 0 :(得分:0)

正如@CharlieClark所猜测的那样,我的特定工作簿的问题是为整个列设置了数据验证。为了给这个问题提供一个满意的答案,无论如何我做了一些实验,试着看看我怎么能自己推断出这个。由于我认为我不可能编写一个实际上可以解决其他问题的方法,我尝试了两种方法来查看问题,基于@BoarGules和@CharlieClark的建议,并将它们作为示例编写:

方法1:将工作簿拆分为较小的部分,比较加载

如果你想弄清楚是什么阻碍了这个过程,我建议你好好考虑一下工作簿包含的内容以及可能导致openpyxl做一些额外处理的原因(更多内容)这在方法2)中。而不是简单地将每个工作表拆分成一个新的工作簿并尝试加载每个工作簿(我做了这个并且大多数较小的工作簿都不会加载 - 我的大多数工作表基本上都具有相同的结构和相同的加载问题),我会尝试思考你有什么内容 - 数据验证,条件格式化,你有什么 - 以及一次删除一种内容类型。

当我删除工作簿中的所有数据时,它突然加载就像一个快照!

方法2:更熟悉源代码并尝试profile - 解决问题的方法

我发现这种方法更令人满意,因为我至少现在对openpyxl如何加载工作簿有一个(非常模糊)的想法,但这种方法确实需要浏览源代码来思考问题 - 如果您不想这样做,请坚持使用方法1.此方法还需要一个好的样本工作簿,以便进行比较。对我而言,由于我的第一次尝试是关注@ CharlieClark的猜测并删除所有数据验证,我使用“固定”工作簿进行比较,这有点作弊但是很好。

使用我的良好的工作簿,我在workbook_load函数上运行了一个快速配置文件,以查看它的外观。我发现通过'tottime'或每个函数 中花费的总时间对结果进行排序最有用:

>>>import profile
>>>from openpyxl import load_workbook
>>>profile.run('wb = load_workbook("good.xlsm")', sort='tottime')

     4228306 function calls (4186125 primitive calls) in 12.859 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    84364    1.828    0.000    6.234    0.000 worksheet.py:138(parse_cell)
   267315    0.625    0.000    0.625    0.000 :0(get)
   197422    0.594    0.000    0.953    0.000 ElementTree.py:1286(read_events)
      284    0.578    0.002    0.578    0.002 :0(feed)
12986/8974    0.516    0.000    2.125    0.000 serialisable.py:42(from_tree)
   538565    0.500    0.000    0.500    0.000 :0(isinstance)
    89339    0.500    0.000    1.000    0.000 cell.py:43(coordinate_from_string)
24877/9318    0.438    0.000    0.984    0.000 serialisable.py:187(__hash__)
   193137    0.422    0.000    0.422    0.000 :0(match)
     5119    0.422    0.000    6.734    0.001 worksheet.py:259(parse_row)
    73194    0.406    0.000    0.562    0.000 base.py:40(__set__)
   168920    0.375    0.000    0.375    0.000 :0(find)
   197144    0.344    0.000    1.906    0.000 ElementTree.py:1218(iterator)
   251298    0.312    0.000    0.312    0.000 :0(getattr)
    84364    0.312    0.000    0.812    0.000 cell.py:106(__init__)
    84364    0.297    0.000    1.203    0.000 cell.py:181(coordinate_to_tuple)
...

我运行了一个加载这个和其他一些工作簿的配置文件,看起来执行时间主要花在workheet.py模块(如上所述)和serialisable.py模块中,我认为这是有意义的很多阅读/处理数据发生的地方。

为了比较,当我让工作簿加载一段时间然后中止时,这就是我为配置文件获得的内容:

>>>import profile
>>>from openpyxl import load_workbook
>>>profile.run('wb = load_workbook("bad.xlsm")', sort='tottime')

     14111962 function calls (14076527 primitive calls) in 27.797 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  3045757    9.797    0.000   20.359    0.000 cell.py:157(rows_from_range)
6091514/6091513    7.062    0.000   10.562    0.000 cell.py:166(<genexpr>)
  3045783    3.500    0.000    3.500    0.000 :0(format)
       19    2.797    0.147   23.156    1.219 :0(extend)
       19    0.469    0.025   23.625    1.243 datavalidation.py:59(expand_cell_ranges)
   366802    0.375    0.000    0.375    0.000 :0(isinstance)
24877/9318    0.344    0.000    0.672    0.000 serialisable.py:187(__hash__)
    15947    0.234    0.000    1.125    0.000 worksheet.py:138(parse_cell)
12686/8831    0.219    0.000   25.172    0.003 serialisable.py:42(from_tree)
   250829    0.188    0.000    0.188    0.000 :0(getattr)
       90    0.172    0.002    0.172    0.002 :0(feed)
    63452    0.172    0.000    0.297    0.000 base.py:40(__set__)
    54974    0.125    0.000    0.125    0.000 :0(get)
    44462    0.125    0.000    0.125    0.000 :0(match)
    18172    0.109    0.000    0.203    0.000 cell.py:43(coordinate_from_string)
    56006    0.094    0.000    0.156    0.000 ElementTree.py:1286(read_events)
    83605    0.078    0.000    0.078    0.000 base.py:25(__set__)
    17709    0.078    0.000    0.141    0.000 sequence.py:24(__set__)
...

因此,查看此配置文件,您可以看到大部分执行时间都花在处理单元格地址(rows_from_range)上,而不是查看实际数据,就像我们在第一个配置文件中看到的那样。我在这里假设这不是想要的。如果您查看个人资料表格中的第五行,我们还会花费大量时间(cumtime列)datavalidation.py函数,expand_cell_ranges,以及所有这些事实它只被称为少数几次,并没有出现在另一个配置文件顶部附近的任何地方。当我挖掘源代码时,我看到expand_cell_ranges函数在循环中调用rows_from_range函数!我想从那里我们可以合理地得出结论,在这种情况下,关于数据验证的一些事情导致openpyxl尝试处理对它们没有任何用处的整个单元地址的重击。由于我已经知道我的工作簿已经为整个空单元列设置了数据验证,因此我认为这是对诊断的一个非常可靠的确认。

如果任何阅读此内容的人需要尝试逆向工程来诊断无法加载的工作簿,我会将上面的第一个配置文件与用于加载问题工作簿的配置文件进行比较,看看有哪些更改。这应该至少为猜测为什么改变了它提供了一个很好的起点。