Blog外挂之:妙用del.icio.us实现“站内相关文章”
By 刘未鹏(pongba)
C++的罗浮宫(http://blog.csdn.net/pongba)
先说明一下,其实这个方法是跟Blog无关的,只要支持插入javascript代码的blog都可以使用。
废话少说,翠花,上图片先:
图片截取自:月光博客
<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 414pt; HEIGHT: 252.75pt" type="#_x0000_t75"><imagedata src="file:///C:%5CDOCUME~1%5Cpongba%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image001.jpg" o:title="ra_demo"></imagedata></shape>
图片截取自:ThinkingParallel
<shape id="_x0000_i1026" style="WIDTH: 382.5pt; HEIGHT: 276.75pt" type="#_x0000_t75"><imagedata src="file:///C:%5CDOCUME~1%5Cpongba%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image002.jpg" o:title="ra_demo1"></imagedata></shape>
<shape id="_x0000_i1027" style="WIDTH: 239.25pt; HEIGHT: 246pt" type="#_x0000_t75"><imagedata src="file:///C:%5CDOCUME~1%5Cpongba%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image003.jpg" o:title="ra_demo2"></imagedata></shape>
是不是很眼馋这些功能?站内相关文章,文章排行,文章评价,文章相关tags,以及全局tags。
所有这些功能的最大好处不是漂亮,而是实用。其实用之处在于能够增加blog的粘着度。一般来说如果是一个简陋的blog,如果别人从一个文章链接跳到你的blog上看了一篇文章,那么很可能看完就走了,但如果能在文章的正下方跟着显示相关文章的话,粘着度就会得到大大的提高。相关文章其实就是简单的关联挖掘,而后者是所有网上营销类网站的最大法宝之一(有人见过不用“读(买)过…的人也读(买)过…”的网上书店吗?)。
可惜的是,除了很少一些独立blog具有这个功能之外,国内外的博客系统目前基本都没有这个功能,似乎就连最好的免费blog系统wordpress也还没有增加这个功能。
CSDN blog 06年的时候还有过一阵子的“相关文章”栏的,如图:
<shape id="_x0000_i1028" style="WIDTH: 413.25pt; HEIGHT: 137.25pt" type="#_x0000_t75"><imagedata src="file:///C:%5CDOCUME~1%5Cpongba%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image004.jpg" o:title="ra_demo4"></imagedata></shape>
可后来广告栏上去了,相关文章栏消失了。不过CSDN blog的广告栏还算是做得蛮有心的,首先不像live space的广告栏那样不尊重用户,弄一个花花绿绿还闪啊闪的Flash大摇大摆的放在blog主页的正上方,跟人家把脑白金广告贴到你家门匾上一样,丑就不说了,太影响阅读情绪。Donews曾经仿效了一次,结果被群起而讨伐之,只好又灰溜溜改回去。其实BSP运营不容易大家也都有数,像新浪blog这样的毕竟在少数。所以放点广告大家也能理解,但所谓盗亦有道,放广告也要讲技巧。我最欣赏的就是Gmail的广告投放,根据email的内容进行关联挖掘,投放最可能对你有用的广告,这甚至已经不能算是一种广告,简直是双赢了。而且文字广告不那么扎眼,较之图片广告看起来要舒服很多;这也是我抛弃hotmail和yahoo mail的原因。CSDN blog的广告栏总算比较人道,不乱投垃圾,不乱放图片,也用上了关联挖掘;但之所以提这个广告栏,最重要的一点还是它很有用,待会你就会知道。
再来说06年还在的那个“相关文章”栏,我不大清楚CSDN为何把这个栏目撤掉,这是个能增加blog粘着度的功能。但最重要的一点是,原先CSDN blog上的“相关文章”栏的“相关”是指全站的相关文章。而不是你自己blog上的相关文章。这就让人很不爽,并不是说全站相关不好,只是应该再增加一个“本站相关文章”才是。
那是不是就没办法了?老大们都是程序员吧,程序员会干嘛?不,不是指泡MM,是编码。所以现在不管怎样,我们还是需要自己实现一个“站内相关文章”功能出来。
我对web开发基本可以说是七窍通了六窍,以前没写过一行Javascript代码,没写过一个网页。大一的时候玩过一阵子flash,早已全还掉了。但web盲归web盲,要实现这个功能,大致思路还是可以想出来的。
CSDN blog不知道有没有开放获取文章tag以及根据tag获取文章的JSON接口,就算开放了也要精确到个人blog才行,就算精确到了个人blog也还有修改tag不方便等问题。所以不管怎样,依赖CSDN blog开放接口是不大可能了。
上次写到如何用del.icio.us给blog加上tags,那个比较简单,只要用del.icio.us现成的tagrolls功能即可。这次仍然还是用del.icio.us的功能,不过这次del.icio.us没有提供现成的功能,要自己根据它开放的JSON接口来实现。
实现耗了一些时间,没办法,谁叫俺不是web开发出身呢?只好乖乖拉出犀牛书,架上google,“边搜索边编程”:)
“本站相关文章”1.0
忙活了几个小时,总算搞定了一个1.0版,顺便也了解了一下JSON和del.icio.us的开发接口,还学了不少javascript。总算也有点了解为什么有那么多人鼓吹动态语言了——上手很快,很直观灵活。另外感觉反射特性(reflection)对web开发实在是很重要。
1.0版实现的效果如下
<shape id="_x0000_i1029" style="WIDTH: 372pt; HEIGHT: 439.5pt" type="#_x0000_t75"><imagedata src="file:///C:%5CDOCUME~1%5Cpongba%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image005.jpg" o:title="ra1"></imagedata></shape>
为什么这个“本站相关文章”会出现在“特别推荐”的上面,等会解释。先来说说这个实现的问题。
我假设你已经像上篇里面说的,将你blog上的所有文章链接导入到一个专门的del.icio.us帐号上了,比如我的。这样一来这个del.icio.us帐号便存储了你的blog上所有文章及其所属tags的信息,这些信息已经足够用了,关键是怎么提取出来。
在最初实现这个功能的时候,本想用URL feed来获取当前url(document.URL)的相关信息,然后从其中抽取出相关的tags,然后再使用posts feed来获取与这些tag相关的posts,便大功告成。
谁知道算盘打得响当当的,实现起来就处处问题了,首先的问题就是URL feed只能获取一个url在del.icio.us全站上的tags,而且还是top tags,如果你的文章被别人save得不多,那么就算你给它加了十个tag,最后返回的tag集合也有可能是空的,因为它返回的是top tags。我不知道del.icio.us是怎么计算top tags的,可能有一个最少被save数目加一个百分比,但无论如何,这个办法是没法获取到你给你的一篇文章加上的所有tags的。没法获取其所有tags,便没法很好地获取与其相关的文章了。
硬着头皮试了一下,能获取到的tags极少,而且还可能是人家标的,不是你自己标的。只好放弃,另辟他径。
一拍脑袋方才想起,del.icio.us的posts feed是能够获取所有posts的详细信息的,获取到的是一个数组,其中每个元素都是一个posts的相关信息集合,后者包括它的链接、标题、注释、tags。这下问题不就结了?只要遍历一下这个数组,把当前文章url往里面一个个的比,直到找到代表当前文章的那个JSON对象,提取其中的tags。然后用这些tags再往里面作一次相关搜索,搜索出相关的JSON对象,提取出他们的标题和链接,done!
目前的版本(2.0+)采用的也正是这个办法。
改进
现在的问题是“相关搜索”如何做。我们知道,一篇文章可能有多个tags,那么怎么判定另一篇文章是和它相关的呢?一个最简单的办法是取该文章的第一个tag(这依赖于你在导入文章链接的时候将其第一个tag设为主tag),然后找出所有包含该tag的其它文章。这个办法是可行的,也是上面展示的1.0版采用的办法。
但这个办法有一个大问题,就是它依赖于你对任意一篇文章所设的第一个tag必须是最能反映这篇文章内容的tag,不够自动化,我们平常给文章设tags的时候一般都是松散的,哪考虑那么多,结果为此就要去del.icio.us上调整tag顺序,很麻烦。此外的一个问题是,和该文章亲缘关系较远的文章就显示不出来了。比如一篇boost源码剖析,它既属于boost又属于GP,还属于C++。那如果选”boost”为它的主tag,那么就只能显示含”boost” tag的文章了,如果这些文章较少,只有聊聊几篇,而这时你想退而求其次显示一些GP的怎么办呢?
另一个问题是1.0版的选择相关文章的算法是从头到尾遍历的,而你选出来的相关文章数目又必须设一个上限,否则遇到大类的时候就会拉出长长一串来,不好看,噪音也太大。这就导致一些比较久远的文章可能总是不会被选到,你可能不希望看到这种情况,因为文章年代久远并不一定代表它就不好看了:-) 这个问题易于解决,只要引入一个随机选择过程即可——先找出所有相关的文章,然后随机选取maxSize个(maxSize是你的“本站相关文章”框的大小限制)。
亲缘关系的判定则要麻烦一些。如果不依赖于用户指定第一个tag为主tag的话,又该如何判定一个tag跟当前文章的主题亲缘关系有多紧密呢?
理想的设想应该是这样的,对于一篇特定的文章,我们想首先找出最能反映它的主题的tag,然后找出所有包含该tag的所有其它文章,显示出来。如果这时候还没达到maxSize个,我们就选一个次tag,接着显示,以此类推…
那么,如何判定一个tag与特定文章的“相关性”便成了最大的问题。以上的问题就是2.0版要解决的问题了。
“本站相关文章”2.0
2.0判断tag相关性依赖于一个简单的观察:即在与一个文章有关的多个tag中,那些总体被使用最少的tag一般就是最specific的,次少的则是次specific的,以此类推。这是因为我们平常对一篇文章分类的时候一般是树状的,越往树顶去的tag越大,同时也越泛。而越往枝叶部分去的tag越小,也越specific。所以,如果将一篇文章的所有tags按从小到大排序,那么遍历过去就基本相当于在tag-tree上从叶节点往根节点移动的过程。
基于这个想法修改了一些代码。效果不错,如下:
这是一篇《读古龙的岁月》系列文章的相关文章列表:
<shape id="_x0000_i1030" style="WIDTH: 344.25pt; HEIGHT: 306pt" type="#_x0000_t75"><imagedata src="file:///C:%5CDOCUME~1%5Cpongba%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image006.jpg" o:title="ra2"></imagedata></shape>
注意,显示在最上面的是关联最紧密的tag和文章,然后亲缘关系逐步疏远。直到满12篇(我设的上限(maxSize)是12)为止。此外,我把通过同一个tag找到的相关文章分了组,并在前面加上了该组的tag,这样使得结构更分明,信噪比也更大一些。
一个要注意的小问题是要避免不同组之间显示的文章的重复。
“本站相关文章”2.1
“本站相关文章”2.1主要添加了一个小功能,就是显示“本文相关tags”,如图:
此外2.1的另一个小修正就是当别人从一个comments链接打开一篇文章的时候能够正确处理。因为这时document.URL并不是你收藏的那个URL,而是后面加上了”#comments”,于是你就得把这个尾巴去掉才行。
为什么链接放在“特别推荐”广告栏的上方
简单的答案是“找不到其它合适的地方好放”。如果你查看一下你的CSDN blog的页面源代码,你会发现有”id”属性的div不是很多。其实最理想的地方是紧跟在你发表的文章的结尾另起一行,但因为发表文章的div块没有id,而通过编号来定位的方法又过于fragile,所以只好退而求其次。
广告栏的div是有id的,名为”csdn_zhaig_ad_yahoo”,之所以有id是因为CSDN本身也要用javascript往这个div里面写内容,所以不妨“复用”一下这个div,把我们的内容也写到里面。幸运的是,这个div的位置刚刚好在文章的正下方。呵呵,希望CSDN不会修改广告栏div的位置,否则只好往comments那个div里面写了:-)
其实不写在这个div里面也可以,你可以写到侧边栏上,不过这在用户行为学上不是最佳的,别人看你文章的时候可能只会一路往下扫着看,一般不会去注意侧边栏的。
CSDN Blog的javascript代码
在写javascript代码的时候也顺便看了一下CSDN Blog本身是如何填充那个广告div的,结果吓了一跳:
document.getElementById("csdn_zhaig_ad_yahoo").innerHTML=outhtml;
幸好main那个div比rightmenu那个div加载得早,而我们的javascript代码放在rightmenu里面。这要是反过来,我们辛辛苦苦写的内容就得生生给覆盖掉了。
其实直接修改innerHTML的这种方式并不是最佳的写页面方式(也不是最厚道的:P)。再看看这个:
document.write('<script language = "JavaScript1.1" src='+str+"><\/script>");
汗,好大一个document.write。这是最不推荐使用的写方式了,而且还是直接写HTML代码…
俺虽然土,只接触web编程一下午,犀牛书基本从索引翻起,那也是知道应该用:
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', calledURL);
ct. appendChild (script);
这种方式,不硬编码,不出现raw string,杀人不见血,实在是居家旅行,必备之良药…
应用到非CSDN Blog上
其实这个方法是跟Blog无关的,只要支持插入javascript代码的blog都可以使用,当然,另一个前提是你得找到能够插入相关文章链接的div才行,比如CSDN Blog就比较厚道,刚好提供了一个广告div:-) 不过你可别像上面给出的代码那样直接写innerHTML,don’t be evil:-) 把人家广告覆盖掉了也不够厚道是不是?人家对你厚道你总得投桃报李吧:P
总结
我们借助del.icio.us的社会化书签功能和开放JSON接口实现了一个非常酷非常有用的“本站相关文章”系统,这个系统可以移植到任何支持插入javascript代码的blog上,并且非常灵活,只依赖于del.icio.us。其唯一的弱点是当前del.icio.us的posts feed目前只支持最多获取100个post。如果哪位老大blog上的文章实在太多的话,有两个权宜之计,一是“精选”一下你的好文章收藏到del.icio.us上,如果“精选”之后精品实在还是太多的话,呵呵,那就只好再申请一个font-size: 12pt; font-family