簡體   English   中英

AWS Lambda、Python3 和大型 XLSX 文件到 CSV 文件

[英]AWS Lambda, Python3 and Large XLSX files to CSV files

我有一批大小從 10Mb 到 400Mb 不等的 XLSX 文件。 它們內部始終具有相同的工作表和結構,但有些包含的數據比其他的多。

我正在嘗試使用 AWS Lambda 處理這些; 它是提交過程的一部分,因此 S3 中的文件丟失是 Lambda 的事件。

我很快了解到 XLSX 是一種可怕的格式,但我無法改變它。 目前我的主要 Lambda 使用這個 class 在網上找到並稍作改動。 memory 的使用率和速度比 Pandas read_excel 有所提高,但仍然不夠。 對於 400Mb 文件,Lambda 只是超時或耗盡其 memory 分配(即使是最大分配)。

對腳本進行一些 memory 分析,我可以看到在 pivot 操作期間大小減小了,但我真的不能跳過它。

Pre-Pivot DF 約為 1230Mb 在此處輸入圖像描述

樞軸后 DF 約為 220Mb 在此處輸入圖像描述

關於如何改進它以提高 memory 效率的任何提示?

每個工作表都需要保存到它自己的 CSV 中,但如果有幫助,它可以分成多個 CSV 文件,比如塊大小樣式的迭代器?

import io
import zipfile
from lxml import etree
from pandas import read_csv, to_numeric


class ExcelParse:
    sheet_xslt = etree.XML('''
        <xsl:stylesheet version="1.0"
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns:sp="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
            >
            <xsl:output method="text"/>
            <xsl:template match="sp:row">
               <xsl:for-each select="sp:c">
                <xsl:value-of select="parent::*/@r"/> <!-- ROW -->
                <xsl:text>,</xsl:text>
                <xsl:value-of select="@r"/> <!--REMOVEME-->
                <xsl:text>,</xsl:text>
                <xsl:value-of select="@t"/> <!-- TYPE -->
                <xsl:text>,</xsl:text>
                <xsl:value-of select="sp:v/text()"/> <!-- VALUE -->
               <xsl:text>\n</xsl:text>
               </xsl:for-each>
            </xsl:template>
        </xsl:stylesheet>
    ''')

    def __init__(self, file):
        self.fh = zipfile.ZipFile(file)
        self.ns = {
            'ns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
        }
        self.shared = self.load_shared()
        self.workbook = self.load_workbook()

    def load_workbook(self):
        # Load workbook
        name = 'xl/workbook.xml'
        root = etree.parse(self.fh.open(name))
        res = {}
        for el in etree.XPath("//ns:sheet", namespaces=self.ns)(root):
            res[el.attrib['name']] = str(
                int(el.attrib['sheetId']) -
                1)  # Sheet ID in the XML starts at 2 for some reason?
        return res

    def load_shared(self):
        # Load shared strings
        name = 'xl/sharedStrings.xml'
        root = etree.parse(self.fh.open(name))
        res = etree.XPath("/ns:sst/ns:si/ns:t", namespaces=self.ns)(root)
        return {str(pos): el.text for pos, el in enumerate(res)}

    def _parse_sheet(self, root):
        transform = etree.XSLT(self.sheet_xslt)
        result = transform(root)
        df = read_csv(io.StringIO(str(result)),
                      header=None,
                      names=['row', 'cell', 'type', 'value'])
        return df

    def read(self, sheet_name):
        sheet_id = self.workbook[sheet_name]
        sheet_path = f'xl/worksheets/sheet{sheet_id}.xml'
        root = etree.parse(self.fh.open(sheet_path))
        df = self._parse_sheet(root)

        # First row numbers are filled with nan
        df['row'] = to_numeric(df['row'].fillna(0))

        # Translate string contents
        cond = (df.type == 's') & (~df.value.isnull())
        df.loc[cond, 'value'] = df[cond]['value'].map(self.shared)
        # Add column number and sort rows
        df['col'] = df.cell.str.replace(r'[0-9]+', '', regex=True)

        # Pivot everything
        df = df.pivot(
            index='row', columns='col',
            values='value').reset_index(drop=True).reset_index(drop=True)
        df.columns.name = None  # pivot adds a name to the "columns" array
        # Sort columns (pivot will put AA before B)
        cols = sorted(df.columns, key=lambda x: (len(x), x))
        df = df[cols]
        df = df.dropna(how='all')  # Ignore empty lines
        df = df.dropna(how='all', axis=1)  # Ignore empty cols

        new_header = df.iloc[0]  # Grab the first row for the header
        df = df[1:]  # Take the data less the header row
        df.columns = new_header  # Set the header row as the df header

        return df


def new_method():
    xlsx = ExcelParse(
        'BigFile.xlsx'
    )
    print(xlsx.workbook)
    df = xlsx.read('Task')

    # for sheet_name, sheet_id in xlsx.workbook.items():
    #     df = xlsx.read(sheet_name)
    #     do stuff


new_method()

為了加快這個過程,你可以嘗試這些東西並檢查

  1. 嘗試使用xlsx2csv轉換 csv 中工作簿中的每個工作表,然后執行 pd.read_csv()。 Csv 讀取速度比 excel 快。
  2. 正如您所說,數據是固定結構,不會改變嘗試在 read_csv 中使用“dtype”選項。 這將有助於 pandas 避免自動識別每列的數據類型,我猜這將節省一些時間。

對於 memory 問題,

  1. 我無法理解 10GB 的 RAM 是否已被完全使用,但無論如何至少據我所知,可能很難進行大幅改進。 使用“dtype”選項可能會有所幫助,因為某些數據類型比其他數據類型占用更多 memory,您可以將所有列設為 str 並進行檢查。 read_csv 也有 chunksize 選項。 你可以探索一下。

  2. 您還可以探索 read_csv 中的 low_memory 和 memory_map 選項。 我還沒有嘗試過,所以不能真正告訴你有效性。

  3. 萬一我弄錯了,如果你在談論存儲 memory,你可以在 read_csv 中使用 storage_options,如果你不這樣做,它將直接從/寫入 S3。

  4. 如果您需要更多存儲空間 memory 在本地檢查EFS for Lambdas。

  5. 另一方面,您可以制作一個 Fargate 映像並從您的 lambda 調用它。如果這不是約束條件,您可以獲得較長的執行時間。 查看更多

請注意,如果 lambda 中的 memory 少於 1.7GB,則您只能獲得一小部分 CPU。 這就是為什么事情看起來出奇地慢。 通過將 memory 加倍,您可以將 CPU 加倍,從而將執行時間減半(對於 CPU 密集型任務)。 Double memory 的一半持續時間成本相同(假設它受 CPU 限制)。

如果它是單線程進程,請嘗試將 memory 增加到 1.7GB。 如果它是多線程的,則更多。 (我認為 pandas 在引擎蓋下使用 aiobotocore 進行下載,所以更多可能會有所幫助。)

這有助於解決速度問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM