有时候咱们在用 Requests 抓取页面的时候,获得的结果可能和在浏览器中看到的是不同的,在浏览器中能够看到正常显示的页面数据,可是使用 Requests 获得的结果并无,这其中的缘由是 Requests 获取的都是原始的 HTML 文档,而浏览器中的页面则是页面又通过 JavaScript 处理数据后生成的结果,这些数据的来源有多种,多是经过 Ajax 加载的,多是包含在了 HTML 文档中的,也多是通过 JavaScript 通过特定算法计算后生成的。git
对于第一种状况,数据的加载是一种异步加载方式,原始的页面最初不会包含某些数据,原始页面加载完后会会再向服务器请求某个接口获取数据,而后数据再被处理才呈现到网页上,这其实就是发送了一个 Ajax 请求。github
照 Web 发展的趋势来看,这种形式的页面愈来愈多,网页原始 HTML 文档不会包含任何数据,数据都是经过 Ajax 来统一加载而后再呈现出来,这样在 Web 开发上能够作到先后端分离,并且下降服务器直接渲染页面带来的压力。ajax
因此若是咱们遇到这样的页面,若是咱们再利用 Requests 等库来抓取原始页面是没法获取到有效数据的,这时咱们须要作的就是分析网页的后台向接口发送的 Ajax 请求,若是咱们能够用 Requests 来模拟 Ajax 请求,那就能够成功抓取了。算法
因此本章咱们的主要目的是了解什么是 Ajax 以及如何去分析和抓取 Ajax 请求。数据库
Ajax,全称为 Asynchronous JavaScript and XML,即异步的 JavaScript 和 XML。
Ajax 不是一门编程语言,而是利用 JavaScript 在保证页面不被刷新、页面连接不改变的状况下与服务器交换数据并更新部分网页的技术。
对于传统的网页,若是想更新其内容,那么必需要刷新整个页面,但有了 Ajax,咱们即可以实如今页面不被所有刷新的状况下更新其内容。在这个过程当中,页面实际是在后台与服务器进行了数据交互,获取到数据以后,再利用 JavaScript 改变网页,这样网页内容就会更新了。编程
咱们在浏览网页的时候会发现不少网页都有上滑查看更多的选项,好比拿微博来讲,咱们以马云的主页为例:https://m.weibo.cn/u/2145291155,切换到微博页面,一直下滑,能够发现下滑几个微博以后,再向下就没有了,转而会出现一个加载的动画,不一下子下方就继续出现了新的微博内容,那么这个过程其实就是 Ajax 加载的过程,如图 6-1 所示:json
图 6-1 页面加载过程
咱们注意到页面其实并无整个刷新,也就意味着页面的连接是没有变化的,可是这个过程网页中却又多了新的内容,也就是后面刷出来的新的微博。这就是经过 Ajax 获取新的数据并呈现而实现的过程。后端
初步了解了 Ajax 以后咱们再来详细了解一下它的基本原理,发送 Ajax 请求到网页更新的这个过程能够简单分为三步:
发送请求解析内容渲染网页下面咱们分别来详细介绍一下这几个过程。api
咱们知道 JavaScript 能够实现页面的各类交互功能,那么 Ajax 也不例外,它也是由 JavaScript 来实现的,实际它就是执行了相似以下的代码:浏览器
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(); Python资源分享qun 784758214 ,内有安装包,PDF,学习视频,这里是Python学习者的汇集地,零基础,进阶,都欢迎
这是 JavaScript 对 Ajax 最底层的实现,实际上就是新建了 XMLHttpRequest 对象,而后调用了onreadystatechange 属性设置了监听,而后调用 open() 和 send() 方法向某个连接也就是服务器发送了一个请求,咱们在前面用 Python 实现请求发送以后是能够获得响应结果的,只不过在这里请求的发送变成了 JavaScript 来完成,因为设置了监听,因此当服务器返回响应时,onreadystatechange 对应的方法便会被触发,而后在这个方法里面解析响应内容便可。
获得响应以后,onreadystatechange 属性对应的方法便会被触发,此时利用 xmlhttp 的 responseText 属性即可以取到响应的内容。这也就是相似于 Python 中利用 Requests 向服务器发起了一个请求,而后获得响应的过程。那么返回内容多是 HTML,多是 Json,接下来只须要在方法中用 JavaScript 进一步处理便可。好比若是是 Json 的话,能够进行解析和转化。
JavaScript 有改变网页内容的能力,解析完响应内容以后,就能够调用 JavaScript 来针对解析完的内容对网页进行下一步的处理了。好比经过document.getElementById().innerHTML 这样的操做即可以对某个元素内的源代码进行更改,这样网页显示的内容就改变了,这样的操做也被称做 DOM 操做,即对 Document网页文档进行操做,如更改、删除等。
如上例中,document.getElementById("myDiv").innerHTML=xmlhttp.responseText 便将 ID 为 myDiv 的节点内部的 HTML 代码更改成服务器返回的内容,这样 myDiv 元素内部便会呈现出服务器返回的新数据,网页的部份内容看上去就更新了。
以上就是Ajax的三个步骤。
咱们观察到,以上的步骤其实都是由 JavaScript 来完成的,它完成了整个请求、解析、渲染的过程。
因此再回想微博的下拉刷新,这其实就是 JavaScript 向服务器发送了一个 Ajax 请求,而后获取新的微博数据,将其解析,并将其渲染在网页中。
所以咱们能够知道,真实的数据其实都是一次次 Ajax 请求获得的,咱们若是想要抓取这些数据,就须要知道这些请求究竟是怎么发送的,发往哪里,发了哪些参数。若是咱们知道了这些,不就能够用 Python 来模拟这个发送操做,获取到这其中的结果了吗?
因此在下一节咱们就来了解下到哪能够看到这些后台 Ajax 操做,去了解它究竟是怎么发送的,发送了什么参数。
仍是以上文中的微博为例,咱们已经知道了拖动刷新的内容是由 Ajax 加载的,并且页面的 URL 没有变化,那么咱们应该到哪去查看这些 Ajax 请求呢?
在这里咱们仍是须要借助于浏览器的开发者工具,咱们以 Chrome 浏览器为例来看一下怎样操做。
首先用 Chrome 浏览器打开微博的连接:https://m.weibo.cn/u/2145291155,随后在页面中点击鼠标右键,会出现一个检查的选项,点击它便会弹出开发者工具,如图 6-2 所示:
图 6-2 开发者工具
那么在 Elements 选项卡便会观察到网页的源代码,右侧即是节点的样式。
不过这不是咱们想要寻找的内容,咱们切换到 Network 选项卡,随后从新刷新页面,能够发如今这里出现了很是多的条目,如图 6-3 所示:
图 6-3 Network 面板结果
前文咱们也提到过,这里其实就是在页面加载过程当中浏览器与服务器之间发送 Request 和接收 Response 的全部记录。
Ajax其实有其特殊的请求类型,它叫作 xhr,在上图中咱们能够发现一个名称为 getIndex 开头的请求,其 Type 为 xhr,这就是一个 Ajax 请求,咱们鼠标点击这个请求,能够查看这个请求的详细信息,如图 6-4 所示:
图 6-4 详细信息
咱们在右侧能够观察到其 Request Headers、URL 和 Response Headers 等信息,如图 6-5 所示:
图 6-5 详细信息
其中 Request Headers 中有一个信息为 X-Requested-With:XMLHttpRequest,这就标记了此请求是 Ajax 请求。
随后咱们点击一下 Preview,便可看到响应的内容,响应内容是 Json 格式,在这里 Chrome 为咱们自动作了解析,咱们能够点击箭头来展开和收起相应内容,如图 6-6 所示:
图 6-6 Json 结果
观察能够发现,这里的返回结果是马云的我的信息,如昵称、简介、头像等等,这也就是用来渲染我的主页所使用的数据,JavaScript 接收到这些数据以后,再执行相应的渲染方法,整个页面就被渲染出来了。
另外也能够切换到 Response 选项卡,能够观察到真实的返回数据,如图 6-7 所示:
图 6-7 Response 内容
接下来咱们切回到第一个请求,观察一下它的 Response 是什么,如图 6-8 所示:
图 6-8 Response 内容
这是最原始的连接 https://m.weibo.cn/u/2145291155 返回的结果,其代码只有五十行,结构也很是简单,只是执行了一些 JavaScript。
因此说,咱们所看到的微博页面的真实数据并非最原始的页面返回的,而是后来执行 JavaScript 后再次向后台发送了 Ajax 请求,拿到数据后再进一步渲染出来的。
接下来咱们再利用 Chrome 开发者工具的筛选功能筛选出全部的 Ajax 请求,在请求的上方有一层筛选栏,咱们能够点击 XHR,这样在下方显示的全部请求便都是 Ajax 请求了,如图 6-9 所示:
图 6-9 Ajax 请求
再接下来咱们咱们不断滑动页面,能够看到在页面底部有一条条新的微博被刷出,而开发者工具下方也一个个地出现 Ajax 请求,这样咱们就能够捕获到全部的 Ajax 请求了。
随意点开一个条目均可以清楚地看到其 Request URL、Request Headers、Response Headers、Response Body等内容,想要模拟请求和提取就很是简单了。
如图所示内容即是马云某一页微博的列表信息,如图 6-10 所示:
图 6-10 微博列表信息
到如今为止咱们已经能够分析出来 Ajax 请求的一些详细信息了,接下来咱们只须要用程序来模拟这些 Ajax 请求就能够轻松提取咱们所须要的信息了。
因此在下一节咱们来用 Python 实现 Ajax 请求的模拟,从而实现数据的抓取。
仍然是拿微博为例,咱们接下来用 Python 来模拟这些 Ajax 请求,把马云发过的微博爬取下来。
咱们打开 Ajax 的 XHR 过滤器,而后一直滑动页面加载新的微博内容,能够看到会不断有Ajax请求发出。
咱们选定其中一个请求来分析一下它的参数信息,点击该请求进入详情页面,如图 6-11 所示:
图 6-11 详情页面
能够发现这是一个 GET 类型的请求,请求连接为:https://m.weibo.cn/api/contai...;value=2145291155&containerid=1076032145291155&page=2,请求的参数有四个:type、value、containerid、page。
随后咱们再看一下其余的请求,观察一下这些请求,发现它们的 type、value、containerid 始终如一。type 始终为 uid,value 的值就是页面的连接中的数字,其实这就是用户的 id,另外还有一个 containerid,通过观察发现它就是 107603 而后加上用户 id。因此改变的值就是 page,很明显这个参数就是用来控制分页的,page=1 表明第一页,page=2 表明第二页,以此类推。
以上的推断过程能够实际观察参数的规律便可得出。
随后咱们观察一下这个请求的响应内容,如图 6-12 所示:
图 6-12 响应内容
它是一个 Json 格式,浏览器开发者工具自动为作了解析方便咱们查看,能够看到最关键的两部分信息就是 cardlistInfo 和 cards,将两者展开,cardlistInfo 里面包含了一个比较重要的信息就是 total,通过观察后发现其实它是微博的总数量,咱们能够根据这个数字来估算出分页的数目。
cards 则是一个列表,它包含了 10 个元素,咱们展开其中一个来看一下,如图 6-13 所示:
图 6-13 列表内容
发现它又有一个比较重要的字段,叫作 mblog,继续把它展开,发现它包含的正是微博的一些信息。好比 attitudes_count 赞数目、comments_count 评论数目、reposts_count 转发数目、created_at 发布时间、text 微博正文等等,得来全不费功夫,并且都是一些格式化的内容,因此咱们提取信息也更加方便了。
这样咱们能够请求一个接口就获得 10 条微博,并且请求的时候只须要改变 page 参数便可,目前总共 138 条微博那么只须要请求 14 次便可,也就是 page 最大能够设置为14。
这样咱们只须要简单作一个循环就能够获取到全部的微博了。
在这里咱们就开始用程序来模拟这些 Ajax 请求,将马云的全部微博所有爬取下来。
首先咱们定义一个方法,来获取每次请求的结果,在请求时page 是一个可变参数,因此咱们将它做为方法的参数传递进来,代码以下:
from urllib.parse import urlencode import requests base_url = 'https://m.weibo.cn/api/container/getIndex?' headers = { 'Host': 'm.weibo.cn', 'Referer': 'https://m.weibo.cn/u/2145291155', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', 'X-Requested-With': 'XMLHttpRequest', } def get_page(page): params = { 'type': 'uid', 'value': '2145291155', 'containerid': '1076032145291155', 'page': page } url = base_url + urlencode(params) try: response = requests.get(url, headers=headers) if response.status_code == 200: return response.json() except requests.ConnectionError as e: print('Error', e.args)
首先在这里咱们定义了一个 base_url 来表示请求的 URL 的前半部分,接下来构造了一个参数字典,其中 type、value、containerid 是固定的参数,只有 page 是可变参数,接下来咱们调用了 urlencode() 方法将参数转化为 URL 的 GET请求参数,即相似于type=uid&value=2145291155&containerid=1076032145291155&page=2 这样的形式,随后 base_url 与参数拼合造成一个新的 URL,而后咱们用 Requests 请求这个连接,加入 headers 参数,而后判断响应的状态码,若是是200,则直接调用 json() 方法将内容解析为 Json 返回,不然不返回任何信息,若是出现异常则捕获并输出其异常信息。
随后咱们须要定义一个解析方法,用来从结果中提取咱们想要的信息,好比咱们此次想保存微博的 id、正文、赞数、评论数、转发数这几个内容,那能够先将 cards 遍历,而后获取 mblog 中的各个信息,赋值为一个新的字典返回便可。
from pyquery import PyQuery as pq def parse_page(json): if json: items = json.get('cards') for item in items: item = item.get('mblog') weibo = {} weibo['id'] = item.get('id') weibo['text'] = pq(item.get('text')).text() weibo['attitudes'] = item.get('attitudes_count') weibo['comments'] = item.get('comments_count') weibo['reposts'] = item.get('reposts_count') yield weibo
在这里咱们借助于 PyQuery 将正文中的 HTML 标签去除掉。
最后咱们遍历一下 page,一共 14 页,将提取到的结果打印输出便可。
if __name__ == '__main__': for page in range(1, 15): json = get_page(page) results = parse_page(json) for result in results: print(result)
另外咱们还能够加一个方法将结果保存到 MongoDB 数据库。
from pymongo import MongoClient client = MongoClient() db = client['weibo'] collection = db['weibo'] def save_to_mongo(result): if collection.insert(result): print('Saved to Mongo')
最后整理一下,最后的程序以下:
import requests from urllib.parse import urlencode from pyquery import PyQuery as pq from pymongo import MongoClient base_url = 'https://m.weibo.cn/api/container/getIndex?' headers = { 'Host': 'm.weibo.cn', 'Referer': 'https://m.weibo.cn/u/2145291155', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', 'X-Requested-With': 'XMLHttpRequest', } client = MongoClient() db = client['weibo'] collection = db['weibo'] max_page = 14 def get_page(page): params = { 'type': 'uid', 'value': '2145291155', 'containerid': '1076032145291155', 'page': page } url = base_url + urlencode(params) try: response = requests.get(url, headers=headers) if response.status_code == 200: return response.json() except requests.ConnectionError as e: print('Error', e.args) def parse_page(json): if json: items = json.get('cards') for item in items: item = item.get('mblog') weibo = {} weibo['id'] = item.get('id') weibo['text'] = pq(item.get('text')).text() weibo['attitudes'] = item.get('attitudes_count') weibo['comments'] = item.get('comments_count') weibo['reposts'] = item.get('reposts_count') yield weibo def save_to_mongo(result): if collection.insert(result): print('Saved to Mongo') if __name__ == '__main__': for page in range(1, max_page + 1): json = get_page(page) results = parse_page(json) for result in results: print(result) save_to_mongo(result)
运行程序后样例输出结果以下:
{'id': '3938863363932540', 'text': '咱们也许不能解决全部的问题,但咱们能够尽本身的力量去解决一些问题。移动互联网不能只是让留守孩子多了一个隔空说话的手机,移动互联网是要让父母和孩子一直在一块儿。过年了,回家吧…… 农村淘宝2016团圆贺岁片《福与李》 ', 'attitudes': 21785, 'comments': 40232, 'reposts': 2561} Saved to Mongo {'id': '3932968885900763', 'text': '跟来自陕甘宁云贵川六省的100位优秀乡村教师共度了难忘的两天,接下来我又得出远门了。。。为了4000万就读于乡村学校的孩子,因此有了这么一群坚毅可爱的老师,有了这么多关注乡村教育的各界人士,这两天感动、欣喜、振奋!咱们在各自的领域里,一直坚持和努力吧!', 'attitudes': 32057, 'comments': 7916, 'reposts': 2332} Saved to Mongo Python资源分享qun 784758214 ,内有安装包,PDF,学习视频,这里是Python学习者的汇集地,零基础,进阶,都欢迎
查看一下 MongoDB,相应的数据也被保存到 MongoDB,如图 6-14 所示:
图 6-14 保存结果
本节代码地址:https://github.com/oldmarkfac...
本节实例的目的是为了演示 Ajax 的模拟请求过程,爬取的结果不是重点,该程序仍有不少能够完善的地方,如页码的动态计算、微博查看全文等,如感兴趣能够尝试一下。经过这个实例咱们主要是为了学会怎样去分析 Ajax 请求,怎样用程序来模拟抓取 Ajax 请求,了解了相关抓取原理以后,下一节的 Ajax 实战演练会更加驾轻就熟。