NO.44-----QQ音乐全站爬虫

news/2024/11/25 21:19:56/

一、目的


       qq音乐提供免费在线试听,但是下载需要付费,通过开发爬虫,绕过付费环节,直接下载我们需要的歌曲。

二、方法


       爬取对象是web端qq音乐,爬取范围是全站的歌曲信息,爬取方式是在歌手列表下获取每一位歌手的全部歌曲。

      整个爬虫项目按功能分为爬虫规则和数据入库。

      爬虫规则:
       在歌手列表https://y.qq.com/portal/singer_list.html按姓氏字母类别对歌手进行分类,遍历每个分类下的每个歌手页面,然后获取每个歌手页面下的全部歌曲信息。设计遍历方案(由内循环到外循环):

  1.       遍历每个歌手的每个页面的歌曲信息。
  2.       遍历每个歌手的歌曲页数;
  3.       遍历每个字母分类下的每个页面的歌手信息;
  4.       遍历每个字母分类下的歌手总页数;   
  5.       遍历26个字母分类的歌手列表;

      理论设计上至少需要五次遍历,实际开发中遍历次数要多得多。整个开发过程采用模块化设计思想,划分模块如下:

  •       歌曲下载
  •       歌手信息和歌曲信息
  •       字母分类下的歌手列表
  •       全部歌手列表

(一)歌曲下载


            下载歌曲前,首先要找到某个歌手某个歌曲的下载链接。

            在网页中,点击播放某歌曲,打开谷歌的开发者模式,在Media选项卡可以找到该歌曲的播放文件,复制该URL在浏览器打开,发现歌曲可以播放:

           分析这个歌曲信息的URL,这是一个GET请求,并附带各种请求参数,如下:

http://dl.stream.qqmusic.qq.com/C400002stZ4548h0kT.m4a?guid=9613835105&vkey=FFE06ED227150F12AC92890FF951088A7B37F68950DD08F8994A633D908621681BC3F74798A3F4F7E30E3B057ECF62EC1AF4A00DAE934E0D&uin=0&fromtag=66


          那么,要实现歌曲的下载,首先要找到歌曲文件的URL请求参数。以vkey为例,复制这个请求参数到其他请求信息的Preview响应内容里查找,结果在JS选项卡下找到该请求参数:

          从上图分析,purl的值是歌曲URL信息的组成部分,再前边只需加上完整的域名就可以得到完整的歌曲文件URL。对于域名的选择,qq音乐提供了五个域名,每个域名都可以获取文件,这是一种集群的管理方式。在req的sip下可以找到具体的五个域名:

       我们继续这个URL请求的地址,这个URL地址很长,并且有复杂的请求参数,请求参数分为三大类型:

  • 整个参数可以直接去掉;
  • 参数值固定不变;
  • 参数值从其他请求信息获取。


      复制整个URL到地址栏进行访问,逐一实验把各参数去掉,观察响应内容是否发生变化 , 对于尚不明确的参数guid和songmid,songmid从命名角度看,是歌曲的唯一标识符,每首歌曲的songmid是固定且唯一的。参数guid则来自cookies,这是一种常见的反爬虫机制。我们将歌曲下载定义为函数download,并设置参数guid、songmid和cookie_dict,分别代表请求参数guid、songmid和用户的cookie信息。具体代码如下:


#guid请求参数来自cookies,这是常见的反爬措施。采用selenium获取cookies
def getCookies():#某个歌手的歌曲信息,用于获取Cookiesurl='https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg?g_tk=5381&'\'jsonpCallback=MusicJsonCallbacksinger_track&loginUin=0&hostUin=0&format=jsonp'\'&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0&singermid=001'\'fNHEf1SFEFN&order=listen&begin=0&num=30&songstatus=1'#配置chrome启动选项chrome_options=Options()#添加启动参数#--headless是不显示浏览器并执行过程chrome_options.add_argument('__headless')driver=webdriver.Chrome(chrome_options=chrome_options)#访问两个url,qq音乐才能生成cookiesdriver.get('https://y.qq.com/')time.sleep(5)driver.get(url)time.sleep(5)#调用chromeDriver自带的方法获取cookiesone_cookie=driver.get_cookies()driver.quit()print(one_cookie)#Cookies格式化cookie_dict={}for i in one_cookie:cookie_dict[i['name']]=i['value']return cookie_dict


        对于cookies信息的获取,需要使用selenium实现,并且进行两次操作,才能获得cookies信息:第一次先访问qq音乐首页,第二次访问歌手页面,在JS选项卡下的请求中能找到cookie信息:

       生成cookies信息还需要将其转化成字典格式。

(二)歌手信息和歌曲信息 


            在上一节中,实现了单首歌曲的下载,调用download()函数,传入不同的参数songmid即可实现下载不同的歌曲。在本节,通过歌手页面获取不同歌曲的songmid值。以邓紫棋为例,打开歌手页面,并在开发者工具下查找歌曲信息,最后在JS选项卡下找到歌曲信息,如下:

     分析图上请求的url,某些参数存在固定规律,比如singermid是每位歌手的唯一标识符;begin是页数,每一页有30个歌曲,第一页为0,第二页为30...其余参数固定不变。

        本小节实现的代码主要针对图上的请求URL进行。首先获取歌手的总歌曲数量,然后根据总歌曲数量来计算页数,最后遍历每一页来获取每首歌曲的信息以及歌曲的songmid进行歌曲下载,代码如下:

#获取歌手的全部歌曲
def get_singer_songs(singermid,cookie_dict):#获取歌手姓名和歌曲总数url='https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg?&loginUin=0&hostUin=0'\'singermid=%s&order=listen&begin=0&num=30&songstatus=1' % (singermid)r=session.get(url)#获取歌手姓名song_singer=r.json()['data']['singer_name']#获取歌曲总数song_count=r.json()['data']['total']#根据歌曲总数计算总页数pagecount=math.ceil(int(song_count)/30)#循环页数,获取每一页歌曲信息for p in range(pagecount):url='https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg?&loginUin=0&hostUin=0'\'singermid=%s&order=listen&begin=%s&num=30&songstatus=1' % (singermid,p*30)r=session.get(url)music_data=r.json()['data']['list']#songname--歌名,albumname--专辑,interval--时长,songmid--歌曲id用于下载#将歌曲信息存放到字典song_dict,用于入库song_dict={}for i in music_data:song_dict['song_name']=i['musicData']['songname']song_dict['song_album']=i['musicData']['albumname']song_dict['song_interval']=i['musicData']['interval']song_dict['song_songmid']=i['musicData']['songmid']song_dict['song_singer']=song_singer#下载歌曲info=download(cookie_dict[pgv_pvid],song_dict['song_songmid'],cookie_dict)#入库处理,参数song_dictif info:insert_data(song_dict)#song_dict清空处理song_dict={}函数get_singer_songs()用于爬取某个歌手的全部歌曲:

参数singermid代表歌手的唯一标识符,只需传入不同歌手的singermid,就能爬取不同歌手的全部歌曲;
代码有两个相同的变量url:第一个动态设置歌手的singermid,获取每位歌手的歌曲总数和歌手姓名;第二个动态设置页数,获取当前歌手每一页的歌曲信息;
下载歌曲调用已实现的download()函数,入库处理是调用入库函数insert_data(),后续小节会介绍。 


(三)分类歌手列表


              通过以上小节内容,现在已经可以下载某一歌手的全部歌曲,只要在这功能基础上遍历输入不同歌手的singermid,就能获取所有不同歌手的全部歌曲信息。用开发者工具对歌手列表进行分析,发现每页有80个歌手,共297页,全站歌手共有23760位。

          将循环次数按字母分类划分。在歌手列表页上使用字母A-Z对歌手进行分类筛选,利用这个分类功能可以将全部歌手分成两层循环。拆分成两层循环主要是为异步编程提供切入点,具体实现方式会在后续小节讲解。首先在网页上单击分类“A”,在开发者模式下JS选项卡下看到相应请求信息:

           点击不同字母和页数,发现参数变化规律:

  • index表示字母,“A”=1,"B"=2;
  • sin根据页数计算歌手数量,第一页为0,第二页为80;
  • cur_page表示当前页,从1开始

           根据上述分析,本章的功能代码如下:

#获取当前字母下全部歌曲

def get_genre_singer(index,page_list,cookie_dict):#参数guid来自cookies的pgv_pvidfor page in page_list:url='https://u.y.qq.com/cgi-bin/musicu.fcg?callback=getUCGI8089630466192212&'\'g_tk=5381&jsonpCallback=getUCGI8089630466192212&loginUin=0&hostUin=0&format=jsonp'\'&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0&data=%7B%22'\'comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A10000%7D%2C%22singerList%22%3A%7B%22module'\'%22%3A%22Music.SingerListServer%22%2C%22method%22%3A%22get_singer_list%22%2C%22'\'param%22%3A%7B%22area%22%3A-100%2C%22sex%22%3A-100%2C%22genre%22%3A-100%2C%22'\'index%22%3A'+str(index)+'%2C%22sin%22%3A'+str((page-1)*80)+'%2C%22cur_page%22%3A'+str(page)+'%7D%7D%7D'r=session.get(url)#循环每一个歌手for k in r.json()['singerList']['data']['singerList']:singermid=k['singer_mid']#传入不同的singermid来获取不同歌手的全部歌曲get_singer_songs(singermid,cookie_dict)


         函数get_genre_singer()是获取单个字母分类的歌手列表,函数参数说明如下:

  • index代表字母      对应index
  • page_list代表当前字母分类下的总页数        对应(page-1)*80
  • cookie_dict代表函数getCookies的返回值,即用户的Cookies信息      对应page

(四)全站歌手列表


         现在得到函数get_genre_singer(),只需传入不同的参数index和page_list即可实现26个英文字母分类的歌手列表。在此基础上遍历26个英文字母即可实现,将这个遍历定义在函数get_all_singer(),具体代码如下:

#单进程单线程
#获取全部歌手
def get_all_singer():#获取字母A-Z已经#的所有歌手for index in range(1,28):#获取每个字母分类下总歌手页数url='https://u.y.qq.com/cgi-bin/musicu.fcg?callback=getUCGI06463872642677693&'\'g_tk=5381&jsonpCallback=getUCGI06463872642677693&loginUin=0&hostUin=0&format=jsonp'\'&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0&data=%7B%22'\'comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A10000%7D%2C%22singerList%22%3A%7B%22module'\'%22%3A%22Music.SingerListServer%22%2C%22method%22%3A%22get_singer_list%22%2C%22'\'param%22%3A%7B%22area%22%3A-100%2C%22sex%22%3A-100%2C%22genre%22%3A-100%2C%22'\'index%22%3A'+str(index)+'%2C%22sin%22%3A0%2C%22cur_page%22%3A1%7D%7D%7D'r=session.get(url,headers=headers)total=r.json()['singerList']['data']['total']pagecount=math.ceil(int(total)/80)page_list=[x for x in range(1,pagecount+1)]#调用函数,获取当前字母下所有歌手get_genre_singer(index,page_list,cookie_dict)if __name__ == '__main__':#执行单线程进程get_all_singer()


          上述代码是整个项目的程序入口,函数运行顺序如下:

  • get_all_singer():循环26个字母,构建参数并调用get_genre_singer()
  • get_genre_singer(index,page_list,cookie_dict):遍历当前分类总页数,获取每页每位歌手的歌曲信息
  • get_singer_songs(singermid,cookie_dict):实现歌手的歌曲入库和下载
  • download(guid,songmid,cookie_dict):下载歌曲
  • getCookies():使用selenium获取用户的cookies
  • insert_data(song_dict):入库处理

          通过函数层层调用实现整个网站的歌曲下载和信息入库。
          将代码进行整合:

爬取:

import requests, time
import math
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from music_db import *
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# 创建请求头和会话
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}
session = requests.session()# 下载歌曲
def download(guid, songmid,cookie_dict):# 参数guid来自cookies的pgv_pvidurl = 'https://u.y.qq.com/cgi-bin/musicu.fcg?-=getplaysongvkey11136773093082608&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={"req":{"module":"CDN.SrfCdnDispatchServer","method":"GetCdnDispatch","param":{"guid":"'+guid+'","calltype":0,"userip":""}},"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"'+guid+'","songmid":["'+songmid+'"],"songtype":[0],"uin":"0","loginflag":1,"platform":"20"}},"comm":{"uin":0,"format":"json","ct":24,"cv":0}}'r = session.get(url, headers=headers,cookies=cookie_dict)purl = r.json()['req_0']['data']['midurlinfo'][0]['purl']# 下载歌曲if purl:url = 'http://isure.stream.qqmusic.qq.com/%s' %(purl)r = requests.get(url, headers=headers)f = open('song/' + songmid + '.m4a', 'wb')f.write(r.content)f.close()return Trueelse:return False# 获取歌手的全部歌曲
def get_singer_songs(singermid, cookie_dict):# 获取歌手姓名和歌曲总数url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg?loginUin=0&hostUin=0&singermid=%s' \'&order=listen&begin=0&num=30&songstatus=1' % (singermid)r = session.get(url)# 获取歌手姓名song_singer = r.json()['data']['singer_name']# 获取歌曲总数songcount = r.json()['data']['total']# 根据歌曲总数计算总页数pagecount = math.ceil(int(songcount) / 30)# 循环页数,获取每一页歌曲信息for p in range(pagecount):url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg?loginUin=0&hostUin=0&singermid=%s' \'&order=listen&begin=%s&num=30&songstatus=1' % (singermid, p * 30)r = session.get(url)# 得到每页的歌曲信息music_data = r.json()['data']['list']# songname-歌名,ablum-专辑,interval-时长,songmid歌曲id,用于下载音频文件# 将歌曲信息存放字典song_dict,用于入库song_dict = {}for i in music_data:song_dict['song_name'] = i['musicData']['songname']song_dict['song_ablum'] = i['musicData']['albumname']song_dict['song_interval'] = i['musicData']['interval']song_dict['song_songmid'] = i['musicData']['songmid']song_dict['song_singer'] = song_singer# 下载歌曲info = download(cookie_dict['pgv_pvid'], song_dict['song_songmid'], cookie_dict)# 入库处理,参数song_dictif info:insert_data(song_dict)# song_dict清空处理song_dict = {}# 获取当前字母下全部歌手
# 修改了请求地址URL以及数据获取
def get_genre_singer(index, page_list, cookie_dict):for page in page_list:url = 'https://u.y.qq.com/cgi-bin/musicu.fcg?-=getUCGI771604139451213&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={"comm":{"ct":24,"cv":0},"singerList":{"module":"Music.SingerListServer","method":"get_singer_list","param":{"area":-100,"sex":-100,"genre":-100,"index":'+str(index)+',"sin":'+str((page-1)*80)+',"cur_page":'+str(page)+'}}}'r = session.get(url)# 循环每一个歌手for k in r.json()['singerList']['data']['singerlist']:singermid = k['singer_mid']get_singer_songs(singermid, cookie_dict)# 单进程单线程
# 获取全部歌手
# 修改了请求地址URL以及数据获取
def get_all_singer():# 获取字母A-Z全部歌手cookie_dict = getCookies()for index in range(1, 28):# 获取每个字母分类下总歌手页数url = 'https://u.y.qq.com/cgi-bin/musicu.fcg?-=getUCGI771604139451213&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={"comm":{"ct":24,"cv":0},"singerList":{"module":"Music.SingerListServer","method":"get_singer_list","param":{"area":-100,"sex":-100,"genre":-100,"index":'+str(index)+',"sin":0,"cur_page":1}}}'r = session.get(url, headers=headers)total = r.json()['singerList']['data']['total']pagecount = math.ceil(int(total) / 80)page_list = [x for x in range(1, pagecount+1)]# 获取当前字母下全部歌手get_genre_singer(index, page_list, cookie_dict)# 使用Selenium获取Cookies
# 因为歌曲下载的请求参数guid是来自Cookies,因此要使用Selenium获取Cookies,这是常见的反爬虫措施之一
def getCookies():# 某个歌手的歌曲信息,用于获取Cookies,因为不是全部请求地址都有Cookiesurl = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg?g_tk=5381&jsonpCallback=MusicJsonCallbacksinger_track&loginUin=0&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0&singermid=001fNHEf1SFEFN&order=listen&begin=0&num=30&songstatus=1'chrome_options = Options()# 设置浏览器参数# --headless是不显示浏览器启动以及执行过程chrome_options.add_argument('--headless')driver = webdriver.Chrome(chrome_options=chrome_options)# 访问两个URL,QQ网站才能生成Cookiesdriver.get('https://y.qq.com/')time.sleep(1)driver.get(url)time.sleep(1)one_cookie = driver.get_cookies()driver.quit()# Cookies格式化cookie_dict = {}for i in one_cookie:cookie_dict[i['name']] = i['value']return cookie_dict# 源码更新后可以与书本的源码对比分析,更新后的爬虫代码只修改了部分代码
# 变动最大是歌曲下载的代码,同时注意函数之间调用的参数都比之前的源码有所变化。
if __name__=='__main__':# 执行单进程单线程#get_all_singer()

数据入库:

from sqlalchemy import *
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base# 连接数据库
engine=create_engine("mysql+pymysql://root:****@localhost:3306/music_db?charset=utf8",echo=False)
# 创建会话对象,用于数据表的操作
DBSession = sessionmaker(bind=engine)
SQLsession = DBSession()
Base = declarative_base()# 映射数据表
class song(Base):# 表名__tablename__ ='song'# 字段,属性song_id = Column(Integer, primary_key=True)song_name = Column(String(50))song_ablum = Column(String(50))song_interval = Column(String(50))song_songmid = Column(String(50))song_singer = Column(String(50))
# 创建数据表
Base.metadata.create_all(engine)
# 数据入库
def insert_data(song_dict):# 连接数据库engine = create_engine("mysql+pymysql://root:****@localhost:3306/music_db?charset=utf8", echo=False)# 创建会话对象,用于数据表的操作DBSession = sessionmaker(bind=engine)SQLsession = DBSession()data = song(song_name = song_dict['song_name'],song_ablum = song_dict['song_ablum'],song_interval = song_dict['song_interval'],song_songmid = song_dict['song_songmid'],song_singer = song_dict['song_singer'],)SQLsession.add(data)SQLsession.commit()if __name__ == '__main__':# 创建数据表Base.metadata.create_all(engine)


http://www.ppmy.cn/news/790747.html

相关文章

QQ音乐爬虫解析

QQ音乐爬虫解析 说明 说明 这个其实之前很久就写好了,但是一直没有公开,但是有朋友说想了解这个,我就将其公开 ---------------------------以下是原文内容------------------------------------------ 定位到的位置是这个地方 需要封装这…

c#实现qq音乐爬虫

c#使用控制台实现QQ音乐爬虫 对于如何分析我没有过程,因为网上很多dalao都有说明,以下是效果图 以下为完整代码 using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.IO; using System.Linq; using System.Net; using Sy…

Fedora 安装 QQ音乐

Fedora 安装 QQ音乐 参考: 在Fedora上安装QQ音乐 (非wine) 非常感谢 xianfengdesign 使用 Fedora 36 安装 QQ Music 1.1.4 Linux 版本 这是使用 Fedora 36 版本 安装 1.1.4 QQ音乐,其他版本不知道,囧 安装 alien …

python爬虫qq付费音乐_Python 应用爬虫下载QQ音乐

Python应用爬虫下载QQ音乐 目录: 1.简介怎样实现下载QQ音乐的过程; 2.代码 1.下载QQ音乐的过程 首先我们先来到QQ音乐的官网: https://y.qq.com/,在搜索栏上输入一首歌曲的名称; 如我在上输入最美的期待,按回车来到这个画面 我们首先要得到这些歌曲名称和其他一些信…

php获取qq音乐的api类,QQ音乐api接口梳理

声明:以下接口仅限于学习使用,严禁用于商业用途 推荐歌单 url: https://u.y.qq.com/cgi-bin/musicu.fcg?callback=recom3477297233556247&g_tk=1278911659&jsonpCallback=recom3477297233556247&loginUin=0&hostUin=0&format=jsonp&inCharset=utf8…

飞轮储能系统的建模与MATLAB仿真(永磁同步电机作为飞轮驱动电机)

简介 飞轮储能系统由于其高储能密度、高效率、轻污染的优点而越来越受到重视。飞轮储能系统以高速旋转的飞轮为依托,通过电力电子设备实现电能与动能的相互转化,从而在负载调峰、功率平抑、不间断电源等多领域都有很好的应用表现。 本文选用永磁同步电机…

linux内核突然崩溃应该怎么检查错误

在对内核做修改时,可能存在直接无法开机或者运行某个应用崩溃的情况,记录一些检查错误的办法。 1. 重新启动系统 首先,尝试重新启动系统,以查看是否可以解决内核崩溃的问题。有时候,崩溃可能是由于暂时的故障或资源问…

NRRD文件格式-NrrdIO库

之前的博客介绍了NRRD文件格式。这篇文章利用Teem提供的NrrdIO库来编译示例程序sampleIO.c。 操作系统:Fedora 30编译器:GCC 9.2.1 打开源代码,可以看到NrrdIO-1.11.0-src-tar.gz和teem-1.110-src.tar.gz两个主要文件。这里我们下载NrrdIO-1…