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.
Ü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.
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:
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.
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.
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.
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.
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.
Ü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