Webscraper-Programmierung mit Python

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
okapirider
User
Beiträge: 8
Registriert: Samstag 9. Januar 2021, 14:27

Hallo zusammen,

eines vorweg: Ich bin blutiger Anfänger und wage mich mit einem 'ambitionierten' Projekt mit Python heran. Ich möchte einen Webscraper programmieren, der bestimmte Informationen von einer CS:GO-Statistik ausliest und in eine .csv überführt. Ich habe mithilfe von einigen Tutorials, die ich online gefunden habe und ein wenig Hilfe von stackoverflow schon ein funktionierendes Skript zusammenbekommen, das aber nicht ganz perfekt funktioniert.

Zur Ausgangslage: Ich möchte Daten von dieser Seite hier abgreifen https://www.hltv.org/stats/players?star ... 2023-02-26

Das geschieht mit folgendem Code:

Code: Alles auswählen

#dependencies
import streamlit as st
import requests
import pandas as pd
from bs4 import BeautifulSoup

st.set_page_config(page_title='CS:GO Players Market Values')
st.header('Display CS:GO Players Market Values below')
st.subheader('Who is the most expensive player?')

url = 'https://www.hltv.org/stats/players?startDate=2022-02-26&endDate=2023-02-26&matchType=Lan'

#empty array
player_list = []

#requesting and parsing the HTML file
response = requests.get(url)
#print(response.status_code)
soup = BeautifulSoup(response.text, 'html.parser')

#selecting the table
table = soup.find('table', class_ = 'stats-table')
#print(table)

#storing all rows into one variable
for player_data in table.find_all('tbody'):
    rows = player_data.find_all('tr')

#looping through the HTML table to scrape the data
    for row in rows:
        nick = row.find_all('td')[0].text
        team = [t.get('title') for t in row.find_all('td')[1].find_all('img')]
        maps = row.find_all('td')[2].text
        rounds = row.find_all('td')[3].text
        kddiff = row.find_all('td')[4].text
        killdeath = row.find_all('td')[5].text
        rating = row.find_all('td')[6].text
        #print(nick)
        #print(team)

        #sending scraped data to the empty array
        player_list.append({
            'Name': nick,
            'Team': team,
            'Maps': maps,
            'Rounds': rounds,
            'K/D diff': kddiff,
            'K/D': killdeath,
            'Rating': rating,
        })
        #print(player_list)

#creating a dataframe
df = pd.DataFrame(player_list)

#naming the columns
df.columns = ['Player', 'Team', 'Maps', 'Rounds', 'K/D D', 'K/D', 'Rating']

#exporting data into Excel
df.to_csv('PlayerData.csv')

print(df.to_markdown())
st.text(df.to_markdown())

Der Streamlit-Bestandteil dient nur für mich zur Visualisierung, der Fokus liegt erst mal auf die Funktionalität das in Excel zu überführen. So weit besteht auch die Grundfunktionalität und das Erzeugen mit der Excel-Tabelle mit den entsprechend Daten ist erfolgreich. Einzig die Spalte "Teams" bereitet mir Probleme, weil das Skript neben den aktuellen Teams der Spieler auch die früheren Teams liefert, siehe hier:

Bild

Daher ist meine Frage zunächst einmal wie ich den command: team = [t.get('title') for t in row.find_all('td')[1].find_all('img')] so modifizieren kann, dass ich nur das aktuelle Team erhalte. Im Quelltext der Ursprungswebsite ist das aktuelle team in dieser Form hinterlegt: <td class="teamCol" data-sort="Outsiders"> .

Hat da jemand eine Idee?

Weiterhin würde ich gerne zu den Spielern von anderen Websites weitere Informationen abrufen und mit in die .CSV schreiben. Ich würde z.B. gerne das Alter der Spieler über Liquipedia scrapen und die Anzahl der Follower von Twitter abrufen. Ich weiß, dass es grundsätzlich möglich sein sollte, das beides zu erreichen doch wie lautet da der Ansatz, dass ich beides Teil meines von meinem player_list [] Objekt werden lasse? Die größte Herausforderung, die ich hier sehe ist, dass ich die beiden Datensätze irgendwie verknüpfen muss, so dass dem richtigen Spieler die richtige Information von z.B. Liquipedia zugeordnet wird.

Für eure Hilfe bin ich sehr dankbar!
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Warum nennst Du erst die Spalten anders, und benennst sie später um, statt die Spalten gleich richtig zu benennen?
Ständig alle Elemente zu suchen, wenn man nur eines davon braucht, ist unsinn.
Und wenn Du nicht alle Teams willst, dann laß einfach die Schleife weg.

Code: Alles auswählen

COLUMNS = ['Player', 'Team', 'Maps', 'Rounds', 'K/D D', 'K/D', 'Rating']

players = []
rows = table.find('tbody').find_all('tr')
for row in rows:
    fields = row.find_all('td')
    player = {
        column: field.text
        for column, field in zip(COLUMNS, fields)
    }
    player['Team'] = fields[1].find('img').get('title')
    players.append(player)

df = pd.DataFrame(players)
df.to_csv('PlayerData.csv')
okapirider
User
Beiträge: 8
Registriert: Samstag 9. Januar 2021, 14:27

Sirius3 hat geschrieben: Sonntag 26. Februar 2023, 10:50 Warum nennst Du erst die Spalten anders, und benennst sie später um, statt die Spalten gleich richtig zu benennen?
Ständig alle Elemente zu suchen, wenn man nur eines davon braucht, ist unsinn.
Und wenn Du nicht alle Teams willst, dann laß einfach die Schleife weg.
Die Antwort auf solche Fragen lautet i.d.R., dass ich es nicht besser weiß und es deshalb so mache ;). Wie gesagt, bin ich Tutorials im Internet gefolgt, um den Code zusammen zu stellen.

Code: Alles auswählen

COLUMNS = ['Player', 'Team', 'Maps', 'Rounds', 'K/D D', 'K/D', 'Rating']

players = []
rows = table.find('tbody').find_all('tr')
for row in rows:
    fields = row.find_all('td')
    player = {
        column: field.text
        for column, field in zip(COLUMNS, fields)
    }
    player['Team'] = fields[1].find('img').get('title')
    players.append(player)

df = pd.DataFrame(players)
df.to_csv('PlayerData.csv')
Dein Vorschlag hat auf jeden Fall sehr geholfen und den gewünschten Output geliefert. Vielen Dank an dieser Stelle. Die Zahlen wurden in der CSV als Text gespeichert, so dass ich nun folgenden Zusatz integriert habe:

Code: Alles auswählen

df = pd.DataFrame(players)
df['Maps'] = df['Maps'].astype(int)
df['Rounds'] = df['Rounds'].astype(int)
df['K/D D'] = df['K/D D'].astype(int)
df['K/D'] = df['K/D'].astype(float)
df['Rating'] = df['Rating'].astype(float)
Das liefert dann die Werte im richtigen Format.
okapirider
User
Beiträge: 8
Registriert: Samstag 9. Januar 2021, 14:27

Noch eine abschließende Frage hierzu, weil ich ein Element (Flag) vergessen habe:

Ich möchte in der Spalte nach "Player" seine Nationalität anzeigen, wenn ich bei

Code: Alles auswählen

COLUMNS = ['Player', 'Flag', ...
hinzufüge, dann verrückt es die nachfolgenden Spalten um 1, so dass die Werte nicht mehr mit den Zeilen korrespondieren.

Mein Workaround wäre hier, 'Flag' an das Ende zu setzen aber das geht sicherlich auch eleganter, oder?

Code: Alles auswählen

#dependencies
import streamlit as st
import requests
import pandas as pd
from bs4 import BeautifulSoup

url = 'https://www.hltv.org/stats/players?startDate=2022-02-26&endDate=2023-02-26&matchType=Lan'

#requesting and parsing the HTML file
response = requests.get(url)
#print(response.status_code)
soup = BeautifulSoup(response.text, 'html.parser')

#selecting the table
table = soup.find('table', class_ = 'stats-table')
#print(table)

#naming the columns
COLUMNS = ['Player', 'Team', 'Maps', 'Rounds', 'K/D Diff', 'K/D', 'Rating', 'Flag']

players = []
rows = table.find('tbody').find_all('tr')
for row in rows:
    fields = row.find_all('td')
    player = {
        column: field.text
        for column, field in zip(COLUMNS, fields)
    }
    player['Team'] = fields[1].find('img').get('title')
    player['Flag'] = fields[0].find('img').get('title')
    players.append(player)

#creating a dataframe
df = pd.DataFrame(players,columns=COLUMNS)
df['Maps'] = df['Maps'].astype(int)
df['Rounds'] = df['Rounds'].astype(int)
df['K/D Diff'] = df['K/D Diff'].astype(int)
df['K/D'] = df['K/D'].astype(float)
df['Rating'] = df['Rating'].astype(float)

#exporting data into Excel
df.to_csv('PlayerData.csv')
df.to_excel('PlayerData.xlsx')

#print the data in console
print(df.to_markdown())
Hier der gesamte Code mit meinem Workaround.

Bild
okapirider
User
Beiträge: 8
Registriert: Samstag 9. Januar 2021, 14:27

Update:

Habe es nun so gelöst:

Code: Alles auswählen

#Move "Nationality" column to position 1
col = df.pop("Nationality")
df.insert(1, "Nationality", col)

def move_column_inplace(df, col, pos):
    col = df.pop(col)
    df.insert(pos, "Nationality", col)
Antworten