本篇博客是《爬虫 120 例》的第 30 例,新学习一个爬虫框架 requests-html,该框架作者就是 requests 的作者,所以盲猜就很好用啦。
知识铺垫工作
requests-html 模块安装使用 pip install requests-html 即可,官方手册查询地址:https://requests-html.kennethreitz.org/,官方并没有直接的中文翻译,在检索过程中,确实发现了一版中文手册,在文末提供。
先看一下官方对该库的基本描述:
- Full JavaScript support!(完全支持 JS,这里手册还重点标记了一下,初学阶段可以先忽略)
- CSS Selectors (a.k.a jQuery-style, thanks to PyQuery).(集成了 pyquery 库,支持 css 选择器)
- XPath Selectors, for the faint at heart.(支持 XPath 选择器)
- Mocked user-agent (like a real web browser).(mock UA 数据,这点不错)
- Automatic following of redirects.(自动跟踪重定向)
- Connection–pooling and cookie persistence.(持久性 COOKIE)
- The Requests experience you know and love, with magical parsing abilities.(额,这最后一点,各位自己领悟吧)
Only Python 3.6 is supported. 仅支持 Python 3.6 ,实测发现 3.6 以上版本依旧可以。
对于该库的简单使用,代码如下所示:
from requests_html import HTMLSession session = HTMLSession() r = session.get('https://python.org/') print(r)首先从 requests_html 库导入 HTMLSession 类,然后将其实例化之后,调用其 get 方法,发送请求,得到的 r 输出为 <Response [200]>,后续即可使用内置的解析库对数据进行解析。
由于该库是解析 html 对象,所以可以查看对应的 html 对象包含哪些方法与与属性。
通过 dir 函数查阅。
print(dir(r.html)) # 输出如下内容: ['__aiter__', '__anext__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_async_render', '_encoding', '_html', '_lxml', '_make_absolute', '_pq', 'absolute_links', 'add_next_symbol', 'arender', 'base_url', 'default_encoding', 'element', 'encoding', 'find', 'full_text', 'html', 'links', 'lxml', 'next', 'next_symbol', 'page', 'pq', 'raw_html', 'render', 'search', 'search_all', 'session', 'skip_anchors', 'text', 'url', 'xpath']该函数只能输入大概内容,细节还是需要通过 help 函数查询,例如:
html 对象的方法包括
- find:提供一个 css 选择器,返回一个元素列表;
- xpath:提供一个 xpath 表达式,返回一个元素列表;
- search: 根据传入的模板参数,查找 Element 对象;
- search_all:同上,返回的全部数据;
html 对象的属性包括
- links:返回页面所有链接;
- absolute_links:返回页面所有链接的绝对地址;
- base_url:页面的基准 URL;
- html,raw_html,text:以 HTML 格式输入页面,输出未解析过的网页,提取页面所有文本;
有了上述内容铺垫之后,在进行 Python 爬虫的编写就会变的容易许多,requests-html 库将通过 3~4 个案例进行学习掌握,接下来进入第一个案例。
目标站点分析
本次要采集的目标网站为:http://www.world68.com/top.asp?t=5star&page=1,目标站点描述为【全球名站】。在获取数据源发送请求前,忽然想起可以动态修改 user-agent,查阅该库源码发现,它只是使用了 fake_useragent 库来进行操作,并无太神奇的地方,所以可用可不用该内容。
其余内容相对比较简单,页码规则如下:
http://www.world68.com/top.asp?t=5star&page=1 http://www.world68.com/top.asp?t=5star&page=2累计页数直接在底部进行了展示,可以设计为用户手动输入,即 input 函数实现。
目标数据存储网站名与网站地址即可,基于此,开始编码。
编码时间
首先通过单线程实现 requests-html 的基本逻辑,注意到下述代码非常轻量,
from requests_html import HTMLSession session = HTMLSession() page_size = int(input("请输入总页码:")) for page in range(1, page_size + 1): world = session.get(f'http://www.world68.com/top.asp?t=5star&page={page}') world.encoding = 'gb2312' # world.html.encoding = "gb2312" # print(world.text) print("正在采集数据", world.url) title_a = world.html.find('dl>dt>a') for item in title_a: name = item.text url = item.attrs['href'] with open('webs.txt', "a+", encoding="utf-8") as f: f.write(f"{name},{url}\n")上述代码重点部分说明如下:
- world.encoding,设置了网页解析编码;
- world.html.find('dl>dt>a') 通过 css 选择器,查找所有的网页标题元素;
- item.text 提取网页标题内容;
- item.attrs['href'] 获取元素属性,即网站域名。
运行效果如下所示,获取到的 3519 个站点,就不在提供了,简单运行 1 分钟代码,即可得到。由于上述代码太少了,完全不够今日代码量,我们顺手将其修改为多线程形式。
在正式进行编码之后,发现存在比较大的问题,编码问题,出现如下错误:
encoding error : input conversion failed due to input error, bytes 0x81 0xE3 0xD3 0xAA encoding error : input conversion failed due to input error, bytes 0x81 0xE3 0xD3 0xAA encoding error : input conversion failed due to input error, bytes 0x81 0xE3 0xD3 0xAA I/O error : encoder error该错误在执行单线程时并未发生,但是当执行多线程时,异常开始出现,本问题在互联网上无解决方案,只能自行通过 requests-html 库的源码进行修改。
打开 requests_html.py 文件,将 417 行左右的代码进行如下修改:
def __init__(self, *, session: Union['HTMLSession', 'AsyncHTMLSession'] = None, url: str = DEFAULT_URL, html: _HTML, default_encoding: str = DEFAULT_ENCODING, async_: bool = False) -> None: # 修改本部分代码 # Convert incoming unicode HTML into bytes. # if isinstance(html, str): html = html.decode(DEFAULT_ENCODING,'replace') super(HTML, self).__init__( # Convert unicode HTML to bytes. element=PyQuery(html)('html') or PyQuery(f'<html>{html}</html>')('html'), html=html, url=url, default_encoding=default_encoding )代码 if isinstance(html, str): 用于判断 html 是否为 str,但是在实测过程中发现 html 是 <class 'bytes'> 类型,所以数据没有进行转码工作,故取消相关判断。
除此以外,通过输出 world.html.encoding 发现网页的编码不是 GB2312 ,而是 gb18030,所以通过下述代码进行了默认编码的设置。
requests_html.DEFAULT_ENCODING = "gb18030"按照如上内容进行修改之后,代码可以正常运行,数据能正确的采集到。
本案例还新增了代码运行时长的计算,具体如下:
# 获取开始时间 start = time.perf_counter() # 执行代码的部分 # 获取时间间隔 elapsed = (time.perf_counter() - start) print("程序运行完毕,总耗时为:", elapsed)完整的代码运行效果如下所示:
收藏时间
代码仓库地址:https://codechina.csdn.net/hihell/python120,去给个关注或者 Star 吧。
==数据没有采集完毕,想要的可以在评论区留言交流==
今天是持续写作的第 <font color=red>212</font> / 365 天。可以<font color=#04a9f4>关注</font>我,<font color=#04a9f4>点赞</font>我、<font color=#04a9f4>评论</font>我、<font color=#04a9f4>收藏</font>我啦。