您的位置:首页 > 编程语言 > Qt开发

虾米音乐解析器Qt版

2014-10-27 13:17 615 查看
比较喜欢虾米的精选集,很多精选集工作的时候听起来特别有感觉。经常带着电脑去教室干活,有何没有网,市面上的下载器大部分在虾米改版之后都挂掉了,所以做个下载器把喜欢的歌拖下来听成为了很重要的需求。百度原理以后发现不难,做起来却是异常艰辛。没玩过多少Qt的我基本上是从新学了一遍,还顺道学习了一下burpsuite抓包,折腾了六天终于是把这个东西弄出来了。

(直接能用的exe文件可以看http://download.csdn.net/detail/zjyl_1994/8086895,已打包所缺失的mingw运行库,安装过开发环境的可以直接去下源码包,那个比较小。)

先上一张完工图,(PS:前一阵虾米改版之后LongkeyMusic就失效了)



虾米的加密算法卡了我两天,初期做出来的解密程序解码部分location的时候还是会出现乱码。静下心来发现是解密思路错了,需要知道余出来几个字母然后他们是矩阵大小+1的长度,其他的是矩阵长度。说多的也看不懂,上代码能更好地理解:

QString Dialog::DecodeXiamiLocation(QString location)
{//解码虾米音乐凯撒矩阵算法
if(!(location.data()[0].isDigit()))
return "ERROR";
int num=location.data()[0].digitValue();
QString loc=location.mid(1,location.length()-1);
int avg_len=loc.length()/num;
int remainder=loc.length()%num;
for(int i=0;i<num-remainder;i++)
loc.insert((remainder*(avg_len+1)+avg_len*(i+1))+i,'~');
int lineLen=loc.length()/num;
QString ret;
for(int j=0;j<lineLen;j++)
for(int i=0;i<num;i++)
ret.append(loc.at(i*lineLen+j));
ret=QUrl::fromPercentEncoding(ret.remove('~').toLocal8Bit()).replace('^','0');
return ret;
}


还有一个最重要的部分就是如何获取网页的源码,我在网上找的代码,都只能单纯的访问,而虾米音乐的页面可以随便访问,xml页面有时候就需要带cookie,翻遍国内网也没有怎么带cookie获取源码的,所以花时间自己改造了一下:

QString Dialog::GetSourcefromURL(QString URL,QString cookie)
{
QUrl url(URL);
QNetworkAccessManager manager;
QEventLoop loop;
QNetworkRequest nr(url);
if(cookie.length()!=0)
nr.setRawHeader(QString("Cookie").toLocal8Bit(),cookie.toLocal8Bit());
QNetworkReply *reply = manager.get(nr);
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
QString src=QString::fromUtf8(reply->readAll());
delete reply;
return src;
}


从试听页面可以获得play函数后面的songID,根据songID可以去xml文件中请求歌曲信息,所以又有了另一个函数:

songInfo Dialog::GetInfofromID(QString songID)
{
QString URL=QString("http://www.xiami.com/widget/xml-single/sid/%1").arg(songID);
QString XML=GetSourcefromURL(URL,"");//从网址获得XML信息
//需要cookie的情况
if(XML.startsWith("<script>"))
{
int startIndex=XML.indexOf("document.cookie=\"")+17;
int endIndex=XML.indexOf("\"",startIndex);
QString cookie=XML.mid(startIndex,endIndex-startIndex);
XML=GetSourcefromURL(URL,cookie);
}
//解析xml
QXmlStreamReader xml(XML);
songInfo thisSong;
while (!xml.atEnd())
{
xml.readNext();
if (xml.tokenType() == QXmlStreamReader::StartElement)
{
if (xml.name() == "location")
thisSong.location=xml.readElementText();
if (xml.name() == "song_name")
thisSong.songName=xml.readElementText();
if (xml.name() == "album_name")
thisSong.album_name=xml.readElementText();
if (xml.name() == "artist_name")
thisSong.artist=xml.readElementText();
}
}
thisSong.songID=songID;
return thisSong;
}


传入虾米音乐的id号,返回一个结构体,获得更多数据,比如专辑名歌曲名歌手下载地址什么的。有些人可能会注意到为什么要判断<script>标签,我来告诉你,某些情况下,频繁的请求虾米音乐的xml会导致返回一个页面,对于浏览器来说,这个页面里的脚本就是设置cookie然后带着cookie刷新。咱们制作的Qt程序没有那么智能,就得手工判断,然后伪造cookie。

正常请求XML,每次请求得到的location都不一样:




 




当使用接口请求多了就会出现最后一张图的情况,这时候就需要我们手动提取sec=一大串那个cookie,然后带着cookie再来一遍。

请求道location并解密的时候,软件差不多主体功能就完事了,为了方便,我又加上了下载功能,因为虾米给的文件名惨不忍睹比如下面这样:



我只想说这种连接扔迅雷里肯定不知道是啥歌,所以得自带一个下载模块,就是这个下载模块,又坑我好几天。

手写了一个非阻塞下载的类QHttpDownload,QNetworkAccessManager的销毁让我很费解,本着C++new了就要delete的好习惯果断增加析构函数,然后delete。结果程序运行完了直接崩溃,段错误。初步怀疑是下载代码有问题,排查过后才发现如果一个QOBJECT有父对象,那么它的销毁由父对象来做,自己delete会导致指针的重复删除。排查解决这个错误又是好几天,期间学习了很多东西,比如Qt的信号和槽,还有Qt对象的内存管理等等。

class QHttpDownload : public QObject
{
Q_OBJECT
public:
explicit QHttpDownload(QObject *parent = 0);
bool startDownload(QString url,QString fileName);
private:
QNetworkAccessManager *manager;
QNetworkReply *reply;
QUrl url;   //存储网络地址
QFile *file;  //文件指针
QMutex lock;//下载任务锁
signals:
void dataProgress(qint64 recv,qint64 total);//进度条外放信号
void finishDownload();//下载完成
private slots:
void httpFinished();//完成下载后的处理
void httpReadyRead();//接收到数据时的处理
};
贴一张软件完工的照片,有时间我会把他上传到我的csdn里,也能帮助一下后续做这种软件的人。



PS:开发环境Qt 5.3+Win7 64bit,经过静态编译,14MB的exe,Linux和Mac应该也能用,需要自行编译对应版本。

源码及程序下载地址:http://download.csdn.net/detail/zjyl_1994/8086383

Have a nice day!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  qt5 音乐 解密 源码 c++