2012年5月20日星期日

通过友情链接进行博客Feed的搜集,你的博客收录了吗

 
 

satan 通过 Google 阅读器发送给您的内容:

 
 

于 12-5-11 通过 Xiaoxia[PG] 作者:Xiaoxia

很久没有发一些有技术含量的文章了,最近发博文都有一种应付式的感觉,真对不起自己。感觉有时候是我沉醉于一样东西太长时间了,把我正常的生活节奏都打乱了,而却没有注意到这样子反而效率很低下。适时抽时间出来总结一下是蛮重要的!所以,以后决定每天都抽一个小时出来自我总结,觉得有所感想就写下来,有技术研究的,就给大家分享一下吧!

这两天在写下面的一个东西,用来搜集博客种子(Feed)的RSS或者Atom地址的。没有种子的博客不会被收录进来。因为只有Feed才对我有用!

网站地址:http://feed.readself.com/


我的需求

因为我最近在做一个读书网站,需要从互联网博客频道中获取文章。之前通过人工添加了将近200个博客的Feed地址,定期更新以获取文章。如今阅读的基本功能已经完成,现在亟需的是大量的文章以供各种实验性的测试,例如文章分类,全文索引等,属于数据挖掘和模式匹配的范畴。

之前已经在构思用何种方式搜集互联网上的Feed来源,李劼杰同学的"中国个人博客索引"给了我很大的灵感。采用博客的"友情链接"的方式进行扩散搜索,而我仿效其方法采用BFS搜集博客的Feed。虽然我们俩做的是不同的方向,但是没想到这么巧合,他所分享的技术东西正是我所要研究的。

话说回来,我要采集的是博客的Feed地址,如何找呢?

寻找Feed地址

最近研究得出的方法是,从博客页面(可以是首页或者是文章页面)的HTML中,遍历所有link标签,采集如下的href属性。

<link rel="alternate" type="application/rss+xml" title="Xiaoxia[PG] &raquo; Feed" href="http://xiaoxia.org/feed/" />

大部分博客的Feed都是按照上面的代码提供,RSS是其中一种聚合类型,还有另外一种常见的是Atom。而常见的RSS还有两种不同的版本,当然这个不需要关心,因为我现在使用FeedParser来帮我采集RSS或者Atom里的内容(这个开源的FeedPaser在处理不正确的RSS时经常出错,需要自己修正代码)。

另外需要注意的是,有点link标签并没有 ref="alternate" 这个属性,同时还有可能把评论的Feed或者RPC的XML收录进来了。这个时候就需要加以区别了。

我目前采用一种评分的方法,即完全匹配标准格式的,打10分满分。遇到下列情况,则会被扣分:

1、没有 ref="alternate" 这个属性。

2、href中含有comment字符。

3、href中含有rpc字符。

4、href中含有rsd字符。

在对所有含有RSS或者Atom的link进行评分后,选取最高分作为Feed的地址。

另外需要注意的问题是,href不一定是完整的URL地址,有可能是相对路径,所以需要对其补全。

寻找友情链接

既然过友情链接进行扩散搜集,就需要对每个博客的友情链接李劼杰同学采用的是固定的模板匹配的方法,这种方法的限制是只能识别支持的博客的友情链接,而面对未知的博客类型,就无法提取友情链接了。所以会影响总体搜集的友情链接的数量。而我采用的方法是,基于概率统计的方式,定位友情链接的容器标签。

首先,枚举所有可以存放友情链接的容器,我取了div、table、ul这三个,当然还可能有其他的我还没有考虑到,但这三个已经涵盖了大部分的博客的情况。

对枚举的每个容器,统计所有的链接(a标签)。判断是否为站外链接,如果是,则增加链接数量,以及增加链接文字数量(为何要这个?因为图片超链接将会被排除),否则增加站内链接数。如果该站外链接为新窗口打开,则有相应的奖励,我目前是多算一次链接数量。

然后,通过上述信息统计这个容器的的分数,分数越高,成为友情链接容器的可能性越大。我的统计方法如下:

1、原始分数 = (站外链接 - 站内链接) * 1000,如果原始分数 <= 0,直接否定。

2、链接文字所占比例 = 链接文字数 / 容器总文字数,用这个比例对原始分数进行正影响。

3、链接标签所占比例 = 链接标签数 / 容器总标签数,用这个比例对原始分数进行正影响。

4、如果最后得分少于1000,直接否定。(这样做是因为链接数太少,不考虑)

最后,对统计的所有容器的分数进行排序,得分最高的,选取为友情链接容器。

统计过程中,我所提及的正影响采用了下面的公式:

输出分数 = 输入分数  * 保持比例 + 输入分数 * (1 - 保持比例) * pow(输入比例,影响力)

为了方便大家理解,直接上代码:

 def affect(points, keep_ratio, ratio, power):     keep = points * keep_ratio     if ratio >= 1.: return points     return keep + (points - keep) * pow(ratio, power)  def calc_link_points(host, ul):     # simplified host 不要子域名部分!     parts = host.split('.')     if parts[-2] in ('com','edu','net','gov','org'):         host = '.'.join(host.split('.')[-3:])     else:         host = '.'.join(host.split('.')[-2:])      link_density = linktext_count = totaltext_count = 0.001     container_count = innerlink_count = 0.001     for a in ul.findAll('a'):         href = a.get('href', '')         # 内部链接         if not href or not href.lower().startswith('http') or host in href:             innerlink_count += 1             continue         # 层次太深         if urlparse(href)[2].strip('/').count('/') >= 1 or '?' in href:             continue         link_density += 1         linktext_count += len(a.text)         if '_blank' == a.get('target'):             link_density += 1     # 统计容器字数     for t in ul.recursiveChildGenerator():         if type(t) is NavigableString:             totaltext_count += len(t)         else:             container_count += 1     points = (link_density - innerlink_count) * 1000     if points < 0: return 0      points = affect(points, 0.1, linktext_count / totaltext_count, 2.)     points = affect(points, 0.1, link_density / container_count, 1.)      if points < 1000: points = 0     return points

测试运行,提取 http://www.lijiejie.com 的友情链接。

root@xiaoxia-pc:~/project/reader/test# python urls.py http://www.lijiejie.com
Fetching http://www.lijiejie.com
Parsing 36827 bytes
Title 李劼杰的博客
Found feed http://www.lijiejie.com/index.php/feed/
12856.8536983 ul
0 div
0 div
0 div
......

Found links 11
http://www.68flash.com/ 68flash
http://hi.baidu.com/d7hack D7Hack
http://www.rrgod.com/ Eddy Blog
http://hi.baidu.com/15108971 hu1s4
http://hi.baidu.com/282635791/ Xch40
http://xiaoxia.org/ Xiaoxia[PG]
http://www.fachun.net 中国博客索引
http://chinahacker.blog.163.com/ 叱诧冰子
http://www.lijiejie.cn 旺园阅览室
http://haipo.me/ 杨海坡
http://timepw.com 连文井

友情链接的容器是一个ul,得分很高。其他容器直接被否决了,没有分数出来。

下面测试 某熊 的 typecho,

root@xiaoxia-pc:~/project/reader/test# python urls.py http://44670.org/
Fetching http://44670.org/
Parsing 11802 bytes
Title 某熊[PG]
Found feed http://44670.org/index.php/feed/atom/
1649.51073438 ul
1363.34619279 div
0 div
0 div
0 ul
0 ul
......
Found links 7
http://sxkdz.0ginr.com/blog/ SXKDZ
http://twd2.me 万呆博客
http://iceboy.org/ iceboy[PG]
http://whitefirer.org whitefirer[PG]
http://hi.baidu.com/vi_orz vienna
http://pm.mafom.com/ 愤怒的泡面
http://newbiecoder.0ginr.com/blog NewbieCoder

有一个ul和一个div的分数很接近,为什么呢?因为ul是被那个div包含的,而div多了一些额外标签和文字,所以分数没有ul那么高。

对于常见的wordpress,zblog,pjblog等都能够准确定位友情链接的区域,那么非常用博客呢?例如 simple-is-better.com(python.cn),

root@xiaoxia-pc:~/project/reader/test# python urls.py http://simple-is-better.com/
Fetching http://simple-is-better.com/
Parsing 36281 bytes
Title python.cn(news, jobs)
Found feed http://feed.feedsky.com/simple-is-better
Abstract 200
34359.9883395 ul
28956.1843727 div
8202.2453766 div
0 div
0 div
0 div
0 div
0 div
......
Found links 26

http://hi.baidu.com/limodou/ limodou 的 Blog
http://www.chenxiaoyu.org/ Smallfish 鱼哥
http://techparty.org/ 珠三角技术沙龙
http://www.autopart007.com/ China auto parts supplies
http://www.mvmap.com/ 名城指南
......

这个34359分的ul和28956分的div,无论选哪个,都是成功定位到友情链接,毫无压力!

写到这里,我的Feed爬虫(8个进程)已经使用这套方法,工作了9个多小时,搜集2万多个博客的Feed地址 :) 因为burst的VPS内存才512MB,比较小,所以开8个进程就足够,还有留下内存跑mysql呢。如果是在一台性能好一点的机器上,开100多个进程同时抓取,不到1个小时就能抓2万个博客的Feed了。

提取网页摘要

对于网页摘要的提取,我没有花太多心思在这个上面。网页摘要是给 http://feed.readself.com 展示用的,对于我来说,没有太大的意义。但是简略的做了一个算法,提取文字最密集,超链接数目最小的容器标签,取其文本的前100个字。其中,用容器的超链接数目与网页总超链接数目的比例对统计文字得到分数进行负影响。我对文字进行了utf8编码是为了增加中文的权重,即一个中文字的权重等价于3个英文字符。

大致代码如下:

def find_text(body):     candidates = []     total_links = len(body.findAll('a')) + 0.001     # 枚举文字容器     for tag in ('div', 'section', 'article', 'td', 'li', 'dd', 'dt'):         for x in body.findAll(tag):             if type(x) is not Tag: continue             points = len(x.text[:100].encode('utf8')) * 1000             points = affect(points, 0.1, 1 - len(x.findAll('a')) * 1. / total_links, 1.)             candidates.append((points, x))     # 排序,取分数最高的容器     candidates.sort(reverse = True)     if candidates:         return candidates[0][1]     return body

例如,对雄哥的博客进行提取网页摘要,

root@xiaoxia-pc:~/project/reader/test# python urls.py http://ibaiyang.org
Fetching http://ibaiyang.org
Parsing 44596 bytes
Title 白 杨
Found feed http://www.ibaiyang.org/feed/
Abstract 在看这边文章前,你还记得你班主任的名字么?一身中,我们或许永远记住那些曾经影响我们命运的人,老师当然在其中,而有如今的我,也正是感谢这些老师的关怀和肯定。昨天晚上,我一个朋友告诉我,他今天和我们初中班

这种方法提取到的网页摘要,通常是一篇文章的正文内容,对大量的中文网页采集的结果来看,效果很不错!但是对英文网页来看,还需要进一步的改进。

夜已深,对于我目前搜集Feed的技巧就介绍到这里!主要是想跟大家交流一下,因为我在这方面的经验还不多,相当于刚入门,所以希望有人站出来,给一些意见或者一些好的提议 :)


 
 

可从此处完成的操作:

 
 

没有评论:

发表评论