百度API 應用實例之音樂搜索

http://blog.csdn.net/lyq8479/article/details/17232631

引言及內容概要

微信公衆平臺支持向用戶回覆音樂消息,用戶收到音樂消息後,點擊即可播放音樂。通過音樂消息,公衆賬號可以實現音樂搜索(歌曲點播)功能,即用戶輸入想聽的音樂名稱,公衆賬號返回對應的音樂(歌曲)。讀者可以關注xiaoqrobot體驗該功能,操作指南及使用如下所示。


考慮到歌曲名稱有重複的情況,用戶還可以同時指定歌曲名稱、演唱者搜索歌曲。下面就爲讀者詳細介紹歌曲點播功能的實現過程。


音樂消息說明

微信公衆平臺開發者文檔中提到,向用戶回覆音樂消息需要構造如下格式的XML數據。

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <xml>  
  2.     <ToUserName><![CDATA[toUser]]></ToUserName>  
  3.     <FromUserName><![CDATA[fromUser]]></FromUserName>  
  4.     <CreateTime>12345678</CreateTime>  
  5.     <MsgType><![CDATA[music]]></MsgType>  
  6.     <Music>  
  7.         <Title><![CDATA[TITLE]]></Title>  
  8.         <Description><![CDATA[DESCRIPTION]]></Description>  
  9.         <MusicUrl><![CDATA[MUSIC_Url]]></MusicUrl>  
  10.         <HQMusicUrl><![CDATA[HQ_MUSIC_Url]]></HQMusicUrl>  
  11.         <ThumbMediaId><![CDATA[media_id]]></ThumbMediaId>  
  12.     </Music>  
  13. </xml>  
上面XML中,需要注意的是<Music>節點中的參數,說明如下:

1)參數Title:標題,本例中可以設置爲歌曲名稱;

2)參數Description:描述,本例中可以設置爲歌曲的演唱者;

3)參數MusicUrl:普通品質的音樂鏈接;

4)參數HQMusicUrl:高品質的音樂鏈接,在WIFI環境下會優先使用該鏈接播放音樂;

5)參數ThumbMediaId:縮略圖的媒體ID,通過上傳多媒體文件獲得;它指向的是一張圖片,最終會作爲音樂消息左側綠色方形區域的背景圖片。
上述5個參數中,最爲重要的是MusicUrl和HQMusicUrl,這也是本文的重點,如何根據歌曲名稱獲得歌曲的鏈接。如果讀者只能得到歌曲的一個鏈接,可以將MusicUrl和HQMusicUrl設置成一樣的。至於ThumbMediaId參數,必須是通過微信認證的服務號才能得到,普通的服務號與訂閱號可以忽略該參數,也就是說,在回覆給微信服務器的XML中可以不包含ThumbMediaId參數


百度音樂搜索API介紹

上面提到,給用戶回覆音樂消息最關鍵在於如何根據歌曲名稱獲得歌曲的鏈接,我們必須找一個現成的音樂搜索API,除非讀者自己有音樂服務器,或者只向用戶回覆固定的幾首音樂。百度有一個非公開的音樂搜索API,之所以說非公開,是因爲筆者沒有在百度官網的任何地方看到有關該API的介紹,但這並不影響讀者對本例的學習,我們仍然可以調用它。百度音樂搜索API的請求地址如下:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. http://box.zhangmen.baidu.com/x?op=12&count=1&title=TITLE$$AUTHOR$$$$  

http://box.zhangmen.baidu.com爲百度音樂盒的首頁地址,上面的鏈接中不用管參數op和count,重點關注TITLE和AUTHOR,TITLE表示歌曲名稱,AUTHOR表示演唱者,AUTHOR可以爲空,參數TITLE和AUTHOR需要進行URL編碼(UTF-8或GB2312均可)。例如,要搜索歌曲零點樂隊的“相信自己”,可以像下面這樣:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // GB2312編碼的音樂搜索鏈接  
  2. http://box.zhangmen.baidu.com/x?op=12&count=1&title=%CF%E0%D0%C5%D7%D4%BC%BA$$%C1%E3%B5%E3%C0%D6%B6%D3$$$$  
  3. // UTF-8編碼的音樂搜索鏈接  
  4. http://box.zhangmen.baidu.com/x?op=12&count=1&title=%E7%9B%B8%E4%BF%A1%E8%87%AA%E5%B7%B1$$%E9%9B%B6%E7%82%B9%E4%B9%90%E9%98%9F$$$$  
通過瀏覽器訪問上面的地址,返回的是如下格式的XML數據:
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <result>  
  2.     <count>1</count>  
  3.     <url>  
  4.         <encode>  
  5.             <![CDATA[http://zhangmenshiting.baidu.com/data2/music/44277542/ZWZla2xra2pfn6NndK6ap5WXcJVob5puZ2trbWprmnBjZ2xolpeZa2drZmWZmZmdl2hjZWhvnWlpYmRtZmltcGplZFqin5t1YWBobW5qcGxia2NmZ2twbzE$]]>  
  6.         </encode>  
  7.         <decode>  
  8.             <![CDATA[44277542.mp3?xcode=a39c6698955c82594aab36931dcbef60139f180191368931&mid=0.59949419022597]]>  
  9.         </decode>  
  10.         <type>8</type>  
  11.         <lrcid>64644</lrcid>  
  12.         <flag>1</flag>  
  13.     </url>  
  14.     <durl>  
  15.         <encode>  
  16.             <![CDATA[http://zhangmenshiting2.baidu.com/data2/music/44277530/ZWZla2xramhfn6NndK6ap5WXcJVob5puZ2trbWprmnBjZ2xolpeZa2drZmWZmZmdl2hjaGhvnZ5qlGRpbpedamJla1qin5t1YWBobW5qcGxia2NmZ2twbzE$]]>  
  17.         </encode>  
  18.         <decode>  
  19.             <![CDATA[44277530.mp3?xcode=a39c6698955c82594aab36931dcbef60439ff9b159af2138&mid=0.59949419022597]]>  
  20.         </decode>  
  21.         <type>8</type>  
  22.         <lrcid>64644</lrcid>  
  23.         <flag>1</flag>  
  24.     </durl>  
  25.     <p2p>  
  26.         <hash>022bc0fbf66cd19bea96db49634419dc2600393f</hash>  
  27.         <url>  
  28.             <![CDATA[ ]]>  
  29.         </url>  
  30.         <type>mp3</type>  
  31.         <size>5236902</size>  
  32.         <bitrate>192</bitrate>  
  33.     </p2p>  
  34. </result>  

返回結果中的主要參數說明如下:

1)<count> 表示搜索到的音樂數;

2)<url>中包含了普通品質的音樂鏈接,<durl>中包含了高品質音樂的鏈接;

3)<encode>中包含了加密後的音樂鏈接,實際上只是對音樂名稱進行了加密,<decode>中包含了解密後的音樂名稱。因此,要獲取音樂的鏈接就需要重點分析<encode>和<decode>中的內容,下面會專門爲讀者進行介紹。

4)<type>表示音樂文件的類型,如rm、wma、mp3等;

5)<lrcid>是歌詞的ID,<url>中的歌詞ID爲64644,那麼如何得到歌詞呢?本例並不關心歌詞,只是附帶提一下。歌詞的地址如下:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. http://box.zhangmen.baidu.com/bdlrc/646/64644.lrc  
其中,http://box.zhangmen.baidu.com/bdlrc/是固定值;646爲歌詞所在目錄名,計算方法爲歌詞ID(64644)除以100,取整數部分;64644.lrc是歌詞文件名。

下面來看如何從<encode>和<decode>中得到音樂鏈接。爲了便於說明,筆者將上面搜索結果中的<url>和<durl>部分抽取出來,並進行了標註,如下圖所示。


上圖中,1和2拼接起來是普通品質音樂的鏈接,3和4拼接起來是高品質音樂的鏈接。也就是說,普通品質和高品質的音樂鏈接如下:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // 普通品質音樂鏈接  
  2. http://zhangmenshiting.baidu.com/data2/music/44277542/44277542.mp3?xcode=a39c6698955c82594aab36931dcbef60139f180191368931  
  3. // 高品質音樂鏈接  
  4. http://zhangmenshiting2.baidu.com/data2/music/44277530/44277530.mp3?xcode=a39c6698955c82594aab36931dcbef60439ff9b159af2138  
參數xcode可以理解爲隨機驗證碼,每次搜索得到的值都不一樣,如果不帶該參數會報未授權異常“401 Authorization Required”。需要注意的是,xcode是有時間限制的,超過限制再訪問鏈接會報異常:{"Error":{"code":"2","Message":"object not exists","LogId":"3456414897"}}。在xcode有效的前提下,通過瀏覽器訪問上面的音樂鏈接,會提示下載音樂。


編程調用百度音樂搜索API

知道如何從API返回結果中得到音樂鏈接後,就可以編寫程序來實現了。筆者將發送HTTP請求、URL編碼、解析XML等操作全部封裝在BaiduMusicService類中,該類的代碼如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. import java.io.InputStream;  
  2. import java.io.UnsupportedEncodingException;  
  3. import java.net.HttpURLConnection;  
  4. import java.net.URL;  
  5. import java.util.List;  
  6.   
  7. import org.dom4j.Document;  
  8. import org.dom4j.Element;  
  9. import org.dom4j.io.SAXReader;  
  10.   
  11. import org.liufeng.course.message.resp.Music;  
  12.   
  13. /** 
  14.  * 百度音樂搜索API操作類 
  15.  *  
  16.  * @author liufeng 
  17.  * @date 2013-12-09 
  18.  */  
  19. public class BaiduMusicService {  
  20.     /** 
  21.      * 根據名稱和作者搜索音樂 
  22.      *  
  23.      * @param musicTitle 音樂名稱 
  24.      * @param musicAuthor 音樂作者 
  25.      * @return Music 
  26.      */  
  27.     public static Music searchMusic(String musicTitle, String musicAuthor) {  
  28.         // 百度音樂搜索地址  
  29.         String requestUrl = "http://box.zhangmen.baidu.com/x?op=12&count=1&title={TITLE}$${AUTHOR}$$$$";  
  30.         // 對音樂名稱、作者進URL編碼  
  31.         requestUrl = requestUrl.replace("{TITLE}", urlEncodeUTF8(musicTitle));  
  32.         requestUrl = requestUrl.replace("{AUTHOR}", urlEncodeUTF8(musicAuthor));  
  33.         // 處理名稱、作者中間的空格  
  34.         requestUrl = requestUrl.replaceAll("\\+""%20");  
  35.   
  36.         // 查詢並獲取返回結果  
  37.         InputStream inputStream = httpRequest(requestUrl);  
  38.         // 從返回結果中解析出Music  
  39.         Music music = parseMusic(inputStream);  
  40.   
  41.         // 如果music不爲null,設置標題和描述  
  42.         if (null != music) {  
  43.             music.setTitle(musicTitle);  
  44.             // 如果作者不爲"",將描述設置爲作者  
  45.             if (!"".equals(musicAuthor))  
  46.                 music.setDescription(musicAuthor);  
  47.             else  
  48.                 music.setDescription("來自百度音樂");  
  49.         }  
  50.         return music;  
  51.     }  
  52.   
  53.     /** 
  54.      * UTF-8編碼 
  55.      *  
  56.      * @param source 
  57.      * @return 
  58.      */  
  59.     private static String urlEncodeUTF8(String source) {  
  60.         String result = source;  
  61.         try {  
  62.             result = java.net.URLEncoder.encode(source, "UTF-8");  
  63.         } catch (UnsupportedEncodingException e) {  
  64.             e.printStackTrace();  
  65.         }  
  66.         return result;  
  67.     }  
  68.   
  69.     /** 
  70.      * 發送http請求取得返回的輸入流 
  71.      *  
  72.      * @param requestUrl 請求地址 
  73.      * @return InputStream 
  74.      */  
  75.     private static InputStream httpRequest(String requestUrl) {  
  76.         InputStream inputStream = null;  
  77.         try {  
  78.             URL url = new URL(requestUrl);  
  79.             HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();  
  80.             httpUrlConn.setDoInput(true);  
  81.             httpUrlConn.setRequestMethod("GET");  
  82.             httpUrlConn.connect();  
  83.             // 獲得返回的輸入流  
  84.             inputStream = httpUrlConn.getInputStream();  
  85.         } catch (Exception e) {  
  86.             e.printStackTrace();  
  87.         }  
  88.         return inputStream;  
  89.     }  
  90.   
  91.     /** 
  92.      * 解析音樂參數 
  93.      *  
  94.      * @param inputStream 百度音樂搜索API返回的輸入流 
  95.      * @return Music 
  96.      */  
  97.     @SuppressWarnings("unchecked")  
  98.     private static Music parseMusic(InputStream inputStream) {  
  99.         Music music = null;  
  100.         try {  
  101.             // 使用dom4j解析xml字符串  
  102.             SAXReader reader = new SAXReader();  
  103.             Document document = reader.read(inputStream);  
  104.             // 得到xml根元素  
  105.             Element root = document.getRootElement();  
  106.             // count表示搜索到的歌曲數  
  107.             String count = root.element("count").getText();  
  108.             // 當搜索到的歌曲數大於0時  
  109.             if (!"0".equals(count)) {  
  110.                 // 普通品質  
  111.                 List<Element> urlList = root.elements("url");  
  112.                 // 高品質  
  113.                 List<Element> durlList = root.elements("durl");  
  114.   
  115.                 // 普通品質的encode、decode  
  116.                 String urlEncode = urlList.get(0).element("encode").getText();  
  117.                 String urlDecode = urlList.get(0).element("decode").getText();  
  118.                 // 普通品質音樂的URL  
  119.                 String url = urlEncode.substring(0, urlEncode.lastIndexOf("/") + 1) + urlDecode;  
  120.                 if (-1 != urlDecode.lastIndexOf("&"))  
  121.                     url = urlEncode.substring(0, urlEncode.lastIndexOf("/") + 1) + urlDecode.substring(0, urlDecode.lastIndexOf("&"));  
  122.   
  123.                 // 默認情況下,高音質音樂的URL 等於 普通品質音樂的URL  
  124.                 String durl = url;  
  125.   
  126.                 // 判斷高品質節點是否存在  
  127.                 Element durlElement = durlList.get(0).element("encode");  
  128.                 if (null != durlElement) {  
  129.                     // 高品質的encode、decode  
  130.                     String durlEncode = durlList.get(0).element("encode").getText();  
  131.                     String durlDecode = durlList.get(0).element("decode").getText();  
  132.                     // 高品質音樂的URL  
  133.                     durl = durlEncode.substring(0, durlEncode.lastIndexOf("/") + 1) + durlDecode;  
  134.                     if (-1 != durlDecode.lastIndexOf("&"))  
  135.                         durl = durlEncode.substring(0, durlEncode.lastIndexOf("/") + 1) + durlDecode.substring(0, durlDecode.lastIndexOf("&"));  
  136.                 }  
  137.                 music = new Music();  
  138.                 // 設置普通品質音樂鏈接  
  139.                 music.setMusicUrl(url);  
  140.                 // 設置高品質音樂鏈接  
  141.                 music.setHQMusicUrl(durl);  
  142.             }  
  143.         } catch (Exception e) {  
  144.             e.printStackTrace();  
  145.         }  
  146.         return music;  
  147.     }  
  148.   
  149.     // 測試方法  
  150.     public static void main(String[] args) {  
  151.         Music music = searchMusic("相信自己""零點樂隊");  
  152.         System.out.println("音樂名稱:" + music.getTitle());  
  153.         System.out.println("音樂描述:" + music.getDescription());  
  154.         System.out.println("普通品質鏈接:" + music.getMusicUrl());  
  155.         System.out.println("高品質鏈接:" + music.getHQMusicUrl());  
  156.     }  
  157. }  

下面對代碼進行簡單的說明:

1)代碼中的Music類是對音樂消息的封裝(不包括ThumbMediaId參數),讀者可以在本系列教程的第4篇中找到該類的定義;

2)運行上述代碼需要引入dom4j的JAR包,筆者使用的是dom4j-1.6.1.jar;

3)searchMusic()方法是提供給外部調用的,在CoreService類中會調用該方法獲得音樂消息需要的Music相關的4個參數(Title、Description、MusicUrl和HQMusicUrl);

4)parseMusic()方法用於解析XML,讀者可以結合代碼中的註釋和之前對XML的分析進行理解,這裏就不再贅述了。

5)116行、127行中的get(0)表示返回多條音樂結果時默認取第一條。


公衆賬號後臺的實現

在公衆賬號後臺的CoreService類中,需要對用戶發送的文本消息進行判斷,如果是以“歌曲”兩個字開頭,就認爲用戶是在使用“歌曲點播”功能,此時需要對“歌曲”兩個字之後的內容進行判斷,如果包含“@”符號,就表示需要按演唱者搜索,否則不指定演唱者。CoreService類的完整代碼如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package org.liufeng.course.service;  
  2.   
  3. import java.util.Date;  
  4. import java.util.Map;  
  5. import javax.servlet.http.HttpServletRequest;  
  6. import org.liufeng.course.message.resp.Music;  
  7. import org.liufeng.course.message.resp.MusicMessage;  
  8. import org.liufeng.course.message.resp.TextMessage;  
  9. import org.liufeng.course.util.MessageUtil;  
  10.   
  11. /** 
  12.  * 核心服務類 
  13.  *  
  14.  * @author liufeng 
  15.  * @date 2013-12-10 
  16.  */  
  17. public class CoreService {  
  18.     /** 
  19.      * 處理微信發來的請求 
  20.      *  
  21.      * @param request 
  22.      * @return 
  23.      */  
  24.     public static String processRequest(HttpServletRequest request) {  
  25.         // 返回給微信服務器的xml消息  
  26.         String respXml = null;  
  27.         // 文本消息內容  
  28.         String respContent = null;  
  29.         try {  
  30.             // xml請求解析  
  31.             Map<String, String> requestMap = MessageUtil.parseXml(request);  
  32.             // 發送方帳號(open_id)  
  33.             String fromUserName = requestMap.get("FromUserName");  
  34.             // 公衆帳號  
  35.             String toUserName = requestMap.get("ToUserName");  
  36.             // 消息類型  
  37.             String msgType = requestMap.get("MsgType");  
  38.   
  39.             // 回覆文本消息  
  40.             TextMessage textMessage = new TextMessage();  
  41.             textMessage.setToUserName(fromUserName);  
  42.             textMessage.setFromUserName(toUserName);  
  43.             textMessage.setCreateTime(new Date().getTime());  
  44.             textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);  
  45.   
  46.             // 文本消息  
  47.             if (MessageUtil.REQ_MESSAGE_TYPE_TEXT.equals(msgType)) {  
  48.                 // 文本消息內容  
  49.                 String content = requestMap.get("Content").trim();  
  50.                 // 如果以“歌曲”2個字開頭  
  51.                 if (content.startsWith("歌曲")) {  
  52.                     // 將歌曲2個字及歌曲後面的+、空格、-等特殊符號去掉  
  53.                     String keyWord = content.replaceAll("^歌曲[\\+ ~!@#%^-_=]?""");  
  54.                     // 如果歌曲名稱爲空  
  55.                     if ("".equals(keyWord)) {  
  56.                         respContent = getUsage();  
  57.                     } else {  
  58.                         String[] kwArr = keyWord.split("@");  
  59.                         // 歌曲名稱  
  60.                         String musicTitle = kwArr[0];  
  61.                         // 演唱者默認爲空  
  62.                         String musicAuthor = "";  
  63.                         if (2 == kwArr.length)  
  64.                             musicAuthor = kwArr[1];  
  65.   
  66.                         // 搜索音樂  
  67.                         Music music = BaiduMusicService.searchMusic(musicTitle, musicAuthor);  
  68.                         // 未搜索到音樂  
  69.                         if (null == music) {  
  70.                             respContent = "對不起,沒有找到你想聽的歌曲<" + musicTitle + ">。";  
  71.                         } else {  
  72.                             // 音樂消息  
  73.                             MusicMessage musicMessage = new MusicMessage();  
  74.                             musicMessage.setToUserName(fromUserName);  
  75.                             musicMessage.setFromUserName(toUserName);  
  76.                             musicMessage.setCreateTime(new Date().getTime());  
  77.                             musicMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_MUSIC);  
  78.                             musicMessage.setMusic(music);  
  79.                             respXml = MessageUtil.musicMessageToXml(musicMessage);  
  80.                         }  
  81.                     }  
  82.                 }  
  83.             }  
  84.             // 未搜索到音樂時返回使用指南  
  85.             if (null == respXml) {  
  86.                 if (null == respContent)  
  87.                     respContent = getUsage();  
  88.                 textMessage.setContent(respContent);  
  89.                 respXml = MessageUtil.textMessageToXml(textMessage);  
  90.             }  
  91.         } catch (Exception e) {  
  92.             e.printStackTrace();  
  93.         }  
  94.         return respXml;  
  95.     }  
  96.   
  97.     /** 
  98.      * 歌曲點播使用指南 
  99.      *  
  100.      * @return 
  101.      */  
  102.     public static String getUsage() {  
  103.         StringBuffer buffer = new StringBuffer();  
  104.         buffer.append("歌曲點播操作指南").append("\n\n");  
  105.         buffer.append("回覆:歌曲+歌名").append("\n");  
  106.         buffer.append("例如:歌曲存在").append("\n");  
  107.         buffer.append("或者:歌曲存在@汪峯").append("\n\n");  
  108.         buffer.append("回覆“?”顯示主菜單");  
  109.         return buffer.toString();  
  110.     }  
上述代碼的邏輯比較簡單,用戶發送“歌曲+名稱”或者“歌曲+名稱@演唱者”就能搜索歌曲,搜索不到時會提示用戶,如果發送其他內容回覆歌曲點播功能的用法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章