Проверка максимумов столбцов и строк с помощью метаданных (в Python)

Введение

Я работаю в компании, которая во многих отношениях находится на переднем крае технологий разработки и развертывания программного обеспечения. Docker, Kubernetes, Istio, gRPC и Kafka - все это в нашей повседневной жизни. Но я не буду об этом говорить. Я собираюсь поговорить о чем-то менее сексуальном, гораздо менее передовом и гораздо менее интересном - файлах .xlsx

Проблема

Моя команда создает внутренние инструменты в компании, которая собирает кучи данных из самых разных мест. Наши пользователи - это коллеги по работе, которые получают некрасивые данные и стараются сделать их менее уродливыми. Они достаточно хорошо знают, как использовать MS Excel, но не обязательно понимают, что приложение на Python интерпретирует их файл иначе, чем Excel. Это приводит к путанице и головной боли в отношении форматов даты, встроенных формул, конечных и начальных пробелов, специальных символов и многого другого. Эти проблемы обычно имеют относительно простые решения, которые можно быстро решить в Google, но одно из них сохранялось: наши пользователи периодически загружают файлы, содержащие, скажем, 16 384 столбца и 200 000 строк. Такой файл обычно лишен значимых данных и обычно является результатом ошибки пользователя.

Но как?

Оказывается, это довольно легко сделать, даже не осознавая. Все, что вам нужно сделать, это нажать ctrl + вниз или вправо и ввести что-нибудь. Один пробел в ячейке XFD1 или A1048576 - все, что нужно для создания излишне большого файла. Также оказывается, что такие файлы сжимаются очень эффективно. Хотя в файле может быть 2 000 000 000 (это 2 миллиарда) ячеек, если они в основном пусты, файл может быть сжат до размера менее одного МБ. Это означает, что проверка размера файла во внешнем интерфейсе нам не помогает. Итак, мы должны принять файл и оттуда разобраться.

Так в чем проблема?

Проблема в том, что затем мы пытаемся создать двумерный массив, содержащий 2 миллиарда элементов, но это не очень хорошо.

Вот что происходит с 1 миллионом элементов (0,05% от 2 миллиардов):

Ситуация ухудшается по мере роста одного измерения, даже если вы сохраните постоянным общее количество элементов ...

Добавьте к этому стоимость потоковой передачи байтов из файла, их разбора на строки и проверки типа каждого элемента, и он начинает выходить из-под контроля.

Не говоря уже о физическом размере созданного объекта.

Даже упрощенная версия этой операции для массива, содержащего 1 миллион элементов, занимает около 1,5 секунд и весит около 20 МБ (см. Этот пост, чтобы узнать о функции get_size, которую я использовал.). Умножьте эти цифры на 2000, и вы поняли. Наше приложение просто не может обрабатывать файл с такими размерами. Это подводит нас к сути проблемы:

Как убедиться, что мы не собираемся загружать в память файл с 10 000 столбцов и 1 млн строк?

Простые внешние проверки размера файла больше не работают. Нам нужно принять файл и проверить его перед чтением его в некую структуру данных. Это подтолкнуло нас к поиску решения, которого просто не существовало.

Исследование

openpyxl - позволяет получать имена листов без загрузки файла, но не более того

xlrd - помимо того, что он устарел, он, похоже, не предлагал заглядывать в метаданные без загрузки файловых данных.

pyexcel-xlsr - казалось многообещающим, но парсер был привередлив и не работал с половиной файлов, которые мы пробовали

pyexcel-xlsx - предлагает интерфейс, который заставляет казаться ленивой загрузкой данных, но он использует openpyxl под капотом, и просмотр больших файлов был также проблематичным.

Как видите, ничто не делает то, что мы хотим. Именно в этот момент мы начали подумывать о том, чтобы просить наших пользователей прекратить использование файлов xlsx. К сожалению, это был не вариант. Одним словом, мы были безнадежны.

Открытие

Чтобы понять, возможно ли то, чего мы пытались достичь, я сделал несколько шагов назад и задал простой вопрос:

Что такое файл xlsx?

Оказывается, ответ примерно такой же простой. Когда Excel сохраняет файл с этим расширением .xlsx, все, что он сохраняет, - это набор файлов xml, заархивированных вместе с предсказуемой структурой.

Как только я это понял, мне оставалось просто найти конкретные метаданные, которые я так отчаянно искал.

Наконец-то я нашел размеры своего файла xlsx. Затем я написал простую библиотеку для анализа этих данных - она ​​быстрая, около 60 строк кода и использует только стандартную библиотеку. Он работает довольно легко при чтении из файловой системы или свойства files веб-формы.



Решение - сделай сам (или воспользуйся моей библиотекой)

Сам код настолько прост, что я разозлился, написав его. Все, что я делаю, это загружаю файл в объект ZipFile (стандартная библиотека), открываю конкретный файл sheet.xml, в который я хочу заглянуть, и использую простое регулярное выражение для получения размеры. Если по какой-то причине этот тег отсутствует в первых 1000 байтах, мы устанавливаем головку чтения обратно на 500 и пробуем следующие 1000.

Мораль истории

Как разработчик, вы слышите один из наиболее часто повторяемых советов: «Не изобретайте велосипед». В общем, это отличный совет. Однако иногда возникает проблема, и вы потратите недели на поиск существующей библиотеки, тогда как вы могли бы построить ответ за два часа в воскресенье вечером. Помните, когда проблема может попасть в эту категорию. Обычно это требует от вас большого количества поисков, но безрезультатно.