10  Ajax数据爬取

https://cuiqingcai.com/202251.html

https://cuiqingcai.com/202252.html

https://cuiqingcai.com/202253.html

10.1 Ajax介绍

Ajax,全称为 Asynchronous JavaScript and XML,即异步的 JavaScript 和 XML。它不是一门编程语言,而是利用 JavaScript 在保证页面不被刷新、页面链接不改变的情况下与服务器交换数据并更新部分网页的技术。

对于传统的网页,如果想更新其内容,那么必须刷新整个页面,但有了 Ajax,便可以在页面不被全部刷新的情况下更新其内容。在这个过程中,页面实际上是在后台与服务器进行了数据交互,获取到数据之后,再利用 JavaScript 改变网页,这样网页内容就会更新了。

例子: https://m.weibo.cn/u/1874566305

页面其实并没有整个刷新,也就意味着页面的链接没有变化,但是网页中却多了新内容,也就是后面刷出来的新微博。这就是通过 Ajax 获取新数据并呈现的过程。

发送 Ajax 请求到网页更新的这个过程可以简单分为以下 3 步:

  • 发送请求
  • 解析内容
  • 渲染网页
var xmlhttp;
if (window.XMLHttpRequest) {
  //code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp = new XMLHttpRequest();
} else {
  //code for IE6, IE5
  xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange = function () {
  if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    document.getElementById("myDiv").innerHTML = xmlhttp.responseText;
  }
};
xmlhttp.open("POST", "/ajax/", true);
xmlhttp.send();

Ajax 其实有其特殊的请求类型,它叫作 xhr。在图中我们可以发现一个名称以 getIndex 开头的请求,其 Type 为 xhr,这就是一个 Ajax 请求。用鼠标点击这个请求,可以查看这个请求的详细信息。 XHR: XmlHttpRequest — Ajax 向服务器发出的请求,请求数据等。

10.2 Scrape Center 案例

本节我们以一个示例网站来试验一下 Ajax 的爬取,其链接为:https://spa1.scrape.center/,该示例网站的数据请求是通过 Ajax 完成的,页面的内容是通过 JavaScript 渲染出来的,页面如图所示:

图 10.1 Scrape Center页面

10.2.1 初步探索

首先,我们先尝试用之前的 requests 来直接提取页面,看看会得到怎样的结果。用最简单的代码实现一下 requests 获取首页源码的过程,代码如下:

import requests

url = 'https://spa1.scrape.center/'
html = requests.get(url).text
print(html)

如果遇到这样的情况,这说明我们现在看到的整个页面便是 JavaScript 渲染得到的,浏览器执行了 HTML 中所引用的 JavaScript 文件,JavaScript 通过调用一些数据加载和页面渲染方法,才最终呈现了图中所示的结果。

在一般情况下,这些数据都是通过 Ajax 来加载的, JavaScript 在后台调用这些 Ajax 数据接口,得到数据之后,再把数据进行解析并渲染呈现出来,得到最终的页面。所以说,要想爬取这个页面,我们可以直接爬取 Ajax 接口获取数据就好了。

10.2.2 爬取列表页

首先我们来分析一下列表页的 Ajax 接口逻辑,打开浏览器开发者工具,切换到 Network 面板,勾选上 Preserve Log 并切换到 XHR 选项卡,如图所示:

image-20210705004826230

接着重新刷新页面,再点击第二页、第三页、第四页的按钮,这时候可以观察到页面上的数据发生了变化,同时开发者工具下方就监听到了几个 Ajax 请求,如图所示:

image-20210705004904893

由于我们切换了 4 页,每次翻页也出现了对应的 Ajax 请求,我们可以点击查看其请求详情。观察其请求的 URL 和参数以及响应内容是怎样的,如图所示。

image-20210705004957327观察到其 Ajax 接口请求的 URL 地址为:https://spa1.scrape.center/api/movie/?limit=10&offset=40 这里有两个参数,一个是limit,这里是 10;一个是 offset,这里也是 40。

通过多个 Ajax 接口的参数,我们可以观察到这么一个规律:limit 一直为 10,这就正好对应着每页 10 条数据;offset 在依次变大,页面每加 1 页,offset 就加 10,这就代表着页面的数据偏移量,比如第二页的 offset 为 10 则代表着跳过 10 条数据,返回从 11 条数据开始的结果,再加上 limit 的限制,那就是第 11 条至第 20 条数据的结果。

可以看到,结果就是一些 JSON 数据,它有一个 results 字段,是一个列表,列表中每一个元素都是一个字典。观察一下字典的内容,这里我们正好可以看到有对应的电影数据的字段了,如 namealiascovercategories,对比下浏览器中的真实数据,各个内容完全一致,而且这个数据已经非常结构化了,完全就是我们想要爬取的数据,真的是得来全不费工夫。

这样的话,我们只需要把所有页面的 Ajax 接口构造出来,所有列表页的数据我们都可以轻松获取到了。

我们先定义一些准备工作,导入一些所需的库并定义一些配置,代码如下:

import requests
import logging

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(levelname)s: %(message)s')

INDEX_URL = 'https://spa1.scrape.center/api/movie/?limit={limit}&offset={offset}'

这里我们引入了 requests 和 logging 库,并定义了 logging 的基本配置,接着我们定义了 INDEX_URL,这里把 limitoffset 预留出来了变成了占位符,可以动态传入参数构造一个完整的列表页 URL。

下面我们来实现一下详情页的爬取。还是和原来一样,我们先定义一个通用的爬取方法,其代码如下:

def scrape_api(url):
    logging.info('scraping %s...', url)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.json()
        logging.error('get invalid status code %s while scraping %s', response.status_code, url)
    except requests.RequestException:
        logging.error('error occurred while scraping %s', url, exc_info=True)

这里我们定义了一个 scrape_api 方法,和之前不同的是,这个方法专门用来处理 JSON 接口,最后的 response 调用的是 json 方法,它可以解析响应的内容并将其转化成 JSON 字符串。

接着在这个基础之上,我们定义一个爬取列表页的方法,其代码如下:

LIMIT = 10

def scrape_index(page):
    url = INDEX_URL.format(limit=LIMIT, offset=LIMIT * (page - 1))
    return scrape_api(url)

这里我们定义了一个 scrape_index 方法,它接收一个参数 page,该参数代表列表页的页码。

这里我们先构造了一个 url,通过字符串的 format 方法,传入 limitoffset 的值。这里 limit 就直接使用了全局变量 LIMIT 的值;offset 则是动态计算的,就是页码数减一再乘以 limit,比如第一页 offset 就是 0,第二页 offset 就是 10,以此类推。构造好了 url 之后,直接调用 scrape_api 方法并返回结果即可。

这样我们就完成了列表页的爬取,每次请求都会得到一页 10 部的电影数据。

由于这时爬取到的数据已经是 JSON 类型了,所以我们不用像之前那样去解析 HTML 代码来提取数据了,爬到的数据就是我们想要的结构化数据,因此解析这一步就可以直接省略啦。

到此为止,我们能成功爬取列表页并提取出电影列表信息了。

p_data = scrape_index(1)
for 1 in rang(1, 5):
    ...

10.2.3 爬取详情页

这时候我们已经可以拿到每一页的电影数据了,但是看看这些数据实际上还缺少了一些我们想要的信息,如剧情简介等信息,所以需要进一步进入到详情页来获取这些内容。

这时候点击任意一部电影,如《教父》,进入其详情页,这时可以发现页面的 URL 已经变成了 https://spa1.scrape.center/detail/40,页面也成功展示了详情页的信息,如图所示:

image-20210705005243372

另外,我们也可以观察到在开发者工具中又出现了一个 Ajax 请求,其 URL 为 https://spa1.scrape.center/api/movie/40/,通过 Preview 选项卡也能看到 Ajax 请求对应响应的信息.

稍加观察就可以发现,Ajax 请求的 URL 后面有一个参数是可变的,这个参数就是电影的 id,这里是 40,对应《教父》这部电影。

如果我们想要获取 id 为 50 的电影,只需要把 URL 最后的参数改成 50 即可,即 https://spa1.scrape.center/api/movie/50/,请求这个新的 URL 我们就能获取 id 为 50 的电影所对应的数据了。

同样,响应结果也是结构化的 JSON 数据,字段也非常规整,我们直接爬取即可。

本节中我们通过一个案例来体会了 Ajax 分析和爬取的基本流程,希望大家通过本节能够更加熟悉 Ajax 的分析和爬取实现。

另外,我们也观察到,由于 Ajax 接口大部分返回的是JSON数据,所以在一定程度上可以避免一些数据提取的工作,这也在一定程度上减轻了工作量。