想做一个歌词查询的工具有很长时间了,之前的想法一直是在web端,提供高质量的封面图,精美排版的歌词,作词作曲演唱者信息,如果可能再加入试听与购买链接,要是能再提供些背后的故事与独家采访就更牛逼了。
这样的想法需要大量的人力物力还有高超的技术才能运转的起来,起码目前我单枪匹马是无法完成的,所以一直拖着无法实现。昨天突然想起来我可以在微信公众平台上面建立一个公众号,提供查询功能,初期只接受文本消息,也只回复文本消息就好,简简单单,也有一定的实用性。
说做就做,昨天做出了第一个版本,这个版本里我只是在数据库中随便添加了几条数据,只有一个表一个字段,完全的LIKE匹配,这样的架构数据量大了之后性能一定歇菜,但是先不管了,于是想先做个爬虫到百度或者虾米上面去爬歌词,在google的时候发现虾米居然有api可以调用,这下爬起来简单多了,今天早上来到公司突然想起来虾米也有搜索功能,于是手动去搜了一下,url非常规范,返回的结果结构也很规范,于是决定修改架构,不自己保存数据了,直接去虾米上搜索后获得歌曲id,再到api去获取歌词的url路径,转而获取歌词内容,这也是第二个版本。
具体流程:
- 用户通过微信向公众帐号发送文本消息
- 微信服务器向自己填写的api路径POST一个xml信息
- 解析获取关键词后到虾米搜索
- 抓取搜索结果获取歌曲id
- 到虾米api去获取歌曲详细信息
- 通过歌词url获取歌词内容
- 包装成xml信息返回给微信服务器
- 微信发送给用户结果
具体代码:
1. 通过微信验证
微信只有在验证时是使用GET方式访问api,其余只有POST,"Hello, it works!"是为了测试方便加上去的
public void ProcessRequest (HttpContext context) {
switch (context.Request.HttpMethod)
{
case "GET":
context.Response.ContentType = "text/plain";
context.Response.Write(context.Request["echostr"] == null ? "Hello, it works!" : context.Request["echostr"]);
break;
case "POST":
WeiXinServlet.DoPost(context);
break;
default:
break;
}
}
2. 定义消息类
由于目前只有文本消息,所以我只定义了消息基类和文本消息类,回复的文本消息类还加了一个toXML()方法,用于返回给微信服务器
public class BaseMessage
{
public string ToUserName { get; set; }
public string FromUserName { get; set; }
public string CreateTime { get; set; }
public string MsgType { get; set; }
}
public class ContentMessage : BaseMessage
{
public string Content { get; set; }
}
public class ReturnContentMessage : ContentMessage
{
public virtual string toXML()
{
return XmlHelper.ObjectToXml(this);
}
}
3. toXML()方法
.NET提供了直接从类序列化为xml的方法,很方便
public static string ObjectToXml(object obj)
{
XmlSerializer serializer = new XmlSerializer(obj.GetType());
using(MemoryStream stream = new MemoryStream())
{
XmlTextWriter xmlTW = new XmlTextWriter(stream, new UTF8Encoding(false));
xmlTW.Formatting = Formatting.Indented;
serializer.Serialize(xmlTW, obj);
xmlTW.Flush();
xmlTW.Close();
UTF8Encoding uTF8 = new UTF8Encoding(false, true);
XmlDocument doc = new XmlDocument();
doc.LoadXml(uTF8.GetString(stream.ToArray()));
XmlDocument docNew = new XmlDocument();
XmlElement newRoot = docNew.CreateElement("xml");
docNew.AppendChild(newRoot);
newRoot.InnerXml = doc.DocumentElement.InnerXml;
return docNew.OuterXml;
}
}
4. doPOST()方法
微信这点不太方便,所有访问你的api的都扔到这一个地址来,你只能自己做判断然后分发处理,同样我只做了文本消息(text)的处理。
public static void DoPost(HttpContext context)
{
StreamReader reader = new StreamReader(context.Request.InputStream, Encoding.UTF8);
string postStr = reader.ReadToEnd();
XmlDocument doc = new XmlDocument();
doc.LoadXml(postStr);
switch(doc.SelectSingleNode("/xml/MsgType").InnerText)
{
case "text":
ContentMessage content = new ContentMessage
{
ToUserName = doc.SelectSingleNode("/xml/ToUserName").InnerText,
FromUserName = doc.SelectSingleNode("/xml/FromUserName").InnerText,
CreateTime = doc.SelectSingleNode("/xml/CreateTime").InnerText,
MsgType = "text",
Content = doc.SelectSingleNode("/xml/Content").InnerText
};
string lyric = Lyrics.ChaGeCi(content.Content);
ReturnContentMessage returnContent = new ReturnContentMessage
{
ToUserName = content.FromUserName,
FromUserName = content.ToUserName,
CreateTime = DateTime.Now.ToString(),
MsgType = "text",
Content = lyric
};
string result = XmlHelper.ObjectToXml(returnContent);
context.Response.ContentType = "text/xml";
context.Response.Write(result);
break;
}
}
5. 具体歌词逻辑获取
目前只取搜索结果的第一条数据,这样最简洁快速。另外引用了外部抓取库HtmlAgilityPack,顺便吐槽一下,这个库的网站上真的是什么文档都没有啊,要想弄懂得去读源码…,在筛选结果时还用到了一点正则表达式
public static string ChaGeCi(string twoChar)
{
string lyric = "";
string keyword = twoChar.Replace(" ", "+");
HtmlWeb web1 = new HtmlWeb();
HtmlDocument doc1 = web1.Load("http://www.xiami.com/search/song-lyric/?key=" + keyword);
HtmlNode node = doc1.DocumentNode.SelectSingleNode("//body//div//div//div//div//div//div//div//div//ul//li[1]//table//tbody//tr//td//a[@href]");
if(node != null)
{
string sid = node.Attributes["href"].Value;
sid = Regex.Replace(sid, @"[^\d]*", "");
Uri uri2 = new Uri("http://www.xiami.com/song/playlist/id/" + sid);
HttpWebRequest req2 = HttpWebRequest.Create(uri2) as HttpWebRequest;
string songPage = "";
using(HttpWebResponse res2 = req2.GetResponse() as HttpWebResponse)
{
StreamReader reader2 = new StreamReader(res2.GetResponseStream());
songPage = reader2.ReadToEnd();
}
if(songPage.Length > 1)
{
XmlDocument xml = new XmlDocument();
xml.LoadXml(songPage);
string title = xml.GetElementsByTagName("title")[0].InnerText;
string artist = xml.GetElementsByTagName("artist")[0].InnerText;
string lyric_url = xml.GetElementsByTagName("lyric")[0].InnerText;
HttpWebRequest req3 = HttpWebRequest.Create(new Uri(lyric_url)) as HttpWebRequest;
using(HttpWebResponse res3 = req3.GetResponse() as HttpWebResponse)
{
StreamReader reader3 = new StreamReader(res3.GetResponseStream());
lyric = reader3.ReadToEnd();
lyric = Regex.Replace(lyric, @"\[.*?\]", "");
lyric += "\n" + title + " - " + artist;
}
}
}
return lyric == "" ? "抱歉,没有找到相关歌词~" : lyric;
}
总结:
- 搜索可能要加上网易云音乐的接口
- 如果有可能的话,后续还可以加上语音的搜索