【python爬虫】网易云歌单下载(scrapy+selenium)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/MLXY123/article/details/84875528

我又滚回来更新了,这一次我们的目标是网易云音乐,想要通过输入歌单的链接,然后把整个歌单的歌曲都下载下来,说做就做,看看这一次有会遇见怎样的问题把。
需要注意的点:

  1. 这一次使用的框架仍然是scrapy,不同于上个框架的是这一回加上了selenium,我是蛮不想借助这个的,但是让工具发挥他最大的用处,这才是我们该做的
  2. VIP才能下载的音乐依然无法下载,现在还没有那个实力,只能下载歌单中不是vip的音乐
  3. 本文用于技术交流,如果侵犯版权立即删除,任何人不能用于商业用途
    好的,那我们就开始吧,一开始我们先来分析一下网易云的歌单
    这是这一次要分析的网址: https://music.163.com/#/playlist?id=61650863 ,顺便卖一波安利,这个歌单蛮不错的,喜欢纯音乐的朋友可以听听看。
    在这里插入图片描述
    首先我们要做的是获取这个歌单所有的歌曲的链接和名字,一开始觉得应该没有什么难度,
    就算歌单是动态加载的,找起来应该也没有什么难度,但是事实是对现在的我确实是蛮难的。
    右键网页源代码,搜索Rain after Summer(歌单第一首歌)发现果然是没有的
    在这里插入图片描述
    接下来便是在f12中查找,确实发现了歌单的信息,本来接下来就是
    在这里插入图片描述
response = requests.get(url,headers=DEFAULT_REQUEST_HEADERS)

但即使发所有的request headers都加上了,这部分内容页面没能显示出来,要是有朋友知道这是怎么回事,希望可以多多指教。
但是我们是不能在一颗树上吊死的,既然这个方法行不通,那么就使别的方法,这次我是使用了selenium,但是用这个的时候也遇见一个坑了,在使用selenium之后本来以为所有问题都会迎刃而解的,但是发现仍然搜索不到任何有关歌单的信息,就纠结测试了许久之后,终于发现了问题的所在,一切都源于他
在这里插入图片描述
这是一个iframe,歌单所有的消息都在这个里面,但是selenium是没有办法自动加载这个里面的内容的,这就导致了我们怎样都看不见歌单的消息。成功得到歌单信息之后,那么就可以开始我们的下载大业了,下面就开始说代码了,里面遇见的问题再一边分析。

使用scrapy进行爬虫,scrapy框架目录结构如下:
在这里插入图片描述
在使用命令行创建scrapy之后,首先是在setting.py中将机器人协议改为false,设置请求头,还有将piplines和middlewares的注释取消掉,这一些上一个项目的时候已经介绍过了,不清楚的可以翻一下上一篇,接下来是items.py中的内容
在这里插入图片描述
准备工作就做到这里了,接下来就开始分析各个代码了
首先是start_music163.py中的内容,这是用来启动这个爬虫的。
在这里插入图片描述
然后是爬虫中的内容,在爬虫开启webdriver,并且设置在爬虫关闭的时候,关闭chrome浏览器,接着讲请求发送出去。

class MusicSpiderSpider(scrapy.Spider):
    name = 'music_spider'
    allowed_domains = ['music.163.com']
    start_urls = ['http://music.163.com/']
    # first_url = 'https://music.163.com/#/playlist?id=107391599'
    driver = webdriver.Chrome()
    driver.set_page_load_timeout(30)

    def __init__(self,gedan_url=None, *args, **kwargs):
        self.first_url = gedan_url

    #获得歌单链接之后,交给parse_catalog解析,但这里有开启中间件(middlewares.py)
    #所以请求会先被传去middlewares.py
    def start_requests(self):
        yield scrapy.http.Request(url=self.first_url,callback=self.parse_catalog)

    def closed(self, spider):
        self.driver.close()

这里是比较重要的一个点。打开middlewares.py,middlewares(中间件)在url请求发送出去之后,会先经过中间件,这个时候我们可以把它捕获,通过selenium的page_source获得更加完整的主页代码,注意看代码中的注释,spider.driver.switch_to.frame(‘g_iframe’)用于解决上面提及的webdriver没有办法自动加载iframe的问题

import scrapy
class Music163DownloaderMiddleware(object):
    #rrequest的请求被传到这里,接下来使用webdriver进行解析,然后再把响应返回给parse_catalog
    def process_request(self, request, spider):
        ids = request.url.split('=')[-1]
        url = 'https://music.163.com/#/playlist?id=%s' % ids
        spider.driver.get(url)
        #注意:下面这一条代码很重要,因为webdriver无法显示出iframe中的东西
        #所以需要使用switch_to.frame('g_iframe')来将隐藏的内容找出来
        spider.driver.switch_to.frame('g_iframe')
        source = spider.driver.page_source
        return scrapy.http.HtmlResponse(url=url,body=source,encoding="utf-8",request=request)

之后我们回到爬虫中来对重新获得response进行数据清洗,通过xpath语法,成功将我们需要歌曲链接,歌曲名字和歌单名字全部传送给我们的piplines.py进行处理

#这里将歌单中每一首个的id和名字传到piplines.py进行处理
    def parse_catalog(self, response):
        # print(response.text)
        songs_list = response.xpath('//table[@class="m-table "]/tbody/tr')
        big_title = response.xpath('//h2[@class="f-ff2 f-brk"]/text()').get()
        big_title = re.sub(r'[<>:"/\\|?*]', '', big_title)
        for song in songs_list:
            song_href = 'https://music.163.com/#'+song.xpath('.//a/@href').get()
            song_title = song.xpath('.//b/@title').get()
            song_ids = re.split('=',song_href)[-1]
            song_title = re.sub(r'[<>:"/\\|?*]', '', song_title)
            item = Music163Item(song_ids = song_ids,song_title = song_title,big_title=big_title)
            yield item

下载也是一个很难的点,因为歌曲链接是jingguoAES加密的,没有学过这方面知识的朋友可能看不懂要如何解密,博主也没能好好理解怎么破解,但是github上的大佬是无所不能的,总会有大佬能成功破解的。

这里要好好感谢这位大佬,顺便贴上github大佬的网址:https://github.com/Jack-Cherish/python-spider/tree/master/Netease ,这位大佬有很多爬虫的项目 可以学习一下。那么我们就借助这位大佬的帮助,继续完成我们这个项目。
以下是那位大佬的破解过程,感兴趣的朋友可以好好研究一下

#下面这一部分是github上的大佬写的,用于破解网易云音乐链接的命名方式,看起来应该是涉及到AES了
#这部分没有专业知识应该是看不懂的,有兴趣的朋友可以研究一下
#github大佬的网址是:https://github.com/Jack-Cherish/python-spider/tree/master/Netease
#顺便一提 这位大佬有很多爬虫的项目 可以学习一下
class Encrypyed():
    #解密算法
    def __init__(self):
        self.modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
        self.nonce = '0CoJUm6Qyw8W8jud'
        self.pub_key = '010001'
    # 登录加密算法, 基于https://github.com/stkevintan/nw_musicbox脚本实现
    def encrypted_request(self, text):
        text = json.dumps(text)
        sec_key = self.create_secret_key(16)
        enc_text = self.aes_encrypt(self.aes_encrypt(text, self.nonce), sec_key.decode('utf-8'))
        enc_sec_key = self.rsa_encrpt(sec_key, self.pub_key, self.modulus)
        data = {'params': enc_text, 'encSecKey': enc_sec_key}
        return data

    def aes_encrypt(self, text, secKey):
        pad = 16 - len(text) % 16
        text = text + chr(pad) * pad
        encryptor = AES.new(secKey.encode('utf-8'), AES.MODE_CBC, b'0102030405060708')
        ciphertext = encryptor.encrypt(text.encode('utf-8'))
        ciphertext = base64.b64encode(ciphertext).decode('utf-8')
        return ciphertext

    def rsa_encrpt(self, text, pubKey, modulus):
        text = text[::-1]
        rs = pow(int(binascii.hexlify(text), 16), int(pubKey, 16), int(modulus, 16))
        return format(rs, 'x').zfill(256)

    def create_secret_key(self, size):
        return binascii.hexlify(os.urandom(size))[:16]

class Crawler():
    def __init__(self, timeout=60, cookie_path='.'):
        self.session = requests.Session()
        self.session.headers.update(DEFAULT_REQUEST_HEADERS)
        self.session.cookies = cookiejar.LWPCookieJar(cookie_path)
        self.download_session = requests.Session()
        self.timeout = timeout
        self.ep = Encrypyed()

    def post_request(self, url, params):
        data = self.ep.encrypted_request(params)
        resp = self.session.post(url, data=data, timeout=self.timeout)
        result = resp.json()
        # print(resp.url)
        if result['code'] != 200:
            click.echo('post_request error')
        else:
            return result

结果处理之后,我们得到了真正的下载链接,那么就可以开始下载了。这里利用requests.session进行下载,这种下载方式也可以用于下载视频,非常建议朋友们学习一下,这里附上文档http://docs.python-requests.org/zh_CN/latest/user/advanced.html

class Music163Pipeline(object):

    def process_item(self, item, spider):
        #下载路径
        path = './'
        gedang_name = os.path.join(path, item['big_title'])
        if not os.path.exists(gedang_name):
            os.mkdir(gedang_name)

        song_id = item['song_ids']
        song_title = item['song_title']

        #将每一首歌的id进行解码,获得下载的url链接
        csrf = ''
        bit_rate = 320000
        url = 'http://music.163.com/weapi/song/enhance/player/url?csrf_token='
        params = {'ids': [song_id], 'br': bit_rate, 'csrf_token': csrf}
        crawl = Crawler()
        result = crawl.post_request(url, params)
        result_url = result['data'][0]['url']

        #获得下载的url之后,便可以开始真正下载了
        response = requests.session()
        resp = response.get(result_url,stream=True)
        with open(gedang_name+r'\\'+song_title+'.mp3','wb') as fp:
            for chunk in resp.iter_content(chunk_size=1024):
                if chunk:
                    fp.write(chunk)
        return item

写到这里就大功告成了,接下来就是运行爬虫,等待结果了
在这里插入图片描述
在这里插入图片描述
大功告成,接下来我们就开始欣赏我们的音乐把,再次安利,这个歌单真的蛮不错的,喜欢纯音乐的朋友不要错过哦。我的分享就到这里了,要是大家能从里面收获到一点点,那对我来说便是极好了,我会把代码发到GitHub上,有兴趣的同学可以下载来研究一下!谢谢大家!
GitHub地址:https://github.com/weakmaple/music_163

展开阅读全文

没有更多推荐了,返回首页