【问题标题】:Read extremely big xlsx file in python在python中读取非常大的xlsx文件
【发布时间】:2018-04-25 06:07:15
【问题描述】:

我需要读取 300gb 的 xlsx 文件。行数 ~ 10^9。我需要从一列中获取值。文件由 8 列组成。我想尽可能快地完成它。

from openpyxl import load_workbook
import datetime
wb = load_workbook(filename="C:\Users\Predator\Downloads\logs_sample.xlsx", 
read_only=True)
ws = wb.worksheets[0]

count = 0
emails = []
p = datetime.datetime.today()
for row in ws.rows:
   count += 1
   val = row[8].value
   if count >= 200000: break
   emails.append(val)
q = datetime.datetime.today()
res = (q-p).total_seconds()
print "time: {} seconds".format(res)
emails = emails[1:]

现在循环需要大约 16 秒来读取 200.000 行。时间复杂度为 O(n)。因此,对于 10^6 行,将读取大约 1.5 分钟。位我们有 10^9。为此,我们必须等待 10^3 * 1.5 = 1500 分钟 = 25 小时。这太糟糕了... 请帮助我解决这个问题。

【问题讨论】:

  • 非常有趣,因为单个 Excel .xlsx 工作表的最大行数只能是 2^20 或 1,048,576 行,而不是十亿行!我从标题中认为您误认为 csv 文件不是 Excel 文件,因此没有限制。但是您确实引用了 .xlsx。很想看到这本史诗般的工作簿。也许行分布在多张纸上?也许文件是用代码而不是 Excel.exe 程序构建的?

标签: python excel bigdata


【解决方案1】:

我刚刚遇到了一个非常相似的问题。我有一堆 xlsx 文件,其中包含一个包含 2 到 400 万行的工作表。

首先,我开始提取相关的 xml 文件(使用 bash 脚本):

f='<xlsx_filename>'
unzip -p $f xl/worksheets/sheet1.xml > ${f%%.*}.xml
unzip -p $f xl/sharedStrings.xml > ${f%%.*}_strings.xml

这会导致所有 xml 文件都被放置在工作目录中。然后,我使用 python 将 xml 转换为 csv。此代码使用ElementTree.iterparse() 方法。但是,只有在处理完每个元素后都被清除,它才能工作(另见here):

import pandas as pd
import numpy as np
import os
import xml.etree.ElementTree as et

base_directory = '<path/to/files>'
file = '<xml_filename>'

os.chdir(base_directory)

def read_file(base_directory, file):

    ns = '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}'

    print('Working on strings file.')

    string_it = et.parse(base_directory + '/' + file[:-4] + '_strings.xml').getroot()
    strings = []

    for st in string_it:
        strings.append(st[0].text)

    print('Working on data file.')

    iterate_file = et.iterparse(base_directory + '/' + file, events=['start', 'end'])

    print('Iterator created.')

    rows = []
    curr_column = ''
    curr_column_elem = None
    curr_row_elem = None
    count = 0

    for event, element in iterate_file:

        if event == 'start' and element.tag == ns + 'row':

            count += 1

            print('                       ', end='\r')
            print(str(count) + ' rows done', end='\r')

            if not curr_row_elem is None:
                rows.append(curr_row_elem)

            curr_row_elem = []
            element.clear()

        if not curr_row_elem is None :
            ### Column element started
            if event == 'start' and element.tag == ns + 'c':
                curr_column_elem = element
                curr_column = ''

            ### Column element ended
            if event == 'end' and element.tag == ns + 'c':
                curr_row_elem.append(curr_column)
                element.clear()
                curr_column_elem.clear()

            ### Value element ended
            if event == 'end' and element.tag == ns + 'v':
                ### Replace string if necessary
                if curr_column_elem.get('t') == 's':
                    curr_column = strings[int(element.text)]
                else:
                    curr_column = element.text

    df = pd.DataFrame(rows).replace('', np.nan)
    df.columns = df.iloc[0]
    df = df.drop(index=0)
    
    ### Export 
    df.to_csv(file[:-4] + '.csv', index=False)

read_file(base_directory, file)

也许这可以帮助您或遇到此问题的任何人。这仍然相对较慢,但是比基本的“解析”要好得多。

【讨论】:

    【解决方案2】:

    一种可能的选择是直接读取.xslx 中的.xml 数据。

    .xlsx实际上是一个zipfile,包含多个xml文件。

    所有不同的电子邮件都可能在xl/sharedStrings.xml,因此您可以尝试在那里提取它们。

    要测试(使用较小的文件):将'.zip' 添加到文件名并查看内容。

    当然,解压缩整个 300GB 文件不是一种选择,因此您必须流式传输压缩数据(zip 中的单个文件),解压缩内存中的部分并提取所需的数据。

    我不懂 Python,所以无法提供代码示例。


    另外:emails.append(val) 将创建一个包含 10 亿个项目的数组/列表。将这些值直接写入文件而不是将它们存储在数组中可能会更好(每次都必须增长并重新分配内存)。

    【讨论】:

      【解决方案3】:

      要有效地运行此类任务,您需要使用数据库。 Sqlite 可以在这里为您提供帮助。

      使用来自 http://pandas.pydata.org/ 的 pandas 和来自的 sqlite http://sqlite.org/

      您可以使用以下方式安装 pandas;来自 Continuum 的 pip 或 conda。

      import pandas as pd
      import sqlite3 as sql
      
      #create a connection/db
      con = sql.connect('logs_sample.db')
      
      #read you file
      df = pd.read_excel("C:\\Users\\Predator\\Downloads\\logs_sample.xlsx")
      
      #send it to the db
      pd.to_sql('logs_sample',con,if_exists='replace')
      

      查看更多,http://pandas.pydata.org

      【讨论】:

      • 此代码 df = d.read_excel("C:\\Users\\Predator\\Downloads\\logs_sample.xlsx") 不适用于 300 GB 的文件,因为文件将被写入 RAM 内存(RAM 小于 300GB...)。所以我们不能用一个命令来读取文件。
      • 分割文件。
      猜你喜欢
      • 2017-12-21
      • 2016-05-27
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多