网易云音乐下载软件
一. 创建软件界面
这里使用tkinter来实现前端界面:
导入库:
from tkinter import *
实现前端界面:
# ****************************最底层画布相关设置***********************
# 创建画布界面
root = Tk()
# 创建界面标题
root.title('网易云下载器')
# 设置界面长和宽
root.geometry('560x450')
# *****************************Label标签******************************
# 添加标签控件,第一个参数表明将label控件安置在root画布界面上
label = Label(root, text='请输入下载歌曲:', font=('华文行楷', 20))
# grid函数用来定位控件,不指定参数时,默认row=0 column=0
label.grid()
# *****************************entry输入框*******************************
entry = Entry(root, font=('隶书', 20))
entry.grid(row=0, column=1)
# *****************************text文本标签******************************
# 列表框
text = Listbox(root, font=('楷书', 20), width=40, height=12)
text.grid(row=1, columnspan=2)
# *****************************Label标签*********************************
# 按钮
button = Button(root, text='下载歌曲', font=('隶书', 15), command=get_music_name)
button.grid(row=2, column=0, sticky=W)
button = Button(root, text='退出歌曲', font=('隶书', 15), command=root.quit)
button.grid(row=2, column=1, sticky=E)
root.mainloop()
运行显示:
二. 后端实现
后端的实现借助于:selenium浏览器自动化测试工具 以及 urllib用于操作URL的库
导入库:
from selenium import webdriver
from urllib.request import urlretrieve
浏览器驱动安装:
选择浏览器并安装相应的驱动,这里我选择Chrome浏览器,所以安装相应的chromedriver:chromedriver官网网址
这里一定要注意Chrome版本与chromedriver版本的对应关系,下载相对应的版本
以上工作完成后,我们开始针对网站进行操作
1. 分析搜索网址
打开网易云官网:官网地址
在搜索框中搜索一首歌的名字,这里我选的歌曲:《绿色》
随后进入搜索结果页面,观察网址
这里可以找到规律,将s="绿色"中的绿色key值替换成其他任意歌曲,都可以得到相应歌曲搜索结果,那么可以将key值进行输入处理
s_name = '绿色'
url = 'https://music.163.com/#/search/m/?s={}&type=1'.format(s_name)
再设置浏览器驱动,并指明驱动路径,这里我的路径放在当前目录下:
path = r'../chromedriver.exe'
driver = webdriver.Chrome(executable_path=path)
启动驱动:
driver.get(url=url)
2. 分析搜索页面与歌曲链接
由于歌曲的链接嵌套在HTML页面中,所以想要获取该链接去下载歌曲,需要对页面进行分析与提取
对页面的内容进行分析与提取有很多种方法,可以使用XPath,正则,bs4等等,但我习惯使用XPath,后面使用XPath进行操作
使用谷歌开发者工具,对歌曲搜索页面的歌曲进行定位与分析
首先可以看到,该页面为HTML内联框架,即HTML中嵌套了一个HTML
对该形式,XPath语法无法直接定位到内部的HTML,所以需要进行文档转换
driver.switch_to.frame('g_iframe')
进入到内部HTML文档后,我们接着往后分析,可以找到,包含歌曲信息的根标签
扩展根标签,展开第二个直接子标签,可以看到包含歌曲的链接
但该链接并没有直接提供一个可以直接下载歌曲的mp3文件,所以,转换思路,进行抓包,利用谷歌开发者工具自带的抓包工具进行抓包。
打开谷歌开发者工具,刷新页面
点击一首歌,使浏览器自动请求歌曲URL,便于我们找寻歌曲链接
点搜索框中,输入mp3,进行匹配,随后获取到歌曲链接
页面点击打开,可以获取该歌曲的mp3文件:
但观察该链接,可以发现一个问题,该链接有时间参数,表明会随着时间而失效,如果使用该链接进行自动化爬取,可能下次爬取就会获取不到任何信息甚至报错,因为链接失效了。
3. 使用歌曲外链
外链的使用与概念:
由于直接获取的歌曲链接存在不稳定性,所以,我们需要一条稳定的,可以使用并且可以长期使用的链接,这里就引入了外链。
可查看百度经验关于外链的概念:外链
如何获取到歌曲的外链?
外链制作网站有很多,有免费的也有收费的;也有网站上通过其他渠道获得的。
这里提供一个制作外链的免费网站:HHTJim’s部落格 Web App
支持网易云音乐,QQ音乐、百度音乐三种外链转换,输入歌曲页面的链接即可
但这里我使用一种较普遍的、大众化的外链链接:
http://music.163.com/song/media/outer/url?id=1345848098.mp3
该id为《绿色》歌曲的id,将该链接输入浏览器进行搜索,可以获得歌曲的mp3文件,即可下载
同样,将链接进行改写
song_id = 1345848098
song_url = 'http://music.163.com/song/media/outer/url?id={}.mp3'.format(song_id)
4. 获取歌曲链接、下载歌曲完整实现
通过上面的外链获取歌曲可以看到,只需获得歌曲的id即可得到歌曲的下载链接,而歌曲的id通过搜索页面定位相应的歌曲而得到。
网易云音乐 ---(搜歌)---> 搜索页面 ---(定位目标歌曲)---> 获取歌曲id
+
歌曲页面网址 ------(外链网站)---(其他途径)------------> 外链
|
----> 歌曲mp3文件
这里有获取歌曲链接和下载歌曲两个功能,所以封装成两个函数
获取歌曲链接:
def get_music_name():
# 获取搜索页面URL
s_name = '绿色'
url = 'https://music.163.com/#/search/m/?s={}&type=1'.format(s_name)
# 设置浏览器驱动,定位路径
path = r'../chromedriver.exe'
driver = webdriver.Chrome(executable_path=path)
driver.get(url=url)
# 浏览器内容进行分析
# 进入内嵌HTML
driver.switch_to.frame('g_iframe')
# 获取歌曲id与歌手名(可自行定位,可定位到结果即可)
se = driver.find_element_by_id('m-search')
mu_id = se.find_element_by_xpath('.//div[@class="item f-cb h-flag "]//div[2]//a').get_attribute('href')
singer_id = se.find_element_by_xpath('.//div[@class="item f-cb h-flag "]//div[4]//a').text
# 取出id那一部分
song_id = m_id.split('=')[1]
# 定位歌曲名字
song_name = req.find_element_by_xpath('.//div[@class="item f-cb h-flag "]//div[2]//b').get_attribute('title')
# 定义一个字典存储:歌曲id、歌曲名字、歌手
item = {}
item["song_id"] = song_id
item["song_name"] = song_name
item["singer_name"] = singer_name
song_load(item=item)
下载歌曲:
def song_load(item):
song_id = item['song_id']
song_name = item['song_name']
singer_name = item['singer_name']
song_url = 'http://music.163.com/song/media/outer/url?id={}.mp3'.format(song_id)
# 歌曲路径
os.makedirs('music', exist_ok=True) # 如果文件夹存在,不会报错,不会继续创建
path = 'music/{}-{}.mp3'.format(song_name, singer_name)
# 下载提示文本
text.insert(END, '歌曲:{},正在下载......'.format(song_name))
# 文本框滚动
text.see(END)
# 更新
text.update()
# 计时start
start = time.clock()
# 下载
urlretrieve(song_url, path)
# 计时end
end = time.clock()
# 结果
cost = end-start
# 下载完毕
# 完毕提示文本
text.insert(END, '歌曲:{},演唱:{},下载完毕, 共花费{:.2f}s!'.format(song_name, singer_name, cost))
# 文本框滚动
text.see(END)
# 更新
text.update()
三. 前后端整合
前面的前端代码与后端代码可以分别运行,但是如何整合到一起,根据用户输入的歌名和歌手,自动下载歌曲呢?
只需要在get_music_name()函数
内部进行更改:
将s_name = '绿色'
改为s_name=entry1.get()
更改url变量为url = 'https://music.163.com/#/search/m/?s={}&type=1'.format(s_name, s_singer)
四. 实现chromeless无头浏览器
至此,已经基本完成了软件的代码设计,但是,每次运行时,总会弹出浏览器,会影响使用体验,那么需要对chrome进行配置
添加一个函数
def share_browser(self):
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
# 本机google浏览器路径
path = r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
chrome_options.binary_location = path
# 路径自定义,这里是我电脑的路径
driver_path = r'D:\PyCharm 2018\3WebSpiders\网易云音乐下载\chromedriver.exe'
browser = webdriver.Chrome(chrome_options=chrome_options, executable_path=driver_path)
return browser
由于上面的函数实现了chrome驱动,那么更改get_music_name
函数中的驱动变量
# path = r'D:\PyCharm 2018\3WebSpiders\网易云音乐下载\chromedriver.exe'
# driver = webdriver.Chrome(executable_path=path)
driver = self.share_browser()
driver.get(url=url)
五. 整体代码
# -*- coding:utf-8 -*-
from tkinter import *
import os
from urllib.request import urlretrieve
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time
# https://music.163.com/#/search/m/?s=绿色&type=1
def get_music_name():
s_name = entry1.get()
url = 'https://music.163.com/#/search/m/?s={}&type=1'.format(s_name)
"https://music.163.com/#/search/m/?s=%E7%BB%BF%E8%89%B2%20%E6%A2%A6%E9%80%B8%E9%A3%9E&type=1"
# path = r'D:\PyCharm 2018\3WebSpiders\网易云音乐下载\chromedriver.exe'
# driver = webdriver.Chrome(executable_path=path)
driver = share_browser()
driver.get(url=url)
driver.switch_to.frame('g_iframe')
# 获取歌曲id
req = driver.find_element_by_id('m-search')
m_id = req.find_element_by_xpath('.//div[starts-with(@class,"item f-cb h-flag ")]//div[2]//a').get_attribute('href')
singer_name = req.find_element_by_xpath('.//div[starts-with(@class,"item f-cb h-flag ")]//div[4]//a').text
song_id = m_id.split('=')[1]
song_name = req.find_element_by_xpath('.//div[@class="item f-cb h-flag "]//div[2]//b').get_attribute('title')
item = {}
item["song_id"] = song_id
item["song_name"] = song_name
item["singer_name"] = singer_name
song_load(item=item)
def song_load(item):
song_id = item['song_id']
song_name = item['song_name']
singer_name = item['singer_name']
song_url = 'http://music.163.com/song/media/outer/url?id={}.mp3'.format(song_id)
# 歌曲路径
os.makedirs('music', exist_ok=True) # 如果文件夹存在,不会报错,不会继续创建
path = 'music/{}-{}.mp3'.format(song_name, singer_name)
# 下载提示文本
text.insert(END, '歌曲:{},正在下载.......Loading......'.format(song_name))
# 文本框滚动
text.see(END)
# 更新
text.update()
# 计时start
start = time.clock()
# 下载
urlretrieve(song_url, path)
# 计时end
end = time.clock()
# 结果
cost = end - start
# 下载完毕
# 完毕提示文本
text.insert(END, '歌曲:{},演唱:{},下载完毕!共花费{:.2f}s!'.format(song_name, singer_name, cost))
# 文本框滚动
text.see(END)
# 更新
text.update()
# 构建浏览器静默模式
def share_browser():
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
# 本机google浏览器路径
path = r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
chrome_options.binary_location = path
browser = webdriver.Chrome(chrome_options=chrome_options)
return browser
# 创建界面
root = Tk()
# 界面标题
root.title('网易云下载器')
# 界面长和宽
root.geometry('620x450')
# 标签控件
label = Label(root, text='请输入下载歌曲:', font=('华文行楷', 20))
# 标签定位
label.grid(row=0, column=0) # 默认row=0 column=0
entry1 = Entry(root, font=('隶书', 20), width=22)
entry1.grid(row=0, column=1)
# 列表框
text = Listbox(root, font=('楷书', 20), width=44, height=12)
text.grid(row=1, columnspan=2)
# 按钮
button = Button(root, text='下载歌曲', font=('隶书', 15), command=get_music_name)
button.grid(row=2, column=0, sticky=W)
button = Button(root, text='退出歌曲', font=('隶书', 15), command=root.quit)
button.grid(row=2, column=1, sticky=E)
root.mainloop()
# pyinstaller -F -w(去除命令行窗口) -i 图片路径 py文件
六. 运行演示