先介绍一下我想到和测试了的五种方法:
方法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):