Réécrire les liens relatifs en html en liens absolus avec python (lxml)

Réécrire les liens relatifs en html en liens absolus avec python (lxml)

lxml

Ceci est une bibliothèque pratique pour gérer le HTML (xml).

lxml - Processing XML and HTML with Python(http://lxml.de/)

lxml is the most feature-rich and easy-to-use library for processing XML and HTML in the Python language.

En utilisant lxml, vous pouvez facilement écrire le processus de réécriture de tous les liens relatifs en html en liens absolus.

Make_links_absolute () est bon pour réécrire tous les liens vers des liens absolus

Utilisez lxml.html.make_links_absolute (). Par exemple, supposons que vous ayez le code HTML suivant (a.html).

a.html

<html>
  <head>
    <style type="text/css">
      .download {background-image:url(./images/download.png);}
    </style>
    <script src="./js/lib.js" type="text/javascript"/>
    <script src="./js/app.js" type="text/javascript"/>
  </head>
  <body>
    <img src="images/icon.png " alt="image"/>
    <a class="download "href="./download">download</a>
  </body>
</html>

Exécutez un code similaire à ce qui suit. Veuillez donner l'URL de base à base_url.

from lxml import html

with open("./a.html", "r") as rf:
    doc = html.parse(rf).getroot()
    html.make_links_absolute(doc, base_url="http://example.net/foo/bar")
    print html.tostring(doc, pretty_print=True)

Il peut être réécrit comme un lien absolu comme suit.

<html>
<head>
<style type="text/css">
      .download {background-image:url(http://example.net/foo/images/download.png);}
    </style>
<script src="http://example.net/foo/js/lib.js" type="text/javascript"></script><script src="http://example.net/foo/js/app.js" type="text/javascript"></script>
</head>
<body>
    <img src="http://example.net/foo/images/icon.png " alt="image"><a class="download " href="http://example.net/foo/download">download</a>
  </body>
</html>

Il est merveilleux que les trois suivants soient également interprétés comme des liens.

Si vous voulez faire quelque chose d'un peu plus compliqué, utilisez rewrite_links ()

Par exemple, vous souhaiterez peut-être réécrire tous les liens vers des liens absolus, mais ne laissez que les fichiers js en tant que liens relatifs.

Jetons un coup d'œil à l'implémentation de make_links_absolute ().

## lxml-3.2.1-py2.7-linux-x86_64.egg/lxml/html/__init__.py

class HtmlMixin(object):
#...
    def make_links_absolute(self, base_url=None, resolve_base_href=True):
        """
        Make all links in the document absolute, given the
        ``base_url`` for the document (the full URL where the document
        came from), or if no ``base_url`` is given, then the ``.base_url`` of the document.

        If ``resolve_base_href`` is true, then any ``<base href>``
        tags in the document are used *and* removed from the document.
        If it is false then any such tag is ignored.
        """
        if base_url is None:
            base_url = self.base_url
            if base_url is None:
                raise TypeError(
                    "No base_url given, and the document has no base_url")
        if resolve_base_href:
            self.resolve_base_href()
        def link_repl(href):
            return urljoin(base_url, href)
        self.rewrite_links(link_repl)

rewrite_links () est utilisé. En d'autres termes, vous devriez l'utiliser.

Supposons que vous souhaitiez modifier un lien autre que le chemin qui fait référence à js dans le code HTML précédent en un lien absolu.

from lxml import html
from urlparse import urljoin
import functools

def repl(base_url, href):
    if href.endswith(".js"):
       return href
    else:
        return urljoin(base_url, href)

with open("./a.html", "r") as rf:
    doc = html.parse(rf).getroot()   
    base_url="http://example.net/foo/bar"
    doc.rewrite_links(functools.partial(repl, base_url))
    print html.tostring(doc, pretty_print=True)

Le lien vers js (attribut src de la balise script) reste un lien relatif.

<html>
<head>
<style type="text/css">
      .download {background-image:url(http://example.net/foo/images/download.png);}
    </style>
<script src="./js/lib.js" type="text/javascript"></script><script src="./js/app.js" type="text/javascript"></script>
</head>
<body>
    <img src="http://example.net/foo/images/icon.png " alt="image"><a class="download " href="http://example.net/foo/download">download</a>
  </body>
</html>

Précautions lors de la réécriture d'un code HTML contenant des caractères multi-octets

Comme mentionné ci-dessus, l'utilisation de lxml est pratique car vous pouvez facilement réécrire d'un lien relatif vers un lien absolu. Vous devez être un peu prudent lorsque vous essayez de convertir du code HTML contenant des caractères multi-octets tels que le japonais.

Par exemple, supposons que vous ayez le code HTML suivant (b.html).

b.html

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
Chaîne de caractères japonais
    <p>AIUEO</p>
  </body>
</html>

Convertissons cela avec lxml.

# -*- coding:utf-8 -*-
from lxml import html

with open("./b.html", "r") as rf:
    doc = html.parse(rf).getroot()
    print html.tostring(doc, pretty_print=True)

Le résultat de sortie est le suivant.

<html>
<head></head>
<body>
    &#26085;&#26412;&#35486;&#12398;&#25991;&#23383;&#21015;
    <p>&#12354;&#12356;&#12358;&#12360;&#12362;</p>
  </body>
</html>

Lorsque le code HTML généré est affiché avec un navigateur, il sera affiché comme la chaîne de caractères voulue. Cependant, lorsque j'ouvre ce code HTML avec un éditeur, etc., il est converti en une liste de plusieurs nombres et symboles. En html, il existe deux types de représentation de caractères, "référence d'entité de caractère" et "référence de caractère numérique". Cela est dû au fait que la chaîne de caractères passée dans le premier a été convertie en ce dernier lorsqu'elle est passée à lxml. (Pour plus de détails, voir http://www.asahi-net.or.jp/~sd5a-ucd/rec-html401j/charset.html#h-5.3.1)

C'est exactement le même format que lors de la conversion d'une chaîne Unicode en une valeur de type str en python comme suit.

print(u"AIUEO".encode("ascii", "xmlcharrefreplace"))
## &#12354;&#12356;&#12358;&#12360;&#12362;

En fait, l'aide de encode () le mentionne également.

$ python
Python 2.7.3 (default, Sep 26 2012, 21:51:14) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> help("".encode)
Help on built-in function encode:

encode(...)
    S.encode([encoding[,errors]]) -> object
    
    Encodes S using the codec registered for encoding. encoding defaults
    to the default encoding. errors may be given to set a different error
    handling scheme. Default is 'strict' meaning that encoding errors raise
    a UnicodeEncodeError. Other possible values are 'ignore', 'replace' and
    'xmlcharrefreplace' as well as any other name registered with
    codecs.register_error that is able to handle UnicodeEncodeErrors.

L'histoire remonte. Si vous voulez passer du html contenant des caractères multi-octets à lxml, Donnez un encodage de chaîne.

# -*- coding:utf-8 -*-
from lxml import html

with open("./b.html", "r") as rf:
    doc = html.parse(rf).getroot()
    print html.tostring(doc, pretty_print=True, encoding="utf-8")

Il a été produit dans un format qui peut être lu par des humains.

<html>
<head></head>
<body>
Chaîne de caractères japonais
    <p>AIUEO</p>
  </body>
</html>

Cela continuera encore un peu.

Continuation, précautions lors de la réécriture de HTML contenant des caractères multi-octets

Pour autant que je regarde autour de moi sur le net, beaucoup d'entre eux ont fini par passer l'encodage à lxml.html.tostring. Il y a encore quelques pièges.

Dans l'exemple précédent, la spécification de codage a été ajoutée à html. Cependant, lxml renvoie une sortie étrange lorsqu'il est passé en HTML sans lui. (Bien que l'on puisse dire que l'existence du html sans encodage est mauvaise en soi. La revendication principale est impuissante devant la réalité)

html (d.html) sans codage spécifié

<html>
  <head>
  </head>
  <body>
Chaîne de caractères japonais
    <p>AIUEO</p>
  </body>
</html>

Le résultat est le suivant.

<html>
<head></head>
<body>
    日本語の文字列
    <p>あいうえお</p>
  </body>
</html>

Cherchons la cause. Jetons un coup d'œil à l'implémentation de lxml.html.parse et lxml.html.tostring. Depuis que j'ai passé l'encodage plus tôt, je peux penser que la chaîne tostring est déjà prise en charge, je vais donc regarder l'analyse.

## lxml-3.2.1-py2.7-linux-x86_64.egg/lxml/html/__init__.py

def parse(filename_or_url, parser=None, base_url=None, **kw):
    """
    Parse a filename, URL, or file-like object into an HTML document
    tree.  Note: this returns a tree, not an element.  Use
    ``parse(...).getroot()`` to get the document root.

    You can override the base URL with the ``base_url`` keyword.  This
    is most useful when parsing from a file-like object.
    """
    if parser is None:
        parser = html_parser
    return etree.parse(filename_or_url, parser, base_url=base_url, **kw)

Il semble qu'il reçoive un argument appelé parser et qu'il lui soit passé par défaut un parser appelé html_parser.

## lxml-3.2.1-py2.7-linux-x86_64.egg/lxml/html/__init__.py

from lxml import etree

# ..snip..

class HTMLParser(etree.HTMLParser):
    """An HTML parser that is configured to return lxml.html Element
    objects.
    """
    def __init__(self, **kwargs):
        super(HTMLParser, self).__init__(**kwargs)
        self.set_element_class_lookup(HtmlElementClassLookup())

# ..snip..

html_parser = HTMLParser()
xhtml_parser = XHTMLParser()

Une classe qui hérite de lxml.etree.HTMLParser est définie comme HTMLParser. L'analyseur par défaut semble être cette instance. Au fait, etree.HTMLParser semble être une classe écrite en C.

## lxml-3.2.1-py2.7-linux-x86_64.egg/lxml/etree/__init__.py

def __bootstrap__():
   global __bootstrap__, __loader__, __file__
   import sys, pkg_resources, imp
   __file__ = pkg_resources.resource_filename(__name__,'etree.so')
   __loader__ = None; del __bootstrap__, __loader__
   imp.load_dynamic(__name__,__file__)
__bootstrap__()

Jetons un œil à la documentation (lxml.etree.HTMLParse: http://lxml.de/3.1/api/lxml.etree.HTMLParser-class.html)

Method Details 	[hide private]
__init__(self, encoding=None, remove_blank_text=False, remove_comments=False, remove_pis=False, strip_cdata=True, no_network=True, target=None, XMLSchema schema=None, recover=True, compact=True)
(Constructor)
	 
x.__init__(...) initializes x; see help(type(x)) for signature

Overrides: object.__init__ 

HTML Parser semble également être en mesure de spécifier le codage. Probablement la valeur par défaut est None, et le comportement à ce moment est comme définir l'encodage en regardant la balise meta de html. S'il n'a pas cette balise meta, il définit probablement l'encodage par défaut pour votre environnement système.

Je vais imiter html_parser et créer une instance.

# -*- coding:utf-8 -*-
from lxml import html

html_parser = html.HTMLParser(encoding="utf-8")

with open("./d.html", "r") as rf:
    doc = html.parse(rf, parser=html_parser).getroot()
    print html.tostring(doc, pretty_print=True, encoding="utf-8")

Il semble que la sortie ait réussi.

<html>
<head></head>
<body>
Chaîne de caractères japonais
    <p>AIUEO</p>
  </body>
</html>

Chardet si vous ne connaissez pas le bon encodage

Si vous ne connaissez pas le bon encodage, vous pouvez utiliser chardet (cchardet).

# -*- coding:utf-8 -*-
import chardet #if not installed. pip install chardet

print chardet.detect(u"AIUEO".encode("utf-8"))
# {'confidence': 0.9690625, 'encoding': 'utf-8'}

## warning
print chardet.detect(u"abcdefg".encode("utf-8")[:3])
# {'confidence': 1.0, 'encoding': 'ascii'}

def detect(fname, size = 4096 << 2):
    with open(fname, "rb") as rf:
        return chardet.detect(rf.read(size)).get("encoding")

print detect("b.html") # utf-8
print detect("a.html") # ascii

C'est tout.

Recommended Posts

Réécrire les liens relatifs en html en liens absolus avec python (lxml)
Convertir une URL absolue en URL relative en Python
Convertir de Markdown en HTML en Python
Réécrire le code Python2 en Python3 (2to3)
Python> Liste> Convertir les chemins relatifs en chemins absolus> all_filepaths = [database_path + fp for fp in train_filepaths]
Pour vider stdout en Python
Connectez-vous au site Web en Python
Parler avec Python [synthèse vocale]
Traitement des URL relatives en python
Publier sur Slack en Python
Une histoire sur la façon de spécifier un chemin relatif en python.
[Python] Comment faire PCA avec Python
Afficher les photos en Python et html
Convertir Markdown en PDF en Python
Comment collecter des images en Python
Comment utiliser SQLite en Python
Exportez le contenu de ~ .xlsx dans le dossier en HTML avec Python
Essayez de calculer Trace en Python
Comment utiliser Mysql avec python
[Introduction à Udemy Python3 + Application] 69. Importation du chemin absolu et du chemin relatif
Comment envelopper C en Python
Comment utiliser ChemSpider en Python
6 façons d'enchaîner des objets en Python
Comment utiliser PubChem avec Python
Comment gérer le japonais avec Python
Une alternative à `pause` en Python
J'ai créé une application Web en Python qui convertit Markdown en HTML
[Introduction à Python] Comment utiliser la classe en Python?
Installez Pyaudio pour lire des vagues en python
mail html avec image à envoyer avec python
J'ai essayé d'implémenter la permutation en Python
Méthode pour créer un environnement Python dans Xcode 6
Comment définir dynamiquement des variables en Python
Comment faire R chartr () en Python
Épingler le répertoire actuel au répertoire de script en Python
[Itertools.permutations] Comment créer une séquence en Python
PUT gzip directement dans S3 en Python
Envoyer des e-mails à plusieurs destinataires avec Python (Python3)
Convertir un fichier psd en png en Python
Exemple de script pour piéger les signaux en Python
J'ai essayé d'implémenter PLSA dans Python 2
Pour définir le codage par défaut sur utf-8 en python
Comment utiliser BigQuery en Python
Connectez-vous à Slack à l'aide de requêtes en Python
Comment obtenir stacktrace en python
Comment afficher la table quatre-vingt-dix-neuf en python
Un moyen simple d'utiliser Wikipedia avec Python
Réécrire le nœud de filtre de SPSS Modeler avec Python
J'ai essayé d'implémenter ADALINE en Python
[Python] pandas à bien comprendre en 10 minutes
Lancer le Webhook entrant vers Mattermost en Python
Module pour générer le mot N-gramme en Python
Pour référencer des variables d'environnement en Python dans Blender
Je voulais résoudre ABC159 avec Python
Comment changer de version de Python dans cloud9
Comment régler le contraste de l'image en Python
Comment utiliser __slots__ dans la classe Python
Comment remplir dynamiquement des zéros avec Python
Gérer les packages python à installer dans des conteneurs
Pour faire fonctionner la station d'horodatage en Python