虾米音乐解析器Qt版
2014-10-27 13:17
615 查看
比较喜欢虾米的精选集,很多精选集工作的时候听起来特别有感觉。经常带着电脑去教室干活,有何没有网,市面上的下载器大部分在虾米改版之后都挂掉了,所以做个下载器把喜欢的歌拖下来听成为了很重要的需求。百度原理以后发现不难,做起来却是异常艰辛。没玩过多少Qt的我基本上是从新学了一遍,还顺道学习了一下burpsuite抓包,折腾了六天终于是把这个东西弄出来了。
(直接能用的exe文件可以看http://download.csdn.net/detail/zjyl_1994/8086895,已打包所缺失的mingw运行库,安装过开发环境的可以直接去下源码包,那个比较小。)
先上一张完工图,(PS:前一阵虾米改版之后LongkeyMusic就失效了)
虾米的加密算法卡了我两天,初期做出来的解密程序解码部分location的时候还是会出现乱码。静下心来发现是解密思路错了,需要知道余出来几个字母然后他们是矩阵大小+1的长度,其他的是矩阵长度。说多的也看不懂,上代码能更好地理解:
还有一个最重要的部分就是如何获取网页的源码,我在网上找的代码,都只能单纯的访问,而虾米音乐的页面可以随便访问,xml页面有时候就需要带cookie,翻遍国内网也没有怎么带cookie获取源码的,所以花时间自己改造了一下:
从试听页面可以获得play函数后面的songID,根据songID可以去xml文件中请求歌曲信息,所以又有了另一个函数:
传入虾米音乐的id号,返回一个结构体,获得更多数据,比如专辑名歌曲名歌手下载地址什么的。有些人可能会注意到为什么要判断<script>标签,我来告诉你,某些情况下,频繁的请求虾米音乐的xml会导致返回一个页面,对于浏览器来说,这个页面里的脚本就是设置cookie然后带着cookie刷新。咱们制作的Qt程序没有那么智能,就得手工判断,然后伪造cookie。
正常请求XML,每次请求得到的location都不一样:
当使用接口请求多了就会出现最后一张图的情况,这时候就需要我们手动提取sec=一大串那个cookie,然后带着cookie再来一遍。
请求道location并解密的时候,软件差不多主体功能就完事了,为了方便,我又加上了下载功能,因为虾米给的文件名惨不忍睹比如下面这样:
我只想说这种连接扔迅雷里肯定不知道是啥歌,所以得自带一个下载模块,就是这个下载模块,又坑我好几天。
手写了一个非阻塞下载的类QHttpDownload,QNetworkAccessManager的销毁让我很费解,本着C++new了就要delete的好习惯果断增加析构函数,然后delete。结果程序运行完了直接崩溃,段错误。初步怀疑是下载代码有问题,排查过后才发现如果一个QOBJECT有父对象,那么它的销毁由父对象来做,自己delete会导致指针的重复删除。排查解决这个错误又是好几天,期间学习了很多东西,比如Qt的信号和槽,还有Qt对象的内存管理等等。
PS:开发环境Qt 5.3+Win7 64bit,经过静态编译,14MB的exe,Linux和Mac应该也能用,需要自行编译对应版本。
源码及程序下载地址:http://download.csdn.net/detail/zjyl_1994/8086383
Have a nice day!
(直接能用的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!
相关文章推荐
- 关于Qt-json解析器
- Qt自带json解析器————未了情
- skyeye 1.2.4模拟s3c2410运行qt-embedded的步骤
- 转:QT实现阴影窗口(三)
- QT QTableWidget 用法总结【转】
- Qt样式表的使用
- Qt程序创建菜单
- QT QPlainTextEdit 获取某行内容
- Linux下编译并使用Qt第三方图形控件qwt(Ubuntu12.04 + Qt4.8.1 + qwt6.1.2)
- QT自绘最小化最大化拖动
- QT5.5移植到ARM全攻略
- Qt中左侧列表与右侧窗口关联
- qt5 + mySql 安装
- Qt学习(1)——Qt第一行代码
- 嵌入式Linux用Qt Designer快速开发[转]
- QT平台上的Json解析
- QT学习记录3
- EasyUi-Parser(解析器)
- 〖Linux〗Qt5.2.0+gsoap开发Android的NDK程序遇到错误的解决
- QT 项目文件介绍