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.
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.
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>
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>
日本語の文字列
<p>あいうえお</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"))
## あいうえお
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.
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>
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