python 爬虫 试了五种爬喜马的方法并做了测试代码(爬虫系列研究告一段落了...)

先介绍一下我想到和测试了的五种方法:

 方法1,使用下面接口:
        通过web_api 'http://m.ximalaya.com/m-revision/page/album/queryAlbumPage/%s?pageSize=1000'%albumid
        获取tracklist,不包函最终声音文件的url,要用 _update_track_media_url 来更新
        返回格式,这个接口,似乎只是返回专辑的首页用的
        分页:不能切换只能看第一页
        页大小:pageSize认是30,最大是1000条,如果pageSize设置超1000的话,返回就会是0条,
        提序:不能排序。
        文件下载地址:无
        专辑中如果超过 1000条声音就不适用了,只能下载前1000条


方法2,使用下面接口:
        http://m.ximalaya.com/m-revision/common/album/queryAlbumTrackRecordsByPage?albumId=15839339&page=2&pageSize=7
        这个接口应该是用于手机端显示的,
        分页(page):可以,
        页大小:pageSize 默认是 7 条,最大20,超20就会报错。
        排序:无法排序。
        文件下载地址: 文件下载地址字段(playPath)   
        page 超 过总页数后 "trackDetailInfos":[] 返回会是空列表,可用来判断是否到尾页了。

方法3,使用下面接口:
        https://www.ximalaya.com/revision/play/album?albumId=15839339&pageNum=1&sort=-1&pageSize=30
        分页(page):可以,
        页大小:pageSize #最大30,超过会自动改为30
        排序:可排序。
        文件下载地址: 文件下载地址字段(src)   
        hasMore 字段为false 表示已尾页,,可用来判断是否到尾页了。
        page 超 过总页数后 "tracksAudioPlay":[] 返回会是空列表,可用来判断是否到尾页了。

方法4,使用下面接口(两接口是一致的):
        https://www.ximalaya.com/revision/album/getTracksList?albumId=15839339&pageNum=11&sort=0
        https://m.ximalaya.com/revision/album/getTracksList?albumId=15839339&pageNum=1&sort=0
        分页(page):可以,
        页大小:pageSize 固定为30。
        排序:可排序。
        文件下载地址: 不含  ,要用 _update_track_media_url 来更新
        page 超 过总页数后 "tracks":[] 返回会是空列表,可用来判断是否到尾页了。

方法5:直接解析专辑网页HTML,取出tracklist

用正则表达式来取出列表

其中,方法 4 方法5 没有直接取到文件下载的路径,所以增加了 通过selenuime 来操纵 chrome , 模拟播放声音动作,并从chrome的 network 日志中取出 media 的url, 更新到 trac_list的 mediaurl 和 title  ,这种取 media 地址的方法会比较慢,但最靠谱,中要是能播放的,就一定能取到。

贴上完整代码,爬虫的实验告一段落,接下准备研究一下 kivy GUI 和制作安卓apk.

#!/usr/bin/python
# -*- coding: utf-8 -*-
# By xulong , [email protected] ,20190606
# python3
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.chrome.options import Options
from urllib import request
from urllib.parse import urlparse
import time
import json
import re
import os

headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"}


def get_track_list_by_ablum_url(ablum_url,way):
    """
    参数:ablum_url,专集的URL,如:https://www.ximalaya.com/youshengshu/22573551
         way,1:方法1,2:方法2,3:方法3,4:方法4,
    返回:track_list,格式例子:
    # index ,title   ,url,mediaurl
    [
    {'index':64,  'title':'第064集', 
     'url':'https://www.ximalaya.com/youshengshu/22573551/187940972','mediaurl':''},
    {'index':65,  'title':'第065集', 
     'url':'https://www.ximalaya.com/youshengshu/22573551/187940972','mediaurl':''}
    ]
    """

    def _update_track_media_url(track_list):
        """
        通过selenuime 来操纵 chrome , 模拟播放声音动作,并从chrome的 network 日志中取出 media 的url,
        更新到 trac_list的 mediaurl 和 title
        """

        if len(track_list) == 0 :
            return

        #chrome 浏览器初始化开始-----------------------------------------------------------------
        chrome_options = Options()
        chrome_options.add_argument('--no-sandbox') #解决DevToolsActivePort文件不存在的报错
        chrome_options.add_argument('window-size=1920x3000') #指定浏览器分辨率
        chrome_options.add_argument('--disable-gpu') #谷歌文档提到需要加上这个属性来规避bug
        chrome_options.add_argument('--hide-scrollbars') #隐藏滚动条, 应对一些特殊页面
        chrome_options.add_argument('blink-settings=imagesEnabled=false') #不加载图片, 提升速度
        chrome_options.add_argument('--headless') #隐藏浏览器窗口

        d = DesiredCapabilities.CHROME
        d['loggingPrefs'] = { 'performance':'ALL' }
        #chrome 浏览器初始化结束-----------------------------------------------------------------


        #更新tract的tittle和mediaurl-begin
        browser = webdriver.Chrome(chrome_options=chrome_options,desired_capabilities=d)
        for tr in track_list:
            idx = tr['index']
            title = tr['title']
            url = tr['trackurl']
            mediaurl = ''
            #取medaiURL,网络可能异常,所以用饭否多取两次,取到了就退出循环
            for t in range(2):
                try:
                    browser.get(tr['trackurl'])
                    if tr['title'] == '':
                        tr['title']=browser.find_element_by_class_name('title-wrapper').text
                    playBtn=browser.find_element_by_class_name('play-btn')
                    playBtn.click()
                    time.sleep(0.001*t)
                    playBtn.click()

                    entries =  browser.get_log('performance')
                    for entry in entries:
                        try:
                            j=json.loads(entry['message'])
                            if j['message']['params']['type']=='Media':
                                mediaurl=(j['message']['params']['request']['url'])
                                #print(mediaurl)
                                if mediaurl != '':
                                    break
                        except Exception as e:
                            pass
                except Exception as e:
                    pass
                if mediaurl != '':
                    break
            #已取完 mediaurl
            tr['mediaurl'] = mediaurl

        browser.quit()
        del browser


    #下面用不同的方法或接口,实现了几个取track list 的 function.

    def _get_track_list_url_1(ablum_url):
        """
        方法1,使用下面接口:
        通过web_api 'http://m.ximalaya.com/m-revision/page/album/queryAlbumPage/%s?pageSize=1000'%albumid
        获取tracklist,不包函最终声音文件的url,要用 _update_track_media_url 来更新
        返回格式,这个接口,似乎只是返回专辑的首页用的
        分页:不能切换只能看第一页
        页大小:pageSize认是30,最大是1000条,如果pageSize设置超1000的话,返回就会是0条,
        提序:不能排序。
        文件下载地址:无
        专辑中如果超过 1000条声音就不适用了,只能下载前1000条
        """
        _r=urlparse(ablum_url)
        _url_prex = '%s://%s'%(_r.scheme,_r.netloc)
        _album_id = os.path.split(ablum_url.rstrip('/'))[1]

        _rlt_list=[]
        _url = 'http://m.ximalaya.com/m-revision/page/album/queryAlbumPage/%s?pageSize=1000'%_album_id
        req=request.Request(url=_url,headers=headers)
        r = request.urlopen(req)
        c = r.read()
        #c = c.decode('utf-8')
        j = json.loads(c)
        if 'freeOrSingleAlbumData' not in j['data']['typeSpecData'].keys(): return _rlt_list
        trs = j['data']['typeSpecData']['freeOrSingleAlbumData']['albumPageTrackRecords']['trackDetailInfos'] 
        _rlt_list.extend([{
                            'index':0,
                            'title':t['trackInfo']['title'],
                            'trackurl':'%s%s'%(_url_prex, t['pageUriInfo']['url']),
                            'mediaurl':t['trackInfo']['playPath']
                            } 
                for t in trs ])
        return _rlt_list

    def _get_track_list_url_2(ablum_url):
        """
        方法2,使用下面接口:
        http://m.ximalaya.com/m-revision/common/album/queryAlbumTrackRecordsByPage?albumId=15839339&page=2&pageSize=7
        这个接口应该是用于手机端显示的,
        分页(page):可以,
        页大小:pageSize 默认是 7 条,最大20,超20就会报错。
        排序:无法排序。
        文件下载地址: 文件下载地址字段(playPath)   
        page 超 过总页数后 "trackDetailInfos":[] 返回会是空列表,可用来判断是否到尾页了。
        """
        _r=urlparse(ablum_url)
        _url_prex = '%s://%s'%(_r.scheme,_r.netloc)
        _album_id = os.path.split(ablum_url.rstrip('/'))[1]

        _page_num = 1
        _page_size = 7 #最大20
        _rlt_list=[]
    
        _url = 'http://m.ximalaya.com/m-revision/common/album/queryAlbumTrackRecordsByPage?albumId=%s&page=%s&pageSize=%s'%(_album_id,_page_num,_page_size)
        while _url != '':
            _url = 'http://m.ximalaya.com/m-revision/common/album/queryAlbumTrackRecordsByPage?albumId=%s&page=%s&pageSize=%s'%(_album_id,_page_num,_page_size)
            req=request.Request(url=_url,headers=headers)
            r = request.urlopen(req)
            c = r.read()
            #c = c.decode('utf-8')
            j = json.loads(c)
            trs = j['data']['trackDetailInfos']
            _rlt_list.extend([{
                                'index':0,
                                'title':t['trackInfo']['title'],
                                'trackurl':'%s%s'%(_url_prex, t['pageUriInfo']['url']),
                                'mediaurl':t['trackInfo']['playPath']
                                } 
                    for t in trs ] )
            _page_num+=1
            if len(trs) == 0:
                _url = ''
        return _rlt_list


    def _get_track_list_url_3(ablum_url):
        """
        方法3,使用下面接口:
        https://www.ximalaya.com/revision/play/album?albumId=15839339&pageNum=1&sort=-1&pageSize=30
        分页(page):可以,
        页大小:pageSize #最大30,超过会自动改为30
        排序:可排序。
        文件下载地址: 文件下载地址字段(src)   
        hasMore 字段为false 表示已尾页,,可用来判断是否到尾页了。
        page 超 过总页数后 "tracksAudioPlay":[] 返回会是空列表,可用来判断是否到尾页了。
        """
        _r=urlparse(ablum_url)
        _url_prex = '%s://%s'%(_r.scheme,_r.netloc)
        _album_id = os.path.split(ablum_url.rstrip('/'))[1]

        _page_num = 1
        _page_size = 30 #最大30,超过会自动改为30
        _rlt_list=[]
    
        _url = 'https://www.ximalaya.com/revision/play/album?albumId=%s&pageNum=%s&sort=0&pageSize=%s'%(_album_id,_page_num,_page_size)
        while _url != '':
            _url = 'https://www.ximalaya.com/revision/play/album?albumId=%s&pageNum=%s&sort=0&pageSize=%s'%(_album_id,_page_num,_page_size)
            req=request.Request(url=_url,headers=headers)
            r = request.urlopen(req)
            c = r.read()
            #c = c.decode('utf-8')
            j = json.loads(c)
            trs = j['data']['tracksAudioPlay']
            _rlt_list.extend([{
                                'index':t['index'],
                                'title':t['trackName'],
                                'trackurl':'%s%s'%(_url_prex, t['trackUrl']),
                                'mediaurl':t['src']
                                } 
                    for t in trs ] )
            _page_num+=1
            if len(trs) == 0:
                _url = ''

        return _rlt_list

    def _get_track_list_url_4(ablum_url):
        """
        方法4,使用下面接口(两接口是一致的):
        https://www.ximalaya.com/revision/album/getTracksList?albumId=15839339&pageNum=11&sort=0
        https://m.ximalaya.com/revision/album/getTracksList?albumId=15839339&pageNum=1&sort=0
        分页(page):可以,
        页大小:pageSize 固定为30。
        排序:可排序。
        文件下载地址: 不含  ,要用 _update_track_media_url 来更新
        page 超 过总页数后 "tracks":[] 返回会是空列表,可用来判断是否到尾页了。
        """
        _r=urlparse(ablum_url)
        _url_prex = '%s://%s'%(_r.scheme,_r.netloc)
        _album_id = os.path.split(ablum_url.rstrip('/'))[1]

        _page_num = 1
        _page_size = 30 #固定为30,无法改
        _rlt_list=[]
    
        _url = 'https://www.ximalaya.com/revision/album/getTracksList?albumId=%s&pageNum=%s&sort=0'%(_album_id,_page_num)

        while _url != '':
            _url = 'https://www.ximalaya.com/revision/album/getTracksList?albumId=%s&pageNum=%s&sort=0'%(_album_id,_page_num)
            print(_url)

            req=request.Request(url=_url,headers=headers)
            r = request.urlopen(req)
            c = r.read()
            #c = c.decode('utf-8')
            j = json.loads(c)
            trs = j['data']['tracks']
            _rlt_list.extend([{
                                'index':t['index'],
                                'title':t['title'],
                                'trackurl':'%s%s'%(_url_prex, t['url']),
                                'mediaurl':''
                                } 
                    for t in trs ] )
            _page_num+=1
            if len(trs) == 0:
                _url = ''
        _update_track_media_url(_rlt_list)
        return _rlt_list

    def _get_track_list_url_5(ablum_url):
        """
        方法5:
        通过分析专辑的网页HTML来获取tracklist,不包函最终声音文件的url,要用 _update_track_media_url 来更新
        """
        _next_page_match_str = r'<li\s+class="page-next page-item _dN2">.*?<a\s+class="page-link _dN2"\s+href="(.*?)"></a></li>'

        #普通可听专辑
        _track_match_str1 = r'<span\s+class="num _OO">(\d+)</span>.*?<a\s+title="(.*?)"\s+href="(.*?)"'
        #vip试听专辑
        _track_match_str2 = r'<li class="_OO"><div class="icon-wrapper _OO">.*?<a\s+title="(.*?)"\s+href="(.*?)"'

        _rlt_list=[]
        _r=urlparse(ablum_url)
        _url_prex = '%s://%s'%(_r.scheme,_r.netloc)
        _url=ablum_url
        while _url != '':
            print(_url)
            req=request.Request(url=_url,headers=headers)
            r = request.urlopen(req)
            c = r.read()
            r.close()
            del(r)
            c = c.decode("utf-8")

            s=re.findall(_track_match_str1,c,re.S )
            if len(s) != 0:
                _rlt_list.extend([{
                                    'index':int(t[0]),
                                    'title':t[1],
                                    'trackurl':'%s%s'%(_url_prex,t[2]),
                                    'mediaurl':''
                                    } 
                        for t in s])
            else:
                s=re.findall(_track_match_str2,c,re.S )
                _rlt_list.extend([{
                                    'index': 0,
                                    'title':t[0],
                                    'trackurl':'%s%s'%(_url_prex,t[1]),
                                    'mediaurl':''
                                    } 
                        for t in s])
            

            #print(c)
            s=re.findall(_next_page_match_str,c,re.S)
            if len(s) == 1:
                _url = '%s%s'%(_url_prex,s[0])
            else:
                _url = ''
        _update_track_media_url(_rlt_list)
        return _rlt_list
    #以上是5种取trackurl的方法


    _rlt_list = []
    if way == 1:
        _rlt_list=_get_track_list_url_1(ablum_url)
    elif way == 2:
        _rlt_list=_get_track_list_url_2(ablum_url)
    elif way == 3:
        _rlt_list=_get_track_list_url_3(ablum_url)
    elif way == 4:
        _rlt_list=_get_track_list_url_4(ablum_url)
    elif way == 5:
        _rlt_list=_get_track_list_url_5(ablum_url)


    print('catch:',_rlt_list)

    return _rlt_list

def download_track_list(track_list,save_path):
    """
    参数:
    track_list(格式例子):
    # index ,title   ,url,mediaurl
    [
    {'index':64,  'title':'第064集', 
     'url':'https://www.ximalaya.com/youshengshu/22573551/187940972','mediaurl':''},
    {'index':65,  'title':'第065集', 
     'url':'https://www.ximalaya.com/youshengshu/22573551/187940972','mediaurl':''}
    ]
    save_path : 文件下载保存的目录
    """
    for tr in track_list:
        if tr['mediaurl'] != '':
            f_path = '%s/%s.m4a'%(save_path,tr['title'])
            print(tr)
            with open(f_path,'wb') as f:
                try:
                    req=request.Request(url=tr['mediaurl'],headers=headers)
                    r = request.urlopen(req)
                    ct=r.read()
                    f.write(ct)
                except http_client.IncompleteRead as e:
                    ct=e.partial
                    f.write(ct)
                except Exception as e:
                    pass



if __name__ == '__main__':

    #针对两种类型的专辑,测试各种取URL的方法,并记录到文件。
    typelist = []
    #普通可听专辑 
    albumURL='https://www.ximalaya.com/xiangsheng/23154860'
    typelist.append({'type':'普通可听专辑','albumurl':albumURL})

    #VIP/付费/试听专辑 
    albumURL='https://www.ximalaya.com/youshengshu/15358207'
    typelist.append({'type':'VIP/付费/试听专辑','albumurl':albumURL})

    for i in range(1,6):
        for t in typelist:
            albumURL = t['albumurl']
            tracklist=get_track_list_by_ablum_url(albumURL,i)
            with open('d:/GetMediaUrlLog.log','a+') as f:
                f.write('方法:%d,类型:%s--------------------------------------\n'%(i,t['type']))
                f.write('序号,  标题,   文件URL\n')
                f.write('-------------------------------------------\n')
                for t in tracklist:
                    f.write('%d,    %s,     %s\n'%(t['index'],t['title'],t['mediaurl']))
                else:
                    f.write('-------------------------------------------\n\n\n\n')


                


    ##下载tracklist
    #save_path = 'd:/test'
    #if  not(os.path.isdir(save_path)):
    #    os.path.mkir(save_path)
    #download_track_list(track_list,save_path):

要执行的exe文件的,请转之前的文章下载(核心用的是每1种方法,gui用的tkinter):

Python 爬虫:喜马拉雅FM音频(这个还能不载,试了一下其它下载工具都不行了)

发布了181 篇原创文章 · 获赞 34 · 访问量 73万+

猜你喜欢

转载自blog.csdn.net/fangkailove/article/details/90985725