понедельник, 31 января 2011 г.

lxml vs BeautifulSoup

Задача была разобрать произвольную веб-страницу на текст и ссылки.
Снчала для разбора html использовал lxml.html:

from lxml import html
from lxml.html import clean
url = 'http://ya.ru'
tree = html.parse(url)
root = tree.getroot()
root.make_links_absolute(url)
links = [l[2] for l in root.iterlinks() 
         if l[0].tag == 'a' and l[1] == 'href' 
         or l[0].tag == 'frame' and l[1] == 'src']


Теперь можно пройтись по дереву тэгов и извлечь, что нужно. Но вот с кодировками как всегда не всё ладно. На данном этапе все текстовые поля уже переведены в юникод, и повлиять на процесс декодирования довольно сложно. Есть пример, когда кодировка угадана парсером неверно. Морда сайта lenta.ru всегда распознаётся как cp1252.
Можно, конечно, сначала скачать страницу, запустить что-нибудь вроде chardet (он эту страницу распознаёт правильно), потом скормить получившуюся строку парсеру:

import urllib2
from lxml import html
import chardet

def convert(s):
  encoding = chardet.detect(s)['encoding']
  if encoding is None:
    return unicode(s)
  return unicode(s, encoding, 'ignore')

url = 'http://lenta.ru'
f = urllib2.urlopen(url)
text = convert(f.read())
root = html.fromstring(text)

Здесь, очевидно, получим замедление работы.

А вот, что получилось с BeautifulSoup:

from lxml import html
from BeautifulSoup import BeautifulSoup  
import urllib2

def beautifulLinks(soup, transformFunc):
    linkTypes = [('a', 'href'),
                 ('frame', 'src'), 
                 ('iframe', 'src')]
    return reduce(lambda x, y: x + 
                  [transformFunc(a[y[1]]) 
                   for a in soup.findAll(y[0]) 
                   if a.has_key(y[1])], linkTypes, [])

url = 'http://lenta.ru'
f = urllib2.urlopen(url)
soup = BeautifulSoup(f, convertEntities='html')
links = beautifulLinks(
soup, 
lambda x: html.urljoin(unicode(url), x))

Данный код быстрее, и кодиорвки распознаются правильно. (Неказистость функции beautifulLinks не в счёт)

P.S.

Работа chardet немного напоминает результат первого из описанных выше подходов. А именно: первое русское слово на морде lenta.ru есть 'Главное'. На этом сайте кодировка 'cp1251', но в метатегах об этом не сказано. И если написать:

>>>chardet.detect(u'Главное'.encode('cp1251'))
{'confidence': 0.87767968407042096, 'encoding': 'MacCyrillic'}

то будет сюрприз. Но вот стоит хотя бы изменить первую заглавную букву на строчную - и всё работает нормально.

>>>chardet.detect(u'главное'.encode('cp1251'))
{'confidence': 0.98999999999999999, 'encoding': 'windows-1251'}

Наверное html.parse определяет кодировку по первому текстовому полю.

Комментариев нет:

Отправить комментарий