@laraxxmara: Noch ein paar Anmerkungen zum Quelltext: Die Einrückung ist nicht konsequent. Üblich sind vier Leerzeichen pro Ebene.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Namen sollten nicht kryptisch abgekürzt und/oder nummeriert werden. `p`, `f`, und `w` sind keine guten Namen, weil sie dem Leser nichts darüber verraten was der Wert dahinter im Kontext des Programms bedeutet. Wenn man `response` meint, sollte man nicht nur `res` schreiben.
`kompetenz_liste` wird definiert, aber nirgends verwendet. Grunddatentypen haben auch nichts in Namen verloren, weil man den Typ während der Entwicklung nicht selten mal ändert, und dann hat man entweder falsche, irreführende Namen im Quelltext oder muss alle betroffenen Namen anpassen.
Die Antwort vom Webserver sollte man vor dem Verarbeiten auf den Status prüfen, also ob das eine erfolgreiche Antwort mit den angeforderten Daten war oder ob der Körper der Antwort eine Fehlerseite enthält.
Beim abfragen und parsen der Webseiten wiederholt sich ein bisschen Code den man IMHO schon sinnvoll in eine eigene Funktion auslagern kann.
”Parallele” Datenstrukturen, also beispielsweise Listen bei denen Elemente mit dem gleichen Index zusammengehören sind keine gute Idee, weil das in der Regel zu mehr Code bei der Verarbeitung führt und fehleranfällig ist. Das fängt beim Scrapen damit an, dass man hofft wenn man die gesamte Seite separat nach zwei zusammengehörenden Kriterien durchsucht, die Listen am Ende a) gleich viele Elemente enthalten, und b) dass die dann auch tatsächlich paarweise zusammengehören. So etwas kann leicht ins Auge gehen. Statt auf der Einstiegsseite Links und Firmennamen getrennt zu suchen, würde man besser die Elemente suchen in denen diese beiden Informationen zusammengefasst sind, und im weiteren dann in jedem dieser Elemente die beiden zusammengehörenden Daten raussuchen.
Mit `links` wird dann ganz ohne Not noch eine dritte Liste parallel zu `angebote` und `firma` erstellt, die man so nicht erstellen würde. `firma` ist als Namen falsch, weil irreführend, weil der Wert gar nicht für *eine* Firma steht, sondern für mehrere Firmen.
Falls man `links` denn erstellen *würde*, könnte man das deutlich kompakter mit einer „list comprehension“ machen:
Code: Alles auswählen
links = []
for angebot in angebote:
link = (angebot.get("href"))
links.append(link)
# =>
links = [angebot.get("href") for angebot in angebote]
``for i in range(len(sequence)):`` um dann mit `i` auf die Elemente von `sequence` zuzugreifen ist in Python ein „anti pattern“, weil man direkt über die Elemente von `sequence` iterieren kann, ohne den Umweg über einen Laufindex. Wenn man über mehrere Objekte ”parallel” iterieren möchte, gibt es die `zip()`-Funktion. Wobei man die hier nur braucht, weil unschönerweise vorher drei ”parallele” Listen erstellt werden, was wie gesagt keine so gute Idee ist.
Die Wirkung von `strip()` ist offensichtlich nicht beziehungsweise falsch verstanden worden. Die beiden folgenden Zeilen haben genau den gleichen Effekt:
Code: Alles auswählen
firmenbezeichnung = firma[i].text.strip("\n").strip(" \n\n").strip(",")
# <=>
firmenbezeichnung = firma[i].text.strip("\n ,")
Falls das überraschend sein sollte, bitte noch mal in der Dokumentation nachlesen was `strip()` genau macht.
Wobei das Komma hier aktuell auch gar keinen Unterschied macht. Hast Du ein Beispiel wo Du tatsächlich vorne oder hinten am Text für den Firmennamen ein Komma hast/hattest?
Das entfernen eines " bei den Profiltextabschnitten macht keinen Sinn. Ich denke Du hast da irgendwie versucht ein Missverständnis zu bereinigen das dadurch entsteht, dass in der CSV der gleiche Trenner für Felder verwendet wird, den Du hier zum Trennen der Abschnitte innerhalb eines einzelnen Feldes verwendest. Die " in der CSV kommen nicht aus den Daten der Webseite, sondern sorgen dafür, dass das Feld mit den Feldtrennern im Inhalt eindeutig lesbar bleibt und da nicht zusätzliche Felder entstehen.
Ich frage mich ob da tatsächlich für jedes Jobangebot mehrere Datensätze erstellt werden sollen? Eines pro Abschnitt auf der Detailseite. Und dann auch noch ohne jegliche Informationen was das jeweils für ein Abschnitt ist.
Zwischenstand:
Code: Alles auswählen
#!/usr/bin/env python3
import csv
import requests
from bs4 import BeautifulSoup
URL = "https://www.stepstone.de/jobs/Junior-Consultant.html"
CSV_FILENAME = "Stepstone_Kompetenzen_Juniorberater.csv"
SEPARATOR = ";"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:39.0)"
}
def get_soup(url):
response = requests.get(url, headers=HEADERS)
response.raise_for_status()
return BeautifulSoup(response.text, "html.parser")
def main():
with open(CSV_FILENAME, "w", newline="", encoding="utf-8") as csv_file:
writer = csv.writer(csv_file, delimiter=SEPARATOR)
writer.writerow(["Jobangebot", "Firma", "Kompetenzen"])
for job_element in get_soup(URL).select(".job-element"):
link_element = job_element.select_one(".job-element__url")
angebotsbezeichnung = link_element.text.strip()
firmenname = job_element.select_one(
".job-element__body__company"
).text.strip()
#
# TODO Sollen hier wirklich mehrere Datensätze pro Jobangebot
# erstellt werden?
#
for profil_abschnittselement in get_soup(
link_element.get("href")
).select(".at-section-text-profile-content"):
writer.writerow(
[
angebotsbezeichnung,
firmenname,
profil_abschnittselement.get_text(
strip=True, separator=SEPARATOR
),
]
)
print("Datei siehe Download Menuleiste als:", CSV_FILENAME)
if __name__ == "__main__":
main()
Von der Länge her ist die `main()`-Funktion noch vertretbar, aber es könnte trotzdem Sinn machen die Eingabe, also in diesem Fall das auslesen der Informationen aus der/den Webseite(n) von der Ausgabe, also das speichern als CSV-Datei, zu trennen. So liessen sich die beiden Teile einfacher separat testen und auch austauschen. Falls man beispielsweise auch Daten von einem anderen Portal scrapen will und/oder die Daten (auch) als JSON oder in einer Datenbank speichern möchte.