Анализатор HTML на Python (неназванный уровень)

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

#python #HTML #pandas #очистка веб-страниц #beautifulsoup

Вопрос:

Я работаю над скребком экрана для извлечения статистики футбола изwww.pro-football-reference.com. В настоящее время я очищаю страницу статистики основного игрока, а затем перехожу на их отдельную страницу с их статистикой по годам.

Я смог успешно реализовать этот процесс с моим первым набором игроков (квотербеки, используя таблицу прохождения). Однако, когда я попытался воссоздать процесс для возврата данных, я получаю дополнительный столбец в моем фрейме данных со значениями «Неназванный: x_level_0». Это мой первый опыт работы с HTML-данными, поэтому я не уверен, какую часть я пропустил, я просто предположил, что это будет тот же код, что и квотербэки.

Ниже приведен пример кода QB и правильный фрейм данных:

 import requests
import urllib.request
import time
from bs4 import BeautifulSoup
import pandas as pd
from pandas import DataFrame
import lxml
import re
import csv
p = 1

url = 'https://www.pro-football-reference.com'
year = 2020
maxp = 300

#Passing Data
r = requests.get(url  '/years/'   str(year)   '/passing.htm')
soup = BeautifulSoup(r.content, 'html.parser')
parsed_table = soup.find_all('table')[0]

results = soup.find(id='div_passing')
job_elems = results.find_all('tr')

df = []
LastNameList = []
FirstNameList = []


for i,row in enumerate(parsed_table.find_all('tr')[2:]):
        dat = row.find('td', attrs={'data-stat': 'player'})
        if dat != None:
            name = dat.a.get_text()
            print(name)
            stub = dat.a.get('href')
            

            #pos = row.find('td', attrs={'data-stat': 'fantasy_pos'}).get_text()
            #print(pos)

            # grab this players stats
            tdf = pd.read_html(url   stub)[1]
            for k,v in tdf.iterrows():
                #Scrape 2020 stats, if no 2020 stats move on
                try:
                    FindYear=re.search(".*2020.*",v['Year'])
                    if FindYear:
                        #If Year for stats is current year append data to dataframe

                        #get Name data
                        fullName = row.find('td', {'class':'left'})['csk']
                        findComma = fullName.find(',',0,len(fullName))
                        lName = fullName[0:findComma]
                        fName = fullName[findComma   1:len(fullName)]
                        
                        LastNameList.append(lName)
                        FirstNameList.append(fName)
                        #get basic stats
                        df.append(v)
                except:
                    pass
  

Этот вывод выглядит следующим образом:

 Philip Rivers
Year      2020
Age         39
Tm         IND
Pos         qb
No.         17
G            1
GS           1
  

Ниже приведен пример кода RB и неверный фрейм данных:

 import requests
import urllib.request
import time
from bs4 import BeautifulSoup
import pandas as pd
from pandas import DataFrame
import lxml
import re
import csv
p = 1


url = 'https://www.pro-football-reference.com'
year = 2020
maxp = 300

#Rushing Data
r = requests.get(url  '/years/'   str(year)   '/rushing.htm')
soup = BeautifulSoup(r.content, 'html.parser')
parsed_table = soup.find_all('table')[0]

results = soup.find(id='div_rushing')
job_elems = results.find_all('tr')

df = []
LastNameList = []
FirstNameList = []


for i,row in enumerate(parsed_table.find_all('tr')[2:]):
        dat = row.find('td', attrs={'data-stat': 'player'})
        if dat != None:
            name = dat.a.get_text()
            print(name)
            stub = dat.a.get('href')
            print(stub)
            

            #pos = row.find('td', attrs={'data-stat': 'fantasy_pos'}).get_text()
            #print(pos)

            # grab this players stats
            tdf = pd.read_html(url   stub)[1]
            for k,v in tdf.iterrows():
                print(v)
                #Scrape 2020 stats, if no 2020 stats move on
                try:
                    FindYear=re.search(".*2020.*",v['Year'])
                    print('found 2020')
                    if FindYear:
                        #If Year for stats is current year append data to dataframe

                        #get Name data
                        fullName = row.find('td', {'class':'left'})['csk']
                        findComma = fullName.find(',',0,len(fullName))
                        lName = fullName[0:findComma]
                        fName = fullName[findComma   1:len(fullName)]
                        
                        LastNameList.append(lName)
                        FirstNameList.append(fName)
                        #get basic stats
                        df.append(v)
                except:
                    pass
  

Этот вывод выглядит следующим образом:

 Unnamed: 0_level_0   Year       2020
Unnamed: 1_level_0   Age          26
Unnamed: 2_level_0   Tm          TEN
Unnamed: 3_level_0   Pos          rb
Unnamed: 4_level_0   No.          22
Games                G             1
                     GS            1
Rushing              Rush         31
                     Yds         116
                     TD            0
  

Пример URL, из которого извлекаются эти данные:https://www.pro-football-reference.com/players/J/JacoJo01.htm

И он выполняет ускоренную обработку и прием. Есть ли что-то еще, на что мне нужно обратить внимание, когда дело доходит до синтаксического анализа HTML?

Я попытался добавить index_col = 1 в мой tdf = pd.read_html (url заглушка)[1]. Однако это просто сгруппировало два значения в один столбец.

Любой вклад в это был бы весьма признателен. Если я могу предоставить какую-либо дополнительную информацию, пожалуйста, дайте мне знать.

Спасибо

Ответ №1:

Вы можете попробовать этот код для анализа таблицы прохождения для каждого игрока (теперь я получаю игроков изhttps://www.pro-football-reference.com/years/2020/passing.htm но вы можете передать ему любой URL-адрес проигрывателя:

 import requests 
from bs4 import BeautifulSoup


def scrape_player(player_name, player_url, year="2020"):
    out = []

    soup = BeautifulSoup(requests.get(player_url).content, 'html.parser')

    row = soup.select_one('table#passing tr:has(th:contains("{}"))'.format(year))
    if row:
        tds = [player_name]   [t.text for t in row.select('th, td')]
        headers = ['Name']   [th.text for th in row.find_previous('thead').select('th')]
        out.append(dict(zip(headers, tds)))

    return out

url = 'https://www.pro-football-reference.com/years/2020/passing.htm'
all_data = []
soup = BeautifulSoup(requests.get(url).content, 'html.parser')
for player in soup.select('table#passing [data-stat="player"] a'):
    print(player.text)
    for data in scrape_player(player.text, 'https://www.pro-football-reference.com'   player['href']):
        all_data.append(data)

df = pd.DataFrame(all_data)
df.to_csv('data.csv')
print(df)
  

Создает этот CSV:

введите описание изображения здесь


РЕДАКТИРОВАТЬ: Для анализа отправки и получения вы можете использовать этот скрипт:

 import requests 
from bs4 import BeautifulSoup, Comment


def scrape_player(player_name, player_url, year="2020"):
    out = []

    soup = BeautifulSoup(requests.get(player_url).content, 'html.parser')
    soup = BeautifulSoup(soup.select_one('#rushing_and_receiving_link').find_next(text=lambda t: isinstance(t, Comment)), 'html.parser')

    row = soup.select_one('table#rushing_and_receiving tr:has(th:contains("{}"))'.format(year))
    if row:
        tds = [player_name]   [t.text for t in row.select('th, td')]
        headers = ['Name']   [th.text for th in row.find_previous('thead').select('tr')[-1].select('th')]
        out.append(dict(zip(headers, tds)))

    return out

url = 'https://www.pro-football-reference.com/years/2020/passing.htm'
all_data = []
soup = BeautifulSoup(requests.get(url).content, 'html.parser')
for player in soup.select('table#passing [data-stat="player"] a'):
    print(player.text)
    for data in scrape_player(player.text, 'https://www.pro-football-reference.com'   player['href']):
        all_data.append(data)

df = pd.DataFrame(all_data)
df.to_csv('data.csv')
print(df)
  

Создает этот CSV:

введите описание изображения здесь

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

1. Большое вам спасибо! Это намного эффективнее и работает как мечта. Если вы не возражаете, можете ли вы подтвердить мое понимание того, как это работает? В scrape_player для переменной строки я устанавливаю тег данных, которые я хочу извлечь, а затем изменяю переменные в моем URL и soup.select, чтобы они были правильной статистикой, которую я ищу (передача, ускоренный запуск, получение и т.д., В зависимости от ее тега в HTML). Это невероятно!

2. @MCJNY1992 Да, каждая таблица имеет уникальный идентификатор, поэтому измените #passing на какую-нибудь другую таблицу, и она должна работать так же.

3. Должен ли я использовать значение между <h2></h2>? Ускоренная обработка и получение называются ускоренной обработкой и получением, что создает ошибку Python для недопустимого символа.

4. @MCJNY1992 Смотрите мою правку о том, как анализировать таблицу Rushing amp; Receiving (фактическая таблица хранится внутри HTML-комментария <!-- ... --> , поэтому вам нужно проанализировать ее оттуда).

5. Понятно, мне придется изучить это позже. Я переключился на то, чтобы больше не читать таблицу Rushing вместо таблиц Passing и добавил Try / Except в scrape players. Как ни странно, я все еще получаю только квотербеков, которые соответствуют таблице прохождения в моем фрейме данных. Я вижу, как он перебирает обратные вызовы из таблицы rushing, но каким-то образом мой фрейм данных оказывается только игроками из таблицы Passing.