Время прочтения: 4 мин.
В наше время все крупные организации (в особенности, работающие в розничной сфере бизнеса) владеют огромным массивом структурированной информации в виде баз данных, электронных таблиц и т.п. В них содержится история развития организации и ее взаимодействия с клиентами: различная контактная информация, совершенные транзакции, осуществленные активности работников, плановые показатели, технические логи т.д. и т.п.
Зачастую для специалиста не составляет труда провести анализ того или иного массива информации, открыв его в табличном редакторе либо сформировав прямой запрос в БД по условиям выборки. Но на практике аналитики данных нередко встречают задачи по структурированию нетабличных текстовых данных, в т.ч. имеющих их табличное отображение, основанное на коде разметки веб-страниц, но не являющимися таблицами в полном смысле этого слова (html, htm и т.п.). Именно с таким видом представления некоторых ежедневных форм нетабличных отчетных данных мы столкнулись в своей работе. О том, как мы решили подобную задачу, параллельно знакомясь с технологией парсинга, расскажем в этой статье.
Для начала предлагаем разобраться в объекте исследования — веб странице и ее коде разметки.
HTML (от англ. HyperText Markup Language — «язык гипертекстовой разметки») — стандартизированный язык разметки документов во Всемирной паутине. Большинство веб-страниц содержат описание разметки на языке HTML (или XHTML). Язык HTML интерпретируется браузерами. Полученный в результате интерпретации форматированный текст отображается на экране монитора компьютера или мобильного устройства. (из Википедии)
Рассмотрим пример таблицы в формате html:
Номер операции | Дата | ФИО | Сумма | Контрагент | Комментарии |
1 | 11.02.2019 | Иванов И.А. | 1233,15 | ООО Ромашка | за лепесток |
2 | 11.02.2019 | Степанов А.О. | 15000,00 | ЗАО Котелок | за дрова |
3 | 11.02.2019 | Лебедева А.Ш. | 7500,50 | ООО Ромашка | |
4 | 11.03.2019 | Вавилов Н.З. | 13,44 | ПАО Связькопыта | возврат |
Код гипертекстовой разметки данной таблицы будет выглядеть так:
<!DOCTYPE html>
<html>
<head>
<title>Отчет по платежам</title>
</head>
<body>
<p style="text-align: center;">Отчет по платежам</p>
<table>
<tbody>
<tr>
<td>Номер операции</td>
<td>Дата</td>
<td>ФИО</td>
<td>Сумма</td>
<td>Контрагент</td>
<td>Комментарии</td>
</tr>
<tr>
<td>1</td>
<td>11.02.2019</td>
<td>Иванов И.А.</td>
<td>1233,15</td>
<td>ООО Ромашка</td>
<td>за лепесток</td>
</tr>
<tr>
<td>2</td>
<td>11.02.2019</td>
<td>Степанов А.О.</td>
<td>15000,00</td>
<td>ЗАО Котелок</td>
<td>за дрова</td>
</tr>
<tr>
<td>3</td>
<td>13.02.2019</td>
<td>Лебедева А.Ш.</td>
<td>7500,50</td>
<td>ООО Ромашка</td>
<td> </td>
</tr>
<tr>
<td>4</td>
<td>17.03.2019</td>
<td>Вавилов Н.З.</td>
<td>13,44</td>
<td>ПАО Связьтранскопыта</td>
<td>возврат</td>
</tr>
</tbody>
</table>
</body>
</html>
Из кода видно, что за структуру таблицы в HTML отвечают так называемые тэги:
- <table></table> — ограничивающие собой саму таблицу;
- <tr></tr> — ограничивающие собой строки таблицы;
- <td></td> — ограничивающие собой ячейки строк.
Теперь перед нами возникает задача: как привести этот закодированный текст в удобный нам табличный вид? И как это сделать если таких файлов формируется десятки (сотни) в день, а анализировать нам надо месяцы (или даже годы)? Здесь нам на помощь приходит Python и его модуль парсера “BS4 (beautifulsoup4)”.
Парсер (англ. parser; от parse — анализ, разбор), или синтаксический анализатор, — часть программы, преобразующей входные данные (как правило, текст) в структурированный формат.
Для успешного решения этой задачи нам потребуется передать для анализа в модуль BS4 Python таблицу ограниченную тегами <table>, предварительно внедрив в код сам модуль:
from bs4 import BeautifulSoup
import csv # модуль для итогового экспорта данных в csv формат
Сначала следует часть типового решения Python по работе с файлом: интересующий нас файл открывается Python, “зачитывается” полностью в переменную и передается модулю bs4.
filename = # путь до файла
infile = io.open(filename, ‘r’) # открывается файл в режиме чтения
data = infile.read() # “чтение” данных из файла в переменную
infile.close() # закрытие файла
soup = BeautifulSoup(‘’.join(data), ‘lxml’) # преобразование данных файла в формат BS4 с учетом предустановок ‘lxml’, ‘html’ и т.п. и передача их в переменную soup
Следующим этапом следует определение самой таблицы в общей массе кода по тегу <Table> и сведение всех ячеек в один список.
rows = [ ]
table_data = soup.find(‘table’) # находим массив данных ограниченных <table>
for td in tabledata.find_all(‘td’):
rows.append(td.text.strip()) # формируем список из значений каждой пары <td></td>, т.е. всех ячеек
Сформированный общий список значений всех ячеек нам надо вернуть по строкам
rows_upd = {} # создаем словарь, ключом которого будет номер строки, значение - список из значений ячеек первой строки
i = 0
while len(rows) > 0:
i += 1
rows_upd[i] = rows[:6] # формируем строки в словаре
del rows[:6] # удаляем переданные в словарь строки из общей массы
Теперь полученный словарь с данными таблицы мы можем с уверенностью подвергнуть анализу как средствами самого Python так и экспортировать в файл, например, в формат csv.
path = # путь к файлу экспорта формата csv
with open(path, ‘a’, newline=’’) as csvfile: # открытие для записи файла csv
writer = csv.writer(csvfile, delimiter=’;’) # определение формата файла, разделителя столбцов
for key in rows_upd.keys():
writer.writerow(rows_upd[key]) # последовательная запись каждой строки словаря согласно ранее сформированного ключа
В конечном итоге мы получаем текстовый файл формата csv с разделителями, позволяющий нам экспортировать данные в любой табличный редактор или базу данных.
Указанный вариант реализации парсинга текстовых отчетов описан в упрощенном виде. Нами он успешно используется при импорте ежедневных текстовых отчетов за период путем дополнительной организации циклов поочередного чтения файлов самим модулем по настройкам вида и структуры отчетов.