在没有自己的音乐搜索引擎的时候,却又想让用户可以较为方便的在自己的网站上搜索网络歌曲,在这里使用的是百度的MP3~
换成以前也许很简单,直接抓取网页就可以获取了网络音乐的实际URL。而现在不行了,搜索出来结果后,需要再次点击请求后台生成一个地址,然后那个页面才有真实MP3的URL地址。我使用的方法可能较为被动,如果百度MP3一些规则一旦改动,下面的代码就跑不起来了(在保持现在规则不变的情况下,看上去还是很完美的)
目前我做的是,只搜索第一页的音乐,如果你想实现与百度一致的翻页,可能还需要再花点时间了(不过应该会很简单了)
首先,我使用的开发环境:
操作系统:xp sp3
web服务器:nginx
flash开发IDE:flashDevelop
网页开发工具:Editplus
思路:
网页获取keyword -->传递给flash –> flash通过nginx反向代理请求百度mp3首页的网页内容 –> 回传给javascript
用户点击“试听”听 –> 将百度MP3首页的临时地址传给flash重新再请求一次(也采用nginx的反向代理) –> 获取最终真实地址的网页内容-->回传给javascript -->脚本通过解码函数再得真实的播放地址。
需要注意项:
1、百度的MP3请求地址,做了防盗链,在flash的http请求头里面需要设置host <ip地址>、清空referer、设置不缓存页面(每次重新请求),nginx代码大致如下:
proxy_set_header host '220.181.38.82';
proxy_set_header referer '';
add_header Cache-Control 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0';
add_header Pragma no-cache;
proxy_pass http://220.181.38.82;
2、需要在服务器(本地)放置crossdomain.xml文件(因为flash的安全策略,请求资源时它会请求当前根目录下的crossdomain.xml文件,不符合规则将报安全沙箱错误)
下面测试一下,到底下面的方法得到的真实的URL是否正确:
注意它百度跳到指定的一个IP上,而不是域名,如果nginx里设置域名也是不行的,一定要用IP。
从两张图的对比来看,试验的页面是可以获得百度MP3的真实的网络地址, 项目测试成功。
nginx配置:
#VHOST: meteoric.com server { listen 80; server_name meteoric.com charset utf-8; access_log off; ssi on; ssi_silent_errors on; location / { root C:\phpApp; index index.html index.php; } location /crossdomain.xml { alias C:/phpApp/searchMusic/crossdomain.xml; } location ~ ^/baidu(/?) { rewrite .* http://www.baidu.com/ redirect; } location ~ ^/m$ { proxy_set_header host '220.181.43.121'; proxy_set_header referer ''; proxy_pass http://mp3.baidu.com; } location ~ ^(.+\.php)(.*)$ { root C:\phpApp; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include php.conf; } } #VHOST: meteoric2.com server { listen 80; server_name meteoric2.com; charset utf-8; location /crossdomain.xml { alias C:/phpApp/searchMusic/crossdomain.xml; } location ~ ^/m$ { proxy_set_header host '220.181.38.82'; proxy_set_header referer ''; add_header Cache-Control 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'; add_header Pragma no-cache; proxy_pass http://220.181.38.82; } }
解析百度MP3首页音乐列表脚本:
if (/<div(?:\s+)id=\"songResults\"[^>]+\>[\n\s]*(<table[\S\s]*?\<\/table\>)/.test(_data)) { var table_str = RegExp['$1']; var tr_reg = /(<tr[^>]*>[\S\s]+?<\/tr>)/; var tr_str = ""; musicList = []; while (tr_reg.test(table_str)) { tr_str = RegExp['$1']; table_str = table_str.replace(tr_str, ""); if (/<td/.test(tr_str)) { musicList.push({ 'tmpurl' : /<td(?:\s+)class=\"second\">[^<]*<a(?:\s+)href=\"([^\"]+)/.test(tr_str) ? RegExp['$1'] : "", 'name' : /<td(?:\s+)class=\"second\">[^>]+>([^<]+)/.test(tr_str) ? RegExp['$1'] : "", 'singer' : /<td(?:\s+)class=\"third\">[^>]+>[^>]+>([^<]+)/.test(tr_str) ? RegExp['$1'] : "", 'relurl' : null, 'size' : /<td(?:\s+)class=\"seventh\">([^<]+)/.test(tr_str) ? RegExp['$1'] : "", 'speed' : /<td(?:\s+)class=\"ninth\">[^>]+>(\d)</.test(tr_str) ? RegExp['$1']*1+1 : "" }); } } }
解析网络音乐真实URL的核心方法:
parseMusicURL : function(data) { if(data) { /var encurl\s*=\s*\"([^\"]*)\"\s*\|\|\s+\"([^\"]*)\"/.test(data); var encurl = RegExp['$1'] || RegExp['$2']; /var song_(\d+)/.test(data); var sertim = RegExp['$1']; if(sertim && encurl) { return this.decodeMusicURL(sertim, encurl); } else { return null; } } else { return null; } }, decodeMusicURL : function(_rId, _url) { var sertim = parseInt(_rId, 10); var url = _url; var len = url.length; var decurl = ""; var asc_arr1 = [], asc_arr2 = []; var key = sertim % 26; key = key ? key : 1; function init(head, bottom, middle){ for (var i = head; i <= bottom; i++) { asc_arr1[i] = i + middle; asc_arr2[i + middle] = i; } } init(0, 9, 48); init(10, 35, 55); init(36, 61, 61); for (var i = 0; i < len; i++) { var word = url.charAt(i); if (/[A-Za-z0-9]/.test(word)) { var pos = asc_arr2[url.charCodeAt(i)] - key; if (pos < 0) pos += 62; word = String.fromCharCode(asc_arr1[pos]); } decurl += word; } return decurl; }
代理请求的Flash代码:
package { import flash.display.Sprite; import flash.events.Event; import flash.events.IOErrorEvent; import flash.external.ExternalInterface; import flash.net.URLLoader; import flash.net.URLRequest; import flash.net.URLRequestMethod; import flash.system.System; /** * ... * @author ZhangYi */ public class Main extends Sprite { private static var CallBack_Fun:String; public function Main():void { if (stage) { init(); } else { addEventListener(Event.ADDED_TO_STAGE, init); } } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); inited(); } private function inited():void { if (ExternalInterface.available) { ExternalInterface.addCallback("loadURL", loadURL); } System.useCodePage = true; var params:Object = root.loaderInfo.parameters; if (params.initCallback) { ExternalInterface.call(params.initCallback); } } /** * 请求指定的地址,获取数据后返回 * * @param _url * @param _callback * @param method */ public function loadURL(_url:String, _callback:String = "", method:String = "get"):void { var req:URLRequest = new URLRequest(_url); req.method = method == "get" ? URLRequestMethod.GET : "POST"; var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE, onCompleteHandler); loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); CallBack_Fun = _callback; loader.load(req); } /** * 数据加载完成 * @param evt */ private function onCompleteHandler(evt:Event):void { var loader:URLLoader = evt.target as URLLoader; ExternalInterface.call(CallBack_Fun, loader.data); } /** * 请求时发生IO错误 * @param evt */ private function ioErrorHandler(evt:IOErrorEvent):void { ExternalInterface.call(CallBack_Fun, null, evt.text); } } }
实际运行请求的效果示意图:
获取音乐列表的请求<也就是百度MP3首页的字符--网页源代码>:
请求网络音乐的真实URL时,网页内有一个javascript解码函数:
除nginx外,其它源码(html、css、flash)都将上传打包。不一定非得用nginx,你也可以使用apache,只是我的开发环境中经常用。