这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战
AJAX(Asynchronous Javascript and XML,异步的 Javascript 和 XML)。AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页的内容。
我们以 豆瓣电影 为例,打开网页之后,按 F12 ,在 Network 中监控一下这个页面。通过测试可以发现,每点击一次“加载更多”按钮,便会多一个响应:
https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=20
https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=40
https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=60
......
我们将其放入浏览器中直接打开,可以看到它以 json 的格式记录了新加载的电影信息。而且观察这些 url 的格式,发现了没?它们前面都长一样,唯一的不同就是最后的 “start = ”不一样,而且每一个相差都是20,你说巧不巧,每次点击加载更多后刷新出来的电影也是20个,放浏览器中直接打开后看到的 json 里包含的也是20个电影信息,这说明了什么就不用我说了吧。
搞清楚了这一点,剩下的工作就简单了,下面我们开始尝试一下。
# 抓取 ajax 页面
# demo 1
import urllib.request
import urllib.parse
import urllib.error
# https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=20
url = 'https://movie.douban.com/j/new_search_subjects?'
params = {'sort':'U', 'range':'0,10', 'tags':'', 'start':'20'}
params_encode = urllib.parse.urlencode(params).encode('utf-8')
headers = {
'Accept': 'text/html',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
}
try:
request = urllib.request.Request(url, data=params_encode, headers=headers)
with urllib.request.urlopen(request) as response:
print(response.read().decode('utf-8'))
except urllib.error.HTTPError as e:
print(e)
except urllib.error.URLError as e:
print(e)
运行结果:
{"data":[{"directors":["涅提·蒂瓦里"],"rate":"9.1","cover_x":4500,"star":"45","title":"摔跤吧!爸爸","url":"movie.douban.com/subject/263…},......]}
# 抓取 ajax 页面
# demo 2
import urllib.request
import urllib.parse
import urllib.error
import json
# https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=20
url = 'https://movie.douban.com/j/new_search_subjects?'
params = {'sort':'U', 'range':'0,10', 'tags':'', 'start':'20'}
params_encode = urllib.parse.urlencode(params).encode('utf-8')
headers = {
'Accept': 'text/html',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
}
try:
request = urllib.request.Request(url, data=params_encode, headers=headers)
with urllib.request.urlopen(request) as response:
# 原来的方法得到编码后数据,因为 json 将其当作字符串,用引号包围起来了
#print(response.read().decode('utf-8'))
# 可以查看响应头,有 Content-Type: application/json; charset=utf-8
#print(response.headers)
print(json.loads(response.read().decode('utf-8')))
except urllib.error.HTTPError as e:
print(e)
except urllib.error.URLError as e:
print(e)
运行结果:
{'data': [{'casts': ['阿米尔·汗', '法缇玛·萨那·纱卡', '桑亚·玛荷塔', '阿帕尔夏克提·库拉那', '沙克希·坦沃'], 'cover_y': 6300, 'rate': '9.1', 'url': 'movie.douban.com/subject/263…', 'title': '摔跤吧!爸爸', 'star': '45', 'directors': ['涅提·蒂瓦里'], 'cover': 'img3.doubanio.com/view/photo/…', 'cover_x': 4500, 'id': '26387939'}, {'casts': ['泰伊·谢里丹', '奥利维亚·库克', '本·门德尔森', '马克·里朗斯', '丽娜·维特'], 'cover_y': 3000, 'rate': '8.7', 'url': 'movie.douban.com/subject/492…', 'title': '头号玩家', 'star': '45', 'directors': ['史蒂文·斯皮尔伯格'], 'cover': 'img1.doubanio.com/view/photo/…', 'cover_x': 2000, 'id': '4920389'},.......]}
# 抓取 ajax 页面
# demo 3
import urllib.request
import urllib.parse
import urllib.error
import json
url = 'https://movie.douban.com/j/new_search_subjects?'
headers = {
'Accept': 'text/html',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
}
# 循环次数自己设置,这里循环三次,爬取前60个电影数据
for p in range(0,3):
page = p * 20
params = {'sort':'U', 'range':'0,10', 'tags':'', 'start':str(page)}
params_encode = urllib.parse.urlencode(params).encode('utf-8')
try:
request = urllib.request.Request(url, data=params_encode, headers=headers)
with urllib.request.urlopen(request) as response:
print(json.loads(response.read().decode('utf-8')))
print('---'*20)
except urllib.error.HTTPError as e:
print(e)
except urllib.error.URLError as e:
print(e)
运行结果:
{'data': [{'casts': ['徐峥', '王传君', '周一围', '谭卓', '章宇'], 'cover_y': 1512, 'rate': '9.0', 'url': 'https://movie.douban.com/subject/26752088/', 'title': '我不是药神', 'star': '45', 'directors': ['文牧野'], 'cover': 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2519070834.jpg', 'cover_x': 1080, 'id': '26752088'}, {'casts': ['周润发', '郭富城', '张静初', '冯文娟', '廖启智'], 'cover_y': 1000, 'rate': '8.1', 'url': 'https://movie.douban.com/subject/26425063/', 'title': '无双', 'star': '40', 'directors': ['庄文强'], 'cover': 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2535096871.jpg', 'cover_x': 714, 'id': '26425063'}, {'casts': ['邓超', '孙俪', '郑恺', '王千源', '王景春'], 'cover_y': 5788, 'rate': '7.4', 'url': 'https://movie.douban.com/subject/4864908/', 'title': '影', 'star': '35', 'directors': ['张艺谋'], 'cover': 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2530513100.jpg', 'cover_x': 3927, 'id': '4864908'}, {'casts': ['沈腾', '宋芸桦', '张一鸣', '张晨光', '常远'], 'cover_y': 950, 'rate': '6.7', 'url': 'https://movie.douban.com/subject/27605698/', 'title': '西虹市首富', 'star': '35', 'directors': ['闫非', '彭大魔'], 'cover': 'https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2529206747.jpg', 'cover_x': 679, 'id': '27605698'}, ...... ]}
------------------------------------------------------------
{'data': [{'casts': ['阿米尔·汗', '法缇玛·萨那·纱卡', '桑亚·玛荷塔', '阿帕尔夏克提·库拉那', '沙克希·坦沃'], 'cover_y': 6300, 'rate': '9.1', 'url': 'https://movie.douban.com/subject/26387939/', 'title': '摔跤吧!爸爸', 'star': '45', 'directors': ['涅提·蒂瓦里'], 'cover': 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2457983084.jpg', 'cover_x': 4500, 'id': '26387939'}, {'casts': ['泰伊·谢里丹', '奥利维亚·库克', '本·门德尔森', '马克·里朗斯', '丽娜·维特'], 'cover_y': 3000, 'rate': '8.7', 'url': 'https://movie.douban.com/subject/4920389/', 'title': '头号玩家', 'star': '45', 'directors': ['史蒂文·斯皮尔伯格'], 'cover': 'https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2516578307.jpg', 'cover_x': 2000, 'id': '4920389'}, {'casts': ['周杰伦', '庾澄庆', '谢霆锋', '李健'], 'cover_y': 1076, 'rate': '6.2', 'url': 'https://movie.douban.com/subject/27187829/', 'title': '中国好声音 第五季', 'star': '30', 'directors': ['金磊'], 'cover': 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2527870040.jpg', 'cover_x': 690, 'id': '27187829'}, ......]}
------------------------------------------------------------
{'data': [{'casts': ['沈腾', '马丽', '尹正', '艾伦', '王智'], 'cover_y': 8268, 'rate': '7.5', 'url': 'https://movie.douban.com/subject/25964071/', 'title': '夏洛特烦恼', 'star': '40', 'directors': ['闫非', '彭大魔'], 'cover': 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2264377763.jpg', 'cover_x': 5906, 'id': '25964071'}, {'casts': ['周冬雨', '马思纯', '李程彬', '李萍', '蔡纲'], 'cover_y': 900, 'rate': '7.6', 'url': 'https://movie.douban.com/subject/25827935/', 'title': '七月与安生', 'star': '40', 'directors': ['曾国祥'], 'cover': 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2378140502.jpg', 'cover_x': 644, 'id': '25827935'}, {'casts': ['本·贝尔特', '艾丽莎·奈特', '杰夫·格尔林', '佛莱德·威拉特', '西格妮·韦弗'], 'cover_y': 4447, 'rate': '9.3', 'url': 'https://movie.douban.com/subject/2131459/', 'title': '机器人总动员', 'star': '45', 'directors': ['安德鲁·斯坦顿'], 'cover': 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p1461851991.jpg', 'cover_x': 3000, 'id': '2131459'}, {'casts': ['拉妮·玛克赫吉', '内拉吉·卡比', '萨钦', '苏普丽雅·皮尔加卡尔', '罗特·萨拉夫'], 'cover_y': 6824, 'rate': '7.5', 'url': 'https://movie.douban.com/subject/30140571/', 'title': '嗝嗝老师', 'star': '40', 'directors': ['西达夫·马贺拉'], 'cover': 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2535365481.jpg', 'cover_x': 4872, 'id': '30140571'}, ......]}
------------------------------------------------------------
总结一下
我们回顾一下刚才我们做了什么,目前很多网站采用了 ajax 异步加载网页的方法,这种方法的好处就是可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容。这也就意味着我们无法仅通过 url 获取到网页中的全部内容(因为不论后续加载了多少内容,网页的url始终没有变过)。
所以我们使用抓包工具查看了一下页面,看看“加载更多”是后台发生了什么事儿,然后我们发现,每次加载时都会出现一个新的响应,这些响应的 url 具有相似的结构,并且在浏览器中直接访问这些 url 时,出现了刚才新刷新出来的内容的 json 格式数据。所以我们明白了,ajax 异步加载网页就是通过这些请求来不断与服务器交换数据的。
接下来的事情就简单了,我们通过爬虫去访问这些响应的 url(可以通过改变参数来控制访问的数量),这样数据不就手到擒来了吗?