ζ༼Ɵ͆ل͜Ɵ͆༽ᶘ

Руководство по сбору данных HTML-таблиц с помощью Pandas и BeautifulSoup

0 комментов
31.03.2022
6 мин чтения

Очень часто приходится сталкиваться с HTML-таблицами при сборе данных с веб-страницы, и без правильного подхода извлечь из них полезные и непротиворечивые данные может быть немного сложно.

В этой статье вы увидите, как выполнить быструю и эффективную очистку этих элементов с помощью двух основных различных подходов: используя только библиотеку Pandas и используя традиционную библиотеку парсинга BeautifulSoup.

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

pandas.read_html(): The Shortcut

Если все, что вам нужно, это получить несколько таблиц со страницы и ничего больше, вам даже не нужно настраивать для этого целый парсер, поскольку Pandas может выполнить эту работу самостоятельно. Функция pandas.read_html() использует некоторые библиотеки очистки, такие как BeautifulSoup и Urllib , для возврата списка, содержащего все таблицы на странице, в виде фреймов данных. Вам просто нужно передать URL-адрес страницы.

dfs = pd.read_html(url)

Все, что вам нужно сделать сейчас, это выбрать нужный DataFrame из этого списка:

df = dfs[4]

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

for df in dfs:
    if len(df) == 20:
        the_one = df
        break

… или по названию его столбцов, например.

for df in dfs:
    if df.columns == ['#', 'Team', 'MP', 'W', 'D', 'L', 'Points']:
        the_one = df
        break

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

Однако, если вы очищаете не только таблицы и используете, скажем, запросы для получения страницы, вам рекомендуется передать page.text в функцию вместо URL:

page = requests.get(url)
soup = BeautifulSoup(page.text, 'html.parser')

dfs = pd.read_html(page.text)

То же самое происходит, если вы используете веб-драйвер Selenium для получения страницы

dfs = pd.read_html(driver.page_source)

Это потому, что вы значительно сократите время, необходимое для запуска вашего кода, поскольку функции read_html() больше не нужно получать страницу. Проверьте среднее время, затраченное на сто повторений в каждом сценарии:

Using the URL:
Average time elapsed: 0.2345 seconds

Using page.text:
Average time elapsed: 0.0774 seconds

Получение элементов таблицы с помощью BeautifulSoup

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

Чтобы охватить это, нам сначала нужно понять стандартную структуру HTML-таблицы:

<table>
    <tr>
        <th>
        <th>
        <th>
        <th>
        <th>
        <th>
        <th>
    </tr>
    <tr>
        <td>
        <td>
        <td>
        <td>
        <td>
        <td>
        <td>
    </tr>
    <tr>
        <td>
        <td>
        <td>
        <td>
        <td>
        <td>
        <td>
    </tr>
.
.
.
</table>

Где tr означает «строка таблицы», th означает «заголовок таблицы» и td означает «табличные данные», где данные хранятся в виде текста.

Шаблон обычно полезен, поэтому все, что нам осталось сделать, это выбрать правильные элементы с помощью BeautifulSoup.

Первое, что нужно сделать, это найти table. Метод find_all() возвращает список всех элементов, удовлетворяющих требованиям, которые мы ему передаем. Затем мы должны выбрать нужную нам таблицу в этом списке:

table = soup.find_all('table')[4]

Например, в зависимости от веб-сайта необходимо будет указать класс или идентификатор таблицы.

Остальной процесс теперь почти интуитивно понятен, верно? Нам просто нужно выбрать все tr теги и текст в th, а также td теги внутри них. Мы могли бы просто использовать find_all() снова, чтобы найти все теги tr, да, но мы также можем перебирать эти теги более простым способом.

children атрибут возвращает итерируемый объект со всеми тегами прямо под родительским тегом, который является таблицей, поэтому он возвращает все теги tr. Поскольку это итерируемый объект, нам нужно использовать его как таковой.

После этого каждый child это tr тег. Нам просто нужно извлечь текст каждого td тег внутри него. Вот код всего этого:

for child in soup.find_all('table')[4].children:
    for td in child:
        print(td.text)

И процесс пошел! После этого у вас есть данные, которые вы искали, и вы можете манипулировать ими так, как вам удобно.

Другие возможности

Допустим, вас не интересует, например, заголовок таблицы. Вместо того, чтобы использовать children, вы можете выбрать первый tr тег, который содержит данные заголовка, и использовать next_siblings атрибут. Это, как и children атрибут, вернет итерируемый, но со всеми остальными tr теги, которые являются дочерними из первого, который мы выбрали. Тогда вы пропустите заголовок таблицы.

for sibling in soup.find_all('table')[4].tr.next_siblings:
    for td in sibling:
        print(td.text)

Возможности безграничны, поэтому обязательно ознакомьтесь с документацией BeautifulSoup, чтобы найти лучший вариант для вашего парсера.

Пример из реальной жизни

На данный момент мы написали очень простой код для извлечения HTML-таблиц с помощью Python. Однако, когда вы делаете это на самом деле, вам, конечно, придется учитывать некоторые другие проблемы.

Например, вам нужно знать, как вы собираетесь хранить свои данные. Будете ли вы напрямую писать это в текстовый файл? Или вы сохраните его в списке или в словаре, а затем создадите .csv файл? Или вы создадите пустой DataFrame и заполните его данными? Конечно, есть много возможностей. Мой выбор заключался в том, чтобы хранить все в большом списке списков, которые позже будут преобразованы в DataFrame и экспортированы как .csv файл.

В другой теме вы можете использовать некоторые try, а также except предложения в вашем коде, чтобы подготовить его к обработке некоторых исключений, которые могут возникнуть по пути. Конечно, вы также захотите вставить несколько случайных пауз, чтобы не перегружать сервер и не быть заблокированным.

В этом примере я очищал таблицу Премьер-лиги после каждого раунда за весь сезон 2019/20, используя большую часть того, что я рассмотрел в этой статье. Это весь код для него:

import pandas as pd
import numpy as np
import requests
from bs4 import BeautifulSoup
from time import sleep


def get_table(round, url=url):
    round_url = f'{url}/{round}'
    page = requests.get(round_url)
    soup = BeautifulSoup(page.text, 'html.parser')

    rows = []
    for child in soup.find_all('table')[4].children:
        row = []
        for td in child:
            try:
                row.append(td.text.replace('\n', ''))
            except:
                continue
        if len(row) > 0:
            rows.append(row)

    df = pd.DataFrame(rows[1:], columns=rows[0])
    return df


for round in range(1, 39):
    table = get_table(round)
    table.to_csv(f'PL_table_matchweek_{round}.csv', index=False)
    sleep(np.random.randint(1, 10))

Там есть все: сбор всех элементов в таблице с использованием атрибута children, обработка исключений, преобразование данных в DataFrame, экспорт файла .csv и приостановка кода на случайное количество секунд. После всего этого все данные, собранные этим кодом, создали эту интересную диаграмму:

Вы не найдете данные, необходимые для построения такой диаграммы, которые ждут вас в Интернете. Но в этом и прелесть парсинга: вы можете получить данные самостоятельно!

В заключение я надеюсь, что это было чем-то полезным, и что у вас больше никогда не возникнет проблем при очистке HTML-таблицы.

3
Сегодня
День улёта