lxml
Dies ist eine praktische Bibliothek für den Umgang mit 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.
Mit lxml können Sie ganz einfach schreiben, wie alle relativen Links in HTML in absolute Links umgeschrieben werden.
Verwenden Sie lxml.html.make_links_absolute (). Angenommen, Sie haben das folgende HTML (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>
Führen Sie den folgenden Code aus. Bitte geben Sie die Basis-URL an base_url an.
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)
Es kann wie folgt als absoluter Link umgeschrieben werden.
<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>
Es ist wunderbar, dass die folgenden drei auch als Links interpretiert werden.
Beispielsweise möchten Sie möglicherweise alle Links in absolute Links umschreiben, aber nur die js-Dateien als relative Links belassen.
Werfen wir einen Blick auf die Implementierung von 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 () wird verwendet. Mit anderen Worten, Sie sollten dies verwenden.
Angenommen, Sie möchten einen anderen Link als den Pfad, der auf js im vorherigen HTML-Code verweist, in einen absoluten Link ändern.
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)
Der Link zu js (src-Attribut des Skript-Tags) bleibt ein relativer Link.
<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>
Wie oben erwähnt, ist die Verwendung von lxml praktisch, da Sie leicht von einem relativen Link zu einem absoluten Link umschreiben können. Sie müssen ein wenig vorsichtig sein, wenn Sie versuchen, HTML zu konvertieren, das Multi-Byte-Zeichen wie Japanisch enthält.
Angenommen, Sie haben das folgende HTML (b.html).
b.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
Japanische Zeichenkette
<p>AIUEO</p>
</body>
</html>
Lassen Sie uns dies mit lxml konvertieren.
# -*- 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)
Das Ausgabeergebnis ist wie folgt.
<html>
<head></head>
<body>
日本語の文字列
<p>あいうえお</p>
</body>
</html>
Wenn das generierte HTML mit einem Browser angezeigt wird, wird es als beabsichtigte Zeichenfolge angezeigt. Wenn ich dieses HTML jedoch mit einem Editor usw. öffne, wird es in eine Liste mit mehreren Zahlen und Symbolen konvertiert. In HTML gibt es zwei Arten der Zeichendarstellung: "Zeichenentitätsreferenz" und "numerische Zeichenreferenz". Dies liegt daran, dass die in der ersteren übergebene Zeichenfolge in die letztere konvertiert wurde, wenn sie an lxml übergeben wird. (Einzelheiten finden Sie unter http://www.asahi-net.or.jp/~sd5a-ucd/rec-html401j/charset.html#h-5.3.1)
Dies ist genau das gleiche Format wie beim Konvertieren einer Unicode-Zeichenfolge in einen Wert vom Typ str in Python wie folgt.
print(u"AIUEO".encode("ascii", "xmlcharrefreplace"))
## あいうえお
In der Hilfe für encode () wird dies ebenfalls erwähnt.
$ 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.
Die Geschichte geht zurück. Wenn Sie HTML mit Multibyte-Zeichen an lxml übergeben möchten, Geben Sie die String-Codierung ein.
# -*- 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")
Es wurde in einem Format ausgegeben, das von Menschen gelesen werden kann.
<html>
<head></head>
<body>
Japanische Zeichenkette
<p>AIUEO</p>
</body>
</html>
Es wird noch etwas länger dauern.
Soweit ich mich im Internet umgesehen habe, haben viele von ihnen die Codierung an lxml.html.tostring übergeben. Es gibt noch ein paar Fallen.
Im vorherigen Beispiel wurde die Codierungsspezifikation zu HTML hinzugefügt. Lxml gibt jedoch eine seltsame Ausgabe zurück, wenn HTML ohne diese übergeben wird. (Obwohl gesagt werden kann, dass die Existenz von HTML ohne Kodierung an sich schlecht ist. Der Hauptanspruch ist vor der Realität machtlos)
HTML (d.html) ohne angegebene Codierung
<html>
<head>
</head>
<body>
Japanische Zeichenkette
<p>AIUEO</p>
</body>
</html>
Das Ergebnis ist wie folgt.
<html>
<head></head>
<body>
æ¥æ¬èªã®æåå
<p>ããããã</p>
</body>
</html>
Lassen Sie uns die Ursache untersuchen. Werfen wir einen Blick auf die Implementierung von lxml.html.parse und lxml.html.tostring. Da ich die Codierung früher bestanden habe, kann ich mir vorstellen, dass der String bereits unterstützt wird, daher werde ich mir die Analyse ansehen.
## 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)
Es scheint, dass es ein Argument namens Parser empfängt und standardmäßig ein Parser namens html_parser übergeben wird.
## 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()
Eine Klasse, die von lxml.etree.HTMLParser erbt, ist als HTMLParser definiert. Der Standardparser scheint diese Instanz zu sein. Etree.HTMLParser scheint übrigens eine in C geschriebene Klasse zu sein.
## 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__()
Werfen wir einen Blick auf die Dokumentation (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__
Es scheint, dass HTML Parser auch die Codierung angeben kann. Wahrscheinlich ist der Standardwert None, und das Verhalten zu diesem Zeitpunkt ähnelt dem Festlegen der Codierung durch Betrachten des Meta-Tags von HTML. Wenn dieses Meta-Tag nicht vorhanden ist, wird wahrscheinlich die Standardcodierung für Ihre Systemumgebung festgelegt.
Ich werde html_parser imitieren und eine Instanz erstellen.
# -*- 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")
Es scheint, dass die Ausgabe erfolgreich war.
<html>
<head></head>
<body>
Japanische Zeichenkette
<p>AIUEO</p>
</body>
</html>
Wenn Sie die richtige Codierung nicht kennen, können Sie chardet (cchardet) verwenden.
# -*- 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
Das ist es.
Recommended Posts