#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.