Максимально быстро получить необходимые данные из html таблицы используя pandas
Пытаюсь вытащить данные из *.html таблицы.
Сначала парсил используя BeautifulSoup, по времени в среднем 2сек. Думаю это долго.
Решил попробовать использовать pandas, но так как не доводилось им пользоваться ранее, возникает ряд вопросов.
Дано:
- HTML файл с множеством таблиц.
- Структура заголовков может меняться (например порядок, их количество или положение относительно тега table).
- Порядок строк и полей так же может меняться.
- Строка
Итогоможет как присутствовать так и отсутствовать.
Задача:
- Получить все
Артикулыи их количественные показатели из полей(Расход, Конечный остаток)в виде:
списка[['Артикул',float(Расход), float(Конечный остаток)]]
или
словаря{str(Артикул):[float(Расход), float(Конечный остаток)]}
Все это необходимо для последующей обработки.
Пример таблицы:
<table style="width:100%; height:0px; " cellspacing=0>
<col width=98>
<col width=490>
<col width=43>
<col width=84>
<col width=84>
<col width=84>
<col width=84>
<col>
<tr class=r3>
<td class="r6c0" colspan=3>Склад</td>
<td class="r6c0" colspan=4>Количество</td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r7>
<td class="r7c0">Артикул</td>
<td class="r7c0">Номенклатура</td>
<td class="r7c0">Ед. изм.</td>
<td class="r7c0">Начальный остаток</td>
<td class="r7c0">Приход</td>
<td class="r7c0">Расход</td>
<td class="r7c0">Конечный остаток</td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r8c0" colspan=3></td>
<td class="r8c3"><span style="white-space:nowrap;max-width:0px;">7 319,000</span></td>
<td class="r8c3"><span style="white-space:nowrap;max-width:0px;">2 569,000</span></td>
<td class="r8c3"><span style="white-space:nowrap;max-width:0px;">2 318,000</span></td>
<td class="r8c3"><span style="white-space:nowrap;max-width:0px;">7 570,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r9c0"><span></span></td>
<td class="r9c1">Категория 1</td>
<td class="r9c0"><span></span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">7,000</span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">2,000</span></td>
<td class="r9c3"><span></span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">9,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r10c0">AC753963</td>
<td class="r10c1">Продукт 1</td>
<td class="r10c0">шт</td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">1,000</span></td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">1,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r10c0">AX352094</td>
<td class="r10c1">Продукт 2</td>
<td class="r10c0">шт</td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">2,000</span></td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">2,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r9c0"><span></span></td>
<td class="r9c1">Категория 2</td>
<td class="r9c0"><span></span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">3 351,000</span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">923,000</span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">689,000</span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">3 585,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r9c0"><span></span></td>
<td class="r15c1">Категория 2.1</td>
<td class="r9c0"><span></span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">10,000</span></td>
<td class="r9c3"><span></span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">1,000</span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">9,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r10c0">ZF1201</td>
<td class="r16c1">Продукт 14</td>
<td class="r10c0">шт</td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">1,000</span></td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">1,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<!--...N-->
<tr class=r3>
<td class="r2240c0" colspan=3><span style="white-space:nowrap;max-width:0px;">Итого</span></td>
<td class="r2240c3"><span style="white-space:nowrap;max-width:0px;">7 319,000</span></td>
<td class="r2240c3"><span style="white-space:nowrap;max-width:0px;">2 569,000</span></td>
<td class="r2240c3"><span style="white-space:nowrap;max-width:0px;">2 318,000</span></td>
<td class="r2240c3"><span style="white-space:nowrap;max-width:0px;">7 570,000</span></td>
<td><span></span></td>
<td></td>
</tr>
</table>
Ответы (1 шт):
Автор решения: CrazyElf
→ Ссылка
У меня получился такой код на вашем примере:
import pandas as pd
def get_column(df, name):
rowcol = ~df[df == name].isna()
col = df.columns[rowcol.sum() > 0][0]
row = df[rowcol.sum(axis=1) > 0].index[0]
return row, col
def parse_vals(s):
return [str(s[0]), float(s[1]), float(s[2])]
html = '''
<table style="width:100%; height:0px; " cellspacing=0>
<col width=98>
<col width=490>
<col width=43>
<col width=84>
<col width=84>
<col width=84>
<col width=84>
<col>
<tr class=r3>
<td class="r6c0" colspan=3>Склад</td>
<td class="r6c0" colspan=4>Количество</td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r7>
<td class="r7c0">Артикул</td>
<td class="r7c0">Номенклатура</td>
<td class="r7c0">Ед. изм.</td>
<td class="r7c0">Начальный остаток</td>
<td class="r7c0">Приход</td>
<td class="r7c0">Расход</td>
<td class="r7c0">Конечный остаток</td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r8c0" colspan=3></td>
<td class="r8c3"><span style="white-space:nowrap;max-width:0px;">7 319,000</span></td>
<td class="r8c3"><span style="white-space:nowrap;max-width:0px;">2 569,000</span></td>
<td class="r8c3"><span style="white-space:nowrap;max-width:0px;">2 318,000</span></td>
<td class="r8c3"><span style="white-space:nowrap;max-width:0px;">7 570,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r9c0"><span></span></td>
<td class="r9c1">Категория 1</td>
<td class="r9c0"><span></span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">7,000</span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">2,000</span></td>
<td class="r9c3"><span></span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">9,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r10c0">AC753963</td>
<td class="r10c1">Продукт 1</td>
<td class="r10c0">шт</td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">1,000</span></td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">1,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r10c0">AX352094</td>
<td class="r10c1">Продукт 2</td>
<td class="r10c0">шт</td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">2,000</span></td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">2,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r9c0"><span></span></td>
<td class="r9c1">Категория 2</td>
<td class="r9c0"><span></span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">3 351,000</span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">923,000</span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">689,000</span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">3 585,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r9c0"><span></span></td>
<td class="r15c1">Категория 2.1</td>
<td class="r9c0"><span></span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">10,000</span></td>
<td class="r9c3"><span></span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">1,000</span></td>
<td class="r9c3"><span style="white-space:nowrap;max-width:0px;">9,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<tr class=r8>
<td class="r10c0">ZF1201</td>
<td class="r16c1">Продукт 14</td>
<td class="r10c0">шт</td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">1,000</span></td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span></span></td>
<td class="r10c3"><span style="white-space:nowrap;max-width:0px;">1,000</span></td>
<td><span></span></td>
<td></td>
</tr>
<!--...N-->
<tr class=r3>
<td class="r2240c0" colspan=3><span style="white-space:nowrap;max-width:0px;">Итого</span></td>
<td class="r2240c3"><span style="white-space:nowrap;max-width:0px;">7 319,000</span></td>
<td class="r2240c3"><span style="white-space:nowrap;max-width:0px;">2 569,000</span></td>
<td class="r2240c3"><span style="white-space:nowrap;max-width:0px;">2 318,000</span></td>
<td class="r2240c3"><span style="white-space:nowrap;max-width:0px;">7 570,000</span></td>
<td><span></span></td>
<td></td>
</tr>
</table>
'''
# читаем html
df = pd.read_html(html)[0]
# ищем столбцы и колонки заголовков
row_article, column_article = get_column(df, 'Артикул')
row_rashod, column_rashod = get_column(df, 'Расход')
row_rest, column_rest = get_column(df, 'Конечный остаток')
# проверка, что заголовки в одной строке
if not row_article == row_rashod == row_rest:
print('Что-то пошло не так! Строки заголовка:', row_article, row_rashod, row_rest)
# отрезаем верх таблицы до заголовков и оставляем только нужные столбцы
df = df.loc[row_article+1:, [column_article, column_rashod, column_rest]]
# убираем строки, где Артикул не указан
df = df.loc[~df[column_article].isna()]
# убираем строку с Итого
df = df.loc[~(df[column_article] == 'Итого')]
# переименовываем колонки в понятные названия
df.columns = ['Артикул', 'Расход', 'Конечный остаток']
print([parse_vals(x) for x in list(df.T.to_dict(orient='list').values())])
И такой результат:
[['AC753963', nan, 1000.0], ['AX352094', nan, 2000.0], ['ZF1201', nan, 1000.0]]
Все нужные приёмы обработки тут есть, можете использовать их с вариациями и на других таблицах.