0
点赞
收藏
分享

微信扫一扫

动态网页Reptile

zmhc 2022-08-05 阅读 43


文章目录

  • ​​引入​​
  • ​​Network​​
  • ​​什么是 XHR​​
  • ​​什么是 JSON​​
  • ​​如何让解析JSON​​
  • ​​获取多页数据​​
  • ​​静态、动态网页爬虫总结​​

引入

我们在使用静态网页爬取方法的时候可能会发生一个问题,有些数据在网页开发者工具中(F12)中存在,而在网页源代码中却没有。

这是因为,例如像电影评论这样的信息是瞬息万变的,可能短短数小时内就多出几百、上千条。如果将每条评论的数据,都通过 HTML 代码的形式写进网页中,那么评论每发生一次变化,就要相应地改变代码,非常繁琐。

那这种实时改变的数据是怎样展现到我们面前的呢?

网页可以通过 API 获取数据,实时更新内容。API 即应用程序接口,它规定了网页与服务器之间可以交互什么数据、通过什么样的方式进行交互。

Network

​Network​​ 记录的是从打开浏览器的开发者工具到网页加载完毕之间的所有请求。如果你在网页加载完毕后打开,里面可能就是空的,我们开着开发者工具刷新一下网页即可。

动态网页Reptile_python


我们还可以点击每一个请求,查看每个请求的详细信息。详细信息里的 Response(响应)里是服务器返回的内容。第一个请求是网页,它的响应是网页源代码(我们可以注意到它的类型是document),即我们使用 requests.get() 获取到的 res.text 内容。

因为一次性加载整个网站很慢,为了提升网页加载速度,有些网站将网站的骨架和内容拆分开,加载骨架后再通过多个请求获取内容,最终组成完整的网站。而有些老的网站或轻量级网站,仍然是一次性返回整个网站的内容,比如豆瓣。

在写爬虫的过程中,我们会经常用到开发者工具中的 Network 面板。接下来我们再来看看 Network 面板 中有哪些常用功能:

动态网页Reptile_爬虫_02


图中 ① 用于清空请求列表;② 勾选后会保留请求记录,常用于发生页面跳转时保留前一个页面的所有请求记录;③ 是请求类型过滤器,All 表示查看全部,点击其他的只会展示对应类型的请求。爬虫中常用的有 All、XHR、Img 和 Media,剩下的了解一下即可:

动态网页Reptile_json_03


④ 展示了一些常用的请求信息,比如请求的名称、状态码、类型、数据大小和耗时等。这些都比较简单,我们只要能看懂,知道是什么意思就行。

动态网页Reptile_python_04


在所有请求类型中,有一类非常重要的类型叫做 XHR。

什么是 XHR

上文我们说过,有些网站为了提升网页加载速度,会先加载骨架,再加载详细的内容。而加载详细内容的过程,就用到了 XHR 技术。

​XHR​​​ 全称 ​​XMLHttpRequest​​,是浏览器内置的对象。浏览器想要在不刷新网页前提下加载、更新局部内容时,必须通过 XHR 向存放数据的服务器发送请求

web前端中的AJAX技术就是使用到了XHR

反过来说,XHR 类型请求里,就藏着我们需要的那些不在网页源码中的数据。

接下来我们尝试寻找电影的评论数据。

网站链接:http://movie.mtime.com/251525

首先点击 Network 中的 XHR 过滤其他类型的请求。可以看到,仍然有很多的请求,我们要从中找到评论数据。笨一点的方法是一条一条地查看,直到找到评论的数据,但这样耗时又耗力。

聪明一点的方法是通过名称来找,既然是评论的数据,请求名称中可能会带有 comment(评论),这样我们就能极大地缩小查找范围。

还有一个小技巧是:由于数据是分页加载,我们可以先将请求记录清空,再点击下方分页导航的第 2 页,这样,加载第 2 页评论的请求说不定就会被我们“守株待兔”逮个正着。

用以上的方法我们很快可以锁定到其中的一个响应:

动态网页Reptile_python_05

经过对比我们可以发现就是这条响应中包含着我们要的信息。

动态网页Reptile_http_06


从图中我们可以看到,该网规定了需要通过 GET 方法,向 Request URL 发送请求。点击第二页查看电影评论的行为,实际上是浏览器帮我们自动填充查询参数,向时光网获取短影评数据,参数详情为:

  • id 为 251525 (movieId=251525) ;
  • 第 2 页内容 (pageIndex=2);
  • 每页 20 条 (pageSize=20)。

遵循某种规则向指定 URL 发送请求,从而获得相应数据的过程,就是 ​​通过 API 获取数据​​​。
下面的 Status Code 是状态码,之前说过,如果是 200,表示成功。其他信息我们无需关注。

另外,为了防止被网站反爬,我们还要在 Headers 中观察一下 Request Headers(请求头),有两个参数要注意。

动态网页Reptile_爬虫_07


user-agent 我们之前已经说过,用途是将爬虫伪装成浏览器;另外一个 referer 字段,字面意思是“发起者,发送人”,用来验证这个请求的发起方是否合法。也就是说,服务器要验证这个请求是由谁发出的,只接受从特写网页上发出该请求,比如这里就是该网的网址。如果这个字段不加,可能会爬取失败。

既然找到了获取评论数据的真正链接,以及相关的请求头参数,接下来我们就可以试着通过爬虫来爬取数据了。我们仍然使用 requests.get() 方法获取刚才找到的 API 地址,但 headers 参数要加上 referer 的信息,假装是由时光网自身发起的请求,代码如下:

import requests

headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'referer': 'http://movie.mtime.com/'
}
res = requests.get('http://front-gateway.mtime.com/library/movie/comment.api?tt=1641893701852&movieId=251525&pageIndex=2&pageSize=20&orderType=1', headers=headers)
print(res.text)

上面代码中的链接中,comment.api? 后面的内容,是我们前面说过的 ​​查询参数​​。这次的查询参数内容有些长,直接放在链接里有些乱。好在 requests.get() 方法提供了 params 参数,能让我们以字典的形式传递链接的查询参数,使代码看上去更加的整洁明了。我们看一下官方文档的介绍:

动态网页Reptile_爬虫_08


也就是说,链接中的 tt=1641893701852&movieId=251525&pageIndex=2&pageSize=20&orderType=1,可以拆分成一个字典:

params = {
"tt": "1641893701852",
"movieId": "251525",
"pageIndex": "2",
"pageSize": "20",
"orderType": "1"
}

res = requests.get(
'http://front-gateway.mtime.com/library/movie/comment.api',
params=params,
headers=headers
)

五个参数中,​​movieId​​​ ​​pageIndex​​​ ​​pageSize​​ 的意义我们可以根据字面意思猜到,应该分别代表 电影在该网中的 ID,评论的第x页 和 每页评论数。

电影的页面地址http://movie.mtime.com/251525中的 251525 和 movieId 的值一致,可以作为佐证。

那剩下的 orderType 和 tt 是什么?

orderType 字面意思是排序方式,而我们发现,短影评页的右上方的确是有这个选项的。值为 1 代表的应该就是按最热排序。

动态网页Reptile_爬虫_09

如果按最新影评来排序,orderType 的值应该是什么?用刚刚学到的“守株待兔”法,打开开发者工具后,点击按最新排序,就可以立即看到新请求中的 orderType 值是 2。

动态网页Reptile_http_10


剩下的 tt 参数,通过经验判断可能是 Unix 时间戳,依据有三:

  • 以 15、16、17 开头,是长整数,与当下时间戳的开头值相符;
  • 字母 t 本身也可能和 time(时间)有关;
  • 多次尝试刷新网页,重复发起请求,发现 tt 的值每次都在变大,非常像时间的增长。

提示:Unix 时间戳是指格林威治时间 1970 年 01 月 01 日 00 时 00 分 00 秒起至现在的总秒数。

获取评论的请求为什么要带上当前的时间戳?具体原因我也不知道,可能是为了验证请求是否过期——如果当前时间戳与服务器时间差距过大,服务器可能认为是非法的过期请求,从而拒绝返回结果(猜想而已)。
有些网站的 API 要求的查询参数非常多,但并不是每个都需要我们去细究并了解原理,只要保证能理解的关键参数都填对就好,其它的参数不懂也没关系,有时可以原封不动照抄进代码中,能返回正确的结果即可。

获取数据的完整代码:

import requests
import time

headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'referer': 'http://movie.mtime.com/'
}

params = {
# 将当前时间戳转为毫秒后取整,作为 tt 的值
"tt": "{}".format(int(time.time() * 1000)),
"movieId": "251525",
"pageIndex": "2",
"pageSize": "20",
"orderType": "1"
}

res = requests.get(
'http://front-gateway.mtime.com/library/movie/comment.api',
params=params,
headers=headers)
print(res.text)

这样就能看到打印的结果:

{
"code": 0,
"data": {
"count": 3307,
"hasMore": true,
"list": [
{
"nickname": "瀛26000",
"userImage": "https://img2.mtime.cn/u/285/2016285/7c677c7f-4503-42e9-a63f-683e9f97148c/128X128.jpg",
"rating": "8.5",
"content": "这是我看过的最有血有肉有人情味还紧跟时代潮流的一版哪吒了,没有金吒木吒只有哪吒,李靖没有让人讨厌的琵琶精小老婆,敖丙也不是奸淫掳掠无恶不作的龙二代,申公豹的口吃设计也承包了一部分笑点,太乙真人的火锅味",
...省略剩下的内容
},

...省略剩下的内容
}

所以,从打印结果上看,res.text 是多层级的字典吗?并不是,只是长得像字典的字符串罢了,我们可以验证一下。

print(type(res.text))
# 输出:<class 'str'>

这种长得像字典的字符串,是一种名为 JSON 的数据格式。我们需要将其转换成真正的 字典/列表,才能从中提取出评论数据

什么是 JSON

​JSON(JavaScript Object Notation)​​是一种轻量级的数据交换格式。 易于人阅读和编写,同时也易于机器解析和生成。

JSON 建构于两种结构:​​键值对的集合​​​ 和 ​​值的有序列表​​,分别对应 Python 里的字典和列表,这些都是常见的数据结构。大部分现代计算机语言都支持 JSON,所以 JSON 是在编程语言之间通用的数据格式。

JSON 本质上就是一个字符串,只是该字符串符合特定的格式要求。也就是说,我们将字典、列表等用字符串的形式写出来就是 JSON,就像下面这样:

# 字典
dict = {'price': 233}

# JSON
json = '{"price": 233}'

# 列表
list = ['x', 'y', 'z']

# JSON
json = '["x", "y", "z"]'

Tips:Python 字符串使用单引号或双引号没有区别,但 JSON 中,字符串必须使用英文的双引号来包裹。

你可能会有疑问,为什么不直接写成字典或列表,而非要写成 JSON 呢?这是因为不同编程语言的数据结构是不一样的,Python 的字典和列表在别的语言中可能并不写成这样。

而 JSON 是一种标准,规定了基本数据结构的写法,不同的编程语言拿到后解析成自己对应的数据结构即可。JSON 就像普通话,而不同语言的数据结构是方言。普通话是大家都能听懂的,而方言不能。

如何让解析JSON

通过官方文档我们可以知道通过调用 json() 方法即可对响应内容解码。

动态网页Reptile_数据_11


转换后的结果是 Python 中对应的字典或列表,我们便可根据 Python 的基础知识提取我们需要的数据。不再需要通过 BeautifulSoup 对网页源代码解析,然后再提取数据了。

其实在提取数据的时候也可以不用json去整理数据,可以直接使用正则表达式进行匹配,直接拿到自己想要的信息。

获取多页数据

方法①:通过修改pageindex
代码实现:

import requests
import time

headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'referer': 'http://movie.mtime.com/'
}

for num in range(1, 6):
params = {
"tt": "{}".format(int(time.time() * 1000)),
"movieId": "251525",
"pageIndex": "{}".format(num),
"pageSize": "20",
"orderType": "1"
}

res = requests.get(
'http://front-gateway.mtime.com/library/movie/comment.api',
params=params,
headers=headers)

comment_list = res.json()['data']['list']
for i in comment_list:
print("用户:", i['nickname'])
print("评论:", i['content'])

# 暂停一下,防止爬取太快被封
time.sleep(1)

方法②:直接修改pagesize即可

import time
import requests

headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36 Edg/101.0.1210.39',
'referer': 'http://movie.mtime.com/'
}

params = {
'tt': '{}'.format(time.time()),
'movieId': '251525',
'pageIndex': '1',
'pageSize': '100',
'orderType': '1'
}

rs = requests.get('http://front-gateway.mtime.com/library/movie/longCommentList.api', params = params,headers = headers)

order = 0

for i in rs.json()['data']['list']:
order += 1
print(order,'\n ',i['nickname'],'\n ',i['content'])

静态、动态网页爬虫总结

根据网站是静态的还是动态的,我们爬虫的策略有所不同。静态网页使用 BeautifulSoup 解析网页源代码,动态网页直接找到加载数据的 API,从 API 中爬取数据。

动态网页Reptile_json_12


举报

相关推荐

0 条评论