一、准备工作
1. 当接手一个新项目,开发人员们并不会一上来就去写代码,他们会先去思考这个项目应当如何实现。
2. 如果说我们是要爬取周杰伦的歌,那么首先要思考的是:哪家网站,拥有周杰伦的歌曲版权?
3. 获取这个问题答案的方法有两种:其一是自己上网搜,其二是听我这个资深乐迷讲——答案是QQ音乐。
4. 首先,我们先去QQ音乐的官网,看看它的robots协议https://y.qq.com/robots.txt。
5. 我们来进入QQ音乐的官网首页:https://y.qq.com,在索框内输入“周杰伦”,然后点击回车。此时,页面会发生跳转,你能看到,我们想要的歌曲信息,就在这个页面里。它的网址会是:https://y.qq.com/portal/search.html#page=1&searchid=1&remoteplace=txt.yqq.top&t=song&w=%E5%91%A8%E6%9D%B0%E4%BC%A6
6. 根据我们已经学过的知识,我们可以借助requests和BeautifulSoup,来爬取想要的数据。根据爬虫四步,我们会利用requests.get()去请求该网址;使用BeautiSoup对请求结果进行解析;利用find_all方法拿到我们想要的标签;提取歌曲清单。
二、代码实现,发现问题
import requests
from bs4 import BeautifulSoup
res_music=requests.get('https://y.qq.com/portal/search.html#page=1&searchid=1&remoteplace=txt.yqq.top&t=song&w=%E5%91%A8%E6%9D%B0%E4%BC%A6')# 请求html,得到response
bs_music=BeautifulSoup(res_music.text,'html.parser')# 解析
htmllist_music=bs_music.find_all('a',class_='js_song')# 查找class属性值为“js_song”的a标签,得到一个由标签组成的列表
for music in list_music:# 对查找的结果执行循环
print(music['title'])
print(res_music.text)
#网页源代码里根本没有我们想要的歌曲清单。
已经验证不是代码本身的问题,但目标却未能得到实现。我们就得往前回滚一步:思考,是不是上一步的分析出了问题?
想找到答案,需要用到一项新技能——翻找Network!
三、 重新分析,关于Network
1. 我们先去看看Network的页面。
在你刚才打开的QQ音乐页面,调用“检查”(ctrl+shift+i)工具,然后点击Network。
2. Network的功能是:记录在当前页面上发生的所有请求。现在看上去好像空空如也的样子,这是因为Network记录的是实时网络请求。现在网页都已经加载完成,所以不会有东西。
3. 找到这个页面的第0个请求:search.html,然后点击它。它就是我们刚刚用requests.get()获取到的网页源代码,它里面不包含歌曲清单。
4. 为了成功抓取到歌曲清单,我们得先找到,歌名藏在哪一个请求当中。再用requests库,去模拟这个请求。
5. 第0行的左侧,红色的圆钮是启用Network监控(默认高亮打开),灰色圆圈是清空面板上的信息。右侧勾选框Preserve log,它的作用是“保留请求日志”。如果不点击这个,当发生页面跳转的时候,记录就会被清空。所以,我们在爬取一些会发生跳转的网页时,会点亮它。
第1行,是对请求进行分类查看。我们最常用的是:ALL(查看全部)/XHR(仅查看XHR,我们等会重点讲它)/Doc(Document,第0个请求一般在这里),有时候也会看看:Img(仅查看图片)/Media(仅查看媒体文件)/Other(其他)。最后,JS和CSS,则是前端代码,负责发起请求和页面实现;Font是文字的字体;而理解WS和Manifest,需要网络编程的知识,倘若不是专门做这个,你不需要了解。
夹在第2行和第1行中间的,是一个时间轴。记录什么时间,有哪些请求。而第2行,就是各个请求,你可以看下面这张表来理解。
6. 什么是XHR?在Network中,有一类非常重要的请求叫做XHR(当你把鼠标在XHR上悬停,你可以看到它的完整表述是XHR and Fetch)。
我们平时使用浏览器上网的时候,经常有这样的情况:浏览器上方,它所访问的网址没变,但是网页里却新加了内容。
典型代表:如购物网站,下滑自动加载出更多商品。在线翻译网站,输入中文实时变英文。比如,你正在使用的教学系统,每点击一次Enter就有新的内容弹出。
这个,叫做Ajax技术(技术本身和爬虫关系不大,在此不做展开,你可以通过搜索了解)。应用这种技术,好处是显而易见的——更新网页内容,而不用重新加载整个网页。又省流量又省时间的,何乐而不为。
如今,比较新潮的网站都在使用这种技术来实现数据传输。只剩下一些特别老,或是特别轻量的网站,还在用老办法——加载新的内容,必须要跳转一个新网址。
这种技术在工作的时候,会创建一个XHR(或是Fetch)对象,然后利用XHR对象来实现,服务器和浏览器之间传输数据。在这里,XHR和Fetch并没有本质区别,只是Fetch出现得比XHR更晚一些,所以对一些开发人员来说会更好用,但作用都是一样的。
显而易见,对照前面的表单。我们的歌曲清单不在网页源代码里,而且也不是图片,不是媒体文件,自然只会是在XHR里。我们现在去找找看,点击XHR按钮。
client_search(客户端搜索)……,我们来点击它。
出现了如上图这样的一个窗口,我们先来看右上方框里标号的内容,从左往右分别是:Headers:标头(请求信息)、Preview:预览、Response:响应、Cookies:Cookies、Timing:时间。
点击Preview,你能在里面发现我们想要的信息:歌名就藏在里面!
四、代码实现
1. 回到原网址,直接用Preview来看就好。列表和字典在此都会有非常清晰的结构,层层展开。我们一层一层地点开,按照这样的顺序:data-song-list-0-name。
2. 歌曲名就在这里,它的键是name。理解这句话:这个XHR是一个字典,键data对应的值也是一个字典;在该字典里,键song对应的值也是一个字典;在该字典里,键list对应的值是一个列表;在该列表里,一共有20个元素;每一个元素都是一个字典;在每个字典里,键name的值,对应的是歌曲名。
3. 利用requests.get()访问这个链接,把这个字典下载到本地。然后去一层一层地读取,拿到歌曲名。
import requests
# 引用requests库
res = requests.get('https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=txt.yqq.song&searchid=60997426243444153&t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=1&n=20&w=%E5%91%A8%E6%9D%B0%E4%BC%A6&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0')
# 调用get方法,下载这个字典
print(res.text)
我们又遇到一个障碍:使用res.text取到的,是字符串。
它不是我们想要的列表/字典,数据取不出来。
4. 新的知识点——json:
1)json是一种特殊的字符串,它是用列表/字典的语法写成的。
a='1,2,3,4'# 这是字符串
b=[1,2,3,4]# 这是列表
c='[1,2,3,4]'# 这是字符串,但它是用json格式写的字符串
2)json则是另一种组织数据的格式,长得和Python中的列表/字典非常相像。它和html一样,常用来做网络数据传输。刚刚我们在XHR里查看到的列表/字典,严格来说其实它不是列表/字典,它是json。
3)直接写成列表/字典不就好了,为什么要把它表示成字符串?答案很简单,因为不是所有的编程语言都能读懂Python里的数据类型(如,列表/字符串),但是所有的编程语言,都支持文本类型。如此,json数据才能实现,跨平台,跨语言工作。
4)json和XHR之间的关系:XHR用于传输数据,它能传输很多种数据,json是被传输的一种数据格式。就是这样而已。
5)我们总是可以将json格式的数据,转换成正常的列表/字典,也可以将列表/字典,转换成json。
6)当我们请求得到了json数据,应该如何读取呢?我们可以在requests库的官方文档中,找到答案。如果你想在Python语言中,实现列表/字典转json,json转列表/字典,则需要借助json模块。
import requests
# 引用requests库
res_music = requests.get('https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=txt.yqq.song&searchid=60997426243444153&t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=1&n=20&w=%E5%91%A8%E6%9D%B0%E4%BC%A6&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0')
# 调用get方法,下载这个字典
json_music = res_music.json()
# 使用json()方法,将response对象,转为列表/字典
print(type(json_music))
# 打印json_music的数据类型
6. 编写代码爬取歌名:
import requests# 引用requests库
res_music=requests.get('https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=txt.yqq.song&searchid=60997426243444153&t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=1&n=20&w=%E5%91%A8%E6%9D%B0%E4%BC%A6&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0')# 调用get方法,下载这个字典
json_music=res_music.json()# 使用json()方法,将response对象,转为列表/字典
list_music=json_music['data']['song']['list']# 一层一层地取字典,获取歌单列表
for music in list_music:# list_music是一个列表,music是它里面的元素
print(music['name'])# 以name为键,查找歌曲名
7. 编写代码爬取更多信息:
print(music['name'])# 以name为键,查找歌曲名
print('所属专辑:'+music['album']['name'])# 查找专辑名
print('播放时长:'+str(music['interval'])+'秒')# 查找播放时长
print('播放链接:https://y.qq.com/n/yqq/song/'+music['mid']+'.html\n\n')# 查找播放链接