爬取网易云音乐某歌手所有专辑内歌曲的爬虫

前言:

​ 开始打算弄个逆向工程,然后把网易云的歌爬下来,但是这件事情是违法的,绝对不是学艺不精,所以我又搜了一个网易云的开放API,虽然下载不了付费歌曲,但是让我这个小白练一练爬虫还是绰绰有余的。

思路:

​ 我最开始以为这个东西很简单,所以我试着通过网易云的搜索栏和post指令获取某一首歌曲的id,但是实际过程中发现post的负载是经过js加密而且高度混淆的,虽然我一直在研究逆向这个东西,但是以我现在目前水平搞出来花费的时间太长,所以我找到网易云的开放api接口来获取免费的音乐。

代码编写:

总体结构:

​ 通过用户输入网易云某个作者的网易云某个作者的热门作品界面链接来获取该作者所有作品。网易云音乐并不是单首音乐作为一个列表的形式展示在一个页面里的,而是放在一个个对应的专辑里面,所以我们先需要找到作者id,再通过作者id找到该作者所有专辑的id,再通过专辑找到单首音乐id,再通过单首音乐id下载该歌曲,其中还涉及到url的拼接;文件的写入;链表,正则表达式,requests,BeautifulSoup4,正则表达式等的使用。总体下来还是很繁琐的。

函数:

定义变量并导入库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import re
from time import sleep
import requests
from bs4 import BeautifulSoup

nnnum = 1
nnum = 0
key = [0]
tag = '='
n = 0
music_id = [0] * 100
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
}

首先:

​ 我们需要把用户输入的链接进行处理得到作者id。

1
2
3
4
5
6
'''
这里的tag是字符串类型的等于号,我们将用户输入的url传入函数,并通过split方法将字符串分为两半分别写入index列表的第一跟第二个元素,然后返回第二个元素。
'''
def id(url, tag):
index = url.split(tag)
return index[1] if len(index) > 1 else exit('输入错误')

然后:

​ 我们再写一个方法把每首歌一一对应的专辑id提取出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_id(url, headers, n):
# 利用requests和bs4将需要的信息找到并提取出来
album_repo = requests.get(url, headers=headers)
album_repo.encoding = 'utf-8'
album_soup = BeautifulSoup(album_repo.text, 'html.parser')
album_ids = album_soup.find_all('a', {'class': 'tit s-fc0'})
# print(type(album_ids))
# 用album_id在album_ids中遍历
for album_id in album_ids:
# 利用.get方法将字典中href键的值提取出来,再利用id()方法将专辑id存放在music_id[]这个链表中
music_id[n] = id(album_id.get('href'), tag)
n += 1
# 一下用于while循环结束的条件,album_ids
return album_ids

接着:

​ 我们再写一个函数用专辑id找到对应歌曲的id和歌曲的名称。(这里用re库跟正则表达式会比较方便一点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 因为是用一个循环调用这个函数,所以需要把num传进去,事实上这样更加方便
def get_name_id(num):
# 得到歌曲页面url
url = 'https://music.163.com/album?id=' + str(music_id[num])
song_repo = requests.get(url=url, headers=headers)
song_repo.encoding = 'utf-8'
song_soup = BeautifulSoup(song_repo.text, 'html.parser')
song = song_soup.find('ul', {'class': 'f-hide'})
# 如果不明白可以把各个阶段的变量打印出来,我就是这么干的
# print(str(song))
# print(song)
# 正则表达式加re库提取song_name和song_id
pattern = r'\d+'
song_id = re.findall(pattern, str(song))
pattern = r'[\u4e00-\u9fa5]+'
song_name = re.findall(pattern, str(song))
# print(song_id[0])
# print(song_name[0])

if song_name != []:
return song_name[0], song_id[0]
else:
# 程序结束是在这里结束的,并不是在main函数结束。
# 最后我发现在这里结束比在main函数结束更加方便。
exit('程序结束,共下载' + str(nnum) + '首歌曲')

最后:

我们用获取的id来下载歌曲

1
2
3
4
5
6
7
8
9
10
def downloda(name, music_id, nnnum):
downloda_api = 'http://music.163.com/song/media/outer/url?id=' + music_id
music = requests.get(url=downloda_api, headers=headers)
# 下载歌曲
try:
with open(str(nnnum) + '.' + name + '.mp3', 'wb') as file:
file.write(music.content)
print(str(nnnum) + '.' + name + '下载成功')
except:
print('下载异常,歌曲可能需要vip权限才能下载')

main:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
url_album = input('请输入网易云音乐歌手页面链接')
# 将专辑id写入music_id
while True:
url = 'https://music.163.com/artist/album?id=' + id(url_album, tag) + '&limit=12&offset=' + str(n)
# 用key控制程序结束
key = get_id(url, headers, n)
if not key:
break
print(music_id)
# 翻页
n += 12
#用sleep()主要是开始调试代码的时候怕不受控,一秒发出去几百条请求,后来也就没删。
sleep(1)
# 歌曲名称,id的获取和歌曲的下载
while nnum < len(music_id):
song_name, song_id = get_name_id(nnum)
downloda(song_name, song_id, nnnum)
nnum += 1
nnnum += 1
sleep(1)

执行结果

image-20231106155956272

image-20231106160014263

The end

总的来说写这个的时候让自己python各种不足暴露出来,心酸满满,收获满满。

同时还可以用pyinstaller进行打包。Github下载链接

百度盘下载地址 提取码:ellb