[PYTHON] Wie erstelle ich einen Crawler?

LAPRAS Output Relay Dies ist der Artikel am 7. Tag! Hallo! Dies ist @Chanmoro, ein LAPRAS-Crawler-Ingenieur!

Neulich schrieb ich einen Artikel mit dem Titel Wie erstelle ich einen Crawler --Basic, aber in diesem Artikel schrieb ich einen Artikel mit dem Titel "Wie man einen Crawler erstellt - Advanced". Ich möchte kurz vorstellen, mit welchen Problemen Sie bei der ernsthaften Entwicklung eines Crawlers konfrontiert sind und welche Art von Design der Crawler zu einem wartbaren Crawler machen kann, der diese Probleme problemlos lösen kann. Ich werde.

Beispiele für häufig auftretende Probleme beim Crawler-Betrieb

Übrigens können Sie den minimalen Crawler mithilfe der in So erstellen Sie einen Crawler --Basic eingeführten Methode implementieren. Von dort aus können Sie den minimalen Crawler jedoch regelmäßiger implementieren. Wenn Sie mit dem wiederholten Crawlen und Aktualisieren von Daten beginnen, treten normalerweise die folgenden Probleme auf.

Für Crawler-Entwickler sind alle "Ja" -Probleme, aber zumindest müssen diese Probleme behoben werden, damit der Crawler in Betrieb bleibt.

Problem "Änderungen der Ziel-HTML-Struktur ändern"

Der Dienst, auf den Sie crawlen, ändert sich ständig, und eines Tages kann sich die HTML-Struktur plötzlich ändern und Sie können möglicherweise nicht die gewünschten Daten abrufen. Je nach Design ist es durchaus möglich, dass ein seltsamer Wert erhalten wird und die Datenbank überschrieben wird, wodurch alle bisher gesammelten Daten zerstört werden.

So habe ich bisher mit dieser Herausforderung umgegangen:

Überprüfen Sie den durch Crawlen erhaltenen Wert

Dies ist möglicherweise das erste, woran Sie denken könnten, um die Validierung der vom Crawling-Ziel abgerufenen Daten zu implementieren. Bei Daten, deren Format und Typ eingeschränkt werden können, können Sie verhindern, dass fremde Daten in die Datenbank gelangen, indem Sie Validierungsregeln implementieren. Die Schwäche dieser Methode besteht jedoch darin, dass sie nicht nur auf einige Elemente angewendet werden kann, da keine Validierungsregeln für Daten definiert werden können, für die ein bestimmtes Format nicht bestimmt werden kann, oder für Daten, für die kein Element erforderlich ist.

Führen Sie regelmäßig einen Test durch, um den gecrawlten Dienst an einem festen Punkt zu beobachten

Diese Methode ist eine Methode zum Erkennen von Änderungen in der HTML-Struktur, indem regelmäßig ein Test ausgeführt wird, um festzustellen, ob die Daten, die durch den tatsächlichen Zugriff auf den Festpunktbeobachtungsdienst = Crawling-Dienst erhalten werden, der erwartete Wert sind. Sie können es implementieren, indem Sie einen Test für ein bestimmtes Konto oder eine bestimmte Seite schreiben und ihn regelmäßig ausführen.

Scrapy hat eine Funktion namens "Vertrag", mit der Sie tatsächlich auf die angegebene URL zugreifen und einen Test für die erhaltene Antwort schreiben können. Daraus entstand die Idee.

Diese Methode ist jedoch nützlich, wenn Sie ein Konto für die Festkomma-Beobachtung erstellen oder Daten selbst vorbereiten können. Andernfalls schlägt der Test jedes Mal fehl, wenn das Zielkonto oder die Zielseite aktualisiert wird. .. (Abhängig von den Details des Tests) Darüber hinaus ist es nicht möglich, strenge Tests für Daten zu schreiben, die sich häufig ändern, z. B. Wechselkursdaten.

Ich habe Ihnen zwei Möglichkeiten gezeigt, wie Sie die sich ändernde HTML-Struktur des gecrawlten Ziels angehen können, aber natürlich nicht alle.

Problem "Fehler beim Crawlen des Ziels oder vorübergehender Fehler"

Es ist möglich, dass eine Fehlerantwort vorübergehend zurückgegeben wird, wenn auf der Seite des Crawlerdienstes ein Fehler auftritt. Dies kann ein Problem sein, wenn Sie die Verarbeitung des Crawlers anhand des Statuscodes der Antwort verzweigen.

Wenn der Antwortstatus beispielsweise 404 ist, wird festgestellt, dass die relevanten Daten gelöscht wurden, und ein Prozess zum Löschen der relevanten Daten wurde implementiert. Wenn zu diesem Zeitpunkt 404 aufgrund eines Spezifikationsfehlers oder eines Fehlers vorübergehend auf der Seite des Crawlerservices zurückgegeben wird, werden die entsprechenden Daten nicht tatsächlich gelöscht, aber die Crawlerseite stellt fälschlicherweise fest, dass sie gelöscht wurden. Ich habe ein Problem.

Um dieses Problem zu beheben, müssen Sie eine Weile warten und erneut versuchen, festzustellen, ob es sich um eine vorübergehende Antwort handelt.

Problem "ID-Änderungen"

Wenn Sie einen Dienst crawlen, der die zu crawlende ID in die URL aufnehmen soll und dessen ID später geändert werden kann, möchten Sie die geänderte ID möglicherweise genauso behandeln wie die Daten vor der Änderung. Es gibt. Crawlen Sie beispielsweise einen Dienst, mit dem Sie die ID des Benutzers oder die URL der einmal veröffentlichten Daten ändern können.

Einige Dienste leiten Sie 301 um. In diesem Fall können Sie die alte URL mit der neuen URL vergleichen, um die ID-Entsprechung anzuzeigen. Der Umgang damit ist relativ einfach und kann durch Abrufen der in der URL enthaltenen ID nach der 301-Umleitung und Aktualisieren der Daten erfolgen. Beachten Sie, dass die ID der Crawler-Zieldaten variabel ist und auf dem Crawlersystem nicht als ID behandelt werden sollte.

Abhängig vom Element lautet die alte URL 404, und Sie kennen möglicherweise nicht die Entsprechung zur neuen URL. Sie müssen daher die Daten der alten URL löschen und warten, bis die neuen Daten der neuen URL hinzugefügt werden. Es ist auch möglich.

Drücken Sie den Crawler mit wartbarem Code aus

Jetzt stelle ich mir vor, dass die meisten Crawler mit den drei Problemen konfrontiert sein werden, die ich bisher eingeführt habe.

[Eingeführter Code](https://qiita.com/Chanmoro/items/c972f0e9d7595eb619fe#python-%E3%81%A7%E3%83%99%E3%82%BF%E3%81%AB%E5%AE % 9F% E8% A3% 85% E3% 81% 99% E3% 82% 8B) war eine ziemlich klebrige Implementierung, daher weiß ich nicht, wo ich nach einer Implementierung suchen soll, die die hier vorgestellten Probleme behebt. Hmm.

Teilen wir zum Beispiel den Crawler-Prozess wie folgt in mehrere Ebenen auf.

import json
import time
import dataclasses
from typing import List, Optional

import requests
from bs4 import BeautifulSoup


@dataclasses.dataclass(frozen=True)
class ArticleListPageParser:
    @dataclasses.dataclass(frozen=True)
    class ArticleListData:
        """
Eine Klasse, die die von der Artikellistenseite abgerufenen Daten darstellt
        """
        article_url_list: List[str]
        next_page_link: Optional[str]

    @classmethod
    def parse(self, html: str) -> ArticleListData:
        soup = BeautifulSoup(html, 'html.parser')
        next_page_link = soup.select_one("nav.navigation.pagination a.next.page-numbers")

        return self.ArticleListData(
            article_url_list=[a["href"] for a in soup.select("#main div.post-item h2 > a")],
            next_page_link=next_page_link["href"] if next_page_link else None
        )


@dataclasses.dataclass(frozen=True)
class ArticleDetailPageParser:
    @dataclasses.dataclass(frozen=True)
    class ArticleDetailData:
        """
Eine Klasse, die die von der Artikeldetailseite abgerufenen Daten darstellt
        """
        title: str
        publish_date: str
        category: str
        content: str

    def parse(self, html: str) -> ArticleDetailData:
        soup = BeautifulSoup(html, 'html.parser')
        return self.ArticleDetailData(
            title=soup.select_one("h1").get_text(),
            publish_date=soup.select_one("article header div.entry-meta").find(text=True, recursive=False).replace("|", ""),
            category=soup.select_one("article header div.entry-meta a").get_text(),
            content=soup.select_one("article div.entry-content").get_text(strip=True)
        )


@dataclasses.dataclass(frozen=True)
class LaprasNoteCrawler:
    INDEX_PAGE_URL = "https://note.lapras.com/"
    article_list_page_parser: ArticleListPageParser
    article_detail_page_parser: ArticleDetailPageParser

    def crawl_lapras_note_articles(self) -> List[ArticleDetailPageParser.ArticleDetailData]:
        """
Crawlen Sie LAPRAS HINWEIS, um alle Artikeldaten abzurufen
        """
        return [self.crawl_article_detail_page(u) for u in self.crawl_article_list_page(self.INDEX_PAGE_URL)]

    def crawl_article_list_page(self, start_url: str) -> List[str]:
        """
Durchsuchen Sie die Artikellistenseite, um alle Artikeldetail-URLs abzurufen
        """
        print(f"Accessing to {start_url}...")
        # https://note.lapras.com/Zugreifen
        response = requests.get(start_url)
        response.raise_for_status()
        time.sleep(10)

        #Rufen Sie die Artikeldetail-URL aus dem Antwort-HTML ab
        page_data = self.article_list_page_parser.parse(response.text)
        article_url_list = page_data.article_url_list

        #Holen Sie sich, wenn es einen Link auf der nächsten Seite gibt
        while page_data.next_page_link:
            print(f'Accessing to {page_data.next_page_link}...')
            response = requests.get(page_data.next_page_link)
            time.sleep(10)
            page_data = self.article_list_page_parser.parse(response.text)
            article_url_list += page_data.article_url_list

        return article_url_list

    def crawl_article_detail_page(self, url: str) -> ArticleDetailPageParser.ArticleDetailData:
        """
Durchsuchen Sie die Artikeldetailseite, um die Artikeldaten abzurufen
        """
        #Greifen Sie auf Artikeldetails zu
        print(f"Accessing to {url}...")
        response = requests.get(url)
        response.raise_for_status()

        time.sleep(10)
        #Artikelinformationen aus Antwort-HTML abrufen
        return self.article_detail_page_parser.parse(response.text)


def collect_lapras_note_articles_usecase(crawler: LaprasNoteCrawler):
    """
Holen Sie sich alle Daten des LAPRAS NOTE-Artikels und speichern Sie sie in einer Datei
    """
    print("Start crawl LAPRAS NOTE.")
    article_list = crawler.crawl_lapras_note_articles()

    output_json_path = "./articles.json"
    with open(output_json_path, mode="w") as f:
        print(f"Start output to file. path: {output_json_path}")
        article_data = [dataclasses.asdict(d) for d in article_list]
        json.dump(article_data, f)
        print("Done output.")

    print("Done crawl LAPRAS NOTE.")


if __name__ == '__main__':
    collect_lapras_note_articles_usecase(LaprasNoteCrawler(
        article_list_page_parser=ArticleListPageParser(),
        article_detail_page_parser=ArticleDetailPageParser(),
    ))

Der Code ist hier. https://github.com/Chanmoro/lapras-note-crawler/blob/master/advanced/crawler.py

Durch die Aufteilung in drei Ebenen, Parser, Crawler und Anwendungsfall, wird es klarer, die folgenden Änderungen vorzunehmen, um das zuvor eingeführte Problem zu beheben.

Zusammenfassung

Übrigens haben wir in diesem Artikel mit dem Titel "Wie man einen Crawler-Advanced macht" die Probleme entwickelt, die häufig auftreten, wenn ein Crawler kontinuierlich betrieben wird, und welche Art von Crawler dafür ausgelegt sein sollte. Ich habe darüber geschrieben, wie einfach es ist.

Dieser Artikel ist nur ein Beispiel für Klarheit. Ich denke, es ist notwendig, das Design weiter zu entwickeln, um die Eigenschaften des gecrawlten Dienstes und die Anforderungen des Dienstes zu erfüllen, der die gecrawlten Daten verwenden möchte. Ich werde.

Ein solches Design ist jedoch nicht auf den Crawler beschränkt, und der größte Teil der Diskussion befasst sich mit dem allgemeinen Datenmodellierungsbereich des Systems, das Daten mit externen Diensten, APIs und Bibliotheken verknüpft. Je weiter Sie den Crawler entwickeln, desto mehr sprechen Sie tatsächlich darüber. Gibt es nicht viele Crawler-spezifische Designs? Ich fühle mich so.

Crawler-Entwicklung basierend auf meiner Erfahrung mit der zweimaligen Entwicklung von Crawlern gemäß den vorherigen Erstellen eines Crawlers --Basic Ich habe die aktuelle Situation von vorgestellt.

Ich hoffe, es ist nützlich für diejenigen, die derzeit Probleme mit der Entwicklung von Crawlern haben oder in Zukunft Crawler entwickeln möchten!

Bitte genießen Sie ein gutes Crawler-Entwicklungsleben!

Recommended Posts

Wie erstelle ich einen Crawler?
Wie erstelle ich einen Crawler?
Wie erstelle ich eine japanisch-englische Übersetzung?
Wie man einen lockeren Bot macht
So erstellen Sie eine rekursive Funktion
[Blender] So erstellen Sie ein Blender-Plug-In
[Python] Wie man eine Klasse iterierbar macht
So erstellen Sie einen benutzerdefinierten Backtrader-Indikator
Wie erstelle ich eine Pelican Site Map?
Wie man ein Dialogsystem für Anfänger erstellt
So erstellen Sie ein Wörterbuch mit einer hierarchischen Struktur.
So erstellen Sie ein QGIS-Plug-In (Paketerzeugung)
Ich las "Wie man ein Hacking Lab macht"
So rufen Sie eine Funktion auf
Wie man ein Terminal hackt
Wie man ein Schießspiel mit toio macht (Teil 1)
So erstellen Sie ein Python-Paket mit VS Code
Grundlagen von PyTorch (2) - Wie erstelle ich ein neuronales Netzwerk?
So erstellen Sie mit Flask einen BOT für Cisco Webex-Teams
[Python] So erstellen Sie eine Liste von Zeichenfolgen Zeichen für Zeichen
So setzen Sie einen symbolischen Link
Wie erstelle ich ein Multiplayer-Online-Actionspiel mit Slack?
So erstellen Sie ein Hacking-Labor - Kali Linux (2020.1) VirtualBox 64-Bit Teil 2-
So erstellen Sie eine virtuelle Brücke
So erstellen Sie ein Hacking-Labor - Kali Linux (2020.1) VirtualBox 64-Bit-Edition -
Wie erstelle ich ein Python-Paket (geschrieben für Praktikanten)
Wie man ein einfaches Flappy Bird-Spiel mit Pygame macht
Wie erstelle ich eine Docker-Datei?
[Blender] So erstellen Sie Blender-Skripte mehrsprachig
So löschen Sie einen Docker-Container
So erstellen Sie eine Konfigurationsdatei
So machen Sie einen String in Python zu einem Array oder ein Array zu einem String
So erstellen Sie einen Befehl zum Lesen der Einstellungsdatei mit Pyramide
So erstellen Sie eine Überwachungskamera (Überwachungskamera) mit Opencv und Python
[C-Sprache] So erstellen, vermeiden und erstellen Sie einen Zombie-Prozess
Spigot (Papier) Einführung in die Erstellung eines Plug-Ins für 2020 # 01 (Umgebungskonstruktion)
So erstellen Sie eine .dylib-Bibliothek aus einer .a-Bibliothek mit OSX (El Capitan)
So führen Sie einen Komponententest durch Teil 1 Entwurfsmuster zur Einführung
[Python] Wie erstelle ich eine Matrix aus sich wiederholenden Mustern (repmat / tile)
So machen Sie Word Cloud-Zeichen monochromatisch
Wie man Selen so leicht wie möglich macht
So erstellen Sie einen Klon aus Github
So teilen und speichern Sie einen DataFrame
So erstellen Sie eine Sphinx-Übersetzungsumgebung
Qiita (1) Wie schreibe ich einen Codenamen?
So fügen Sie ein Paket mit PyCharm hinzu
So zeichnen Sie ein Diagramm mit Matplotlib
Verwendung von FastAPI ② Erweitert - Benutzerhandbuch
[Python] So konvertieren Sie eine zweidimensionale Liste in eine eindimensionale Liste
Machen wir einen Jupyter-Kernel
[Colab] So kopieren Sie einen riesigen Datensatz
[Python] So invertieren Sie eine Zeichenfolge
So installieren Sie ein Paket mithilfe eines Repositorys
[Ubuntu] So führen Sie ein Shell-Skript aus
Wie bekomme ich Stacktrace in Python?
[Cocos2d-x] So erstellen Sie eine Skriptbindung (Teil 2)
So machen Sie Multi-Boot-USB (Windows 10-kompatibel)
So erstellen Sie ein Repository aus Medien