目录
通过前面的学习,我们已经掌握了正则表达式、XPath、BeautifulSoup三种网页解析方法,通过这三种方法我们几乎能爬取所有的静态网页,这样的网页在浏览器中展示的内容都位于HTML源代码中。但是,由于主流网站使用JavaScript展示网页内容,和静态网页不同的是,使用JavaScript时,很多内容并不会出现在网页源代码中,那么之前的方法就不够用了。而当下互联网中使用了JavaScript的动态网页所占的比例远高于静态网页。因此,我们需要用到抓取动态网页的两种技术,一是通过浏览器审查元素找到真实网页地址,这将是本讲的重点。二是使用模拟浏览器的方法,这将在下一讲中介绍。
一、AJAX技术介绍
AJAX Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
AJAX 的价值在于通过在后台与服务器进行少量数据交换就可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下对网页的某部分进行更新。一方面减少了网页重复内容的下载,另一方面节省了流量,因此AJAX得到了广泛使用。例如新浪微博的评论数据、QQ音乐的歌手列表中的歌手信息等都使用了AJAX技术。关于AJAX的更多信息请访问W3school。
![](https://i-blog.csdnimg.cn/blog_migrate/4f854e3f268003a68c6775b3487bb1df.png)
如果你去检查QQ音乐歌手列表的源代码,你会发现源代码中并没有网页上显示的歌手信息。像这种网页上面存在的某些信息,在源代码中却不存在的情况,绝大多数就是使用了异步技术。
异步技术在实现时主要将txt、json等文本及xml通过JavaScript加载到网页中,或者使用asp文件实现与数据库的通信。
二、JSON介绍与使用
JSON(JavaScript Object Notation) JavaScript 对象表示法,是一种轻量级的数据交换格式。JSON字符串与Python的字典非常相似,只有一些细微差别。
JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在网络或者程序之间轻松地传递这个字符串,并在需要的时候将它还原为各编程语言所支持的数据格式。
1.将字典转换为JSON
首先import JSON模块
import json
使用json.dumps(dict) ,将字典转换为JSON
# 字典
data = {
"country": None,
"singer_id": 4558,
"singer_mid": '0025NhlN2yWrP4',
"singer_name": '周杰伦',
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M0000025NhlN2yWrP4.webp"
}
# JSON
data_json = json.dumps(data,indent=4) # 将字典转为JSON,indent可以为JSON格式的字符串添加4个空格的缩进
print(data_json)
print(type(data))
print(type(data_json))
# 输出
{
"country": null,
"singer_id": 4558,
"singer_mid": "0025NhlN2yWrP4",
"singer_name": "\u5468\u6770\u4f26",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M0000025NhlN2yWrP4.webp"
}
<class 'dict'>
<class 'str'>
字典和JSON的区别:
- json的key只能是字符串,python的dict可以是任何可hash对象(hashtable type)。
- json的key可以是有序、重复的;dict的key不可以重复。
- json的value只能是字符串、浮点数、布尔值或者null,或者它们构成的数组或者对象。
- json的字符串强制双引号,dict字符串可以单引号、双引号。
- dict可以嵌套tuple,json里只有数组。
- json:true、false、null。
- python:True、False、None。
- json中文必须是unicode编码,如"\u6211"。
- json的类型是字符串,字典的类型是字典。
不仅是字典,Python中的列表或者包含字典的列表,也可以转换为JSON格式的字符串。
data = [
{
"country": "",
"singer_id": 4558,
"singer_mid": "0025NhlN2yWrP4",
"singer_name": "周杰伦",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M0000025NhlN2yWrP4.webp"
},
{
"country": "",
"singer_id": 6016498,
"singer_mid": "002YetSZ06c9c9",
"singer_name": "王靖雯不胖",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M000002YetSZ06c9c9.webp"
}
]
dict_to_json = json.dumps(data,indent=4)
print(dict_to_json)
# 输出
[
{
"country": "",
"singer_id": 4558,
"singer_mid": "0025NhlN2yWrP4",
"singer_name": "\u5468\u6770\u4f26",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M0000025NhlN2yWrP4.webp"
},
{
"country": "",
"singer_id": 6016498,
"singer_mid": "002YetSZ06c9c9",
"singer_name": "\u738b\u9756\u96ef\u4e0d\u80d6",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M000002YetSZ06c9c9.webp"
}
]
2.将JSON转换为字典
json_to_dict = json.loads(dict_to_json)
json_to_dict是一个嵌套了字典的列表,可以像使用普通字典一样来使用它。关于JSON的更多信息请访问W3school JSON教程。
三、爬取动态数据
以下以查找哔哩哔哩网站的视频数据,了解解析异步数据的过程(仅做技术交流,支持原创)。
目标网站:https://www.bilibili.com/v/technology/career/#/ 这里选择的是哔哩哔哩网站知识板块的职业职场栏目。
将页面往下拖动一点,我们可以看到这里有最新的视频,并支持投稿时间排序和视频热度排序。我们点选“视频热度排序”后,我们立马就能看到近七天内播放次数最多的视频了。
此时我们看到,目前排第一的视频名称是“如何3秒钟看出一个人的实力?|奸商行走江湖7年的经验分享”。姑且我们把这个标题记住,也许大家在实操的时候已经换了另外一个视频,大家把这个标题记住就好,因为我们现在要尝试去网页背后的数据中找到这段信息。OK,让我们开始吧~
![](https://i-blog.csdnimg.cn/blog_migrate/7211c6d04c0171856d5ab9330c1f3516.png)
1.在网页源代码中查找
在网页的源代码中肉眼粗略一瞥,就知道这些视频信息并不存在于网页源代码中,肯定采用异步加载技术,将它藏在了某个地方。
![](https://i-blog.csdnimg.cn/blog_migrate/09c6f5ed6ec53f3d0db96acf5f4073fa.png)
2.使用Chrome检查工具,找到有效请求
使用Chrome检查工具,看看该网页有哪些请求。
![](https://i-blog.csdnimg.cn/blog_migrate/59fc79128a10cdb9a8c088dcbcc924c8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d5a86cea1a71dc96818cef5a768a7f89.png)
刷新页面后出现了非常多的请求信息,一共70条,我们需要的数据就藏在这里面。但究竟是哪一条呢?爬虫老手都知道,一般会藏在JS或者XHR两种类型文件中。
OK,下面我们选中JS类型。
![](https://i-blog.csdnimg.cn/blog_migrate/1107e9cad0b8cceb5dfc9adc1edf31d0.png)
这里筛选出了16个js文件,其中有四个文件的Size比较大,都值得怀疑,我们逐个检查一下。
![](https://i-blog.csdnimg.cn/blog_migrate/c2427107c9af2a56e63e88a185054bca.png)
第一个视频文件的标题大家还记得吗?似乎这里没有,说明这并不是我们要找的,排除掉第一条。我们继续排查第二条可疑数据。
![](https://i-blog.csdnimg.cn/blog_migrate/6a601540fbc9e4d34c05179c7d934184.png)
Great~在这条数据中,我们找到的第一条视频的标题。但是这是否就是我们需要找的数据呢?
我们检查后发现,这个文件中只有11条数据,但是每一页实际有20条数据。我们已经很接近答案了,但还要继续的去寻找一小会。
![](https://i-blog.csdnimg.cn/blog_migrate/57c600ab8fe1a6a56cefe313f0fd023d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/76e2c1484a4b12b778eda56d0ca8d30b.png)
在第三条数据中,无论是数量还是内容,与网页上的内容都是一致的,这就是我们要找的信息。
【提示】如果有些网站还是难以发现目标数据,可以单击下一页按钮,看看新出现了哪些请求,我们要找的数据一定会在这些新的请求中。
3.找到目标链接
切换到Headers页面,复制请求的url和user-agent的值
![](https://i-blog.csdnimg.cn/blog_migrate/be96a3f9f0675cb7b3bb063bd1267900.png)
4.分析url结构
page=1,代表当前所在的页面,我们可以改变这里的数值,从而抓取不同页。
time_from=20210418&time_to=20210425,这是查询的时间范围,如果需要我们可以修改这个值。
最后“&callback=jsonCallback_bili_38459188744461153”删掉即可,不影响抓取数据。
5.在python获得当前页面的json数据
import requests
import json
headers = {
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36'
}
url='https://s.search.bilibili.com/cate/search?main_ver=v3&search_type=video&view_type=hot_rank&order=click©_right=-1&cate_id=209&page=2&pagesize=20&jsonp=jsonp&time_from=20210418&time_to=20210425'
response = requests.get(url,headers=headers)
data = json.loads(response.content) # 将json转为dict
print(data)
# 输出(截取了部分数据为例)
{'exp_list': None, 'code': 0, 'cost_time': {'params_check': '0.000225', 'illegal_handler': '0.000003', 'as_response_format': '0.001596', 'as_request': '0.025710', 'deserialize_response': '0.000474', 'as_request_format': '0.000481', 'total': '0.029003', 'main_handler': '0.028654'}, 'show_module_list': ['activity', 'web_game', 'card', 'media_bangumi', 'media_ft', 'bili_user', 'user', 'star', 'video'], 'result': [{'senddate': 1619002527, 'rank_offset': 21, 'tag': '知识分享官,学习,自媒体,经验分享,VLOG,UP主,摄影,干货,野生技术协会,打卡挑战', 'duration': 1199, 'id': 630249948, 'rank_score': 34921, 'badgepay': False, 'pubdate': '2021-04-21 18:55:27', 'author': '箱庭计划', 'review': 180, 'mid': 8542312, 'is_union_video': 0, 'rank_index': 0, 'type': 'video', 'arcrank': '0', 'play': '34921', 'pic': '//i0.hdslb.com/bfs/archive/7210f4290c26007036fb244c306f1cb5fe681365.jpg', 'description': '很多朋友问过我如何才能加入自己喜欢的博主团队,这次视频我就采访了五位不同分区的百万博主的工具人来跟你们聊聊他们的经验。 希望本期视频能帮到大家了解更多的幕后故事\n视频制作:Altria 特别感谢:平守 海一 兔导 悦近来远 红橙滚滚 以及他们背后的老板们的授权', 'video_review': 388, 'is_pay': 0, 'favorites': 1824, 'arcurl': 'http://www.bilibili.com/video/av630249948', 'bvid': 'BV1H84y1F7Gy', 'title': '如何才能为喜欢的up主工作?来听5位不同分区百万博主工具人来分享经验'}, {'senddate': ......, 'show_column': 0, 'rqt_type': 'search', 'numPages': 740, 'numResults': 14782, 'crr_query': '', 'pagesize': 20, 'suggest_keyword': '', 'egg_info': None, 'cache': 0, 'exp_bits': 1, 'exp_str': '', 'seid': '9018545404356211375', 'msg': 'success', 'egg_hit': 0, 'page': 2}
6.在python中获得各条评论的数据
所有评论数据藏在result,result是一个列表,每一条评论数据以字典的形式存在列表中。
vidinfo_list = [] # 生成一个空列表用于保存所有楼层的数据
for item in data['result']: # data['result']获得所有评论数据,并循环读取每一层楼
vidinfo = {} # 生成一个空字典用于保存每一层楼的数据
vidinfo['title'] = item['title'] # 获得每一层楼中的标题
vidinfo['author'] = item['author'] # 获得每一层楼中的作者
vidinfo['description'] = item['description'] # 获得每一层楼中的视频描述
vidinfo['url'] = item['arcurl'] # 获得每一层楼中的视频链接
vidinfo_list.append(vidinfo) # 将信息保存到列表中
# 输出
[{'title': '如何才能为喜欢的up主工作?来听5位不同分区百万博主工具人来分享经验', 'author': '箱庭计划', 'description': '很多朋友问过我如何才能加入自己喜欢的博主团队,这次视频我就采访了五位不同分区的百万博主的工具人来跟你们聊聊他们的经验。 希望本期视频能帮到大家了解更多的幕后故事\n视频制作:Altria 特别感谢:平守 海一 兔导 悦近来远 红橙滚滚 以及他们背后的老板们的授权', 'url': 'http://www.bilibili.com/video/av630249948'}, {'title': '北邮研究生面试Java岗,阿里面试官看重基础接连追问!', 'author': '公子龙龙龙', 'description': '答应你们的后端面试视频来了!', 'url': 'http://www.bilibili.com/video/av460183222'}......]
在json文件中还有一些其他有用的信息,‘numPages’是总的页数,理论上,在批量爬取时,我们可以使用它循环爬取所有页的评论信息。但是实际上,服务器的反爬虫机制不会让我们轻松如愿,需要使用一些更厉害爬虫技术来突破限制,例如随机更换请求头、使用代理IP等。在后续的章节中,我们将进一步学习这些内容。
四、作业
爬取QQ音乐排行榜数据。在QQ音乐的排行榜栏目下有着飙升榜、热歌榜等不同的榜单。每个榜单都显示了排名前20的歌曲信息,这些信息的采用了AJAX技术显示在网页中。
本次作业我们需要完成以下任务:
1.找一个你自己喜欢的榜单。
2.通过Chrome开发者工具寻找到正确的数据来源和网址。
3.编写一段爬虫代码将歌曲名称(song_title)、排名(rank)、歌手姓名(singer_name)爬取出来并保存在csv中。
4.需要提交的文件:.py文件,.csv文件。
5.隐晦的技术提示:找到目标网址似乎并不难,但是它好像看起来有些长了。但是网址中的信息好像不太够,我们是不是该传递一些参数进去呢?传递的方法我们已经学过了,但是要传递哪些参数呢?编辑器报错了,似乎是由于有些参数在字典中的样式不对,是不是应该是\转义一下。(目前来看,网上可查的视频和文章都无法直接解决这个任务。所以只能靠你自己了。加油~~!)
![](https://i-blog.csdnimg.cn/blog_migrate/a7c4e28fe540e460c39f7833cd89facc.png)