jueves, octubre 27, 2005

Parsers SAX de html en python


Incluidos en python tenemos dos posibles parsers a usar:

Actualmente estoy usando HTMLParser.HTMLParser. Un problema que tenemos con este parser es que necesitamos detectar correctamente los inicios y los fines de tags, y hay tags que a veces no traen el fin de tag. Para evitar esto, y teniendo en cuenta que estamos en una etapa del proyecto totalmente de desarrollo (avanzando en funcionalidad), solventamos el problema usando tidy y generado xhtml.

El problema de esta solución es que tidy incrementa bastante el tiempo de procesamiento. Esto, unido a la idea de que haya parsers que puedan procesar html de una manera mas robusta que HTMLParser.HTMLParser y sean mas rápidos (por ejemplo hechos en C) me llevó a mirar libxml2.
Lo bueno de libxml2 es que tiene bindings a python, con lo cual algo (no sencillo) que nos quitamos del medio. Además hay una librería ya mas 'python-oriented', lxml. El problema con lxml, es que no es SAX.

Aparte de libxml2, hay un par de parsers candidatos a usar que no he evaluado:
Según he leído, en principio el segundo puede ser mejor.

Sobre libxml2



Las primeras pruebas que he realizado con el parser SAX incluido en libxml2 han sido muy satisfactorias (unas 10 veces mas rápido que HTMLParser).
Pero necesito hacer mas pruebas, sobre todo con html mal formado.

Los mayores inconvenientes que le he encontrado son:
  • Falta de documentación.
  • No cubre todo el API de C.
  • Mas delicado de usar (tenemos que gestionar nosotros mismos la memoria)
Ya haciendo mis pruebas, lo que no he podido hacer es abortar el procesamiento de un documento.
Si el motivo por el cual queremos parar el parseo es porque nos hemos excedido del tiempo, la solución es meter el documento en porciones (es lo mas sensato además).
En caso contrario, pues es un problema que espero averiguar...

Para acabar adjunto un ejemplo que viene incluido con la distribución:


#!/usr/bin/python -u
import sys
import libxml2

# Memory debug specific
libxml2.debugMemory(1)

log = ""

class callback:
def startDocument(self):
global log
log = log + "startDocument:"

def endDocument(self):
global log
log = log + "endDocument:"

def startElement(self, tag, attrs):
global log
log = log + "startElement %s %s:" % (tag, attrs)

def endElement(self, tag):
global log
log = log + "endElement %s:" % (tag)

def characters(self, data):
global log
log = log + "characters: %s:" % (data)

def warning(self, msg):
global log
log = log + "warning: %s:" % (msg)

def error(self, msg):
global log
log = log + "error: %s:" % (msg)

def fatalError(self, msg):
global log
log = log + "fatalError: %s:" % (msg)

handler = callback()

ctxt = libxml2.htmlCreatePushParser(handler, "b">
ctxt.htmlParseChunk(chunk, len(chunk), 0)
chunk = "ar
"
ctxt.htmlParseChunk(chunk, len(chunk), 1)
ctxt=None

reference = """startDocument:startElement html None:startElement body None:startElement foo {'url': 'tst'}:error: Tag foo invalid
:characters: bar:endElement foo:endElement body:endElement html:endDocument:"""
if log != reference:
print "Error got: %s" % log
print "Exprected: %s" % reference
sys.exit(1)

# Memory debug specific
libxml2.cleanupParser()
if libxml2.debugMemory(1) == 0:
print "OK"
else:
print "Memory leak %d bytes" % (libxml2.debugMemory(1))
libxml2.dumpMemory()

1 comentario:

cesarob dijo...

¡Ojo! Beatiful Soup no es un Parser SAX.