[PYTHON] Comment accélérer la belle instanciation de soupe

C'est le résultat lorsque la vitesse d'exécution du bot de recherche d'images à l'aide de BeautifulSoup a été améliorée. J'espère que cela sera utile pour ceux qui sont en difficulté car la vitesse d'exécution du grattage est lente.

Comment faire

environnement

scénario

Vous pouvez accélérer en spécifiant un code de caractère approprié dans l'argument de BeautifulSoup: ** from_encoding **.

from urllib import request
import bs4

page = request.urlopen("https://news.yahoo.co.jp/")
html = page.read()
# from_Remplacez le code de caractère du site à gratter dans l'encodage(Dans le cas de Yahoo News cette fois, utf-8)
soup = bs4.BeautifulSoup(html, "html.parser", from_encoding="utf-8")

Comment vérifier le code de caractère

Fondamentalement, il est écrit après charset = dans la balise meta.

<!--Dans le cas de Yahoo News-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

Comparaison du temps d'exécution

Je l'ai vérifié avec le script suivant. Mesuré avant et après la création d'une instance

verification_bs4.py


from urllib import request as req
from urllib import parse
import bs4
import time
import copy

url = "https://news.yahoo.co.jp/"
page = req.urlopen(url)
html = page.read()
page.close()

start = time.time()
soup = bs4.BeautifulSoup(html, "html.parser")
print('{:.5f}'.format(time.time() - start) + "[s] html.parser, None")

start = time.time()
soup = bs4.BeautifulSoup(html, "lxml")
print('{:.5f}'.format(time.time() - start) + "[s] lxml, None")

start = time.time()
hoge = copy.copy(soup)
print('{:.5f}'.format(time.time() - start) + "[s] copy(lxml, None)")

start = time.time()
soup = bs4.BeautifulSoup(html, "html.parser", from_encoding="utf-8")
print('{:.5f}'.format(time.time() - start) + "[s] html.parser, utf-8")

start = time.time()
soup = bs4.BeautifulSoup(html, "lxml", from_encoding="utf-8")
print('{:.5f}'.format(time.time() - start) + "[s] lxml, utf-8")

start = time.time()
hoge = copy.copy(soup)
print('{:.5f}'.format(time.time() - start) + "[s] copy(lxml, utf-8)")

start = time.time()
soup = bs4.BeautifulSoup(html, "lxml", from_encoding="utf-16")
#La valeur de retour est vide car le code de caractère est différent
print('{:.5f}'.format(time.time() - start) + "[s] lxml, utf-16")

Le résultat de sortie est ici.

% python verification_bs4.py
2.10937[s] html.parser, None
2.00081[s] lxml, None
0.04704[s] copy(lxml, None)
0.03124[s] html.parser, utf-8
0.03115[s] lxml, utf-8
0.04188[s] copy(lxml, utf-8)
0.01651[s] lxml, utf-16

Résumé

En spécifiant le code de caractère dans ** from_encoding **, nous avons pu accélérer l'instanciation. En regardant le code qui dit que BeautifulSoup est lent, je ne l'ai pas assigné à from_encoding, donc je pense que c'est la cause.

Pour ceux qui ont le temps

Je me demandais pourquoi il avait de telles spécifications, alors j'ai vérifié le code source. Cependant, je ne touche généralement pas autant à Python, donc j'écris peut-être quelque chose qui ne tient pas compte. Le code source est ici

Raison d'être en retard

Probablement à cause de la classe ** EncodingDetector ** définie dans ** bs4 / dammit.py **. Ce qui suit est un extrait de code partiel.

class EncodingDetector:
    """Suggests a number of possible encodings for a bytestring.

    Order of precedence:

    1. Encodings you specifically tell EncodingDetector to try first
    (the override_encodings argument to the constructor).

    2. An encoding declared within the bytestring itself, either in an
    XML declaration (if the bytestring is to be interpreted as an XML
    document), or in a <meta> tag (if the bytestring is to be
    interpreted as an HTML document.)

    3. An encoding detected through textual analysis by chardet,
    cchardet, or a similar external library.

    4. UTF-8.

    5. Windows-1252.
    """
    @property
    def encodings(self):
        """Yield a number of encodings that might work for this markup.

        :yield: A sequence of strings.
        """
        tried = set()
        for e in self.override_encodings:
            if self._usable(e, tried):
                yield e

        # Did the document originally start with a byte-order mark
        # that indicated its encoding?
        if self._usable(self.sniffed_encoding, tried):
            yield self.sniffed_encoding

        # Look within the document for an XML or HTML encoding
        # declaration.
        if self.declared_encoding is None:
            self.declared_encoding = self.find_declared_encoding(
                self.markup, self.is_html)
        if self._usable(self.declared_encoding, tried):
            yield self.declared_encoding

        # Use third-party character set detection to guess at the
        # encoding.
        if self.chardet_encoding is None:
            self.chardet_encoding = chardet_dammit(self.markup)
        if self._usable(self.chardet_encoding, tried):
            yield self.chardet_encoding

        # As a last-ditch effort, try utf-8 and windows-1252.
        for e in ('utf-8', 'windows-1252'):
            if self._usable(e, tried):
                yield e

Si vous traduisez le commentaire écrit au début du cours, il ressemblera à ceci (traduction DeepL)

    """"Nous suggérons quelques encodages possibles pour les chaînes d'octets.

L'ordre de priorité est le suivant.

    1.L'encodage que vous avez demandé à EncodingDetector d'essayer en premier
Remplacement d'argument du constructeur_Utilisez des encodages).

    2.L'encodage déclaré dans la chaîne d'octets elle-même.
Déclaration XML(Lorsque la chaîne d'octets est interprétée comme XML)
document), Ou<meta>Dans le tag(Chaîne d'octets
Interprété comme un document HTML)。

    3.L'encodage détecté par l'analyse de texte par Charde.
Utilisez cchardet ou une bibliothèque externe similaire.

    4. 4.UTF-8。

    5. Windows-1252。
    """

En déduisant des commentaires et du traitement, je pense qu'il est lent car il est en cours de traitement jusqu'à ce que la liste 1 à 5 ci-dessus réussisse dans l'ordre. En regardant 2, le code de caractère devinant à partir de la balise meta mentionnée plus tôt est également fait automatiquement, donc je pense que c'est une considération pour que vous puissiez l'utiliser sans spécifier le code de caractère en regardant la source du site Web. Cependant, lors du grattage, je pense que je vérifie généralement le code source, donc je ne pense pas qu'il devrait être si tard. (Nous n'avons pas vérifié quel processus est le goulot d'étranglement, alors veuillez me donner quelqu'un.)

Pourquoi la copie est rapide

Dans le script de mesure du temps d'exécution précédent, la méthode copy.copy () est utilisée pour dupliquer l'instance, et la raison pour laquelle c'est si rapide est dans \ _ \ _ copy \ _ \ _ de bs4 / __ init__.py. Ce qui suit est un extrait de code partiel.

__init__.py


class BeautifulSoup(Tag):

    def __copy__(self):
        """Copy a BeautifulSoup object by converting the document to a string and parsing it again."""
        copy = type(self)(
            self.encode('utf-8'), builder=self.builder, from_encoding='utf-8'
        )

        # Although we encoded the tree to UTF-8, that may not have
        # been the encoding of the original markup. Set the copy's
        # .original_encoding to reflect the original object's
        # .original_encoding.
        copy.original_encoding = self.original_encoding
        return copy

C'est plus rapide parce que j'ai choisi utf-8 ici. Cependant, au contraire, si le code de caractère du site de grattage est autre que utf-8, il sera plus lent. Dans le script de mesure suivant, le code de caractère est mesuré par price com de shift-jis.

verification_bs4_2.py


from urllib import request as req
from urllib import parse
import bs4
import time
import copy

url = "https://kakaku.com/"
page = req.urlopen(url)
html = page.read()
page.close()

start = time.time()
soup = bs4.BeautifulSoup(html, "html.parser")
print('{:.5f}'.format(time.time() - start) + "[s] html.parser, None")

start = time.time()
soup = bs4.BeautifulSoup(html, "lxml")
print('{:.5f}'.format(time.time() - start) + "[s] lxml, None")

start = time.time()
soup = bs4.BeautifulSoup(html, "lxml", from_encoding="shift_jis")
print('{:.5f}'.format(time.time() - start) + "[s] lxml, shift_jis")

start = time.time()
hoge = copy.copy(soup)
print('{:.5f}'.format(time.time() - start) + "[s] copy(lxml, shift_jis)")

Le résultat de sortie est ici.

% python verification_bs4_2.py
0.11084[s] html.parser, None
0.08563[s] lxml, None
0.08643[s] lxml, shift_jis
0.13631[s] copy(lxml, shift_jis)

Comme mentionné ci-dessus, la copie est plus lente que utf-8. Cependant, dans le cas de shift-jis, même si rien n'est spécifié dans ** from_encoding **, la vitesse d'exécution n'a guère changé. ~~ Je ne sais plus ça </ font> ~~

finalement

Merci d'avoir lu jusqu'ici! À la fin, je suis désolé que ça soit compliqué. Je me demande pourquoi plus de 90% de tous les sites Web dans le monde sont utf-8 mais lents. J'ai créé un article parce que je sentais que c'était un problème que les sites qui recherchaient avec BeautifulSoup et atteignaient le sommet n'en aient pas parlé. Si vous le trouvez utile, ce serait encourageant si vous pouviez "LGTM".

référence https://stackoverrun.com/ja/q/12619706

Recommended Posts

Comment accélérer la belle instanciation de soupe
Comment accélérer les calculs Python
Comment accélérer Scicit-Learn comme Conda Numpy
Comment augmenter la vitesse de traitement de l'acquisition de la position des sommets
Numba pour accélérer en Python
Résumé de l'utilisation de pandas.DataFrame.loc
Résumé de l'utilisation de pyenv-virtualenv
Projet Euler 4 Tentative d'accélération
Résumé de l'utilisation de csvkit
[DRF] Extrait pour accélérer PrimaryKeyRelatedField
[Python] Comment obtenir la fraction d'un nombre naturel à grande vitesse
Indispensable si vous utilisez Python! Comment utiliser Numpy pour accélérer les calculs!
Comment configurer l'environnement de développement d'ev3dev [version Windows]
[Python] Résumé de l'utilisation des pandas
[Mémo] Comment utiliser BeautifulSoup4 (1) Afficher html
Comment se débarrasser des longues inclusions
Comment vérifier la version de Django
Comment configurer SVM à l'aide d'Optuna
Comment installer CatBoost [à partir de janvier 2020]
Comment calculer Utiliser% de la commande df
[Python2.7] Résumé de l'utilisation d'unittest
Résumé des procédures jusqu'à l'enregistrement PyPI
Jupyter Notebook Principes d'utilisation
Bases de PyTorch (1) -Comment utiliser Tensor-
Résumé de l'utilisation de la liste Python
[Python2.7] Résumé de l'utilisation du sous-processus
Résumé de l'écriture d'AWS Lambda
[Question] Comment utiliser plot_surface de python
Résumé de la façon de définir la charpie principale (pep8, pylint, flake8) de Python
Comment calculer la volatilité d'une marque
Comment utiliser Folium (visualisation des informations de localisation)
Comment trouver la zone du diagramme de Boronoi
[Python] Comment utiliser deux types de type ()
Comment suivre le travail avec Powershell
Résumé de la façon d'importer des fichiers dans Python 3
Comment configurer une forêt aléatoire à l'aide d'Optuna
Pas beaucoup de mention de la façon d'utiliser Pickle
Résumé de l'utilisation de MNIST avec Python
Comment récupérer des données de courses de chevaux avec Beautiful Soup
Comment spécifier des attributs avec Mock of Python
Comment implémenter "named_scope" de RubyOnRails avec Django
Comment mesurer la vitesse de la ligne depuis le terminal
Comment configurer une forêt aléatoire à l'aide d'Optuna
Organiser les outils Python pour accélérer le mouvement initial des compétitions d'analyse de données
Comment obtenir des éléments de type dictionnaire de Python 2.7
Comment configurer un serveur de développement local
Vitesse: ajouter un élément à la fin du tableau Python
[Python] Faites de votre mieux pour accélérer SQL Alchemy
[Java] Comment basculer entre plusieurs versions de Java
Comment accélérer la méthode d'application de Pandas avec une seule phrase (avec calcul de vérification)
Comment afficher la date de modification d'un fichier en langage C jusqu'à nanosecondes
Comment connaître le numéro de port du service xinetd
Essai et erreur pour accélérer les captures d'écran Android
Comment obtenir le nombre de chiffres en Python
Remarques sur l'utilisation d'AIST Spacon ABCI
J'ai essayé de résumer comment utiliser matplotlib de python
Comment configurer un environnement Python à l'aide de pyenv
Remarques sur la façon d'utiliser lors de la combinaison de pandas.
La décision de scikit-learn Comment visualiser un modèle en bois
[Blender] Résumé de la procédure d'installation / de mise à jour / de désinstallation des modules complémentaires