分布式爬虫实战_抓取QQ音乐全站歌手的歌曲

鳄鱼君Ba

发表文章数:518

Vieu四代商业主题

高扩展、安全、稳定、响应式布局多功能模板。

¥69 现在购买
首页 » Python教程 » 分布式爬虫实战_抓取QQ音乐全站歌手的歌曲

以前写过一篇抓取QQ音乐排行榜的教程,在这篇文章将实现全站歌曲抓取。在歌手列表下获取每一位歌手的全部信息。爬取数量比较大,所以使用异步编程实现分布式爬虫开发,以提高爬虫效率,整个功能分为爬虫规则和数据入库,分别对应music.py和music_db.py,需要提前建好文件。

爬虫规则是在歌手列表(https://y.qq.com/portal/singer_list.html)中按照字母类别对歌手进行分类,遍历每个分类下的每位歌手页面,然后获取每位歌手页面下的全部歌曲信息。根据该设计方案列出遍历次数,并将代码编写在music.py文件中
● 遍历每个歌手的歌曲页数。
● 遍历每个字母分类的每页歌手信息。
● 遍历每个字母分类的歌手总页数。
● 遍历26个字母分类和1个特殊符号的歌手列表。

在功能上至少需要实现4次遍历,但实际开发中往往比这个次数要多。整个项目模块的划分如下:
● 歌曲下载。
● 歌手信息和歌曲信息。
● 字母分类下的歌手列表。
● 全站歌手列表。

歌曲下载

进入歌手列表,随便选择一位歌手的歌曲进行播放,在播放之前需要打开浏览器开发工具,捕捉网站的媒体请求,如果没有捕捉到,刷新重新播放即可,可以按照文件大小排列,选择大一点的文件,音乐普遍超过M,Kb文件都不是音乐地址:

分布式爬虫实战_抓取QQ音乐全站歌手的歌曲

找到之后,复制URL地址到浏览器是可以打开播放的,这个就是音乐的下载地址,直接就可以下载到本地了。通过对比发现,该URL是通过GET请求访问歌曲文件,需要携带参数。

若要实现歌曲下载,首先找到URL的请求参数。将某个请求参数进行复制,在Network的其他选项卡里,分别在每个请求信息的响应内容里查找这个请求参数,以参数vkey的值为例,在每个请求信息的响应内容使用“Ctrl+F”进行全局查找,最终在JS选项卡下找到该参数:

分布式爬虫实战_抓取QQ音乐全站歌手的歌曲
分布式爬虫实战_抓取QQ音乐全站歌手的歌曲

purl的值是歌曲文件路径的构成部分,,只需再加上一个域名即可得到完整的歌曲文件路径。而对于域名的选择,QQ音乐提供了5个可选域名,sip的值,每个域名都可以获取歌曲文件,这是一种集群的管理方式。从图上的请求信息里可以找到具体的域名。

根据上述分析,目前能确定歌曲文件路径的请求参数是可以在JS选项卡的某个请求信息里获取。我们对这个请求信息的URL和请求参数进行分析,发现它的URL很长,并且设有复杂的请求参数。

在Chrome中抓到的数据接口为:https://u.y.qq.com/cgi-bin/musics.fcg?-=getplaysongvkey44912426431900543&g_tk=2001554752&sign=zza1qocq06wblhu2c21ec5cd13119ff1382c88787a9fe49&loginUin=1552797557&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data=%7B%22req%22%3A%7B%22module%22%3A%22CDN.SrfCdnDispatchServer%22%2C%22method%22%3A%22GetCdnDispatch%22%2C%22param%22%3A%7B%22guid%22%3A%22172848856%22%2C%22calltype%22%3A0%2C%22userip%22%3A%22%22%7D%7D%2C%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%22172848856%22%2C%22songmid%22%3A%5B%22002E3MtF0IAMMY%22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%221552797557%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A1552797557%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D

在火狐浏览器抓到的数据接口为:https://u.y.qq.com/cgi-bin/musicu.fcg?callback=getplaysongvkey7398934035082677&g_tk=1550558782&jsonpCallback=getplaysongvkey7398934035082677&loginUin=1552797557&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0&data=%7B%22req%22%3A%7B%22module%22%3A%22CDN.SrfCdnDispatchServer%22%2C%22method%22%3A%22GetCdnDispatch%22%2C%22param%22%3A%7B%22guid%22%3A%222891348329%22%2C%22calltype%22%3A0%2C%22userip%22%3A%22%22%7D%7D%2C%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%222891348329%22%2C%22songmid%22%3A%5B%22004dFFPd4JNv8q%22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%221552797557%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A1552797557%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D

两个不同的接口都可以获取vkey参数的值,后者可以删掉没用的参数,前者带有sign和getplaysongvkey且都是变化的,所以选择后者:

分布式爬虫实战_抓取QQ音乐全站歌手的歌曲

对于尚不明确的请求参数guidsongmid,从命名方式来看,请求参数songmid是代表歌曲的唯一标识值,每首歌曲的songmid都是固定的。而参数guid却来自Request Headers的Cookies(pgv_pvid的值),这是一种常见的反爬虫机制,同时发现每个请求信息都不是带有Cookies。

将歌曲下载定为函数download,并设置函数参数guid、songmid和cookie_dict,分别代表请求参数guid、songmid和Cookies,具体代码如下:

import requests
# 创建请求头和requests会话对象session
headers={
    "User-Agent":"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
}
session=requests.session()
# 下载歌曲
def download(guid,songmid,song_singer,song_name,cookie_dict):
    # 参数guid来自cookies的pgv_pvid
    url='https://u.y.qq.com/cgi-bin/musicu.fcg?callback=getplaysongvkey7398934035082677&g_tk=1550558782&jsonpCallback=getplaysongvkey7398934035082677&loginUin=0&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0&data=%7B%22req%22%3A%7B%22module%22%3A%22CDN.SrfCdnDispatchServer%22%2C%22method%22%3A%22GetCdnDispatch%22%2C%22param%22%3A%7B%22guid%22%3A%222891348329%22%2C%22calltype%22%3A0%2C%22userip%22%3A%22%22%7D%7D%2C%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%22'+guid+'%22%2C%22songmid%22%3A%5B%22'+songmid+'%22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%220%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A0%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D'
    response=session.get(url,headers=headers,cookies=cookie_dict)
    result=re.findall('getplaysongvkey.*?\((.*?)\)',response.content.decode())[0]
    purl=eval(result)['req_0']['data']['midurlinfo'][0]['purl']
    if purl:
        url='http://isure.stream.qqmusic.qq.com/%s'% purl
        res=requests.get(url,headers=headers)
        print('正在下载歌曲:%s-%s' % (song_singer,song_name))
        file_path = '{0}/{1}/{2}'.format('G:','QQ_music', song_singer)
        path = '{0}.m4a'.format(song_name)
        if not os.path.exists(file_path):
            os.makedirs(file_path)
            with open(os.path.join(file_path, path), 'wb') as f:
                f.write(res.content)
            return True
        else:
            with open(os.path.join(file_path, path), 'wb') as f:
                f.write(res.content)
            return True
    else:
        return False

请求头headers和会话对象session是整个文件的全局变量。函数download一共执行了两次HTTP请求,具体说明如下:
(1)通过函数参数guid和songmid来构建该URL地址;然后对该URL发送GET请求,并设有请求头和用户Cookies信息,这样可让QQ网站认为这次请求是合法的,并非是爬虫程序;最后将得到的响应内容并进行清洗,提取purl的值。
(2)判断purl的值是否为空,有些音乐因为版权原因无法下载。如果purl的值不为空,则通过purl的值来构建歌曲文件路径,然后向歌曲文件路径发送GET请求,将该请求的响应内容以字节的形式写入m4a文件,这样可完成歌曲的下载。


函数download的参数guid和cookie_dict是来自用户Cookies信息,而用户Cookies信息无法通过Requests模块获取,因此,我们需要借助Selenium实现。

通常情况下,使用Selenium访问网站就会自动生成相应的用户Cookies信息,但在QQ音乐网站来说,第一次访问是不会生成用户Cookies信息,这个你可以自己去试试,第二次访问的时候才会生成用户Cookies信息。在第二次访问的时候还需要选择有用户Cookies信息的URL,因为QQ音乐并不是任何URL都有用户Cookies信息。

根据上述的分析,我们将用户Cookies信息的获取定义为函数getCookies,函数代码如下:

def getCookies():
    options=Options()
    # 设置浏览器参数
    #--headless不显示浏览器窗口
    options.add_argument('--headless')
    driver=webdriver.Chrome(options=options)
    # 访问两个URL,网站才会生成Cookies
    driver.get('https://y.qq.com/')
    time.sleep(5)
    cookie=driver.get_cookies()
    driver.quit()
    # cookies格式化
    cookie_dict={}
    for i in cookie:
        cookie_dict[i['name']]=i['value']
    print(cookie_dict)

函数getCookies在每次请求之间设置了5秒的等待时间,这是为了等待网站加载完成,否则网站尚未加载完成是无法读取用户Cookies信息的。

歌手的歌曲信息

现在只要调用函数download并传入不同的参数songmid即可下载不同的歌曲,songmid需要在歌手页面来获取,别问我怎么知道的,找出来的。以周杰伦为例,打开歌手页面并在开发者工具查找歌曲信息,分别在Doc、XHR和JS里使用Ctrl+F快速查找某一首歌曲信息,这里还是主要找js和XHR这两个请求:

分布式爬虫实战_抓取QQ音乐全站歌手的歌曲

(1)total是当前歌手的全部歌曲数目。
(2)List是歌曲信息列表。对某首歌的信息进行分析,在信息中找到歌曲标识符、歌名、所属专辑和时长,分别对应songmid、songname、albumname和interval

使用Chrome浏览器抓包,找到的数据接口为:https://u.y.qq.com/cgi-bin/musics.fcg?-=getSingerSong6556679413382471&g_tk=2001554752&sign=zzaj4imyl9rjjphnsqb4e2b21d73a0803bed8de8596b96d436&loginUin=1552797557&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={%22comm%22:{%22ct%22:24,%22cv%22:0},%22singerSongList%22:{%22method%22:%22GetSingerSongList%22,%22param%22:{%22order%22:1,%22singerMid%22:%22001fNHEf1SFEFN%22,%22begin%22:0,%22num%22:10},%22module%22:%22musichall.song_list_server%22}},参数如下图,可以看到参数包含sign和getSongersong,这两个参数是可变的,构造起来比较麻烦。

分布式爬虫实战_抓取QQ音乐全站歌手的歌曲

,QQ音乐现在每个歌手只显示10首歌曲,其余需要在客户端才能看到,也就是说QQ音乐的接口只显示10条歌曲,数量有点少。在百度流传有以前的接口:https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg?loginUin=0&hostUin=0&singermid=001fNHEf1SFEFN&order=listen&begin=0&num=10&songstatus=1,可以进行翻页配置。这个应该是QQ音乐以前的接口,现在更换接口,以前的还能使用。

(1)singermid是指歌手的mid,其作用与songmid相同,用于标识歌手的唯一性。
(2)begin代表歌曲页数,在网页上单击第二页的时候,会触发相同的GET请求,发现请求参数begin变为30,说明页数不是按1、2、3……计算的,而是按照(p-1)×30的计算公式获取页数的。
(3)num是每页歌曲数量的间隔数。

分析发现,只要动态设置请求参数singermid和begin的值,就能获取不同歌手的全部歌曲信息。将其功能定义为函数get_singer_songs,函数参数为singermid和cookie_dict,函数代码如下:

# 获取歌手的全部歌曲
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=001fNHEf1SFEFN&order=listen&begin=0&num=30&songstatus=1'
    res=session.get(url)
    songs_singer=res.json()['data']['singer_name']
    # 获取歌曲总页数
    songcount=res.json()['data']['total']
    # 根据歌曲总数计算总页数
    pagecount=math.ceil(int(songcount/10))
    for i in range(pagecount):
        url='https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg?loginUin=0&hostUin=0&singermid=001fNHEf1SFEFN&order=listen&begin=%s&num=30&songstatus=1'% (i*30)
        response=session.get(url)
        # 获取每页的歌曲信息
        music_data=response.json()['data']['list']
        # songname——歌名 albumname——专辑   interval——时长 songmid歌曲id用于下载音乐文件
        song_dict={}
        for a in music_data:
            song_dict['song_name']=a['musicData']['songname']
            song_dict['album_name']=a['musicData']['albumname']
            song_dict['song_interval']=a['musicData']['interval']
            song_dict['song_name']=a['musicData']['songname']
            song_dict['song_mid']=a['musicData']['songmid']
            song_dict['song_singer']=songs_singer
            # 下载歌曲
            guid=cookie_dict['pgv_pvid'] # 调用getCookies函数获取pgv_pvid
            info=download(guid,song_dict['song_mid'],songs_singer,song_dict['song_name'],cookie_dict)
            if info:
                insert_data(song_dict) # 数据入库
            # songe_dict清空处理
            song_dict={}

分类歌手列表

现在已实现获取单个歌手的全部歌曲信息,只要在此功能的基础上遍历输入不同歌手的singermid,就能获取不同歌手的歌曲信息。从开发者工具中对歌手列表(y.qq.com/portal/singer_list.html)进行抓包分析,

分布式爬虫实战_抓取QQ音乐全站歌手的歌曲

singerlist是每页80位歌手的信息列表,每条信息的singer_mid是歌手的singermid

接口为:https://u.y.qq.com/cgi-bin/musicu.fcg?callback=getUCGI26402856510842676&g_tk=1550558782&jsonpCallback=getUCGI26402856510842676&loginUin=1552797557&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0&data=%7B%22comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A0%7D%2C%22singerList%22%3A%7B%22module%22%3A%22Music.SingerListServer%22%2C%22method%22%3A%22get_singer_list%22%2C%22param%22%3A%7B%22area%22%3A-100%2C%22sex%22%3A-100%2C%22genre%22%3A-100%2C%22index%22%3A-100%2C%22sin%22%3A0%2C%22cur_page%22%3A1%7D%7D%7D,很容易就可以找到,接下来翻页,查看新增的网络请求,观察发现,该接口变化的参数为:

(1)index代表字母分类A,从1开始,2代表字母B,以此类推。
(2)sin是根据页数计算歌手数量,如第一页为0,每页80位歌手,第二页为80,第三页为160,以此类推。
(3)cur_page代表当前页数,从1开始,每页以1递增,如第二页为2,以此类推。

综合上述分析,将分类歌手的singermid获取功能定义为函数get_genre_singer,代码如下所示:

# 获取当前字母下全部歌手
def get_singer(index,page_list,cookie_dict):
    for page in page_list:
        url='https://u.y.qq.com/cgi-bin/musicu.fcg?loginUin=0&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0&data=%7B%22comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A0%7D%2C%22singerList%22%3A%7B%22module%22%3A%22Music.SingerListServer%22%2C%22method%22%3A%22get_singer_list%22%2C%22param%22%3A%7B%22area%22%3A-100%2C%22sex%22%3A-100%2C%22genre%22%3A-100%2C%22index%22%3A'+str(index)+'%2C%22sin%22%3A'+str((page-1)*80)+'%2C%22cur_page%22%3A'+str(page)+'%7D%7D%7D'
        res=session.get(url)
        # 循环每一个歌手
        for k in res.json()['singerList']['data']['singerlist']:
            singermid=k['singer_mid']
            get_singet_songs(singermid,cookie_dict)

函数get_singer用于爬取当前字母分类下全部歌手的歌曲信息,函数说明如下:
(1)index代表字母的数字,如1代表字母A,2代表字母B,以此类推。
(2)page_list代表当前字母分类的总页数。
(3)cookie_dict代表用户Cookies信息。
(4)外层for循环用于遍历当前分类的总页数。
(5)内层for循环用于遍历当前分类每页每位歌手的singermid,并调用函数get_singer_songs获取每一位歌手的全部歌曲。

全站歌手列表

对于函数get_inger,只需传入不同的函数参数indexpage_list就能获取不同分类的歌手列表。因此通过遍历26个字母和特殊符号#即可实现,将这个遍历定义为函数get_all_singer,具体的代码如下:

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?loginUin=0&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0&data=%7B%22comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A0%7D%2C%22singerList%22%3A%7B%22module%22%3A%22Music.SingerListServer%22%2C%22method%22%3A%22get_singer_list%22%2C%22param%22%3A%7B%22area%22%3A-100%2C%22sex%22%3A-100%2C%22genre%22%3A-100%2C%22index%22%3A'+str(index)+'%2C%22sin%22%3A0%2C%22cur_page%22%3A1%7D%7D%7D'
        res = session.get(url,headers=headers)
        total=res.json()['singerList']['data']['total']
        pageconut=math.ceil(int(total)/80)
        page_list=[x for x in range(1,pageconut+1)]
        # 获取当前字母下的全部歌手
        get_singer(index,page_list,cookie_dict)
# 主程序运行
if __name__ == '__main__':
    get_all_singer()

函数get_all_singer的功能是构建参数index、page_list和cookie_dict,具体说明如下:
(1)调用函数getCookies,获取用户Cookies信息,将其作为参数cookie_dict。
(2)执行for循环27次,循环从1开始至27结束,循环次数代表参数index。
(3)每次循环代表每个字母,在当前循环下,通过发送请求来获取当前字母的歌手总页数。
(4)将总页数生成列表结构,作为参数page_list。
(5)调用函数get_singer,从而实现全站歌曲下载。


上述函数get_all_singer也是整个项目程序的运行入口,程序运行执行函数的顺序如下:
● get_all_singer():循环26个字母和特殊符号#,构建参数并调用函数get_genre_singer。
● get_singer(index, page_list, cookie_dict):遍历当前分类总页数,获取每页每位歌手的歌曲信息。
● get_singer_songs(singermid, cookie_dict):实现歌手的歌曲入库和下载。
● download(guid,songmid,song_singer,song_name,cookie_dict):下载歌曲。
● getCookies():使用Selenium获取用户Cookies信息。
● insert_data(song_dict):数据入库处理。

每个函数之间通过层层的调用来实现整个网站的歌曲下载,每次函数调用都会传入不同的函数参数,使得函数之间存在一定的关联。

数据存储

在函数get_singet_songs内部调用了insert_data函数,insert_data函数主要实现数据入库,讲音乐的信息存储到MySQL数据库中。根据数据库命名,SQLAlchemy映射数据库及函数insert_data代码参考:

from sqlalchemy import *
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# 连接数据库
engine=create_engine("mysql+pymysql://root:1234@localhost:3306/music_db?charset=utf-8")
# 创建回话对象,用于数据表操作
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))
    album_name=Column(String(50))
    song_interval=Column(String(50))
    song_singer=Column(String(50))
    song_mid=Column(String(50))
# 创建数据表
Base.metadata.create_all(engine)
# 定义函数insert_db
def insert_db(song_dict):
    # 连接数据库
    engine=create_engine("mysql+pymysql://root:1234@localhost:3306/music_db?charset=utf-8")
    # 创建会话对象
    DBSession=sessionmaker(bind=engine)
    SQLsession=DBSession()
    data=song(
        song_name=song_dict['song_name'],
        song_singer=song_dict['song_singer'],
        album_name=song_dict['album_name'],
        song_interval=song_dict['song_interval'],
        song_mid=song_dict['song_mid'],
    )
    SQLsession.add(data)
    SQLsession.commit()

将上述代码存放在music_db.py文件,在music.py文件中导入music_db.py的函数insert_data即可实现数据入库,这里不过多介绍!

分布式策略

爬取全站歌曲信息是按照字母A~Z和符号#依次爬取,这是在单进程单线程的情况下运行。如果将这27次循环分为27个进程同时执行,每个进程只需执行对应的字母分类,假设执行一个字母分类的爬取时间相同,那么多进程并发的效率是单进程的26倍

除了运用多进程之外,代码大部分是IO密集型,那么在每个进程下使用多线程也可以提高每个进程的运行效率。歌手列表页是通过两层循环实现,第一层是循环每个分类字母,现将每个分类字母作为一个单独进程处理;第二层是循环每个分类的歌手总页数,可将这个循环使用多线程处理。假设每个进程使用10条线程(线程数可自行设定,具体看实际需求),那么每个进程的效率也相对提高10倍。

分布式策略考虑的因素有网站服务器负载量、网速快慢、硬件配置和数据库最大连接量。举个例子,爬取某个网站1000万数据,从数据量分析,当然进程和线程越多,爬取的速度越快。但往往忽略了网站服务器的并发量,假设设定10个进程,每个进程200条线程,每秒并发量为200×10=2000,若网站服务器并发量远远低于该并发量,在发送请求到网站的时候,就会出现卡死的情况,导致请求超时(即使对超时做了相应处理),无形之中增加了等待时间。除此之外,进程和线程越多,对运行爬虫程序的系统的压力越大,若涉及数据入库,还要考虑并发数是否超出数据库的最大连接数的情况。

根据上述分布式策略,在music_db.py中添加如下代码:

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
# 多线程
def MyThread(index,cookie_dict):
    # 每个字母分类的歌手列表页数
    url = 'https://u.y.qq.com/cgi-bin/musicu.fcg?loginUin=0&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0&data=%7B%22comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A0%7D%2C%22singerList%22%3A%7B%22module%22%3A%22Music.SingerListServer%22%2C%22method%22%3A%22get_singer_list%22%2C%22param%22%3A%7B%22area%22%3A-100%2C%22sex%22%3A-100%2C%22genre%22%3A-100%2C%22index%22%3A' + str(
        index) + '%2C%22sin%22%3A0%2C%22cur_page%22%3A1%7D%7D%7D'
    res = session.get(url, headers=headers)
    total = res.json()['singerList']['data']['total']
    pageconut = math.ceil(int(total) / 80)
    page_list = [x for x in range(1, pageconut + 1)]
    thread_number=10
    # 将每个分类总页数平均分给线程数
    list_intervale=math.ceil(len(page_list)/thread_number)
    # 设置线程对象
    Thread=ThreadPoolExecutor(max_workers=thread_number)
    for i in range(thread_number):
        # 计算每条线程应执行的页数
        start_num=list_intervale*i
        if list_intervale*(i+1)<=len(page_list):
            end_num=list_intervale*(i+1)
        else:
            end_num=len(page_list)
        #每个线程各自执行不同的歌手列表页
        Thread.submit(get_singer,index,page_list[start_num:end_num],cookie_dict)
# 多进程
def MyProcess():
    with ProcessPoolExecutor(max_workers=2) as executor:
        cookie_dict=getCookies()
        for index in range(1,28):
            # 创建27个进程
            executor.submit(MyThread,index,cookie_dict)
# 主程序运行
if __name__ == '__main__':
    MyProcess()

代码中定义了函数myProcess和myThread,分别实现多进程和多线程:
(1)多进程函数myProcess:主要是循环字母A~Z和符号#,将每个字母独立创建一个进程,每个进程执行函数是myThread,参数是当前的分类字母和用户Cookies信息。
(2)多线程函数myThread:首先传入参数index来获取当前分类的歌手总页数,然后得到的总页数和设定的线程数计算每条线程应执行的页数,最后遍历设定的线程数,让每条线程执行相应的页数。例如总页数100页,10条线程,每条线程应执行10页,第一条线程执行0~10页,第二条线程执行10~20页,以此类推。线程调用的函数是get_singer。

在实现分布式爬虫的时候,必须注意的是:
(1)全局变量不能放在if __name__==’__main__’中,因为使用多进程的时候,新开的进程不会在此获取数据。
(2)使用SQLalchemy入库最好重新创建一个数据库连接,如果多个线程和进程共同使用一个连接,就会出现异常。
(3)分布式策略最好在程序代码的最外层实现。例如在项目中,函数get_singer_songs里有两个for循环,不建议在此使用分布式处理,在代码底层实现分布式不是不可行,只是代码变动太大,而且考虑的因素较多,代码维护相对较难。

数据量较大,抓取需要的时间比较长。文章仅用来爬虫学习,切勿用于商业用途,请在学习24小时之后忘记它。

全部代码参考:

抱歉,隐藏内容 回复 后刷新可见

未经允许不得转载:作者:鳄鱼君Ba, 转载或复制请以 超链接形式 并注明出处 鳄鱼君Ba
原文地址:《分布式爬虫实战_抓取QQ音乐全站歌手的歌曲》 发布于2020-06-08

分享到:
赞(0) 赏杯咖啡

评论 1

1 + 2 =
  1. #1

    秀儿,是你么?

    popopo4周前 (06-09)回复

文章对你有帮助可赏作者一杯咖啡

支付宝扫一扫打赏

微信扫一扫打赏

Vieu4.6主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。
切换注册

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录
切换登录

注册