I am trying to get clean text from some web pages.
I have read a lot of tutorials and finally ended up with python lxml
+ beautifulsoup
+ requests
modules .
The reason for using lxml
for such a task is that it cleans html files better than beautiful soup do.
I ended up with test script like this:
from bs4 import UnicodeDammit
import re
import requests
import lxml
import lxml.html
from time import sleep
urls = [
"http://mathprofi.ru/zadachi_po_kombinatorike_primery_reshenij.html",
"http://ru.onlinemschool.com/math/assistance/statistician/",
"http://mathprofi.ru/zadachi_po_kombinatorike_primery_reshenij.html",
"http://universarium.org/courses/info/332",
"http://compsciclub.ru/course/wordscombinatorics",
"http://ru.onlinemschool.com/math/assistance/statistician/",
"http://lectoriy.mipt.ru/course/Maths-Combinatorics-AMR-Lects/",
"http://www.youtube.com/watch?v=SLPrGWQBX0I"
]
def check(url):
print "That is url {}".format(url)
r = requests.get(url)
ud = UnicodeDammit(r.content, is_html=True)
content = ud.unicode_markup.encode(ud.original_encoding, "ignore")
root = lxml.html.fromstring(content)
lxml.html.etree.strip_elements(root, lxml.etree.Comment,
"script", "style")
text = lxml.html.tostring(root, method="text", encoding=unicode)
text = re.sub('\s+', ' ', text)
print "Text type is {}!".format(type(text))
print text[:200]
sleep(1)
if __name__ == '__main__':
for url in urls:
check(url)
Inetrmediate de- and reencoding to the original encoding is needed because the html page could contain some characters that are encoded differently from the most others. Such an occasion breaks further lxml tostring
method.
However my code is not working properly with all tests. Sometimes (especially with last two urls) it outputs the mess:
...
That is url http://ru.onlinemschool.com/math/assistance/statistician/
Text type is <type 'unicode'>!
Онлайн решение задач по математике. Комбинаторика. Теория вероятности. Close Авторизация на сайте Введите логин: Введите пароль: Запомнить меня Регистрация Изучение математики онлайн.Изучайте математ
That is url http://lectoriy.mipt.ru/course/Maths-Combinatorics-AMR-Lects/
Text type is <type 'unicode'>!
ÐаÑемаÑика. ÐÑÐ½Ð¾Ð²Ñ ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°ÑоÑики и ÑеоÑии ÑиÑел / ÐидеолекÑии ФизÑеÑа: ÐекÑоÑий ÐФТР- видеолекÑии по Ñизике,
That is url http://www.youtube.com/watch?v=SLPrGWQBX0I
Text type is <type 'unicode'>!
ÐÑновнÑе ÑоÑмÑÐ»Ñ ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°ÑоÑики - bezbotvy - YouTube ÐÑопÑÑÑиÑÑ RU ÐобавиÑÑ Ð²Ð¸Ð´ÐµÐ¾ÐойÑиÐоиÑк ÐагÑÑзка... ÐÑбеÑиÑе ÑзÑк.
This mess is somehow connected with encoding ISO-8859-1
, but i cannot find out how.
For each of two last urls i get:
In [319]: r = requests.get(urls[-1])
In [320]: chardet.detect(r.content)
Out[320]: {'confidence': 0.99, 'encoding': 'utf-8'}
In [321]: UnicodeDammit(r.content, is_html=True).original_encoding
Out[321]: 'utf-8'
In [322]: r = requests.get(urls[-2])
In [323]: chardet.detect(r.content)
Out[323]: {'confidence': 0.99, 'encoding': 'utf-8'}
In [324]: UnicodeDammit(r.content, is_html=True).original_encoding
Out[324]: u'utf-8'
So i guess lxml
makes internal decoding based on the wrong assumptions of an input string. I think that it don't even try to make guess about input string encoding. It seems that in the core of lxml happens something like this :
In [339]: print unicode_string.encode('utf-8').decode("ISO-8859-1", "ignore")
ÑÑÑока
How could i resolve my issue and clean all urls from html tags ? Maybe i should use another python modules or do it another way ? Please, give me your suggestions.
I finally figured it out. The solution is not to use
root = lxml.html.fromstring(content)
but configure an explicit Parser object which can be told to use particular encoding enc
:
htmlparser = etree.HTMLParser(encoding=enc)
root = etree.HTML(content, parser=htmlparser)
I found, additionally, that even UnicodeDammit
make obvious mistakes when deciding about encoding of a page. So i have added another if
block:
if (declared_enc and enc != declared_enc):
Here is the snippet of a result:
from lxml import html
from lxml.html import etree
import requests
from bs4 import UnicodeDammit
import chardet
try:
self.log.debug("Try to get content from page {}".format(url))
r = requests.get(url)
except requests.exceptions.RequestException as e:
self.log.warn("Unable to get page content of the url: {url}. "
"The reason: {exc!r}".format(url=url, exc=e))
raise ParsingError(e.message)
ud = UnicodeDammit(r.content, is_html=True)
enc = ud.original_encoding.lower()
declared_enc = ud.declared_html_encoding
if declared_enc:
declared_enc = declared_enc.lower()
# possible misregocnition of an encoding
if (declared_enc and enc != declared_enc):
detect_dict = chardet.detect(r.content)
det_conf = detect_dict["confidence"]
det_enc = detect_dict["encoding"].lower()
if enc == det_enc and det_conf < THRESHOLD_OF_CHARDETECT:
enc = declared_enc
# if page contains any characters that differ from the main
# encodin we will ignore them
content = r.content.decode(enc, "ignore").encode(enc)
htmlparser = etree.HTMLParser(encoding=enc)
root = etree.HTML(content, parser=htmlparser)
etree.strip_elements(root, html.etree.Comment, "script", "style")
text = html.tostring(root, method="text", encoding=unicode)