对于第一种状况,数据加载是一种异步加载方式,原始的页面最初不会包含某些数据,原始页面加载完后,会再向服务器请求某个接口获取数据,而后数据才被处理从而呈现到网页上,这其实就是发送了一个Ajax请求。python
照Web发展的趋势来看,这种形式的页面愈来愈多。网页的原始HTML文档不会包含任何数据,数据都是经过Ajax统一加载后再呈现出来的,这样在Web开发上能够作到先后端分离,并且下降服务器直接渲染页面带来的压力。web
因此若是遇到这样的页面,直接利用requests等库来抓取原始页面,是没法获取到有效数据的,这时须要分析网页后台向接口发送的Ajax请求,若是能够用requests来模拟Ajax请求,那么就能够成功抓取了。ajax
Ajax,全称为Asynchronous JavaScript and XML,即异步的JavaScript和XML。它不是一门编程语言,而是利用JavaScript在保证页面不被刷新、页面连接不改变的状况下与服务器交换数据并更新部分网页的技术。算法
有的网页,一直往下滑,就会出现一个加载的页面,不一下子下方就继续出现了新的内容,个过程其实就是Ajax加载的过程。编程
咱们注意到页面其实并无整个刷新,也就意味着页面的连接没有变化,可是网页中却多了新内容,也就是后面刷出来的新微博。这就是经过Ajax获取新数据并呈现的过程。json
初步了解了Ajax以后,咱们再来详细了解它的基本原理。发送Ajax请求到网页更新的这个过程能够简单分为如下3步:后端
(1) 发送请求; (2) 解析内容; (3) 渲染网页。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();
这是JavaScript对Ajax最底层的实现,实际上就是新建了XMLHttpRequest对象,而后调用onreadystatechange属性设置了监听,而后调用open()和send()方法向某个连接(也就是服务器)发送了请求。前面用Python实现请求发送以后,能够获得响应结果,但这里请求的发送变成JavaScript来完成.因为设置了监听,因此当服务器返回响应时,onreadystatechange对应的方法便会被触发,而后在这个方法里面解析响应内容便可。
所以,咱们知道,真实的数据其实都是一次次Ajax请求获得的,若是想要抓取这些数据,须要知道这些请求究竟是怎么发送的,发往哪里,发了哪些参数。若是咱们知道了这些,就能够用Python模拟这个发送操做,获取到其中的结果了。
咱们用程序模拟这些Ajax请求,并爬取微博的前十页
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/2830678474', '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': '2830678474', 'containerid': '1076032830678474', '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=2830678474&containerid=1076032830678474&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('data').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,一共10页,将提取到的结果打印输出便可:
if __name__ == '__main__': for page in range(1, 11): json = get_page(page) results = parse_page(json) for result in results: print(result)
{'id': '4544491111844364', 'text': '老婆真好啊,今天感受工做有点累不太开心,而后老婆晚上和我开了一下视频对我笑了笑撒了撒娇,我瞬间又开心又好了,感受内心暖暖的,我老婆最好了!', 'attitudes': 12, 'comments': 3, 'reposts': 0} {'id': '4543259479641769', 'text': '我老婆最好看了', 'attitudes': 12, 'comments': 0, 'reposts': 0} {'id': '4543252072761828', 'text': '不知道你们是否已经对抖音有了一种厌倦?最先的时候我以为内容质量还行,如今没刷几个视频,不少都是广告、带货、博人眼球、摆拍、空洞的内容,质量愈来愈差,越看越没劲,卸了卸了,仍是撸代码好玩。', 'attitudes': 10, 'comments': 8, 'reposts': 0} {'id': '4541086507209784', 'text': '铁窗爱情3', 'attitudes': 23, 'comments': 3, 'reposts': 1} {'id': '4541085119162017', 'text': '即使没收费,那直播搞这个操做也是太服了。', 'attitudes': 3, 'comments': 0, 'reposts': 0} {'id': '4539580352832042', 'text': '铁窗爱情2', 'attitudes': 3, 'comments': 4, 'reposts': 0} {'id': '4538323178106309', 'text': '老婆返校了,可是出不来,因而就有了铁窗爱情。@长泽牙妹 北京', 'attitudes': 20, 'comments': 6, 'reposts': 0}