python编写GUI版网易云音乐爬虫

先来看效果图:

TIM截图20180716011847.png

使用方法:

网易云音乐,选择你想要下载的歌手或者歌单的音乐
复制页面url地址,粘贴到程序的输入框中,点击下载按钮或者敲回车。
等待下载完成,或者中途停止。

TIM截图20180716011908.png

代码如下:

import os
import tkinter
import requests
from loguru import logger
from lxml import etree

# 标记是否停止下载,初始值为False,当值为True时,停止下载
from tqdm import tqdm

flag = False


class MusicSpider(object):

    def __init__(self):
        pass

    @logger.catch()
    def download_songs(self, text, entry):
        self.text = text
        self.entry = entry
        url = self.entry.get()  # 获取输入框中的字符串
        url = url.replace('/#', '').replace('https', 'http')  # 对字符串进行去空格和转协议处理
        # 当没有输入url就点击下载或者回车的时候,在文本框中显示提示
        if url == '':
            self.text.insert(tkinter.END, '请输入您要下载的歌单的url地址!')
            return
        # 网易云音乐外链url接口:http://music.163.com/song/media/outer/url?id=xxxx
        out_link = 'http://music.163.com/song/media/outer/url?id='
        # 请求头
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36',
            'Referer': 'https://music.163.com/',
            'Host': 'music.163.com'
        }
        # 请求页面的源码
        res = requests.get(url=url, headers=headers).text

        tree = etree.HTML(res)
        # 音乐列表
        song_list = tree.xpath('//ul[@class="f-hide"]/li/a')
        # 如果是歌手页面
        artist_name_tree = tree.xpath('//h2[@id="artist-name"]/text()')
        artist_name = str(artist_name_tree[0]) if artist_name_tree else None

        # 如果是歌单页面:
        song_list_name_tree = tree.xpath('//h2[contains(@class,"f-ff2")]/text()')
        song_list_name = str(song_list_name_tree[0]) if song_list_name_tree else None

        # 设置音乐下载的文件夹为歌手名字或歌单名
        folder = './' + artist_name if artist_name else './' + song_list_name

        if not os.path.exists(folder):
            os.mkdir(folder)

        def get_real_address(src):
            res = requests.get(src, headers=headers, allow_redirects=False)
            return res.headers['Location'] if res.status_code == 302 else None

        for i, s in enumerate(song_list):
            href = str(s.xpath('./@href')[0])
            id = href.split('=')[-1]
            src = out_link + id  # 拼接获取音乐真实的src资源值
            title = str(s.xpath('./text()')[0])  # 音乐的名字
            filename = title + '.mp3'
            filepath = folder + '/' + filename
            location = get_real_address(src)
            r = requests.get(location, stream=True)  # 音乐的二进制数据
            mp3_size = int(r.headers['Content-Length'])
            info = '开始下载第%d首音乐:%s\n' % (i + 1, filename)

            if flag:  # 当停止下载时,显示信息,跳出循环,代码不再向下执行
                self.text.insert(tkinter.END, '停止下载')
                self.text.see(tkinter.END)
                self.text.update()
                break
            self.text.insert(tkinter.END, info)  # 在文本框中显示下载信息
            self.text.see(tkinter.END)
            self.text.update()

            try:  # 下载音乐
                # with open(filepath, 'wb') as f:
                #     f.write(data)

                with open(filepath, 'wb') as f:
                    for data in tqdm(
                            iterable=r.iter_content(),
                            total=mp3_size,
                            unit='b',
                            desc=filepath,
                            unit_scale=True
                    ):
                        f.write(data)
            except Exception as e:
                logger.error(e)
                self.text.insert(tkinter.END, e)  # 将错误信息显示在前端文本框中
                self.text.see(tkinter.END)
                self.text.update()

        if not flag:  # 中间没有点击停止下载,程序会走到这里
            self.text.insert(tkinter.END, '下载完成')  # 在前端文本框中显示下载完毕
            self.text.see(tkinter.END)
            self.text.update()



class Application(object):
    def __init__(self):
        # 创建主窗口Tk()
        self.window = tkinter.Tk()
        # 设置一个标题,参数类型:string
        self.window.title('网易云音乐下载器——Powered by 王涛哥哥')
        # 设置主窗口大小和位置
        # self.window.geometry('800x500+240+120')
        self.center_window(self.window, 800, 500)  # 窗口居中,宽800,高500
        # 使用frame增加上中下4个框架
        self.fm1 = tkinter.Frame(self.window)  # fm1存放label标签
        self.fm2 = tkinter.Frame(self.window)  # fm2存放url输入框,下载按钮
        self.fm3 = tkinter.Frame(self.window)  # fm3存放下载信息显示的文本框
        self.fm4 = tkinter.Frame(self.window)  # fm4用来存放底下停止和退出按钮
        self.fm1.pack()
        self.fm2.pack()
        self.fm3.pack()
        self.fm4.pack()

        # 创建一个标签
        self.label = tkinter.Label(self.fm1, text='输入你要下载的歌单的url,点击下载或者回车!', font=('微软雅黑', 15), width=35)
        # 显示,布局管理器----可以理解为一个弹性容器
        self.label.pack(fill=tkinter.X)

        # 创建一个输入框,用来接收用户输入的歌单的url
        self.entry = tkinter.Entry(self.fm2, width=46, bg='pink', font=('微软雅黑', 20))
        self.entry.grid(row=0, column=0, rowspan=1, columnspan=10, padx=2)
        self.entry.bind("<Key-Return>", self.press_enter)  # 输入歌单url之后直接按回车键,触发press_enter函数

        # 创建下载按钮
        self.btn_download = tkinter.Button(self.fm2, text='下载', command=self.crawl, bg='red', font=('微软雅黑', 12))
        self.btn_download.grid(row=0, column=30, rowspan=1, columnspan=1, padx=5, pady=3)

        # 创建一个文本控件,用于显示多行文本,显示音乐下载信息
        self.text = tkinter.Text(self.fm3, width=110, height=18, font=('微软雅黑', 10))
        self.text.pack(side=tkinter.LEFT, fill=tkinter.Y)

        # 创建一个滚动条
        self.scroll = tkinter.Scrollbar(self.fm3)
        self.scroll.pack(side=tkinter.RIGHT, fill=tkinter.Y)

        # 关联滚动条和文本  config, 相互关联
        self.scroll.config(command=self.text.yview())
        self.text.config(yscrollcomman=self.scroll.set)

        # 创建停止和退出按钮
        btn_stop = tkinter.Button(self.fm4, text='停止', command=self.stop, bg='gray', font=('微软雅黑', 16))
        btn_quit = tkinter.Button(self.fm4, text='退出', command=self.window.quit, bg='gray', font=('微软雅黑', 16))

        btn_stop.pack(side='left', padx=100, pady=10)
        btn_quit.pack(side='right', padx=100, pady=10)

    def stop(self):
        global flag  # 将flag设为全局变量,以便下载过程中能随时获取
        flag = True
        return flag

    def crawl(self):
        text = self.text
        entry = self.entry
        MusicSpider.download_songs(self, text, entry)

    def press_enter(self, even):
        return self.crawl()

    def center_window(self, root, width, height):
        screenwidth = root.winfo_screenwidth()
        screenheight = root.winfo_screenheight()
        size = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
        # logger.info(size)
        root.geometry(size)

    def run(self):
        # 进入消息循环,显示主窗口
        self.window.mainloop()


if __name__ == '__main__':
    app = Application()
    app.run()

文件名为163M.py ---> 多进程下载版本
导出exe文件:
进入文件所在目录:
pyinstaller -F -w -i favicon.ico 163M.py
-F打包成单文件程序,
-w 是windows程序,不显示命令行窗口。如果执行中需要显示命令行,不要加这个参数。
-i favicon.ico 相当于 --icon=favicon.ico 打包程序的时候给给它指定一个图标。

导出exe文件需要用到pyinstaller模块
安装命令 pip install pyinstaller
没有python环境又不想安装python的同学可以到我的码云仓库下载 网易云音乐下载器.exe
即可

多进程下载脚本版

import os
import time
from multiprocessing import freeze_support, Pool
from threading import RLock

import psutil as psutil
import requests
from loguru import logger
from lxml import etree
from tqdm import tqdm


class M163:
    headers = {
        'Connection': 'keep-alive',
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
        'Referer': 'https://music.163.com/',
        'Host': 'music.163.com'
    }

   
    def download(self, url):
        url = url.replace('/#', '').replace('https', 'http')  # 对字符串进行去空格和转协议处理
        # 当没有输入url就点击下载或者回车的时候,在文本框中显示提示
        if url == '':
            return
        # 网易云音乐外链url接口:http://music.163.com/song/media/outer/url?id=xxxx
        out_link = 'http://music.163.com/song/media/outer/url?id='

        session = requests.Session()
        html = session.get(url=url, headers=self.headers).text

        tree = etree.HTML(html)

        # 音乐列表
        song_list = tree.xpath('//ul[@class="f-hide"]/li/a')
        # 如果是歌手页面
        artist_name_tree = tree.xpath('//h2[@id="artist-name"]/text()')
        artist_name = str(artist_name_tree[0]) if artist_name_tree else None

        # 如果是歌单页面:
        song_list_name_tree = tree.xpath('//h2[contains(@class,"f-ff2")]/text()')
        song_list_name = str(song_list_name_tree[0]) if song_list_name_tree else None
        # 设置音乐下载的文件夹为歌手名字或歌单名
        folder = artist_name if artist_name else song_list_name
        if not os.path.exists(folder):
            os.makedirs(folder)
        st_time = int(time.time())
        cpu_count = psutil.cpu_count()
        freeze_support()  # for Windows support
        pool = Pool(
            processes=cpu_count,
            initializer=tqdm.set_lock, initargs=(RLock(),)
        )

        for i, song in enumerate(song_list, 1):
            href = str(song.xpath('./@href')[0])
            id = href.split('=')[-1]
            song_url = out_link + id  # 拼接获取音乐真实的src资源值
            title = str(song.xpath('./text()')[0])  # 音乐的名字
            filename = title + '.mp3'
            filepath = os.path.join(folder, filename)
            logger.info(f'开始下载第{i}首音乐:{filename}')
            pool.apply_async(func=self.download_song, args=(song_url, filepath, session,))
        pool.close()
        pool.join()

        end_time = int(time.time())
        time_take = end_time - st_time
        logger.success(f'专辑《{song_list_name}》已下载完毕,耗时{time_take}s')

    def download_song(self, song_url, filepath, session):
        location = self.get_real_address(song_url, session)
        r = session.get(location, stream=True)
        mp3_size = int(r.headers['Content-Length'])
        try:  # 下载音乐
            # 调用iter_content,一块一块的遍历要下载的内容,搭配stream=True,此时才开始真正的下载
            # iterable:可迭代的进度条 total:总的迭代次数 desc:进度条的前缀
            with open(filepath, 'wb') as f:
                for data in tqdm(
                        iterable=r.iter_content(),
                        total=mp3_size,
                        unit='b',
                        desc=filepath.split('/')[-1],
                        unit_scale=True,
                        # gui=True,
                ):
                    f.write(data)
            logger.info(f'音乐:{filepath.split("/")[-1]} 下载完成')
        except Exception as e:
            logger.error(e)

    def get_real_address(self, url, session):
        res = session.get(url, headers=self.headers, allow_redirects=False)
        return res.headers['Location'] if res.status_code == 302 else None


if __name__ == "__main__":
    app = M163()
    url = 'https://music.163.com/#/playlist?id=4956037794'
    app.download(url)

如果这篇文章对你有帮助,不妨点个赞哦 (˙˘˙)ᓂ--♡

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容