在爬虫的过程中,难免会遇到各种各样的问题。在这里,为大家分享一个关于etree.HTML解析异常的问题。
1.问题描述:
爬虫过程中,一般会使用requests.get()方法获取一个网页上的HTML内容,然后通过lxml库中的etree.HTML来解析这个网页的结构,最后通过xpath获取自己所需的内容。

本人爬虫的具体代码可简单抽象如下:

	res = requests.get(url)
	html = etree.HTML(res.text)
	contents = html.xpaht('//div/xxxx')

然后遇到了如下的错误信息:

	Traceback (most recent call last):
	  File "xxxxxxxx.py", line 157, in <module>
	    get_website_title_content(url)
	  File "xxxxxxxx.py", line 141, in get_website_title_content
	    html = etree.HTML(html_text)
	  File "src\lxml\etree.pyx", line 3170, in lxml.etree.HTML
	  File "src\lxml\parser.pxi", line 1872, in lxml.etree._parseMemoryDocument
	ValueError: Unicode strings with encoding declaration are not supported. Please use bytes input or XML fragments without declaration.

关键错误就是 ValueError: Unicode strings with encoding declaration are not supported. Please use bytes input or XML fragments without declaration.

2.解决方法
通过查阅相关资料,造成这个错误的原因其实是requests返回的 res.text 和 res.content 两者区别的问题。查阅requests源代码中是text和content定义(如下所示)可知:res.text返回的是Unicode类型的数据,而res.content返回的是bytes类型的数据。

	@property
    def content(self):
        """Content of the response, in bytes."""

        if self._content is False:
            # Read the contents.
            if self._content_consumed:
                raise RuntimeError(
                    'The content for this response was already consumed')

            if self.status_code == 0 or self.raw is None:
                self._content = None
            else:
                self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b''

        self._content_consumed = True
        # don't need to release the connection; that's been handled by urllib3
        # since we exhausted the data.
        return self._content

	@property
    def text(self):
        """Content of the response, in unicode.

        If Response.encoding is None, encoding will be guessed using
        ``chardet``.

        The encoding of the response content is determined based solely on HTTP
        headers, following RFC 2616 to the letter. If you can take advantage of
        non-HTTP knowledge to make a better guess at the encoding, you should
        set ``r.encoding`` appropriately before accessing this property.
        """

        # Try charset from content-type
        content = None
        encoding = self.encoding

        if not self.content:
            return str('')

        # Fallback to auto-detected encoding.
        if self.encoding is None:
            encoding = self.apparent_encoding

        # Decode unicode from given encoding.
        try:
            content = str(self.content, encoding, errors='replace')
        except (LookupError, TypeError):
            # A LookupError is raised if the encoding was not found which could
            # indicate a misspelling or similar mistake.
            #
            # A TypeError can be raised if encoding is None
            #
            # So we try blindly encoding.
            content = str(self.content, errors='replace')

        return content

导致该错误的原因是etree解析是不支持编码声明的Unicode字符串的
因此解决方法很简单,第一种就是直接使用 res.content,如下:

	res = requests.get(url)
	html = etree.HTML(res.content )
	contents = html.xpath('//div/xxxx')

第二种方法则是将Unicode字符串转换为bytes数组,如下:

	res = requests.get(url)
	html_text = bytes(bytearray(res.text, encoding='utf-8'))
	html = etree.HTML(html_text)
	contents = html.xpath('//div/xxxx')

更多推荐

爬虫踩坑系列——etree.HTML解析异常