Web Crawler Fehler auf letzter Seite

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,

ich bin mal wieder am üben und habe ein Problem mit dem Abruf einer mehrseitigen Webseite mit meinem Crawler. Hier paar Details:
  • Insgesamt gibt es 6 seiten auf dem Website
  • Link zu nächsten Seite ist immer unter: <a href="index.php?page=2" class="btn btn-primary">Zur nächsten Seite!</a>
  • Sobald es diesen Link nicht mehr gibt, soll das Programm stoppen und die bisher gesammelten Werte ausgeben
  • Diesen Fall habe ich auf unterschiedliche Weisen versucht nachzustellen
  • bspw. mit while next_page is not None or "" or False
  • oder while next_page.attrs["href"] is not None or "" or False
  • oder noch komplett andere Ansätze wie eine If Bedingung, andere Logik etc.
  • Bisher ohne Erfolg. Das Programm bricht ab, da auf der letzten Seite folgende Fehlermeldung kommt: AttributeError: 'NoneType' object has no attribute 'attrs'
  • hat jemand einen Tipp, wie ich diese abfangen kann, so dass das Programm vernünfig beendet?

Code: Alles auswählen

import requests
from bs4 import BeautifulSoup
# urljoin baut weiterführende links aus reletiven pfadangaben zusammen
# angegeben muss der initiale link, wobei nur die base als root genommen wird unabhängig ob die url die erste Seite ist
# oder bereits tiefer in der Hierarchie
from urllib.parse import urljoin

# um eine Wartezeit zwischen den einzelnen Durchläufen einer Schleife zu definieren, muss Modul "tine" eingebunden werden
import time

class CrawledArticle:
    def __init__(self, title, emoji, content, image):
        self.title = title
        self.emoji = emoji
        self.content = content
        self.image = image


class ArticleFetcher:
    def fetch(self):
        url = "http://python.beispiel.programmierenlernen.io/index.php"
        next_page = "init"
        while next_page is not None or "" or False:
            r = requests.get(url)
            doc = BeautifulSoup(r.text, "html.parser")

            # für alle Elemente der Klasse "card" wird eine Schleife erzeugt
            articles = []
            next_page = doc.select_one(".navigation .btn-primary")
            print(next_page)
            for element in doc.select(".card"):
                # die Klassenbezeichnung in der Schleife bezieht sich nur auf Elemente innerhalb der Schleifendefinition
                # (Klasse "card" in dem Beispiel)
                emoji = element.select_one(".emoji").text
                content = element.select_one(".card-text").text
                # mehrere span Elemente in .card-title
                # mit "select" als Liste ausgeben und entsprechendes Element als Index definieren. .text nur Inhalt ausgeben
                title = element.select(".card-title span")[1].text
                image = urljoin(url, element.select_one("img").attrs["src"])
                # Objekt mit jedem Scheifendurchlauf an Liste anhängen, wodurch man die einzelnen Ergebnisse außerhalb der
                # Schleife benutzen kann
                crawled = CrawledArticle(title, emoji, content, image)
                articles.append(crawled)

                # Außerhalb der Schleife müssen die Ergebnisse jedes Durchlaufs an eine Liste gehängt werden
                # geht so nur mit jedem Schleifendurchlauf
                # print(crawled.title)
            url = urljoin(url, next_page.attrs["href"])
            print(url)
        return articles

ausgabe = ArticleFetcher()
ausgabe_liste = ausgabe.fetch()
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast eine falsche Vorstellung was `next_page is not None or "" or False` bedeutet, nämlich, mit Klammern geschrieben `(next_page is not None) or "" or False` und da weder "" noch False jemals war ist, auch kurz `next_page is not None`. Korrekter wäre `next_page not in (None, "", False)` wobei Du hier die Frage beantworten solltest, welche Werte next_page überhaupt annehmen kann. Zwei der drei Möglichkeiten können nie eintreten.

Die Bedingung auf next_page ist auch an der falschen Stelle. Statt next_page mit einem Dummy-Wert zu belegen, dass die while-Schleife überhaupt startet, solltest Du eine while-True-Schleife benutzen, und an der passenden Stelle per if und break die Schleife verlassen. Wo wäre denn die richtige Zeile für solch eine if-Abfrage?

`articles` wird an der falschen Stelle initialisiert.
DrRocket
User
Beiträge: 30
Registriert: Freitag 11. Mai 2018, 15:11

Ok, danke für die Klarstellung. Leider ist der Fehler der selbe auch wenn ich Deine Hinweise umsetzte. Kernproblem bei meiner Frag ist folgendes:

  • next_page.attrs["href"] nimmt am Ende (wenn es keine weiteren Seiten mehr zum crawlen gibt) den Wert "None" an.
  • Trotz if Bedingung wo explizit gesagt wird, dass sobald next_page.attrs["href"] den Wert "None" hat, die Schleife verlassen werden soll, passiert dies nicht
  • Stattdessen wird die unten aufgeführte Fehlermeldung ausgeworfen
Fehlermeldung beim Versuch next_page.attrs["href"] für eine nicht mehr existierende Seite mit der Basis-URL zu joinen:

Code: Alles auswählen

Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "C:\Program Files\JetBrains\PyCharm 2018.1.2\helpers\pydev\_pydev_bundle\pydev_umd.py", line 194, in runfile
    pydev_imports.execfile(filename, global_vars, local_vars)  # execute the script
  File "C:\Program Files\JetBrains\PyCharm 2018.1.2\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "C:/Users/Home/OneDrive/Python_Uebungen/crawler.py", line 55, in <module>
    ausgabe_liste = ausgabe.fetch()
  File "C:/Users/Home/OneDrive/Python_Uebungen/crawler.py", line 47, in fetch
    print(next_page.attrs["href"])
AttributeError: 'NoneType' object has no attribute 'attrs'
  • Ich "printe" den Wert von "next_page.attrs["href"]" bei jedem Durchlauf"
  • Vor dem Fehler ist der Wert "None"
  • Liegt es daran, dass attrs["href"] hier garnicht existiert? Muss ich die Bedingung auf einen anderen Wert anpassen? Probiert habe ich bisher"", False und None.
Vermutlich hast Du recht und ich habe hier ein Grundlegendes Verständnisproblem. Aber was genau stimmt an meinem Code nicht und warum wird die Schleife nicht verlassen?

hier nochmal der aktuelle Code, falls einer mal selber testen möchte:

Code: Alles auswählen

import requests
from bs4 import BeautifulSoup
# urljoin baut weiterführende links aus reletiven pfadangaben zusammen
# angegeben muss der initiale link, wobei nur die base als root genommen wird unabhängig ob die url die erste Seite ist
# oder bereits tiefer in der Hierarchie
from urllib.parse import urljoin

# um eine Wartezeit zwischen den einzelnen Durchläufen einer Schleife zu definieren, muss Modul "tine" eingebunden werden
import time

class CrawledArticle:
    def __init__(self, title, emoji, content, image):
        self.title = title
        self.emoji = emoji
        self.content = content
        self.image = image


class ArticleFetcher:
    def fetch(self):
        url = "http://python.beispiel.programmierenlernen.io/index.php"
        while True:
            r = requests.get(url)
            doc = BeautifulSoup(r.text, "html.parser")

            # für alle Elemente der Klasse "card" wird eine Schleife erzeugt
            articles = []
            next_page = doc.select_one(".navigation .btn-primary")
            print(next_page)
            for element in doc.select(".card"):
                # die Klassenbezeichnung in der Schleife bezieht sich nur auf Elemente innerhalb der Schleifendefinition
                # (Klasse "card" in dem Beispiel)
                emoji = element.select_one(".emoji").text
                content = element.select_one(".card-text").text
                # mehrere span Elemente in .card-title
                # mit "select" als Liste ausgeben und entsprechendes Element als Index definieren. .text nur Inhalt ausgeben
                title = element.select(".card-title span")[1].text
                image = urljoin(url, element.select_one("img").attrs["src"])
                # Objekt mit jedem Scheifendurchlauf an Liste anhängen, wodurch man die einzelnen Ergebnisse außerhalb der
                # Schleife benutzen kann
                crawled = CrawledArticle(title, emoji, content, image)
                articles.append(crawled)

                # Außerhalb der Schleife müssen die Ergebnisse jedes Durchlaufs an eine Liste gehängt werden
                # geht so nur mit jedem Schleifendurchlauf
            # print(next_page.attrs["href"])
            if next_page.attrs["href"] in (None, "", False):
                return articles
            else:
                url = urljoin(url, next_page.attrs["href"])
                print(url)


ausgabe = ArticleFetcher()
ausgabe_liste = ausgabe.fetch()
DrRocket
User
Beiträge: 30
Registriert: Freitag 11. Mai 2018, 15:11

Habe es eben selbst rausgefunden. bei der If-Bedingung habe ich mit einem BeautifulSoup Objekt gearbeitet, welches anscheinend nicht richtig initialisiert werden konnte, da die angefragte Eigenschaft nicht existiert (keine weiteren Webseiten mehr da). Ich habe dieses Objekt durch die Variable next_page ersetzt und damit geht es. Trotzdem danke für die Hilfe!
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Nochmal die Frage, welchen der Werte kann `next_page` überhaupt annehmen None, "" oder False? Und warum prüfst Du auf die beiden anderen, die es nie annehmen kann?

Das Ergebnis ist nicht das, was Du haben möchtest. Du bekommst nur die Ergebnisse der letzten Seite.

Die Klasse ArticleFetcher ist unsinnig, da sie nur aus einer Methode besteht. Schreib das als einfache Funktion.
Antworten