[PYTHON] Comment créer un robot - Avancé

Relais de sortie LAPRAS C'est l'article du 7ème jour! salut! Voici @Chanmoro, un ingénieur de robot d'exploration LAPRAS!

L'autre jour, j'ai écrit un article intitulé Comment créer un robot --Basic, mais dans cet article, j'ai écrit un article intitulé «Comment faire un robot - Avancé». Je voudrais brièvement présenter le type de problèmes auxquels vous êtes confronté lorsque vous développez un robot et quel type de conception le robot peut transformer en un robot maintenable qui peut facilement résoudre ces problèmes. Je vais.

Exemples de problèmes courants dans le fonctionnement du robot

En passant, vous pouvez implémenter le robot d'exploration minimum par la méthode introduite dans Comment créer un robot d'exploration --Basic, mais à partir de là, vous pouvez implémenter le robot d'exploration minimum plus régulièrement. Lorsque vous démarrez l'opération d'exploration et de mise à jour répétées des données, vous rencontrez généralement les problèmes suivants.

Pour les développeurs de robots d'exploration, tous sont des problèmes qui donnent l'impression qu'il y a quelque chose, mais pour maintenir le robot en fonctionnement, il est nécessaire au moins de résoudre ces problèmes.

Problème de modification de la structure HTML de la destination

Le service sur lequel vous explorez est en constante évolution et un jour, la structure HTML peut changer soudainement et vous ne pourrez peut-être pas obtenir les données souhaitées. Selon la conception, il est fort possible qu'une valeur étrange soit obtenue et que la base de données soit écrasée, détruisant toutes les données accumulées jusqu'à présent.

Voici comment j'ai relevé ce défi jusqu'à présent:

--Valider la valeur obtenue par l'exploration

Valider la valeur obtenue par l'exploration

Ceci, peut-être la première chose à laquelle vous pourriez penser, est un moyen d'implémenter la validation sur les données extraites de la destination d'exploration. Pour les données dont le format et le type peuvent être limités, vous pouvez empêcher des données étranges d'entrer dans la base de données en implémentant des règles de validation. Cependant, la faiblesse de cette méthode est qu'elle ne peut pas être appliquée uniquement à certains éléments car les règles de validation ne peuvent pas être définies pour les données pour lesquelles un format spécifique ne peut pas être déterminé ou les données qui ne sont pas un élément obligatoire.

Exécuter périodiquement un test pour observer le service analysé à un point fixe

Cette méthode est une méthode pour détecter les changements dans la structure HTML en exécutant périodiquement un test pour voir si les données obtenues en accédant réellement au service point fixe observation = crawling sont la valeur attendue. Vous pouvez l'implémenter en écrivant un test pour un compte ou une page spécifique et en l'exécutant régulièrement.

scrapy a une fonctionnalité appelée contrat, qui vous permet d'accéder réellement à une URL spécifiée et d'écrire un test pour la réponse obtenue, ce qui m'a donné une idée.

Cependant, cette méthode est utile si vous pouvez créer un compte pour l'observation en virgule fixe ou préparer vous-même des données, sinon le test échouera à chaque fois que le compte ou la page cible est mis à jour. .. (En fonction des détails du test) De plus, il n'est pas possible d'écrire des tests rigoureux pour les données qui changent fréquemment, telles que les données de change.

Je vous ai montré deux façons d'aborder la structure HTML changeante de la destination explorée, mais bien sûr pas toutes.

Problème "Échec de la destination de l'exploration ou erreur temporaire"

Il est possible qu'une réponse d'erreur soit renvoyée temporairement en cas d'échec du côté du service d'analyse. Cela peut poser un problème si vous branchez le traitement du robot d'exploration en consultant le code d'état de la réponse.

Par exemple, si l'état de réponse est 404, il est déterminé que les données pertinentes ont été supprimées, et un processus de suppression des données pertinentes a été mis en œuvre. À ce stade, si 404 est temporairement renvoyé du côté du service d'analyse en raison d'un bogue de spécification ou d'une erreur, les données correspondantes ne sont pas réellement supprimées, mais le côté du robot détermine par erreur qu'il a été supprimé. J'ai un problème.

Pour résoudre ce problème, il est efficace d'attendre un moment et de réessayer de distinguer s'il s'agit d'une réponse temporaire.

Problème de "changements d'identité"

Si vous souhaitez analyser un service conçu pour inclure l'ID à analyser dans l'URL et que l'ID peut être modifié ultérieurement, vous souhaiterez peut-être traiter l'ID modifié comme les données avant la modification. Il y a. Par exemple, une analyse de service conçue pour vous permettre de modifier l'ID de l'utilisateur ou l'URL des données une fois publiées.

Certains services vous redirigeront 301, donc dans ce cas, vous pouvez comparer l'ancienne URL avec la nouvelle URL pour voir la correspondance d'identifiant. Gérer cela est relativement facile et peut être suivi en obtenant l'identifiant contenu dans l'URL après la redirection 301 et en mettant à jour les données. Notez que l '«id» des données de destination de l'exploration est variable et ne doit pas être traité comme un id sur le système du robot.

En outre, selon l'élément, l'ancienne URL sera 404 et vous ne connaissez peut-être pas la correspondance avec la nouvelle URL, vous devez donc supprimer les données de l'ancienne URL et attendre que les nouvelles données de la nouvelle URL soient ajoutées. C'est également possible.

Exprimer le robot d'exploration avec un code maintenable

Maintenant, j'imagine que la plupart des robots d'exploration seront confrontés aux trois problèmes que j'ai introduits jusqu'à présent.

[Code introduit](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) était une implémentation assez collante, donc je ne sais pas où chercher une implémentation qui résout les problèmes présentés ici. Hmm.

Ainsi, par exemple, divisons le processus du robot en plusieurs couches comme suit.

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:
        """
Une classe qui représente les données extraites de la page de liste d'articles
        """
        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:
        """
Une classe qui représente les données extraites de la page de détail de l'article
        """
        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]:
        """
Explorez LAPRAS NOTE pour obtenir toutes les données d'article
        """
        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]:
        """
Explorez la page de la liste d'articles pour obtenir toutes les URL de détails de l'article
        """
        print(f"Accessing to {start_url}...")
        # https://note.lapras.com/Accéder
        response = requests.get(start_url)
        response.raise_for_status()
        time.sleep(10)

        #Obtenez l'URL des détails de l'article à partir du code HTML de la réponse
        page_data = self.article_list_page_parser.parse(response.text)
        article_url_list = page_data.article_url_list

        #Obtenir s'il y a un lien sur la page suivante
        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:
        """
Explorez la page de détails de l'article pour obtenir les données de l'article
        """
        #Accéder aux détails de l'article
        print(f"Accessing to {url}...")
        response = requests.get(url)
        response.raise_for_status()

        time.sleep(10)
        #Obtenir des informations sur l'article à partir du HTML de réponse
        return self.article_detail_page_parser.parse(response.text)


def collect_lapras_note_articles_usecase(crawler: LaprasNoteCrawler):
    """
Récupérez toutes les données de l'article LAPRAS NOTE et enregistrez-les dans un fichier
    """
    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(),
    ))

Le code est ici. https://github.com/Chanmoro/lapras-note-crawler/blob/master/advanced/crawler.py

En séparant en trois couches, analyseur, robot d'exploration et cas d'utilisation, il devient plus clair d'apporter les modifications suivantes pour résoudre le problème introduit précédemment.

Résumé

D'ailleurs, dans cet article, intitulé «Comment faire un robot d'exploration avancé», nous avons développé les problèmes qui surviennent souvent lors de l'exploitation d'un robot d'exploration en continu et quel type de robot doit être conçu pour les résoudre. J'ai écrit à quel point c'est facile.

Cet article n'est qu'un exemple pour plus de clarté, je pense donc qu'il est nécessaire de concevoir davantage la conception pour répondre aux caractéristiques du service analysé et aux exigences du service qui souhaite utiliser les données analysées. Je vais.

Cependant, une telle conception ne se limite pas au robot d'exploration, et la plupart des discussions portent sur la plage de modélisation de données générale du système qui a une liaison de données avec des services externes, des API et des bibliothèques, donc plus vous développez le robot, plus vous en parlez réellement. N'y a-t-il pas beaucoup de conceptions spécifiques aux robots d'exploration? Je me sens comme ça.

Développement du robot d'exploration basé sur mon expérience de développement de robot d'exploration à deux reprises, en suivant le précédent Comment créer un robot d'exploration --Basic J'ai présenté la situation réelle de.

J'espère que cela sera utile pour ceux qui ont actuellement des difficultés à développer un robot ou qui souhaitent développer un robot dans le futur!

Veuillez profiter d'une bonne vie de développement de crawler!

Recommended Posts

Comment créer un robot - Avancé
Comment créer un robot - Basic
Comment faire une traduction japonais-anglais
Comment créer un bot slack
Comment créer une fonction récursive
[Blender] Comment créer un plug-in Blender
[Python] Comment rendre une classe itérable
Comment créer un indicateur personnalisé Backtrader
Comment créer un plan de site Pelican
Comment créer un système de dialogue dédié aux débutants
Comment créer un dictionnaire avec une structure hiérarchique.
Comment créer un plug-in QGIS (génération de package)
J'ai lu "Comment créer un laboratoire de piratage"
Comment appeler une fonction
Comment pirater un terminal
Comment faire un jeu de tir avec toio (partie 1)
Comment créer un package Python à l'aide de VS Code
Bases de PyTorch (2) -Comment créer un réseau de neurones-
Comment créer un BOT Cisco Webex Teams à l'aide de Flask
[Python] Comment créer une liste de chaînes de caractères caractère par caractère
Comment mettre un lien symbolique
Comment créer un jeu d'action multijoueur en ligne avec Slack
Comment créer un laboratoire de piratage - Kali Linux (2020.1) VirtualBox 64 bits Partie 2-
Comment créer un pont virtuel
Comment créer un laboratoire de piratage - Kali Linux (2020.1) VirtualBox 64-bit edition -
Comment créer un package Python (écrit pour un stagiaire)
Comment faire un simple jeu Flappy Bird avec Pygame
Comment créer un Dockerfile (basique)
[Blender] Comment rendre les scripts Blender multilingues
Comment supprimer un conteneur Docker
Comment créer un fichier de configuration
Comment transformer une chaîne en tableau ou un tableau en chaîne en Python
Comment faire une commande pour lire le fichier de paramètres avec pyramide
Comment créer une caméra de surveillance (caméra de sécurité) avec Opencv et Python
[Langage C] Comment créer, éviter et créer un processus zombie
Spigot (Paper) Introduction à la création d'un plug-in pour 2020 # 01 (Construction de l'environnement)
Comment créer une bibliothèque .dylib à partir d'une bibliothèque .a avec OSX (El Capitan)
Comment faire un test unitaire Part.1 Modèle de conception pour l'introduction
[Python] Comment créer une matrice de motifs répétitifs (repmat / tile)
Comment rendre les caractères de Word Cloud monochromatiques
Comment rendre le sélénium aussi léger que possible
Comment créer un clone depuis Github
Comment diviser et enregistrer un DataFrame
Comment créer un environnement de traduction sphinx
Qiita (1) Comment écrire un nom de code
Comment ajouter un package avec PyCharm
Comment dessiner un graphique avec Matplotlib
Comment utiliser FastAPI ② Advanced - Guide de l'utilisateur
[Python] Comment convertir une liste bidimensionnelle en liste unidimensionnelle
Faisons un noyau jupyter
[Colab] Comment copier un vaste ensemble de données
[Python] Comment inverser une chaîne de caractères
Comment installer un package à l'aide d'un référentiel
[Ubuntu] Comment exécuter un script shell
Comment obtenir stacktrace en python
[Cocos2d-x] Comment créer une liaison de script (partie 2)
Comment créer une clé USB à démarrage multiple (compatible Windows 10)
Comment créer un référentiel à partir d'un média