模板的定义
QQ 阅读预先定义了一些模板,有哪些模板可以在下面数组中可见:
其中 tmp后面的第一个数据标识这个模板分几块,第二个数字标识这是分几块模板的第几套模板
这个模板在下面文件的 15004 行可见
http://rescdn.qqmail.com/rss/zh_CN/htmledition/js/reader/reader069eec.js
var
adw={1:['tmp1_1'],2:['tmp2_1'],3:['tmp3_1'],4:['tmp4_1','tmp4_2','tmp4_3'],5:['tmp5_1','tmp5_2','tmp5_3','tmp5_4','tmp5_5','tmp5_6'],6:['tmp6_1'],7:['tmp7_1']},
这个js的文件加载比较特殊,在QQ阅读的页面
是通过下面代码加载的:
<script>function loadJsFile(_aoFiles)
{var _oHead = document.getElementsByTagName("head")[0];for (var i=0, _len=_aoFiles.length; i<_len; i++){var s = document.createElement("script");s.src = _aoFiles[i];s.charset = "GB18030";
_oHead.appendChild(s);}}if (top == self){loadJsFile(["/cgi-bin/rss_main?sid=MTOtOYx56MMzN9&t=reader_data.js&s=mag&r=388&init=true&update=1",
"http://rescdn.qqmail.com/rss/zh_CN/htmledition/js/reader/reader069eec.js"
]);}else
{//for webqq2
document.write('<script src="/cgi-bin/rss_main?sid=MTOtOYx56MMzN9&t=reader_data.js&s=mag&r=464&init=true&update=1"></scr'+'ipt>');document.write('<script src="http://rescdn.qqmail.com/rss/zh_CN/htmledition/js/reader/reader069eec.js"></scr'+'ipt>');}</script>
每套模板的样式文件我们可以在对应的 qboard 开头的CSS中定义,不同宽度的屏幕是不同的css:
<script>gbISbigScreen = false;
if(screen.width>1279&&screen.height>899){
document.write('<link rel="stylesheet" type="text/css" href="http://rescdn.qqmail.com/rss/zh_CN/htmledition/style/reader/r_bigscreen02aad7.css" />');document.write('<link rel="stylesheet" type="text/css" href="http://rescdn.qqmail.com/rss/zh_CN/htmledition/style/reader/qboard_bigscreen02aad7.css" />');gbISbigScreen = true;
}else
{document.write('<link rel="stylesheet" type="text/css" href="http://rescdn.qqmail.com/rss/zh_CN/htmledition/style/reader/qboard02aad7.css" />');}if (top != self) {document.write('<link rel="stylesheet" type="text/css" href="http://rescdn.qqmail.com/rss/zh_CN/htmledition/style/reader/r_webqq02aad7.css" />')}</script>
以模板3_1 为例,,它的样式如下: tmp3 这里的三标识他内部有三个区块, box0,box1,box2 分别就是这三个区块:
每个区块都可能有 图(articleimg )也可能有内容(singlecontent )。
/* 模板3_1 b */
.tmp3_1 .box0 {width:600px; border-right:1px solid #D9D9D9; height:535px; overflow:hidden; float:left;}.tmp3_1 .box0 .singlecontent {margin:0 10px 0 0; height:530px; overflow:hidden;}.tmp3_1 .box0 .articleimg {width:590px; height:330px; overflow:hidden;}.tmp3_1 .box0 .articleimg img {width:590px}.tmp3_1 .box1 {width:469px; height:265px; border-bottom:1px solid #D9D9D9; overflow:hidden; float:left;}.tmp3_1 .box1 .singlecontent {margin:0 0 10px 10px; height:235px; overflow:hidden;}.tmp3_1 .box1 .articleimg {width:250px; height:180px; overflow:hidden; margin-right:10px; float:left;}.tmp3_1 .box1 .articleimg img {width:250px}.tmp3_1 .box2 {width:469px; height:269px; overflow:hidden; float:left;}.tmp3_1 .box2 .singlecontent {margin:10px 0 0 10px; height:235px; overflow:hidden;}.tmp3_1 .box2 .articleimg {width:250px; height:180px; overflow:hidden; margin-right:10px; float:left;}.tmp3_1 .box2 .articleimg img {width:250px}
获取数据
这个入口页的数据是通过下面的网址获得的。它返回的是一个 json 数据
这个页面的数据是通过下面网址获得的数据,返回的数据也是 json 格式的,注意这里一次性取了30篇文章的摘要等列表信息。
30篇文章最终会被分成7页。
这些数据中包含文章的标题、摘要,包含的图片(数量地址尺寸等), 视频,音乐,评论分享等。
http://reader.qq.com/cgi-bin/rss_list?r=13098493385&pagetype=mag&page=0&perpage=30&mode=digest&cache=save&id=10005&sid=MTOtOYwpxMMzN9&t=reader_data.js&s=art&resp_charset=UTF8最终内容页,是通过下面网址获得的数据,注意,这里一次性获得了30篇文章。
http://reader.qq.com/cgi-bin/rss_list?r=13098433171&pagetype=mag&page=0&perpage=30&mode=detail&cache=load&id=10005&sid=MTOOYwpx56MMzN9&t=reader_data.js&s=art&resp_charset=UTF8
模板匹配算法
前面提到, 从服务器端下载30篇文章的信息, 然后分成7页。
那么第一页一共几篇文章,第二页一共几篇文章是如何算的呢?
- 先假设我们取的数据可以分7页。混淆后代码的 bac 这个变量记录这个信息
- 我们开始遍历下载下来的文章(应该是在30篇以内的,含30).我们根据一定算法计算累计文章的权重,一旦权重合计超过270,就分页。这样,我们就可以知道每一页应该放几篇文章,分别是哪几篇文章。这个算法的代码如下:代码我增加了一下注释,标明权重的算法。
- 我们把分页逻辑存入 OV 这个混淆过的变量中。后面的显示,以及实际是分了几页,都是基于这个对象。
- 一页显示几篇文章,就限定了该页可用的模板数。然后我们根据一个简单的算法,就可以算出该用哪个模板。如下代码:
上面算法的代码注释部分:代码是用的QQ阅读加密后的代码,其中注释是我分析后加的
// 模板算法函数
// 传入参数
// ve 需要处理的文章项ID集合,
// at 类型, 这个算法中,跟at没有关系。
// 这里剔除了一些跟算法无关的代码,以变更易读
bSV: function (ve, at) {
var ad = this,aQ = ve.length,
// 如果逻辑对象无初始值或者其值为 0、-0、null、""、false、undefined 或者 NaN,
// 那么对象的值为 false。否则,其值为 true
// 我们的模板,最多分成了几块
// 除非 at 参数是数组,否则我们最多按照分7页的逻辑来进行分页处理
ih=at||{},bac= ih.maxCount||7,diO= ih.type=="comment"?5:ih.minCount||2, // 没地方用它OV = [],Np = 0,qg = 150,EZ,acm;// 获得文章列表各项数据对象的集合
var aOO = ad.dataCenter_().getArtsById(null, ve);var aby = {};
// 遍历所有文章
while (Np < aQ) {
var gF = Np, // gF 递增 对应每一条文章顺序号, 从 1 到 30 , 每次加一RR = 0,hc = [],gV = 0, // 记录我们当前选用的模板会是分成几块的。
alS = false,
bYg = false;
// 遍历所有待处理文章, 并且最多分7页
while (gF < aQ && gV < bac) {
// 文章ID最后 (5-文章ID编号中下划线的个数)位数字
var bsV = Number(ve[gF].split("_").join("").substring(ve[gF].length - 5), 10);var dF = aOO[gF];
if (dF.unread == 1) {
alS = true;
}var aKc = 0;
if (dF.images && dF.images.width > qg) {
// 有图片,且图片宽度大于 150, 则权重加50
bYg = true;
aKc += 50;}else {
// 没有图,权重 20
aKc += 20;}RR += aKc;// 标题,正文最大长度
var aex = Math.max(dF.title.length + dF.content.length,dF.root ? (dF.root.title.length + dF.root.content.length) : 0,dF.webpage ? (dF.webpage.title.length + dF.webpage.content.length) : 0);var aPl;
if (aex > 1000) {
// 如果总内容长度超过 1000 ,权重 加 30
aPl = 30;}else {
// 如果总内容长度没有超过 1000, 权重加 10
aPl = 10;}RR += aPl;// 当前我们要把几篇文章放在一个模板中,即预计我们要把模板分几块。
gV = gF + 1 - Np;/* adw 是记录我们有几个模板的多维数组
adw={1:['tmp1_1'],2:['tmp2_1'],3:['tmp3_1'],4:['tmp4_1','tmp4_2','tmp4_3'],5:['tmp5_1','tmp5_2','tmp5_3','tmp5_4','tmp5_5','tmp5_6'],6:['tmp6_1'],7:['tmp7_1']},*/var bGl = adw[gV].length;!aby[gV] && (aby[gV] = 0); // 初始记录 几块的模板用了几次。
var bkx = (bGl - aby[gV]) * 70; // gV 块的模板如果一次用一个的话,还有几个可用。RR += bkx;bsV %= 40; // 来自服务器端的,根据文章ID产生的, 最多是40, 最小是 0 的权重,
RR += bsV;gF++;// 累计权重超过 270, 需要分页
if (RR > 270)
break;
RR -= bkx; // 显然能执行到这里,意味着模板中还要增加块,所以要剔除之前增加的这个权重。
}var arZ = false;// 如果最后一页按照这个算法,算出来权重合计小于 200, 分页数小于 3
// 我们从头过一遍已有的分页数据,发现如果某页块数+还有的文章数 《 7, 就把这些文章放入到这些页显示
// 否则这些文章单独放一个页面来显示。
if (gF == aQ && gV < 3 && RR < 200) {
for (var i = OV.length - 1; i >= 0 && !arZ; i--) {// 新的可能合并两页后的页面块数
var eQ = OV[i].len + gV;
// 如果某页块数+还有的文章数 《 7
if (eQ < bac) {
// 我们使用服务器端返回的文章编号的最后三位数字跟这个区块的模板数的模来确定我们要用的模板
acm = OV[i].articles[0].split("_").join("");
EZ = adw[eQ][Number(acm.substring(acm.length - 3), 10) % adw[eQ].length];OV[i].len = eQ;OV[i].tmp = EZ;for (var j = Np; j < Np + gV; j++) {OV[i].articles.push(ve[j]);}arZ = true;
}if (arZ)
break;
}}// 最后剩余文章不存在合并到其他页面的情况
if (!arZ) {
for (var i = Np; i < Np + gV; i++) {hc.push(ve[i]);}// 我们使用服务器端返回的文章编号的最后三位数字跟这个区块的模板数的模来确定我们要用的模板
acm = hc[gV - 1].split("_").join("");
EZ = adw[gV][Number(acm.substring(acm.length - 3), 10) % adw[gV].length];OV.push({len: gV,tmp: EZ,unread: alS,articles: hc});// aby 是一个记录 n块模板我们用了几次, 比如4块的模板我们在这次运算中用了几次。
!aby[gV] && (aby[gV] = 0);aby[gV]++;}Np += gV;}return OV;
}
参考文章:
flipboard简单分析
http://weiye.info/blog/2010/09/flipboard-implement-research/