1:使用手册
一个基于python,搭建在Serverless云函数上的免费音乐下载程序。可剥离云函数本地运行。
通过构造形如<https://m.sci.ci/>音乐相关信息
这样的网址,就可在浏览器上下载喜欢的音乐了。
demo
简单的例子,想听爱的供养?
尝试在浏览器地址栏输入m.sci.ci/爱的供养
现在试试👉m.sci.ci/爱的供养
返回的音乐文件是杨幂版本的,不喜欢?
尝试重新构造链接m.sci.ci/张靓颖张杰爱的供养
现在返回的就是张杰和张靓颖合唱版啦!
歌曲下载链接仅能保存一天!音乐文件以你的搜索内容为名。比如张靓颖张杰爱的供养.mp3
而不是原来的名称,文件中歌曲的专辑、歌词等相关信息也是没有的。
2:搭建自己的服务
Serverless又称无服务、云函数,可通过一个api触发你写好的程序,并返回数据给请求者。
项目地址:https://github.com/xieqifei/free-music
2.1 依赖
youtube-dl
提供通过youtube视频url,下载视频或mp3音乐文件,需要通过pip安装
qiniu
提供音乐储存服务,需要通过pip安装
requests
发送网络请求
json
解析json数据
需要在谷歌云函数 requrements.txt
中填写
1 2 3
| qiniu==7.3.1 requests==2.22.0 youtube-dl==2021.1.16
|
2.2 音频提取和储存
以下程序运行在谷歌Serverless服务上。需要你添加自己的七牛云储存桶,如果有必要,可以使用谷歌提供的免费youtube api获取搜索结果,程序会优先使用爬虫获取搜索结果,当程序频繁运行时,爬虫会失效,为了提高容错,建议添加谷歌api,此api每天可以发起100次请求来获取youtube搜索结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
| # -\*- encoding: utf-8 -\*- ''' @file\_name :lambda\_function.py @description : @time :2021/02/02 22:20:14 @author :Qifei @version :1.0 '''
import requests import youtube\_dl from qiniu import Auth import json import re from qiniu.services.storage.bucket import BucketManager import os
#七牛云对象储存AK和SK,以及储存桶名,储存桶的cdn域名 access\_key = '' secret\_key = '' bucket\_name = '' qnurl = "https://qn.xieqifei.com/"
#谷歌api,用于获取youtube搜索结果 google\_appKey=""
q = Auth(access\_key, secret\_key)
def handler(request): musicname = request.url.split("/")[-1] video\_url = get\_video\_url\_in\_youtube\_from\_crawler(musicname)
#遵循同源cors协议,可跨域访问 headers = { 'Access-Control-Allow-Origin': '\*' } if video\_url == 500: #爬虫失效,切换api video\_url = get\_video\_url\_in\_youtube\_form\_api(google\_appKey,musicname) if video\_url == 500: return ("error",400,headers) filename = dl\_mp3(musicname,video\_url) localpath = "/tmp" dl\_url = uploadfile(localpath,filename) return (dl\_url, 200, headers)
#将文件上传到七牛 def uploadfile(localpath,filename): #上传后保存的文件名 key = filename #生成上传 Token,可以指定过期时间等 token = q.upload\_token(bucket\_name, key, 3600) url = 'https://upload-z2.qiniup.com' files = {'file': open(localpath+"/"+filename, 'rb')} data = {'token':token,'fileName':filename,'key':key} response = requests.post(url, files=files, data=data) set\_liftime(filename) url=qnurl+filename delete\_tmp(filename) return url
#设置保存在七牛云上的音乐文件1天后自动删除 def set\_liftime(musicname): #初始化BucketManager bucket = BucketManager(q) #你要测试的空间, 并且这个key在你空间中存在 key = musicname #您要更新的生命周期 days = '1' ret, info = bucket.delete\_after\_days(bucket\_name, key, days) print(info)
##下载mp3格式文件 def dl\_mp3(musicname,url): ydl\_opts = { 'format': 'bestaudio/best', 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192', }], 'outtmpl': '/tmp/{}.mp3'.format(musicname), 'quiet': False } with youtube\_dl.YoutubeDL(ydl\_opts) as ydl: ydl.download([url]) # info = ydl.extract\_info(url, download=False) filename = "{}.mp3".format(musicname) return filename
#调用谷歌api获取youtube搜索结果,返回匹配度最高的视频链接 def get\_video\_url\_in\_youtube\_form\_api(appKey,music\_name): print("从google api获取数据") url = "https://youtube.googleapis.com/youtube/v3/search?q="+music\_name+"&key="+appKey resp = requests.get(url) resp\_json = resp.json() if resp.status\_code ==200: return "https://www.youtube.com/watch?v="+resp\_json['items'][0]['id']['videoId'] else: return 500
#利用爬虫获取youtube搜索结果 def get\_video\_url\_in\_youtube\_from\_crawler(keyword): url="https://m.youtube.com/results?search\_query="+keyword print("通过爬虫获取数据") resp = requests.get(url) if resp.status\_code==200: result\_json = re.findall(r'ytInitialData = (.\*);</script>', resp.text)[0] result\_obj = json.loads(result\_json) try: video\_url = "https://www.youtube.com/watch?v="+result\_obj['contents']['twoColumnSearchResultsRenderer']['primaryContents']['sectionListRenderer']['contents'][0]['itemSectionRenderer']['contents'][0]['videoRenderer']['videoId'] except KeyError: return 500 return video\_url else: return 500
def delete\_tmp(filename): os.system("rm -f /tmp/"+filename) print("从tmp删除"+filename)
|
上述代码,会提取谷歌请求路径中的最后一个地址作为搜索内容,比如 https://service-azkac26i-1258461674.hk.apigw.tencentcs.com/release/APIGWH-1612290368/爱的供养
那么爱的供养将作为搜索关键词,下载爱的供养最匹配的视频的音频 ,再保存到七牛云对象储存桶中。
此代码仅能用于谷歌serverless,由于权限问题,在其他serverless,比如腾讯、亚马逊上,在将下载的视频提取音频时,ffmpeg没有对视频的写权限,只有谷歌无服务框架支持。所以只能在谷歌上运行。
由于谷歌Serverless不支持自定义api网址,而谷歌的域名在国内又无法访问,因此,我们需要利用腾讯的香港serverless再构建一个云函数。腾讯云函数支持自定义api。
我相信通过一定的代码修改,ffmpeg是有办法处理下载的视频文件的,但是由于时间关系,我不愿意再去做尝试;额,这里就直接用了谷歌Serverless做视频处理,再通过腾讯serverless自定义api并返回给用户一个网页,网页里提示用户等待音频处理,并发送一个异步请求给谷歌云函数,当音频处理完毕,谷歌云函数返回给网页,音频文件在七牛云储存桶的保存地址。
2.3 网页构建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| # -\*- encoding: utf-8 -\*- ''' @file\_name :scf.py @description :用于腾讯云函数,自定义域名,返回一个静态页面,页面异步请求谷歌云函数,谷歌下载并处理音乐。然后返回链接给静态页面 @time :2021/02/03 00:49:36 @author :Qifei @version :1.0 '''
def handler(event,context): musicname = event['path'].split("/")[-1] html='<!DOCTYPE html><html><head><meta charset="utf-8"><title>处理中</title></head><body><h1><strong>服务器正在处理你的请求,请不要关闭页面,音乐将在倒计数结束前抵达。。</strong></h1><p id="demo"></p><h1 id="show" name="n1" ></h1><img src="https://qn.xieqifei.com/tipps.png" alt="tipps" width="300"/><script>var xhr=new XMLHttpRequest();xhr.onload=function(){window.location.replace(xhr.responseText);};xhr.onerror=function(){document.getElementById("demo").innerHTML="请求出错"};xhr.open("GET","https://service-azkac26i-1258461674.hk.apigw.tencentcs.com/release/APIGWHtmlDemo-1612290368/'+musicname+'",true);xhr.send();function countdown() {var n = 20;var interval;if (n > 0) {interval = setInterval(() => {n--;document.getElementById("show").innerHTML = n;if (n<= 0) {clearInterval(interval)}}, 1000)} } countdown();</script></body></html>' return { "isBase64Encoded": False, "statusCode": 200, "headers": {'Content-Type': 'text/html','Access-Control-Allow-Origin':'\*'}, "body": html }
|
来看看网页部分,也就是上述代码中的html变量,格式化后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>处理中</title> </head> <body> <h1><strong>服务器正在处理你的请求,请不要关闭页面,音乐将在倒计数结束前抵达。。</strong></h1> <p id="demo"></p> <h1 id="show" name="n1"></h1> <img src="https://qn.xieqifei.com/tipps.png" alt="tipps" width="300" /> <script> var xhr = new XMLHttpRequest(); xhr.onload = function() { window.location.replace(xhr.responseText); }; xhr.onerror = function() { document.getElementById("demo").innerHTML = "请求出错" }; xhr.open("GET", "https://service-azkac26i-1258461674.hk.apigw.tencentcs.com/release/APIGWH12290368/'+musicname+'", true); xhr.send(); function countdown() { var n = 20; var interval; if (n > 0) { interval = setInterval(() = >{ n--; document.getElementById("show").innerHTML = n; if (n <= 0) { clearInterval(interval) } }, 1000) } } countdown(); </script> </body> </html>
|
网页会有一个文字提醒,并有一个20秒倒计时的标签,通常视频的下载和处理都会在十几秒内完成,当谷歌上的云函数处理完音频后,会直接返回给网页音频地址,而网页通过js直接跳转到音乐的储存链接,可以直接播放,也可以下载。
2.4 中转节点
正如2.2节所讲,谷歌云函数的api在国内是无法访问的。当2.3节的网页发送到用户浏览器时,谷歌云函数api是本地发起的请求,如果用户在国内,那么请求将被长城防火墙拦截,导致请求失败。为了让全球用户都可以使用服务,我们需要在腾讯香港云函数上搭建一个中转节点。此节点用于接收网页的异步请求,并转发到谷歌云函数,激活音乐下载服务,并转发结果给网页。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| # -\*- encoding: utf-8 -\*- ''' @file\_name :scf\_node.py @description :解决国内用户无法直接请求谷歌api问题 @time :2021/02/06 12:16:35 @author :Qifei @version :1.0 ''' import requests from urllib.parse import quote
def handler(event,context): musicname = event['path'].split("/")[-1] resp = requests.get("https://asia-east2-you\*\*\*\*\*\*\*ch-303517.cloudfunctions.net/music/"+musicname) if(resp.status\_code == 200): mscurl = resp.text return { "isBase64Encoded": False, "statusCode": 200, "headers": {'Content-Type': 'text/html; charset="UTF-8','Access-Control-Allow-Origin':'\*'}, "body": mscurl } else: return { "isBase64Encoded": False, "statusCode": 500, "headers": {'Content-Type': 'text/plain','Access-Control-Allow-Origin':'\*'}, }
|
3:结语
重新梳理一遍整个过程。
- 我们在谷歌云函数上搭建了一个youtube视频下载并提取其音频的服务。
- 然后构建了一个网页,这个网页用于向谷歌云函数发起请求。这个网页由一个腾讯云函数下发给用户。
- 因为网页的请求是用户发起的,那么国内用户无法访问谷歌云函数的api,我们又在腾讯云函数香港上搭建了一个中专服务函数,此函数用于接收网页请求,代网页请求谷歌并返回音乐下载链接给网页。
如果你的服务对象是国外用户,那么中专节点是不需要的。