第六章 Ajax数据爬取
-
有时候我们在用requests抓取页面的时候,得到的结果可能和在浏览器中看到的不一样:这是因为requests 获取的都是原始的HTML文档,而浏览器中的页面则是经过JavaScript处理数据后生成的结果,这些数据的来源有多种,可能是通过Ajax加载的,可能是包含在HTML文档中的,也可能是经过JavaScript和特定算法计算后生成的。
对于第一种情况,数据加载是一种异步加载方式,原始的页面最初不会包含某些数据,原始页面加载完后,会再向服务器请求某个接口获取数据,然后数据才被处理从而呈现到网页上,这其实就是发送了一个Ajax请求。 -
Ajax
Ajax,全称为Asynchronous JavaScript and XML,即异步的JavaScript 和XML。它不是一门编程语言,而是利用JavaScript 在保证页面不被刷新、页面链接不改变的情况下与服务器交换数据并更新部分网页的技术。
比如微博的“加载”按钮。页面其实并没有整个刷新,也就意味着页面的链接没有变化,但是网页中却多了新内容,也就是后面刷出来的新微博。这就是通过Ajax获取新数据并呈现的过程。
-
Ajax请求到网页更新的这个过程可以简单分为3步:
-
发送请求
这是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元素内部便会呈现出服务器返回的新数据,网页的部分内容看上去就更新了。
这3个步骤其实都是由JavaScript 完成的,它完成了整个请求、解析和渲染的过程。再回想微博的下拉刷新,这其实就是JavaScript向服务器发送了一个Ajax请求,然后获取新的微博数据,将其解析,并将其渲染在网页中。
-
-
以微博为例走一下Ajax分析方法
Ajax有特殊的请求类型,叫xhr,这种类型的文件就是Ajax请求。
Request Headers中有一个信息为X-Requested-With:XMLHttpRequest。
preview返回的是个人信息如昵称、简介、头像等,以JSON格式存储。
过滤请求
Network——XHR,此时下方显示的所有请求便都是Ajax请求了。
接下来,不断滑动页面,可以看到页面底部有一条条新的微博被刷出,而开发者工具下方也一个个地出现Ajax请求,这样我们就可以捕获到所有的Ajax请求了。
随意点开一个条目,都可以清楚地看到其Request URL、Request Headers、 Response Headers、Response Body等内容,此时想要模拟请求和提取就非常简单了。 -
分析Ajax爬取今日头条街拍美图
import requests from urllib.parse import urlencode from requests import codes import os from hashlib import md5 from multiprocessing.pool import Pool import re ''' https://github.com/Python3WebSpider/Jiepai ''' def get_page(offset): params = { 'aid': '24', 'offset': offset, 'format': 'json', #'keyword': '街拍', 'autoload': 'true', 'count': '20', 'cur_tab': '1', 'from': 'search_tab', 'pd': 'synthesis' } base_url = 'https://www.toutiao.com/api/search/content/?keyword=%E8%A1%97%E6%8B%8D' url = base_url + urlencode(params) try: resp = requests.get(url) print(url) if 200 == resp.status_code: print(resp.json()) return resp.json() except requests.ConnectionError: return None def get_images(json): if json.get('data'): data = json.get('data') for item in data: if item.get('cell_type') is not None: continue title = item.get('title') images = item.get('image_list') for image in images: origin_image = re.sub("list", "origin", image.get('url')) yield { 'image': origin_image, # 'iamge': image.get('url'), 'title': title } print('succ') def save_image(item): img_path = 'img' + os.path.sep + item.get('title') print('succ2') if not os.path.exists(img_path): os.makedirs(img_path) try: resp = requests.get(item.get('image')) if codes.ok == resp.status_code: file_path = img_path + os.path.sep + '{file_name}.{file_suffix}'.format( file_name=md5(resp.content).hexdigest(), file_suffix='jpg') if not os.path.exists(file_path): print('succ3') with open(file_path, 'wb') as f: f.write(resp.content) print('Downloaded image path is %s' % file_path) print('succ4') else: print('Already Downloaded', file_path) except requests.ConnectionError: print('Failed to Save Image,item %s' % item) def main(offset): json = get_page(offset) for item in get_images(json): print(item) save_image(item) GROUP_START = 0 GROUP_END = 7 if __name__ == '__main__': pool = Pool() groups = ([x * 20 for x in range(GROUP_START, GROUP_END + 1)]) pool.map(main, groups) pool.close() pool.join()