Klassen und Methoden strukturieren

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
DrRocket
User
Beiträge: 30
Registriert: Freitag 11. Mai 2018, 15:11

Hallo zusammen,

zurzeit arbeite ich an meinem ersten Projekt, einem Webcrawler. Dabei muss ich Daten, die ich aus der Website extrahiere, formatieren. Würdet ihr die Logik dazu in eine neue Klasse auslagern? Geht speziell um den Teil "for element_inner2" im nachfolgenden Code:

Code: Alles auswählen

def data_crawler():

    # Browser soll im Hintergrund laufen und nicht bei jedem Aufruf eine neue Instanz erzeugt werden
    options = Options()
    options.headless = True

    # "options" funktioniert nur mit firefox
    driver = webdriver.Firefox(options=options)

    for element in link_crawler():
        time.sleep(randint(5, 15))
        driver.get(element)
        soup_data = BeautifulSoup(driver.page_source, "lxml")
        link = element

        with open("soup_data.txt", mode="a", encoding="utf-8", newline="") as file:
            file.write(soup_data.prettify())

        for element_inner1 in soup_data.select(".single-detail "):
            product_id = re.findall(r"\d+", element_inner1.select_one("div .product-id").text.strip())
            titel = element_inner1.select_one(".product h1").text.strip()

        for element_inner2 in soup_data.select("ul[class='product-list list-line'] > li"):
            if "Erstzulassung" in element_inner2.find_all("span")[0].text:
                erstzulassung = element_inner2.find_all("span")[1].text
            elif "Kilometerstand" in element_inner2.find_all("span")[0].text:
                kilometerstand = element_inner2.find_all("span")[1].text
            elif "Kraftstoff" in element_inner2.find_all("span")[0].text:
                kraftstoff = element_inner2.find_all("span")[1].text
            elif "Getriebe" in element_inner2.find_all("span")[0].text:
                getriebe = element_inner2.find_all("span")[1].text
            elif "Bruttolistenpreis" in element_inner2.find_all("span")[0].text:
                bruttolistenpreis = element_inner2.find_all("span")[1].text
            elif "Kaufpreis" in element_inner2.find_all("span")[0].text:
                kaufpreis = element_inner2.find_all("span")[1].text
            elif "Anzahlung" in element_inner2.find_all("span")[0].text:
                anzahlung = element_inner2.find_all("span")[1].text
            elif "Laufzeit" in element_inner2.find_all("span")[0].text:
                laufzeit = element_inner2.find_all("span")[1].text
            elif "Rate (brutto)" in element_inner2.find_all("span")[0].text:
                rate_brutto = element_inner2.find_all("span")[1].text
            else:
                continue
Also sowas wie:

Code: Alles auswählen

class DataNormalizer:
	def erstzulassung(self, erstzlassnung):
		...LOGIK hier...
Und den DataNormalizer dann für jede zu formatierende Eigenschaft im DataFetcher aufrufen?
DrRocket
User
Beiträge: 30
Registriert: Freitag 11. Mai 2018, 15:11

Im Grunde geht es hier um den Programmierstil. Ist es besser die Logik für die Formatierung der einzelnen Elemente in der "for element_inner2" auszulagern oder soll diese diekt in der if / elif Bedingung stehen? Was ist der richtige Stil?
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ungetestet:

Code: Alles auswählen

def get_product_info(soup):
    product_selector = "ul[class='product-list list-line'] > li"
    categories = {"Erstzulassung", "Kilometerstand", ...}
    product_info = {}
    for match in soup.select(product_selector).find_all("span"):
        category = match[0].text
        if category in categories:
            product_info[category] = match[1].text
    return product_info
Noch effizienter ist es, den Selector und die gewünschten Kategorien als Argumente mitzugeben, weil sie dann nicht jedes Mal erneut erstellt werden müssen.

Übrigens werden hier auch die überflüssigen find_all()-Aufrufe vermieden, denn einer reicht ja aus.

Und eine Klasse würde ich bei diesem Entwicklungsstand nicht nehmen. Welchen Vorteil würde das deiner Meinung nach bringen?

Über das Ermitteln der ID bin ich auch gestolpert. Dir ist klar, dass re.findall() eine Liste liefert? Kann es sein, dass du eigentlich das hier willst?

Code: Alles auswählen

product_id = re.search("\d+", text).group(0)
Auch dies lässt sich natürlich in meine vorgeschlagene Funktion einbauen, sodass auch diese Info im Dictionary steht.
DrRocket
User
Beiträge: 30
Registriert: Freitag 11. Mai 2018, 15:11

@snafu:

Code: Alles auswählen

product_id = re.search("\d+", text).group(0)
Ja, genau das wollte ich haben. Bei den regulären Ausdrücken bin ich noch recht am Anfang und kannte bis dato group() nicht. So wie ich das verstanden habe, nimmt group(0) alles was dem dem regulären Ausdruck entspricht (hier "\d+", also alle Ziffern im text) und gruppiert das zu einem Wert, der dann zurückgegeben werden kann oder?
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

DrRocket hat geschrieben: Mittwoch 5. Dezember 2018, 10:50 So wie ich das verstanden habe, nimmt group(0) alles was dem dem regulären Ausdruck entspricht (hier "\d+", also alle Ziffern im text) und gruppiert das zu einem Wert, der dann zurückgegeben werden kann oder?
Sagen wir mal so: Reguläre Ausdrücke geben oft mehrere Gruppen als Ergebnis. Die erste Gruppe (Zählung beginnt bei Null) entspricht quasi dem gesamten regulären Ausdruck, wie du schon richtig erkannt hast. Meistens will man die auch und man kann die Null auch weglassen, also einfach nur group() schreiben.

Bezüglich "gruppiert das zu einem Wert": Soweit ich weiß, besteht die Gruppierung bereits, wenn das Match-Objekt erstellt wurde. Der group()-Aufruf dient dann nur noch zum Abfragen der gewünschten Gruppe.

search() funktioniert halt so, dass es den ersten passenden Treffer liefert. findall() hingegen liefert alle passenden Treffer als Liste. Dann gibt es noch match(), welches im Unterschied zu search() nur einen Treffer gibt, wenn der Ausdruck auf den Textanfang passt. match() ist beim Validieren von Nutzereingaben ganz gut, aber zum Aufspüren von bestimmten Mustern ist oftmals search() besser.

Mal als Beispiel zum Unterschied zwischen search() und match() und der Verwendung von group():

Code: Alles auswählen

text = 'Product ID 0815'
print(re.search(r'\d+', text).group())
m = re.match(r'Product ID (\d+)', text)
print(m.group(0))
print(m.group(1))
DrRocket
User
Beiträge: 30
Registriert: Freitag 11. Mai 2018, 15:11

Klasse, danke für die ausführliche Erklärung.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@DrRocket: zur Logik, wo was gemacht werden soll, `Normalisierung` hört sich so an, als ob das generell nötig ist, um mit den Daten zu arbeiten. Dann sollte das schon beim Einlesen gemacht werden. Datenkonvertierung zu einem bestimmten Zweck, erst dort, wo sie gebraucht wird. Um das aber genauer Beurteilen zu können, müßtest Du noch genauer beschreiben, was Du eigentlich machen möchtest.
Antworten