有些时候,我们直接通过网络请求库请求网页地址时,得到的响应结果可能跟浏览器中右键查看网页源码所看到的内容不一样。例如,在抓取:https://www.feeair.com/threeCode.html (飞啊网)这个网页时,通过浏览器看到的页面数据是正常的,但是通过 requests库直接去请求这个地址,返回的网页源代码数据和在浏览器上看到的是不同的。

这是为什么呢?实际上,这是因为 requests 获取到的都是原始的 HTML 文档,而浏览器中的页面是经过 JavaScript 处理数据后生成的结果,这些数据来源有多种,可能是通过 Ajax 加载的,也可能是包含在 HTML 文档中的,还有可能是经过 JavaScript 和特殊的算法计算后生成的。对于第一种情况,数据加载是一种异步加载方式,原始的页面最初不会包含某些数据,原始页面加载完成后,再通过 JS 向服务器发送一个或多个请求获取数据,数据才会被处理并呈现在网页上,这其实就是发送了一个 Ajax请求。

Ajaxe简介

Ajax 的全称为 Asynchronous JavaScript and XML,即异步的 JavaScript 和 XML,它不是新的编 程语言,而是一种使用现有标准的新方法,它可以在不重新加载整个页面的情况下与服务器交换数据并更新部分网页的数据。在 W3School 网站上也有几个关于 Ajax 的小实例,有兴趣的读者可以打开网址 http://www.w3school.com.cn/tiy/t.asp?f=ajax_get 去体验一下。

实例引入

下面通过一个实例来了解 Ajax 请求,这里以前面提到的“飞啊网”为例,在浏览器中打开链接,如图1所示。

飞啊网首页地址:https://www.feeair.com/threeCode.html


获取ajaxresult里的数据_ajax

图1 飞啊网首页

 在页面上的“三字代码查询”输入框中输入“PEK”,然后点击【查询】按钮将会出现如图2所示的搜索结果。


获取ajaxresult里的数据_获取ajaxresult里的数据_02

图2 搜索结果

 得到查询结果后仔细观察查询前和查询后的页面,特别是 URL 地址栏,可以发现查询前和查询后页面的 URL 没有任何变化,但下面的列表中的数据却不一样了,这其实就是 Ajax 的效果,在不刷新全部页面的情况下,通过 Ajax 异步加载数据,实现数据局部更新。

Ajax的基本原理

初步了解了 Ajax 之后,下面再来详细了解它的基本原理。发送 Ajax 请求到网页更新的过程可以简单地分为以下 3 个步骤。

  1. 发送请求
  2. 解析返回数据
  3. 渲染数据到网页

在具体讲解这 3 个步骤之前,可以先看一下它的抽象过程图,如图3所示。


获取ajaxresult里的数据_获取ajaxresult里的数据_03

图3 ajax抽象原理图

1.发送请求

我们知道,JavaScript 可以实现页面的各种交互功能。Ajax 也不例外,它的底层也是由 JavaScript 实现的。要使用 Ajax 技术,需要先创建一个 XMLHttpRequest 对象,缺少它就不能实现异步传输。所以执行 Ajax 时,实际上需要执行以下代码。

var xmlhttp;
if (window.XMLHttpRequest) {
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xmlhttp=new XMLHttpRequest();
}else {
// IE6, IE5 浏览器执行代码
xmlhttp=new ActtveXObject("Microsott.XMLHTTP");
}
xmlhttp.open("GET","/try/ajax/demo_get2.php?fname=Henry&lname=Ford",true);
xmlhttp.send();
xmlhttp.open("POST","/try/ajax/demo_post2.php",true);
xmlhttp.setRequestHeader("Content-type","applicatton/x-www-form-urlen coded");
xmlhttp.send("fname=Henry&lname=Ford");

在网页中为某些事件的响应绑定异步操作:通过上面创建的 xmlhttp 对象传输请求、携带数据。 在发出请求前要先定义请求对象的 method、要提交给服务器中哪个文件进行请求的处理、要携带 哪些数据,以及判断是否异步。

其中,与普通的 Request 提交数据一样,这里也分两种方式:GET 和 POST,在实际使用时可根据需求自主选择。GET 和 POST 都是向服务器提交数据,并且都会从服务器获取数据。它们之间的区别如下。

  1. 传送方式:GET 通过地址栏传输,POST 通过报文传输。
  2. 传送长度:GET 参数有长度限制(受限于 URL 长度),而 POST 无限制。

对于 GET 方式的请求,浏览器会把 HTTP header 和 data 一并发送出去,服务器响应 200(返回数据);而对于 POST,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 OK(返回数据)。也就是说,GET 只需要汽车跑一趟就把货送到了,而 POST 得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。因为 POST 需要两步,时间上消耗的要多一点,看起来 GET 比 POST 更有效。 因此推荐用 GET 替换 POST 来优化网站性能。

2.解析返回数据

服务器在收到请求后,就会把附带的参数数据作为输入传给处理请求的文件,例如,前面代码中所示:把 fname=Henry&lname=Ford 作为输入,传给“/try/ajax/demo_get2.php”文件。然后该文件根据传入的数据做出处理,最终返回结果,并通过 Response 对象发回去。客户端根据 xmlhttp 对象来获取 Response 的内容,返回的 response 内容可能是 HTML 也可能是 JSON,接下来只需要在方法中用 JavaScript 做进一步的处理即可。例如,当为 JSON 时可以进行解析和转化。

例如,这里使用谷歌浏览器打开前面的飞啊网后,按【F12】 键打开调试模式,然后在页面的搜索框中输入“PEK”并单击【搜索】按钮。之后在调试面板中切 换到【Network】选项卡,找到名称为 getAirInfo


获取ajaxresult里的数据_获取ajaxresult里的数据_04

图4 请求响应结果

 3.渲染网页

JavaScript有改变网页内容的能力,所以在通过 Ajax 请求获取到返回数据后,通过解析就可以调用指定的网页DOM对象进行更新、修改等数据处理了。例如,通过 document.getElementById().innerHTML 操作,便可以对某个元素内的元素进行修改,这样网页显示的内容就改变了,这样的操作也被称为 DOM 操作,即对 Document 网页文档进行操作,如修改、 删除等。

Ajax方法分析

这里仍以飞啊网为例,我们都知道在条件筛选框中输入机场三字码时刷新内容由 Ajax 加载,而且页面的 URL 没有变化,那么应该到哪里去查看这些 Ajax 请求呢?前面小节其实有简单提到,但是笔者这里还是要重新详细讲解下。

这里需要借助浏览器的开发者工具,下面以 Chrome 浏览器为例来介绍。

步骤1:用 Chrome 打开网址:https://www.feeair.com/threeCode.html

步骤2:在页面中右击,在弹出的快捷菜单中选择【检查】选项或按【F12】键,此时会弹出开发者工具,如图 5 所示,在【Elements】选项卡中会显示网页的源代码,其下方是节点的样式。不过这不是我们想要寻找的内容。

步骤3:切换到【Network】选项卡,重新刷新当前页面,可以发现这里出现了非常多的条目, 其实这些条目就是页面在加载过程中浏览器与服务器之间发送请求和接收响应的所有记录,如图6 所示。

图5

 


获取ajaxresult里的数据_获取ajaxresult里的数据_05

图6 network选项卡

getAirInfo”的请求,其 Type 为 xhr,这就是一个名副其实的 Ajax 请求。单击这个请求就可以看到它的详细信息。


获取ajaxresult里的数据_javascript_06

图7 getAirInfo请求信息

 步骤4:单击【getAirInfo】请求,会看到右侧有它的一些详细信息,如图 8 所示,这里有 5 个选项卡,选择【Preview】或【Response】选项卡就会看到当前 Ajax 请求向服务器端发起请求后服务器端响应的内容了。得到这些内容后,JS 就可以解析处理,将它更新到网页中。


获取ajaxresult里的数据_网络爬虫_07

图8 响应结果

 

在进行请求分析时,若发现条目太多,不方便直接找出xhr方法时,这里教大家一个小技巧: 单击图9中的【Type】选项,就可以快速地对请求进行筛选分类,这时再按分类去查找xhr就快速多了。


获取ajaxresult里的数据_获取ajaxresult里的数据_08

图9 筛选请求条目

 使用Python网络请求库发起请求获取数据

在前面的分析中,已经找到到了一个名为“getAirInfo”的请求返回了查询的机场信息,接下来需要进一步分析它的请求参数然后使用Python的网络请求库来发起请求获取数据。

通过浏览器抓包找到“getAirInfo”请求后,直接单击它一下,这时候看到【Headers】下面有很多关于请求的详细信息,例如:

  1. 请求链接 Request URL 为:https://www.feeair.com/wp-json/myapi/getAirInfo
  2. 请求的方法Request Method为:POST

继续拖动滚动条到最下方 From Data 处,可以看到这里有三个参数:condition、page_no和page_size分别对应的含义是:要查询的三字码、页码、每页显示的大小。除此之外,还传递了一些请求头信息,如图10中的Request Headers。


获取ajaxresult里的数据_网络爬虫_09

图10 请求头信息

 接着看看它的响应结果是什么样的。选择【Preview】选项卡,将会出现如图 11 所示的内容,这些内容是 JSON 格式,浏览器开发工具自动做了解析方便查看。可以看到有 6 个信息,分别是:

  1. SUCCESS:表示请求是否成功
  2. TotalCount:查询的总数
  3. TotalPage:总页数
  4. airInfoList:基础信息列表
  5. pageNo:页码
  6. status:请求状态码

新手读者需要注意的是,这6个响应信息不是固定的,每个网站或每个不同的请求响应的内容都不一样。


获取ajaxresult里的数据_javascript_10

图11 响应结果

 编写Python代码获取数据

下面使用 Python 的 requests 库编写代码来模拟抓取。首先在代码中定义出要请求的URL地址和请求需要提交的参数信息,然后再引入requests库进行请求获取数据。示例代码如下:

import requests

url = "https://www.feeair.com/wp-json/myapi/getAirInfo"
data = {
    "condition": "pek",
    "page_no": "1",
    "page_size": "30"
}
resp = requests.post(url, data=data).json()
print(resp)

运行结果如下:

{
    "TotalCount": 0,
    "TotalPage": 0,
    "pageNo": 1,
    "airInfoList": [
        {
            "id": "3376",
            "code": "PEK",
            "icao": "ZBAA",
            "airport_name": "Beijing Capital International Airport 北京首都国际机场",
            "city": "Beijing 北京市",
            "area": "Beijing 北京",
            "country": "China 中国 (CN)",
            "time_zone": "+08:00",
            "continent": "Asia",
            "airport_type": "海关机场",
            "longitude": "40.0798573",
            "dimention": "116.6031121",
            "bank_interest": "Open on Saturday, closed on Sunday.",
            "url": "/cn/code/beijing_capital_international_airport_pek.html",
            "star": "4",
            "yj_num": "6",
            "update_time": "2019-07-03 10:16:45",
            "type": 1
        }
    ],
    "status": 1,
    "SUCCESS": true
}

通过上述方法成功得到了关于北京首都机场的相关信息,另外,还可以增加一个方法,用于将数据保存到数据库中或Excel中等。

新手问答

1.为什么有些 Ajax 接口在模拟了它的参数请求之后却抓取不到数据?

答:首先这里需要了解的一点就是,现在很多网站是有反爬策略的,也就是说,它不想让别人 通过爬虫获取到它的数据,所以采取了一些手段,如对接口的参数加密、IP 访问频率控制等。遇 到这种问题,很可能是你模拟的接口中有些参数是需要加密或用别的特殊方法获取值携带到请求中 的,如 tooken 之类的。遇到这种网站就需要进一步地分析它的参数或换思路爬取。

2.Ajax 接收到的数据类型是什么?

答:后台返回的数据主要有 3 种类型:String、JSON 串和 JSON 对象。接收到了这 3 种类型的 数据,可以通过 JSON 对象直接循环使用、JSON 串转 JSON 使用和 String 直接使用。具体方式可 根据实际得到的数据来选择。