python3编写网络爬虫13-Ajax数据爬取
一、Ajax数据爬取
1. 简介:Ajax 全称Asynchronous JavaScript and XML 异步的Javascript和XML。
它不是一门编程语言,而是利用JavaScript在保证页面不被刷新,页面链接不改变的情况下与服务器交换数据,
获得数据后,再利用JavaScript改变页面。
示例:新浪微博 热门
2. 基本原理
2.1 发送请求
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属性会被触发 然后解析里面的内容。
2.2 解析内容
得到响应内容后,onreadystatechange属性对应的方法便会触发,利用xmlhttp的responseText属性接收。
类似python中利用requests向服务器发送请求 然后得到响应结果的过程。
返回的结果可能是HTML 也可能是JSON 只需要在js中做进一步处理 例如返回json 可以进行解析和转化。
2.3 渲染页面
js有改变网页内容的能力,解析完响应内容后 调用js里面document.getElementById().innerHTML 改变某个元素内的源代码
这样网页的内容就改变了 简称DOM操作
2.4 总结 3个步骤都是js完成的 微博下拉实际上就是js向服务器发送一个Ajax请求 然后获取服务器响应内容
解析并渲染到网页中。真实数据都是js一次次ajax请求得到的。
如果需要抓取数据 就要知道 请求怎么发送的? 发送到哪里?发送了哪些参数?
3. Ajax分析方法
3.1 查看方法
测试地址:
https://m.weibo.cn/u/1195242865
浏览器开发者工具 ajax请求类型为xhr
Request Headers信息为 X-Requested-With: XMLHttpRequest 标记此请求为Ajax请求
3.2 过滤请求
点击开发者工具Network -> XHR 过滤为ajax请求 不断滑动页面 出现新的请求
发送请求地址:
https://m.weibo.cn/api/container/getIndex?type=uid&value=1195242865&containerid=1076031195242865&since_id=4311085060664535
参数:
type: uid
value: 1195242865
containerid: 1076031195242865
page:1
4. 提取结果
from urllib.parse import urlencode import requests from pyquery import PyQuery as pq base_url = 'https://m.weibo.cn/api/container/getIndex?'#请求url的前半部分 headers = { 'Host':'m.weibo.cn', 'Referer': 'https://m.weibo.cn/u/1195242865', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36', 'X-Requested-With': 'XMLHttpRequest', } # 构造url 发送请求 def get_page(page): params = { 'type': 'uid', 'value': '1195242865', 'containerid': '1076031195242865', '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) #解析方法 提取id 正文 赞数 评论数 转发数 遍历cards 获取mblog中的各个信息 def parse_page(json): if json: items = json.get('data').get('cards') for item in items: # print(item) item = item.get('mblog') if item: 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 #遍历page 一共10页 将提取到的结果打印输出 if __name__ == '__main__': for page in range(1,3): json = get_page(page) results = parse_page(json) for result in results: print(result)
5. 添加到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')
查看mongo内容
启动mongo服务 查看库 show dbs; 查看当前在哪个库 db.getName(); 进入库 use dbname;
查看数据 db.dbname.find(); 删除当前所在库 db.dropDatabase();
至此分析模拟Ajax请求爬取微博列表完成 爬取结果不重要 还有好多地方可以完善 比如动态计算页码,查看微博全文等等。
主要是让大家了解抓取原理
实例:爬取今日头条街拍图片
地址:http://www.toutiao.com 关键字 :街拍
offset: 0 format: json keyword: 街拍 autoload: true count: 20 cur_tab: 1 from: search_tab pd: synthesis
基本代码
import requests import json import time import re import os from random import choice from hashlib import md5 url = "https://www.toutiao.com/search_content/?" header = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36', } keyword = '街拍' has_gallery_lists = [] no_gallery_lists = [] def SearchPageParser(offset=0): payload = { 'offset': offset, 'format': 'json', 'keyword': keyword, 'autoload': 'true', 'count': 30, 'cur_tab': 1, 'from': 'search_tab' } count = 0 try: response = requests.get(url, headers=header, params=payload) content = None #打印拼接请求后的url # print("Parser " + response.url) if response.status_code == requests.codes.ok: content = response.text data = json.loads(content) if not data: return for article in data.get('data'): if True == article.get('has_gallery') and True == article.get('has_image'): has_gallery_lists.append(article.get('article_url')) count += 1 if False == article.get('has_gallery') and True == article.get('has_image'): no_gallery_lists.append(article.get('article_url')) count += 1 return count except Exception as e: print(e) return #保存本地函数 def SaveImage(imageURL,title): #判断文件夹是否存在 if not os.path.exists(title): os.mkdir(title) try: response = requests.get(imageURL) if response.status_code == 200: file_path = '{0}/{1}.{2}'.format(title, md5(response.content).hexdigest(), 'jpg') # 判断是否重名 if not os.path.exists(file_path): with open(file_path,'wb') as f: f.write(response.content) else: print('Already Downloaded',file_path) except: print('Failed to Save Image') #第一种页面 def HasGalleryParser(): if 0 == len(has_gallery_lists): return # 正则 pattern = re.compile('gallery: JSON\.parse\("(.*?)max_img', re.S) pattern_t = re.compile('<title>(.*?)</title>', re.S) while has_gallery_lists: this = has_gallery_lists.pop() try: response = requests.get(this, headers=header) content = None if response.status_code == requests.codes.ok: content = response.text data = pattern.findall(content) pattern_t.findall(content) if data: #去掉多余符号 data = data[0][:-4].replace('\\', '') + ']}' img_urls = json.loads(data).get('sub_images') title = "".join(pattern_t.findall(content)) for img_url in img_urls: #保存函数 SaveImage(img_url.get('url'),title) else: print("BadPageURL[GalleryParser, {0:s}]".format(this)) except Exception as e: print(e) return time.sleep(0.25) #第二种页面 def NoGalleryParser(): if 0 == len(no_gallery_lists): return while no_gallery_lists: this = no_gallery_lists.pop() #正则匹配 pattern = re.compile('<img src="(.*?)"', re.S) pattern_t = re.compile('<title>(.*?)</title>',re.S) try: response = requests.get(this, headers=header) content = None if response.status_code == requests.codes.ok: content = response.text img_urls = pattern.findall(content) img_title = "".join(pattern_t.findall(content)) if img_urls: for img_url in img_urls: #保存函数 SaveImage(img_url,img_title) else: # 过滤地址 print("BadPageURL[NoGalleryParser, {0:s}]".format(this)) except Exception as e: print(e) return time.sleep(0.25) if __name__ == "__main__": #计数变量 x, count = 0, 0 # 获取头条页面 cnt_urls = SearchPageParser(x) while count < 20 and cnt_urls: cnt_urls = SearchPageParser(x + 20) count += cnt_urls x += 20 time.sleep(0.55) #打印分页地址 # print("Get {0:d} URL(s) in total.".format(count)) # 分析页面 HasGalleryParser() NoGalleryParser()