最近在听喜马拉雅有声书“侯卫东官场笔记”。
故事很吸引人,阿陈播的也非常到位。只是有一个痛点:每一集开头有长达40秒的片头介绍,声音非常大,而且每一集都重复。晚上躺在床上听的时候,会被这个片头震得耳朵疼,睡意全无。
所以就有了这个想法,能否实现跳过这个片头。于是捣鼓了一下,实现了下面这样的功能。
1. 访问自己在腾讯云买的服务器。
2. 打开网页,从第42秒自动开始播放,播放结束后,自动播放下一集。
效果如下:
以下记录以下主要的实现:
在此之前你需要有:
1. 一个阿里云或者腾讯云服务器
2. 服务器配置django,nginx, uwsgi
有了以上软硬件,就可以开始撸起袖子开干:
1. 通过albumId获取所有的 "index", "trackId"
albimid对应的是专辑(侯卫东官场笔记)
index对应每一集的序号(第108集)
trackid对应每一集的音频文件id(1320317, 需要通过这个id去获取音频文件的下载地址)
主要接口通过分析喜马拉雅network请求获得:
a, 获取每页的data数据(其中包括index, trackid)
headers = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Cache-Control': 'max-age=0', 'Connection': 'keep-alive', 'Host': 'www.ximalaya.com', 'If-None-Match': 'W/"2cc08-HvI5ufGZ9TNYyyZOgJLO8mPSV64"', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36' } def getAllIDs(page): s = requests.session() ret = s.get(url=page,headers=headers).content.decode('utf-8') j=json.loads(ret) tracks=j['data']['tracks'] ret=[] for track in tracks: ret.append('{},{}'.format(track['index'], track['trackId'])) return ret
b. 遍历所有分页,传入上面的接口
def getAllPages(): allPages = [] basePage = 'http://www.ximalaya.com/revision/album/getTracksList?albumId=3071669&pageNum=' for i in range(1, 25): page = basePage + '{}'.format(i) allPages.append(page) return allPages
c. 通过trackid获取对应的media文件
def getDownloadUrl(index,id): print('{},{} starting'.format(index,id)) s=requests.session() ret=s.get(url='http://www.ximalaya.com/revision/play/tracks?trackIds='+id,headers=headers).content j=json.loads(ret) src= j['data']['tracksForAudioPlay'][0]['src'] print('{},{},{} ending'.format(index, id,src)) return '{},{},{}\n'.format(index,id,src)
d. 保存 序号和音频url到数据库中
在django model中:
class XimalayaMedia(models.Model): """ 喜马拉雅media """ name = models.CharField(max_length=4, verbose_name="专辑名称") index=models.IntegerField(verbose_name=u'序号') xmlyid=models.IntegerField(verbose_name=u'喜马拉雅id') url = models.CharField(max_length=500, verbose_name="地址") localurl=models.CharField(max_length=500, verbose_name="本地地址",default='') add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间") class Meta: verbose_name = "喜马拉雅音频" verbose_name_plural = verbose_name def __str__(self): return "{}-{}".format(self.name,self.index)
至此实现了爬取每一集对应的音频文件地址,并保存到数据库中。效果如下:
2. 接下来通过网页访问音频文件,并设置开始播放时间和自动播放。
由于音频文件是m4a格式的,所以这里比较麻烦。由于html默认的audio支持的是mp3格式,所以没办法直接使用音频地址。
我的解决方法是,服务器后天去下载对应的音频流,然后保存成mp3格式,再通过网页访问位于自己服务器的mp3文件。
a. 前端html:
{% load staticfiles %} <html> <style> .mainContent { width: 100%; min-height: 100vh; background-color: #ffff; margin: 0 auto; } .mid-content { text-align: center; padding: 50px 0; width: 1200px; height: 600px; margin: 0 auto; } .header { font-family: 'PingFangSC-Medium', 'Microsoft YaHei', sans-serif; text-align: center; color: #ffffff; font-size: 20px; font-weight: 700; } input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none !important; margin: 0; } .nextPre { color: #262728; font-size: 12px; cursor: pointer; } </style> <body> <div class="mainContent"> <div class="mid-content"> {# <img style="display: none;width: 600px" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1529053949564&di=9fad90c910eb153bd5ff179d5f6827cb&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fimgad%2Fpic%2Fitem%2Fac345982b2b7d0a2f4bb5ca6c0ef76094b369a9f.jpg">#} <div> <h2>第{{ media.index }}集</h2> <img src="http://imagev2.xmcdn.com/group8/M05/C8/8D/wKgDYVZqgCKRiQk1AAdB3O60pYc894.jpg!op_type=5&upload_type=album&device_type=ios&name=mobile_large&strip=0&quality=7"> </div> <div class="audioList" style="margin-top: 20px"> <audio class="myaudio" controls="controls"> <source src="{{ MEDIA_URL }}{{ media.localurl }}" type="audio/mp3"> Your browser does not support the audio element. </audio> </div> <div style="width: 270px;margin: 20px auto;"> <label class="nextPre" data-type="pre" style="float: left">上一首</label> <label class="nextPre btnnext" data-type="next" style="float: right">下一首</label> <div style="clear: both"></div> </div> </div> </div> </body> <script src="/static/js/jquery-2.2.3.js"></script> <script> $().ready(function () { $('.nextPre').click(function () { var type = $(this).data('type'); var location = ""; if (type == "pre") { location = "{% url 'xmly' pre %}"; } else { location = "{% url 'xmly' next %}"; } window.location.href=location; }); {# var myplayer = new MyPlayer().init();#} var audio = $('.myaudio')[0]; audio.loop = false; audio.currentTime = 42; audio.playbackRate=1.3; audio.play(); audio.addEventListener('ended', function () { //自动播放下一页的 var lista = window.location.href.split('/'); var next = parseInt(lista[lista.length - 1]) + 1; lista[lista.length - 1] = next; window.location.href = lista.join('/'); }, false); }); </script> </html>
b. url 设置路由
url(r'^xmly/(?P<id>\d+)$', IndexView.as_view(), name='xmly'),
c. IndexView中:
class IndexView(View): def get(self, request,id): def getDownloadUrlFromLocal(): with open('F:/2.txt', 'r') as f: return f.readlines() def retriveMedia(index, url): print('starting download {}.mp3 from {}'.format(index, url)) s = requests.session() ret = s.get(url).content filepath='{}-{}.mp3'.format(media.name, media.index) localurl = os.path.join(MEDIA_ROOT, filepath) with open(localurl, 'wb') as f: f.write(ret) print('end download {}.mp3 from {}'.format(index, url)) return filepath media=XimalayaMedia.objects.filter(index=id).first() if media.localurl=="": executor = ThreadPoolExecutor(max_workers=1) tasks=[] task = executor.submit(retriveMedia, index=media.index,url=media.url) tasks.append(task) for future in as_completed(tasks): media.localurl = future.result() media.save() pre=media.index-1 next=media.index+1 return render(request, 'index.html', locals())
关于优化:
后续可以继续优化:
1. 再优化接口,比如,只需要传入喜马拉雅某专辑的albumid, 以后要听其他专辑的话,就会更加自动化。
2. 如果能直接使用m4a文件,在前端访问就好了,这样就不需要再去下载。