Форум программистов, компьютерный форум, киберфорум
nekit270
Войти
Регистрация
Восстановить пароль
Карта форума Блоги Сообщество Поиск Заказать работу  
Добро пожаловать, здесь я программирую бессмысленно и беспощадно
Рейтинг: 1.00. Голосов: 2.

Чтение из XLSX-файла с помощью Python

Запись от nekit270 размещена 11.12.2023 в 02:42
Метки excel, python, xlsx

Вдохновившись этой статейкой от sqltd1, решил написать на питончике скрипт для чтения данных из ячейки XLSX-файла.

Но не просто написать скрипт, а сделать это без внешних библиотек и всего прочего.


Формат XLSX

XLSX представляет собой zip-архив. Для извлечения значений нужны два файла из него:
/xl/worksheets/<лист>.xml - собственно лист
/xl/sharedStrings.xml - строки


Извлечение значений

Для распаковки использую модуль zipfile, а в качестве простенького XML-парсера подойдут регулярные выражения.

Код класса (42 строки, поэтому под спойлером):
Кликните здесь для просмотра всего текста

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import zipfile, re
 
class XlsxReader:
    __worksheet = None
    __strings = []
 
    def __init__(self, file, worksheet):
        # Открытие XLSX-файла как ZIP-архива
        with zipfile.ZipFile(file) as zip:
            # Чтение файла листа из архива
            self.__worksheet = zip.read(f'xl/worksheets/{worksheet}.xml').decode('utf-8')
 
            # Чтение файла хранилища строк из архива
            strings_file = zip.read('xl/sharedStrings.xml').decode('utf-8')
 
            # Добавление строк в список
            for match in re.finditer('\\<si\\>\\<t\\>([^\\<]+)\\</t\\>\\</si\\>', strings_file):
                self.__strings.append(match.group(1))
 
    def read(self, cell):
        # Если в документе есть строковые значения
        if len(self.__strings) > 0:
            # Поиск элемента <c r="ячейка" t="s"><v>индекс</v></c>
            s_match = re.search(f'\\<c r="{cell}" t="s"\\>\\<v\\>([^\\<]+)\\</v\\>\\</c\\>', self.__worksheet)
 
            # Если элемент найден
            if(s_match != None and len(s_match.groups()) > 0):
                # Найти строку в списке по индексу и вернуть
                return self.__strings[int(s_match.group(1))]
 
        # В документе нет строковых значений
 
        # Поиск элемента <c r="ячейка"><v>значение</v></c>
        n_match = re.search(f'\\<c r="{cell}"\\>\\<v\\>([^\\<]+)\\</v\\>\\</c\\>', self.__worksheet)
 
        # Если элемент найден
        if(n_match != None and len(n_match.groups()) > 0):
            # Вернуть его
            return n_match.group(1)
        
        # Если ничего не найдено, вернуть None
        return None


Пример использования:
Python
1
2
3
4
5
6
7
from xlsx_reader import XlsxReader
 
reader = XlsxReader('test.xlsx', 'sheet1')
print('A1', reader.read('A1'))
print('B2', reader.read('B2'))
print('B8', reader.read('B8'))
print('D6', reader.read('D6'))
Тестировал я на этом файле:
Нажмите на изображение для увеличения
Название: Снимок экрана 2023-12-11 022747.png
Просмотров: 104
Размер:	8.6 Кб
ID:	8395
test.xlsx

Результат:
Код:
A1 5
B2 92
B8 String2
D6 TestString

Версия без комментариев (для пущей экономии места и использования в качестве библиотеки):
Кликните здесь для просмотра всего текста

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import zipfile, re
 
class XlsxReader:
    __worksheet = None
    __strings = []
 
    def __init__(self, file, worksheet):
        with zipfile.ZipFile(file) as zip:
            self.__worksheet = zip.read(f'xl/worksheets/{worksheet}.xml').decode('utf-8')
 
            strings_file = zip.read('xl/sharedStrings.xml').decode('utf-8')
 
            for match in re.finditer('\\<si\\>\\<t\\>([^\\<]+)\\</t\\>\\</si\\>', strings_file):
                self.__strings.append(match.group(1))
 
    def read(self, cell):
        if len(self.__strings) > 0:
            s_match = re.search(f'\\<c r="{cell}" t="s"\\>\\<v\\>([^\\<]+)\\</v\\>\\</c\\>', self.__worksheet)
 
            if(s_match != None and len(s_match.groups()) > 0):
                return self.__strings[int(s_match.group(1))]
 
        n_match = re.search(f'\\<c r="{cell}"\\>\\<v\\>([^\\<]+)\\</v\\>\\</c\\>', self.__worksheet)
 
        if(n_match != None and len(n_match.groups()) > 0):
            return n_match.group(1)
        
        return None


Внимание! Даже если тип значения в XLSX-файле - не строка, возвращено будет значение типа str. Это сделано специально, чтобы вызывающий мог сам конвертировать его в нужный тип числа.

P.S. Обратите внимание, что, к примеру, "Лист 1" называется "sheet1" внутри XLSX-файла. Использовать нужно именно "внутреннее" название.
Размещено в Без категории
Показов 1196 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru