問題描述
使用 Pandas 的 read_excel
方法讀取一個 16 萬行的 Excel 文件報 AssertionError
錯誤:
"/Users/XXX/excel_test/venv/lib/python3.7/site-packages/xlrd/xlsx.py", line 637, in do_row
assert 0 <= self.rowx < X12_MAX_ROWS
AssertionError
背後原理
Excel 文件有兩種默認格式,在 Excel 2007 以前,使用擴展名爲 .xls
格式的文件,這種文件格式是一種特定的二進制格式,最多支持 65,536 行(在 Excel 97 之前支持的最大行數是 16,384),256 列表格。從 Excel 2007 版開始,默認採用了基於 XML 的新的文件格式 .xlsx
,支持的表格行數達到了 1,048,576,列數達到了 16,384。需要注意的是,將 .xlsx
格式的文件轉換爲 .xls
格式的文件時,65,536 行和 256 列之後的數據都會被丟棄。
版本 | 最大行數 | 最大列數 | 文件格式 |
---|---|---|---|
Excel 97 之前 | 16,384 | 256 | .xls |
Excel 97 到 Excel 2003 | 65,536 | 256 | .xls |
Excel 2007 及以後版本 | 1,048,576 | 16,384 | .xlsx |
Pandas 讀取 Excel 文件的引擎是 xlrd
,xlrd
在讀取 Excel 文件時,xlrd/xlsx.py
文件的 637 行會對行號做斷言,判斷行號是否在 0 - 1,048,576(Excel支持的最大行數) 的範圍內。這段代碼是這樣的:
row_number = row_elem.get('r')
if row_number is None: # Yes, it's optional.
self.rowx += 1
explicit_row_number = 0
if self.verbosity and not self.warned_no_row_num:
self.dumpout("no row number; assuming rowx=%d", self.rowx)
self.warned_no_row_num = 1
else:
self.rowx = row_number - 1
explicit_row_number = 1
assert 0 <= self.rowx < X12_MAX_ROWS
代碼會從 Excel 文件中獲取 row_number,這個 row_number 是每一行的行號,正常文件行號從 1 開始,而出現問題的文件行號從 0 開始,當行號爲 0,進入 else 語句,導致越界問題。
解決辦法
除了 xlrd
, Pandas 還支持 openpyxl
(0.25 版),openpyxl
是一個專門用來操作 .xlsx
格式文件的 Python 庫,和 xlrd
相比它的速度會慢一些,但是不會碰到上面所說的問題。這是 openpyxl 中 reader/excel.py
文件處理行的代碼:
def parse_row(self, row):
attrs = dict(row.attrib)
if "r" in attrs:
self.max_row = int(attrs['r'])
else:
self.max_row += 1
keys = set(attrs)
for key in keys:
if key.startswith('{'):
del attrs[key]
keys = set(attrs)
if keys != set(['r', 'spans']) and keys != set(['r']):
# don't create dimension objects unless they have relevant information
self.row_dimensions[attrs['r']] = attrs
cells = [self.parse_cell(el) for el in row]
return self.max_row, cells
openpyxl 在處理行時,並沒有對行號進行斷言,即使行號第一位是 0,也不會導致報錯,但這會導致第一行數據的缺失,需要進行額外處理。
使用 Pandas + openpyxl 讀取 Excel 文件
首先安裝 openpyxl
:
pip install openpyxl
Pandas 的 read_excel 方法中,有 engine
字段,可以指定所使用的處理 Excel 文件的引擎,填入 openpyxl
,再讀取文件就可以了。
import pandas as pd
df = pd.read_excel('./data.xlsx', engine='openpyxl')
print(len(df)) # 160000
參考文檔
https://office-watch.com/2010/excel-a-history-of-rows-and-columns/
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_excel.html