谈谈新库requests-html以及python爬虫 requests

转载自品略图书馆 http://www.pinlue.com/article/2020/05/1402/2810512743930.html

一 介绍

Python上有一个非常着名的HTTP库——requests,相信大家都听说过,用过的人都说非常爽!现在requests库的作者又发布了一个新库,叫做requests-html,看名字也能猜出来,这是一个解析HTML的库,具备requests的功能以外,还新增了一些更加强大的功能,用起来比requests更爽!接下来我们来介绍一下它吧。

 

 

# 官网解释'''This library intends to make parsing HTML (e.g. scraping the web) as simple and intuitive as possible.

If you’re interested in financially supporting Kenneth Reitz open source, consider visiting this link. Your support helps tremendously with sustainability of motivation, as Open Source is no longer part of my day job.

When using this library you automatically get:

Full JavaScript support!

CSS Selectors (a.k.a jQuery-style, thanks to PyQuery).

XPath Selectors, for the faint at heart.

Mocked user-agent (like a real web browser).

Automatic following of redirects.

Connection–pooling and cookie persistence.

The Requests experience you know and love, with magical parsing abilities.

Async Support

'''

 

 

官网告诉我们,它比原来的requests模块更加强大,并且为我们提供了一些新的功能!

支持JavaScript

支持CSS选择器(又名jQuery风格, 感谢PyQuery)

支持Xpath选择器

可自定义模拟User-Agent(模拟得更像真正的web浏览器)

自动追踪重定向

连接池与cookie持久化

支持异步请求

二 安装

安装requests-html非常简单,一行命令即可做到。需要注意一点就是,requests-html只支持Python 3.6或以上的版本,所以使用老版本的Python的同学需要更新一下Python版本了。

# pip3 install requests-html

三 如何使用requests-html?

在我们学爬虫程序的时候用得最多的请求库就是requests与urllib,但问题是这些包只给我们提供了如何去目标站点发送请求,然后获取响应数据,接着再利用bs4或xpath解析库才能提取我们需要的数据。

 

以往爬虫的请求与解析

而在requests-html里面只需要一步就可以完成而且可以直接进行js渲染!requests的作者Kenneth Reitz 开发的requests-html 爬虫包 是基于现有的框架 PyQuery、Requests、lxml、beautifulsoup4等库进行了二次封装,作者将Requests的简单,便捷,强大又做了一次升级。

requests-html和其他解析HTML库最大的不同点在于HTML解析库一般都是专用的,所以我们需要用另一个HTTP库先把网页下载下来,然后传给那些HTML解析库。而requests-html自带了这个功能,所以在爬取网页等方面非常方便。

1、基本使用

 

 

from requests_html import HTMLSession

# 获取请求对象

session = HTMLSession()

# 往新浪新闻主页发送get请求

sina = session.get('https://news.sina.com.cn/')

# print(sina.status_code)

sina.encoding = 'utf-8'

# 获取响应文本信息,与requests无区别

print(sina.text)

 

 

2、获取链接(links与abolute_links)

links返回的结果                                  absolute_links返回的结果

 

            

 

from requests_html import HTMLSession

# 获取请求对象

session = HTMLSession()

# 往京东主页发送get请求

jd = session.get('https://jd.com/')

# 得到京东主页所有的链接,返回的是一个set集合

print(jd.html.links)

print('*' * 1000)

# 若获取的链接中有相对路径,我们还可以通过absolute_links获取所有绝对链接

print(jd.html.absolute_links)

 

 

3、CSS选择器与XPATH

request-html支持CSS选择器和XPATH两种语法来选取HTML元素。首先先来看看CSS选择器语法,它需要使用HTML的 find 函数来查找元素。

 

 

'''  CSS选择器 and XPATH   1.通过css选择器选取一个Element对象   2.获取一个Element对象内的文本内容   3.获取一个Element对象的所有attributes    4.渲染出一个Element对象的HTML内容   5.获取Element对象内的特定子Element对象,返回列表   6.在获取的页面中通过search查找文本    7.支持XPath    8.获取到只包含某些文本的Element对象'''

 

 

view code

四 支持JavaScript

支持JavaScript是我觉得作者更新后最为牛逼的一个地方,但是需要在第一次执行render的时候下载chromeium,然后通过它来执行js代码。

1、render的使用

 

 

from requests_html import HTMLSession

session = HTMLSession()

url = 'http://www.win4000.com/'

obj = session.get(url)

obj.encoding = 'utf-8'

obj.html.render()

 

 

注意:第一次运行render()方法时,它会将Chromium下载到您的主目录中(例如~/.pyppeteer/)。这种情况只发生一次。

2、 下载Chromeium问题

因为是从国外的站点下载几分钟才3%,实在是太慢了。所以我们需要通过国内的镜像去下载!需要做以下几步:

手动下载Chrome

先去国内源下载自己需要的版本,地址:https://npm.taobao.org/mirrors/chromium-browser-snapshots/

 

修改chromeium_downloader.py文件

下载后之后解压后,进入python安装目录下的\Lib\site-packages\pyppeteer目录, 并打开chromium_downloader.py文件。

 

View Code

五 自定义User-Agent

有些网站会使用User-Agent来识别客户端类型,有时候需要伪造UA来实现某些操作。如果查看文档的话会发现HTMLSession上的很多请求方法都有一个额外的参数**kwargs,这个参数用来向底层的请求传递额外参数。我们先向网站发送一个请求,看看返回的网站信息。

 

 

from requests_html import HTMLSession# pprint可以把数据打印得更整齐from pprint import pprintimport jsonget_url = 'http://httpbin.org/get'

session = HTMLSession()

# 返回的是当前系统的headers信息

res = session.get(get_url)

pprint(json.loads(res.html.html))

# 可以在发送请求的时候更换user-agent

ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0'

post_url = 'http://httpbin.org/get'

res = session.get(post_url, headers={'user-agent': ua})

pprint(json.loads(res.html.html))

# 如果你有需要可以在header中修改其他参数。

 

 

六 模拟表单提交(POST)

HTMLSession封装了一整套的HTTP方法,包括get、post、delete等, 对应HTTP中各个方法。

 

 

# 表单登录r = session.post('http://httpbin.org/post', data={'username': 'tank_jam', 'password': 'tank9527'})pprint(json.loads(r.html.html))''' # 打印结果{'args': {}, 'data': '', 'files': {}, 'form': {'password': 'tank9527', 'username': 'tank_jam'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '35', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'httpbin.org', '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'}, 'json': None, 'origin': '112.65.61.109, 112.65.61.109', 'url': 'https://httpbin.org/post'}'''

 

七 支持异步请求

requests-html内部就封装好了aynsc异步请求的功能,可以提高我们的爬虫效率。

 

 

from requests_html import AsyncHTMLSessionfrom requests_html import HTMLSessionimport time

# 使用异步发送请求

async_session = AsyncHTMLSession()

async def get_baidu():

url = 'https://www.baidu.com/'

res = await async_session.get(url)

print(res.html.absolute_links)

async def get_sougou():

url = 'https://www.sogou.com/'

res = await async_session.get(url)

print(res.html.links)

start_time = time.time()

async_session.run(get_baidu, get_sougou)

print('耗时:', time.time() - start_time)

# 同步发送请求

session = HTMLSession()

start_time = time.time()

res = session.get('https://www.baidu.com/')

print(res.html.links)

res = session.get('https://www.sogou.com/')

print(res.html.absolute_links)

print('耗时:', time.time() - start_time)

 

 

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.html2. 原理

不得不膜拜 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)

# for

通过迭代器实现了智能发现分页,这个迭代器里面会用一个叫 _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 contentwith an updated version, with JavaScript executed.

使用非常简单,直接调用以下方法:

r.html.render()

第一次使用的时候会下载 Chromium,不过国内你懂的,自己想办法去下吧,就不要等它自己下载了。render 函数可以使用 js 脚本来操作页面,滚动操作单独做了参数。这对于上拉加载等新式页面是非常友好的。

8. 总结

Reitz 大神设计出来的东西还是一如既往的简单好用,自己不多做,大多用别人的东西组装,简化 api。真是够人性。不过有的地方还是优化空间,希望有兴趣和精力的童鞋去 github 上关注一下这个项目。

阅读目录

昨天写了requests库好!最近requests库的作者又发布了一个新库,叫做requests-html,看名字也能猜出来,这是一个解析HTML的库,而且用起来和requests一样爽,下面就来介绍一下它。

一、安装

pip install requests-html

二、基本使用

获取网页

 

 

from requests_html import HTMLSession

session = HTMLSession()

r = session.get('https://www.qiushibaike.com/text/')

// 查看页面内容

print(r.html.html)

 

 

获取链接

links和absolute_links两个属性分别返回HTML对象所包含的所有链接和绝对链接(均不包含锚点)。

# 获取链接print(r.html.links)print(r.html.absolute_links)

结果如下

{'/article/104353012', '/article/120616112', '/users/32331196/'}{'https://www.qiushibaike.com/imgrank/', 'https://www.qiushibaike.com/article/120669516', 'https://www.qiushibaike.com/article/120682041'}

获取元素

request-html支持CSS选择器和XPATH两种语法来选取HTML元素。首先先来看看CSS选择器语法,它需要使用HTML的find函数,该函数有5个参数,作用如下:

selector,要用的CSS选择器;

clean,布尔值,如果为真会忽略HTML中style和script标签造成的影响(原文是sanitize,大概这么理解);

containing,如果设置该属性,会返回包含该属性文本的标签;

first,布尔值,如果为真会返回第一个元素,否则会返回满足条件的元素列表;

_encoding,编码格式。

例子:

 

 

# 首页菜单文本print(r.html.find('div#menu', first=True).text)# 首页菜单元素print(r.html.find('div#menu a'))# 段子内容print(list(map(lambda x: x.text, r.html.find('div.content span'))))

 

结果如下,

 

 

热门 24小时 热图 文字 穿越 糗图 新鲜[, , , , , , ]['有一次,几位大城市的朋友来家里玩,我招待他们吃风干羊肉做臊子的饸饹面,这是我们老家最具特色的美食!饭快熟的时候,老婆让我在园子里摘点“芫荽 ”,朋友问我,“芫荽”是什么东东?我给他们翻译解释说:我们本地土话叫“芫荽”,你们城里人讲普通话叫香菜,他们还大笑了一场。\n前天下雨没事儿干,翻看新华字典,突然发现“芫荽”才是香菜的学名,Tm香菜才是土话!而且我们地方方言就这两个字发音还特别标准!', '昨天晚上跟老婆吵架,他抓起我的手机就摔了。我立马摔了他的,结果我的还能用,他的坏了。高潮是人家立刻出门买了个新的!我艹,是不是中计了??', '小姨要去高铁站,我看着大大小小的箱子说:坐公交车要转车,转来转去要一个多小时,太不方便了,不如我开车送你吧。\n小姨迟疑了一下,同意了。\n我准时把小姨送到了高铁站,正好赶上检票。\n小姨高兴地说:自己开车就是方便,不过幸好你妈聪明,让我们提前两个多小时就出发了!'

 

XPAT语法,需要另一个函数xpath的支持,它有4个参数:

selector,要用的XPATH选择器;

clean,布尔值,如果为真会忽略HTML中style和script标签造成的影响(原文是sanitize,大概这么理解);

first,布尔值,如果为真会返回第一个元素,否则会返回满足条件的元素列表;

_encoding,编码格式。

还是上面的例子,不过这次使用XPATH语法:

print(r.html.xpath("//div[@id='menu']", first=True).text)print(r.html.xpath("//div[@id='menu']/a"))print(r.html.xpath("//div[@class='content']/span/text()"))

输出和上面那个几乎一样,之所以说是“几乎”,因为第三个输出会多出几个换行符,不知道什么原因。需要注意的一点是如果XPATH中包含text()或@href这样的子属性,那么结果相应的会变成简单的字符串类型,而不是HTML元素。

['\n\n\n我一份文件忘家里了,又懒得回家取,就给小姨子发短信息: 帮我把文件送来,晚上我谢谢你。等半天也没送来文件,我只好打个车回家自己拿,到家一进屋,我就发现气氛不对劲,老婆铁青着脸,两手掐着腰,小姨子站旁边对我怒目而视。']

元素内容

糗事百科首页LOGO的HTML代码如下所示:

我们来选取这个元素:

e = r.html.find("div#hd_logo", first=True)

要获取元素的文本内容,用text属性:

print(e.text)# 糗事百科

要获取元素的attribute,用attr属性:

print(e.attrs)# {'class': ('logo',), 'id': 'hd_logo'}

要获取元素的HTML代码,用html属性:

print(e.html)#

要搜索元素的文本内容,用search函数,比如说我们现在想知道是糗事什么科:

print(e.search("糗事{}科")[0])# 百

最后还有前面提到的两个链接属性:

print(e.absolute_links)print(e.links)# {'https://www.qiushibaike.com/'}# {'/'}

print(e.absolute_links)print(e.links)# {'https://www.qiushibaike.com/'}# {'/'}

三、进阶用法

JavaScript支持

有些网站是使用JavaScript渲染的,这样的网站爬取到的结果只有一堆JS代码,这样的网站requests-html也可以处理,关键一步就是在HTML结果上调用一下render函数,它会在用户目录(默认是~/.pyppeteer/)中下载一个chromium,然后用它来执行JS代码。下载过程只在第一次执行,以后就可以直接使用chromium来执行了。

render函数还有一些参数,顺便介绍一下(这些参数有的还有默认值,直接看源代码方法参数列表即可):

retries: 加载页面失败的次数

script: 页面上需要执行的JS脚本(可选)

wait: 加载页面钱的等待时间(秒),防止超时(可选)

scrolldown: 页面向下滚动的次数

sleep: 在页面初次渲染之后的等待时间

reload: 如果为假,那么页面不会从浏览器中加载,而是从内存中加载

keep_page: 如果为真,允许你用r.html.page访问页面

比如说简书的用户页面上用户的文章列表就是一个异步加载的例子,初始只显示最近几篇文章,如果想爬取所有文章,就需要使用scrolldown配合sleep参数模拟下滑页面,促使JS代码加载所有文章。

智能分页

有些网站会分页显示内容,例如reddit。

 

 

>>> r = session.get('https://reddit.com')>>> for html in r.html:... print(html)…

 

请求下一个网页就很容易了

>>> r = session.get('https://reddit.com')>>> r.html.next()'https://www.reddit.com/?count=25&after=t3_81pm82'

直接使用HTML

前面介绍的都是通过网络请求HTML内容

 

 

>>> from requests_html import HTML>>> doc = """"""

>>> html = HTML(html=doc)

>>> html.links

{'https://httpbin.org'}

 

 

直接渲染JS代码也可以:

 

 

# 和上面一段代码接起来>>> script = """ () => { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, deviceScaleFactor: window.devicePixelRatio, } } """>>> val = html.render(script=script, reload=False)

>>> print(val)

{'width': 800, 'height': 600, 'deviceScaleFactor': 1}

>>> print(html.html)

 

 

自定义请求

前面都是简单的用GET方法获取请求,如果需要登录等比较复杂的过程,就不能用get方法了。HTMLSession类包含了丰富的方法,可以帮助我们完成需求。下面介绍一下这些方法。

自定义用户代理

有些网站会使用UA来识别客户端类型,有时候需要伪造UA来实现某些操作。如果查看文档的话会发现HTMLSession上的很多请求方法都有一个额外的参数**kwargs,这个参数用来向底层的请求传递额外参数。我们先向网站发送一个请求,看看返回的网站信息。

from pprint import pprintr = session.get('http://httpbin.org/get')pprint(json.loads(r.html.html))

返回结果如下:

 

 

{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Host': 'httpbin.org', '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'}, 'origin': '110.18.237.233', 'url': 'http://httpbin.org/get'}

 

可以看到UA是requests-html自带的UA,下面换一个UA:

ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0'r = session.get('http://httpbin.org/get', headers={'user-agent': ua})pprint(json.loads(r.html.html))

可以看到UA确实发生了变化:

 

 

{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Host': 'httpbin.org', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) ' 'Gecko/20100101 Firefox/62.0'}, 'origin': '110.18.237.233', 'url': 'http://httpbin.org/get'}

 

当然这里仅仅是换了一个UA,如果你有需要可以在header中修改其他参数。

模拟表单登录

HTMLSession带了一整套的HTTP方法,包括get、post、delete等,对应HTTP中各个方法。比如下面我们就来模拟一下表单登录:

# 表单登录r = session.post('http://httpbin.org/post', data={'username': 'yitian', 'passwd': 123456})pprint(json.loads(r.html.html))

结果如下,可以看到forms中确实收到了提交的表单值:

 

 

{'args': {}, 'data': '', 'files': {}, 'form': {'passwd': '123456', 'username': 'yitian'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Content-Length': '29', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'httpbin.org', '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'}, 'json': None, 'origin': '110.18.237.233', 'url': 'http://httpbin.org/post'}

 

如果有上传文件的需要,做法也是类似的。如果了解过requests库的同学可能对这里的做法比较熟悉,没有错,这其实就是requests的用法。requests-html通过暴露**kwargs的方法,让我们可以对请求进行定制,将额外参数直接传递给底层的requests方法。所以如果有什么疑问的话,直接去看requests文档就好了。

前言

requests虽好,但有个遗憾,它无法加载JavaScript,当访问一个url地址的时候,不能像selenium一样渲染整个html页面出来。

requests-html终于可以支持JavaScript了,这就相当于是一个真正意义上的无界面浏览器了。

中文文档地址:https://cncert.github.io/requests-html-doc-cn/#/

JavaScript支持

当第一次使用render() 渲染页面的时候,会自动下载chromium,但只会下载这一次,后面就不会下载了。

from requests_html import HTMLSessionsession = HTMLSession()

r = session.get('https://www.cnblogs.com/yoyoketang/')

r.html.render() # 首次使用,自动下载chromium

render()渲染页面

到底渲染html页面是个什么概念呢?可以请求之后对比抓包看下,不使用render()之前,只发一个请求

使用render()之后,会发很多请求,类型于手工在浏览器上输入url后,浏览器渲染整个完整的页面,这正是我们想要的模拟浏览器发请求

案例

接下来访问我的博客地址后,抓取我的个人信息

from requests_html import HTMLSessionsession = HTMLSession()

r = session.get('https://www.cnblogs.com/yoyoketang/', verify=False)

r.html.render() # 首次使用,自动下载chromium

# print(r.html.html)

d = r.html.find("#profile_block", first=True)

print(d.text)

打印结果

 

你可能感兴趣的:(Python,python,xmlhttprequest)