近日在练习使用requests-html库来编写爬虫,不得不说这个库确实挺好用,里面的render()功能可以用来运行html文档中的JavaScript,在爬取带有JavaScript的网页时很方便,但在使用时发现一个问题,当网页内容里包含中文,且网页本身的charset不是utf-8,而是gbk等其他字符集的时候,render()后的网页中的中文会显示乱码,网上查阅了一些资料也没能解决这个问题。
话说目前网上关于requests-html库的文章还是比较少,最后还是决定自己啃源码来解决这个问题。以下的代码是源码中HTML类的render()方法的片段:
if self.url == DEFAULT_URL:
reload = False
if send_cookies_session:
cookies = self._convert_cookiesjar_to_render()
for i in range(retries):
if not content:
try:
content, result, page = self.session.loop.run_until_complete(self._async_render(url=self.url, script=script, sleep=sleep, wait=wait, content=self.html, reload=reload, scrolldown=scrolldown, timeout=timeout, keep_page=keep_page, cookies=cookies))
except TypeError:
pass
else:
break
if not content:
raise MaxRetries("Unable to render the page. Try increasing timeout")
html = HTML(url=self.url, html=content.encode(DEFAULT_ENCODING), default_encoding=DEFAULT_ENCODING)
self.__dict__.update(html.__dict__)
self.page = page
return result
仔细查看后发现这一句:
html = HTML(url=self.url, html=content.encode(DEFAULT_ENCODING), default_encoding=DEFAULT_ENCODING)
这句代码说明了使用render()方法后,系统会将html的内容重置,在其参数中看到html = content.encode(DEFAULT_ENCODING),说明html的内容是将content重新编码,字符集用了DEFAULT_ENCODING,而DEFAULT_ENCODING又是啥?让我们拉回到requests-html源码的最开始的地方:
DEFAULT_ENCODING = 'utf-8'
DEFAULT_URL = 'https://example.org/'
DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8'
DEFAULT_NEXT_SYMBOL = ['next', 'more', 'older']
DEFAULT_ENCODING = 'utf-8',但网页本身用的字符集是'gbk',与render()编码的字符集不匹配,这就是导致render()后中文显示乱码的原因。
所以我们只需对源码做一个小的改动就能解决这个问题,如何改动呢?我们需要的结果是在render()方法重置html时,content.encode用的字符集是网页本身的字符集。其实requests-html中已经自带了对网页本身字符集的判断方法及可以使用的相关参数了,上面提到的HTML类是一个叫做BaseParser类的子类,在BaseParser中作者写了一个encoding的方法用于获取网页所用的字符集,继续看源码:
def encoding(self) -> _Encoding:
"""The encoding string to be used, extracted from the HTML and
:class:`HTMLResponse <HTMLResponse>` headers.
"""
if self._encoding:
return self._encoding
# Scan meta tags for charset.
if self._html:
self._encoding = html_to_unicode(self.default_encoding, self._html)[0]
# Fall back to requests' detected encoding if decode fails.
try:
self.raw_html.decode(self.encoding, errors='replace')
except UnicodeDecodeError:
self._encoding = self.default_encoding
从方法的备注中就能看出,这个方法是从HTML文档或者是文档的请求头中获取encoding string,也就是我们要的字符集。如果将这个字符集用在之前的render()方法中,就能将render()后的html文档用正确的字符集编码了,修改如下:
html = HTML(url=self.url, html=content.encode(self.encoding), default_encoding=DEFAULT_ENCODING)
即将原本content.encode(DEFAULT_ENCODING)改成了content.encode(self.encoding)。
至此,中文乱码的问题搞定。