python网络爬虫学习笔记(十一):Ajax数据爬取


有时候咱们在用requests抓取页面的时候,获得的结果可能和在浏览器中看到的不同:在浏览器中能够看到正常显示的页面数据,可是使用requests获得的结果并无。这是由于requests获取的都是原始的HTML文档,而浏览器中的页面则是通过JavaScript处理数据后生成的结果,这些数据的来源有多种,多是经过Ajax加载的,多是包含在HTML文档中的,也多是通过JavaScript和特定算法计算后生成的。

对于第一种状况,数据加载是一种异步加载方式,原始的页面最初不会包含某些数据,原始页面加载完后,会再向服务器请求某个接口获取数据,而后数据才被处理从而呈现到网页上,这其实就是发送了一个Ajax请求。python

照Web发展的趋势来看,这种形式的页面愈来愈多。网页的原始HTML文档不会包含任何数据,数据都是经过Ajax统一加载后再呈现出来的,这样在Web开发上能够作到先后端分离,并且下降服务器直接渲染页面带来的压力。web

因此若是遇到这样的页面,直接利用requests等库来抓取原始页面,是没法获取到有效数据的,这时须要分析网页后台向接口发送的Ajax请求,若是能够用requests来模拟Ajax请求,那么就能够成功抓取了。ajax

1.基本介绍

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

有的网页,一直往下滑,就会出现一个加载的页面,不一下子下方就继续出现了新的内容,个过程其实就是Ajax加载的过程。编程

咱们注意到页面其实并无整个刷新,也就意味着页面的连接没有变化,可是网页中却多了新内容,也就是后面刷出来的新微博。这就是经过Ajax获取新数据并呈现的过程。json

2.基本原理

初步了解了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模拟这个发送操做,获取到其中的结果了。

3.实战

咱们用程序模拟这些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的前半部分。接下来,构造参数字典,其中typevaluecontainerid是固定参数,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}