Python 网页解析库:用 requests-html 爬取网页
1. 开始
Python 中能够进行网页解析的库有很多,常见的有 BeautifulSoup 和 lxml 等。在网上玩爬虫的文章通常都是介绍 BeautifulSoup 这个库,我平时也是罕用这个库,最近用 Xpath 用得比拟多,应用 BeautifulSoup 就不大习惯,很久之前就晓得 Reitz 大神出了一个叫 Requests-HTML 的库,始终没有趣味看,这回可算歹着机会用一下了。
应用 pip install requests-html
装置,上手和 Reitz 的其余库一样,轻松简略:
from requests_html import HTMLSession
session = HTMLSession()
r = session.get('https://www.python.org/jobs/')
这个库是在 requests 库上实现的,r 失去的后果是 Response 对象上面的一个子类,多个一个 html
的属性。所以 requests 库的响应对象能够进行什么操作,这个 r 也都能够。如果须要解析网页,间接获取响应对象的 html 属性:
r.html
2. 原理
不得不膜拜 Reitz 大神太会组装技术了。实际上 HTMLSession 是继承自 requests.Session 这个外围类,而后将 requests.Session 类里的 requests 办法改写,返回本人的一个 HTMLResponse 对象,这个类又是继承自 requests.Response, 只是多加了一个 _from_response
的办法来结构实例:
class HTMLSession(requests.Session):
# 重写 request 办法,返回 HTMLResponse 结构
def request(self, *args, **kwargs) -> HTMLResponse:
r = super(HTMLSession, self).request(*args, **kwargs)
return HTMLResponse._from_response(r, self)
class HTMLResponse(requests.Response):
# 结构器
@classmethod
def _from_response(cls, response, session: Union['HTMLSession', 'AsyncHTMLSession']):
html_r = cls(session=session)
html_r.__dict__.update(response.__dict__)
return html_r
之后在 HTMLResponse 里定义属性办法 html,就能够通过 html 属性拜访了, 实现也就是组装 PyQuery 来干。外围的解析类也大多是应用 PyQuery 和 lxml 来做解析,简化了名称,挺讨巧的。
3. 元素定位
元素定位能够抉择两种形式:
css 选择器
- css 选择器
- xpath
# css 获取有多少个职位
jobs = r.html.find("h1.call-to-action")
# xpath 获取
jobs = r.html.xpath("//h1[@class='call-to-action']")
办法名非常简单,合乎 Python 优雅的格调,这里无妨对这两种形式简略的阐明:
4. CSS 简略规定
- 标签名 h1
- id 应用
#id
示意 - class 应用
.class_name
示意 - 谓语示意:
h1[prop=value]
5. Xpath 简略规定
- 门路
// 或者 /
- 标签名
- 谓语 [@prop=value]
- 轴定位
名称:: 元素名[谓语]
定位到元素当前势必要获取元素外面的内容和属性相干数据,获取文本:
jobs.text
jobs.full_text
获取元素的属性:
attrs = jobs.attrs
value = attrs.get("key")
还能够通过模式来匹配对应的内容:
## 找某些内容匹配
r.html.search("Python {}")
r.html.search_all()
这个性能看起来比拟鸡肋,能够深入研究优化一下,说不定能在 github 上混个提交。
6. 人性化操作
除了一些根底操作,这个库还提供了一些人性化的操作。比方一键获取网页的所有超链接,这对于整站爬虫应该是个福音,URL 治理比拟不便:
r.html.absolute_links
r.html.links
内容页面通常都是分页的,一次抓取不了太多,这个库能够获取分页信息:
print(r.html)
# 比拟一下
for url in r.html:
print(url)
后果如下:
# print(r.html)
<HTML url='https://www.python.org/jobs/'>
# for
<HTML url='https://www.python.org/jobs/'>
<HTML url='https://www.python.org/jobs/?page=2'>
<HTML url='https://www.python.org/jobs/?page=3'>
<HTML url='https://www.python.org/jobs/?page=4'>
<HTML url='https://www.python.org/jobs/?page=5'>
通过迭代器实现了智能发现分页,这个迭代器外面会用一个叫 _next
的办法,贴一段源码感触下:
def get_next():
candidates = self.find('a', containing=next_symbol)
for candidate in candidates:
if candidate.attrs.get('href'):
# Support 'next' rel (e.g. reddit).
if 'next' in candidate.attrs.get('rel', []):
return candidate.attrs['href']
通过查找 a 标签外面是否含有指定的文原本判断是不是有下一页,通常咱们的下一页都会通过 下一页
或者 加载更多
来疏导,他就是利用这个标记来进行判断。默认的以列表模式存在全局:['next', 'more', 'older']
。我集体认为这种形式十分不灵便,简直没有扩展性。 感兴趣的能够往 github 上提交代码优化。
7. 加载 js
兴许是思考到了当初 js 的一些异步加载,这个库反对 js 运行时,官网阐明如下:
Reloads the response in Chromium, and replaces HTML content
with an updated version, with JavaScript executed.
应用非常简单,间接调用以下办法:
r.html.render()
第一次应用的时候会下载 Chromium,不过国内你懂的,本人想方法去下吧,就不要等它本人下载了。render 函数能够应用 js 脚本来操作页面,滚动操作独自做了参数。这对于上拉加载等旧式页面是十分敌对的。
8. 总结
Reitz 大神设计进去的货色还是判若两人的简略好用,本人不多做,大多用他人的货色组装,简化 api。真是够兽性。不过有的中央还是优化空间,心愿有趣味和精力的童鞋去 github 上关注一下这个我的项目。