使用 Python 的 Requests-HTML 库进行网页解析

发布于 更新于
本文目录
  1. 1. 开始
  2. 2. 原理
  3. 3. 元素定位
  4. css 选择器
  5. 4. CSS 简单规则
  6. 5. Xpath简单规则
  7. 6. 人性化操作
  8. 7. 加载 js
  9. 8. 总结

技术文章测试开发

Python 中可以进行网页解析的库有很多,常见的有 BeautifulSoup 和 lxml 等。在网上玩爬虫的文章通常都是介绍 BeautifulSoup 这个库,我平常也是常用这个库,最近用 Xpath 用得比较多,使用 Beauti

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 上关注一下这个项目。

来源:testingpai

继续阅读