Разделите строку таблицы на 2 при очистке с помощью pandas read_html

#python #pandas #web-scraping #beautifulsoup #html-table

#python #pandas #веб-очистка #beautifulsoup #html-таблица

Вопрос:

Не удается получить правильный формат строки при использовании pandas read_html() . Я ищу корректировки либо самого метода, либо базового html (очищенного через bs4), чтобы получить желаемый результат.

Текущий вывод:

Текущий неправильный формат вывода
(обратите внимание, что это 1 строка, содержащая два типа данных. в идеале он должен быть разделен на 2 строки, как показано ниже)

Желаемый:

Желаемый формат вывода

код для репликации проблемы:

 import requests
import pandas as pd
from bs4 import BeautifulSoup  # alternatively

url = "http://ufcstats.com/fight-details/bb15c0a2911043bd"

df = pd.read_html(url)[-1]  # last table
df.columns = [str(i) for i in range(len(df.columns))]

# to get the html via bs4
headers = {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET",
    "Access-Control-Allow-Headers": "Content-Type",
    "Access-Control-Max-Age": "3600",
    "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0",
}
req = requests.get(url, headers)
soup = BeautifulSoup(req.content, "html.parser")
table_html = soup.find_all("table", {"class": "b-fight-details__table"})[-1]

 

Ответ №1:

Как (быстро) исправить с beautifulsoup

Вы можете создать a dict с заголовками из table , а затем выполнить итерацию по каждому td , чтобы добавить список значений, хранящихся в p :

 data = {}

header = [x.text.strip() for x in table_html.select('tr th')]

for i,td in enumerate(table_html.select('tr:has(td) td')):
    data[header[i]] = [x.text.strip() for x in td.select('p')]

pd.DataFrame.from_dict(data)
 

Пример

 import requests
import pandas as pd
from bs4 import BeautifulSoup  # alternatively

url = "http://ufcstats.com/fight-details/bb15c0a2911043bd"

# to get the html via bs4
headers = {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET",
    "Access-Control-Allow-Headers": "Content-Type",
    "Access-Control-Max-Age": "3600",
    "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0",
}
req = requests.get(url, headers)
soup = BeautifulSoup(req.content, "html.parser")
table_html = soup.find_all("table", {"class": "b-fight-details__table"})[-1]

data = {}

header = [x.text.strip() for x in table_html.select('tr th')]

for i,td in enumerate(table_html.select('tr:has(td) td')):
    data[header[i]] = [x.text.strip() for x in td.select('p')]

pd.DataFrame.from_dict(data)
 

Вывод

Истребитель Sig. str Sig. str. % Head Тело Нога Расстояние Клинч Основание
Джоанна Вуд 27 из 68 39% 8 из 36 3 из 7 16 из 25 26 из 67 1 из 1 0 из 0
Тайла Сантос 30 из 60 50% 21 из 46 3 из 7 6 из 7 19 из 42 0 из 0 11 из 18

Комментарии:

1. отличное решение, спасибо! Мне пришлось немного настроить его, чтобы получить правильный формат для таблиц с более чем 1 строкой. например, ваш код прерывается, когда url =» ufcstats.com/fight-details/18f19b1422e8154b «. внесенные мной корректировки: « из коллекций импортируйте данные defaultdict = defaultdict(список) заголовок = [x.text.strip() для x в table_html.select(‘tr th’)] i =0 для td в table_html.выберите(‘tr:имеет (td) td’): данные [заголовок[i]].расширьте([x.text.strip() для x в td.select(‘p’)]) i =1, если i==len(заголовок): i=0pd.DataFrame.from_dict(данные) «

Ответ №2:

Аналогичная идея использовать enumerate для определения количества строк, но использовать :-soup-contains для целевой таблицы, затем nth-child выбрать для извлечения соответствующей строки во время понимания списка. pandas чтобы преобразовать результирующий список списков в фрейм данных. Предполагается, что строки добавляются по тому же шаблону, что и текущие 2.

 from bs4 import BeautifulSoup as bs
import requests
import pandas as pd

r = requests.get('http://ufcstats.com/fight-details/bb15c0a2911043bd')
soup = bs(r.content, 'lxml')
table = soup.select_one(
    '.js-fight-section:has(p:-soup-contains("Significant Strikes"))   table')

df = pd.DataFrame(
    [[i.text.strip() for i in table.select(f'tr:nth-child(1) td p:nth-child({n 1})')]
     for n, _ in enumerate(table.select('tr:nth-child(1) > td:nth-child(1) > p'))], columns=[i.text.strip() for i in table.select('th')])

print(df)