饭否消息析取之regex vs xml

October 8th, 2008 Categories: 教程

页内导航:

批量导出饭否程序的方法很多,但是基本思路都是先将该网页保存到本地,然后将有用的饭否消息析取出来。本文不讨论如何下载饭否网页了(使用迅雷、wget、curl等),重点讨论对于下载到本地的网页,如何将有用的饭否消息析取出来。

小插曲:能否只用官方的API来获取全部饭否消息?

您或许会提议为什么不使用饭否自身的API。是的,饭否的API更快捷方便,兼容性很强。只是,饭否官方只提供下载前20条饭否消息的API。如果纯粹使用饭否官方API来下载全部饭否消息的方法也不是没有,只是很邪恶:

1
2
3
4
5
6
while (true)
{
    download 20 messages via API;
    store them;
    delete this 20 messages via API;
}

一边下载一边删除,确实总能得到全部消息。删除了前面的20条,能保证后面的20条以新消息的面目出现。这在理论上是行得通的。但是我们需要的是英雄Heroes里Peter那样无损的复制方式,而不是Sylar那样的残忍的剪切方式,呵呵。既然官方的API有限制,我们就自己动手了。请继续阅读本文。

饭否消息结构

打开一个饭否消息网页的源代码,例如本人的
http://fanfou.com/regex/p.1(其实http://fanfou.com/regex是http://fanfou.com/regex/p.1的快捷方式。这里使用完整的路径,以便体现其一般性。),观察可见,有用的饭否消息在这个框架里面:(代码较长,阅读请点击展开)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 
<ol>
    <li>
        <span class="content">
            代码非抄不能懂也。</span>
        <span class="stamp">
            <a class="time" title="2008-10-03 12:07" href="/statuses/QD6qHiqUbeE">
                2008-10-03 12:07</a>
            <span class="method">
                通过 <a href="http://del.icio.us/fanfou/API%E5%BA%94%E7%94%A8" target="_blank">
                    API</a>
            </span>
        </span>
    </li>

    <li>
        <span class="content">
            向自由的身心致敬! - 早嗷嗷也盼~晚安安也盼~望穿安安双眼~~怎知道今日里打土匪进深山自己的队伍来哎到嗷~面安前安呐啊啊啊啊啊~~~ <a href="http://fanfou.com/linkto/aHR0cDovL3d3dy5kb3ViYW4uY29tL2V2ZW50LzEwMjczNDg3Lw" target="_blank">
                http://www.douban.com/event/10273487/</a>
        </span>
        <span class="stamp">
            <a class="time" title="2008-10-06 14:07" href="/share/bd96z1U-gHw">
                2008-10-06 14:07</a>
            <span class="method">
                通过<a href="http://help.fanfou.com/share_button.html" target="_blank">
                    饭否分享</a>
            </span>
        </span>
    </li>

    <li>
        <span class="content">
            <a class="photo" href="http://fanfou.com/photo/8JsezhHM_VU">
                <img src="http://photo.fanfou.com/m0/00/19/e2_36807.jpg" alt="caixinceshi - no description" />
            </a>
            上传了新照片:caixinceshi - no description</span>
        <span class="stamp">
            <span class="time" title="2008-10-03 11:33">
                2008-10-03 11:33</span>
            <span class="method">
                通过<a href="http://help.fanfou.com/mobile_mms.html" target="_blank">
                    彩信</a>
            </span>
        </span>
    </li>
    #更多的<li>...</li>条目,每页最多20条。
</ol>

Tips:在分析饭否源代码时,饭否消息全在一行,不便于阅读。您可以拷贝所需要的代码(注意前后结构的匹配呼应)到vim中,执行:%s/>/>\r/g(将每个>后面加上一个换行符),再按ggvG全选,按=格式代码,所有的代码就成了漂亮的缩进格式,便于阅读了。


使用regex解析饭否消息

下面是使用regex来解析饭否消息的代码(直接拷贝自本人原来的perl抓饭程序。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
my $ffmsg=qr{
    <li>
        <span class="content">
            (.*?)
        </span>
        <span class="stamp">
            <a href="/(?:statuses|share)/([-_a-zA-Z0-9]{11})" class="time" title="([-: 0-9]{16})">
                [^<]+
            </a>
            <span class="method">
                通过(网页|(?:\s*<[^>]+>)[^<]+(?:</a>))
            </span>
        </span>
    </li>
' }xi;

可以看出,使用正则表达式,能够比较真实地再现原网页代码的风貌。有几处小地方需要说明一下:

  • 在第一组小括号里,我使用了([^<]+?)来捕获消息正文(一条完整的消息可以分为:消息正文;发送时间;消息uuid例如QD6qHiqUbeE,发送方法,类型(彩信还是文本))。最初是使用.*?的。但是这样不精确,有时候两条消息竟然混合在一起。而([^<]+?)捕获的是从当前位置开始至下一个<之前的所有内容。或许您会问,这不怕受到消息正文中可能出现的<的影响吗?答案是:不会受到影响。因为饭否会把所有的<以及其实有可能影响解析的字符,都转换成<的形式了,因此它不影响解析。同时,使用精确的正则表达式有助于提高效率,让不匹配的正则式尽早失败。
  • (?:statuses|share)。这条正则表达式是用来捕获饭否的uuid。它不但能捕获以普通方法发布的消息(网页、短信、手机、API、IM工具等),还能捕获由“饭否分享”工具发布的消息。我不是很喜欢饭否分享这个工具。(或许改天有时间写篇文章,揭露它的缺点?)之所以把“饭否分享”消息和普通消息分开来说,是因为两者的结构是不一样的。
  • 通过(网页|(?:\s*<[^>]+>)[^<]+(?:))这条正则式,既用了捕获型括号,又用了非捕获型括号。使用后者,能有效地避免程序太复杂,便于按序号引用($1,$2等,如果越多则越混乱,修改正则式后,更是乱成一团遭),还能节省内存(如果程序中捕获了太多的内容,而不及时释放,或许会占尽资源。毕竟不是只捕获几十字节。要考虑到饭否用户或许有近十万条的饭否消息。指的是苹果流冰这样的“万玻南痨话”)
  • xi选项:x是为了使用忽略空白字符和允许注释;i选项是忽略大小写。

使用正则表达式来析取饭否消息文本,需要考虑的细节很多。一处不细致,程序运行起来就会给你难看。饭否彩信的格式就略过不分析了。道理相同,点到为止。


使用xml解析饭否消息

再来看一下在python下,使用xml来解析饭否消息。注:该程序参考了ppippyfan程序。
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from xml.dom import minidom, Node #引人解析工具:xml小马驹!
node = minidom.parse(urllib2.urlopen("http://fanfou.com/zhasm/p.1"))
#抓取页面http://fanfou.com/zhasm/p.1 的全部内容到变量node中
l = node.getElementsByTagName("ol")[0]
#将饭否消息部分内容保存到变量l中
for c in range(0, number):
    # 时间
    if l.childNodes[c].hasAttribute("class"):
        continue
    content = (
            #时间:
            l.childNodes[c].childNodes[5].\
            firstChild.getAttribute("title"),
            #消息正文 :
            childNodes[c].childNodes[0].toxml()[22:-7],
            #uuid
            l.childNodes[c].childNodes[5].firstChild.\
            getAttribute("href")[10:]
            )

在xml文件中,前后呼应的标签,成了鲜活的特征,这些特征可以被xml解析函数很容易地辨识出来,并提取出所需内容。

  • childNodes[c].childNodes[0].toxml()[22:-7]:这条语句的意思是,对于每一条饭否消息(childNodes[c]),其消息内容的第一个节点(childNodes[0]),截取其第23字节到倒数第7字节的内容。它是指哪一段呢?其实就是每一对<span class=”content”>…</span>之间点号所示的内容。
  • 每条消息的发送时间、正文、uuid,保存在tuple中。

取得了内容之后,至于之后的煎炒烹炸,就悉听尊便了。

值得一提的是,本人在大量下载饭否消息时,不止一次遇到过饭否页面无法访问的情况。问了饭否郭万怀,答曰为了减轻服务器负载,每个IP地址下每分钟允许访问100个页面。超过此数就会自动屏蔽。我测试的结果是少于100页。比较靠谱的间隔是,每析取一页,sleep(15)。是有些慢了。没办法。当然,也有人说,执行本人以前写的抓饭程序,一次下载几百页,并没有遇到当机情况。那我只能说是您的RP高、运气好了。


两者比较

 

个人认为,xml与regex相比,有如下特点:

  • 通用性:xml具有通用性,不单单能解析饭否消息,其它符合规则的html文本,同样能够较少地改动代码,即可解析;而正则表达式则具有专用性,不能放之四海而皆准。当饭否的界面、框架有微调时,估计使用正则表达式解析的工具首先倒下。
  • 可读性:有人说perl是只写语言,regex尤甚。这是在说perl或regex代码在编写时性之所至,酣畅淋漓,执行也很高效。只是,如果代码格式混乱且无注释文档的话,隔数日、数月再读,仿佛读天书一般。而使用xml库来解析的python语言,则由于代码格式整齐,库函数见名知意,因而具有较强的可读性。这样说,总体是这样。不过我们可以尽可能把代码(即使是perl或regex的)写的整齐已读,尤其是考虑到perl支持/x选项。
  • 效率:良好编译的正则式,其执行效率应该优于xml解析。但是,使用xml能够节省编程时间;使用正则式牺牲一部分的编程时间,理论上能提高一点点效率。有兴趣的读者可以编写一段程序,循环个成千上万次,比较一下平均时间。

写到这里,对照金庸先生在《鹿鼎记》第五章:“金戈运启驱除会,玉匣书留想象间”两种武功的比较,颇有意味:
“大慈大悲千叶手”招式太多,记起来麻烦。而“八卦游龙掌”只有八八六十四式,但反复变化,尽可敌得住千叶手。那么哪一门功夫厉害些?这两门都是上乘掌法,说不上哪一门功夫厉害。谁的功夫深,用得巧妙,谁就胜了。

以本文来看,regex就相当于是大慈大悲千叶手了,需要留意的细节太多;xml方式呢,就相当于只有八八六十四式的“八卦游龙掌”。两种工具都很有用。

呼吁官方提供更多功能

离题了。这里顺便发发牢骚而已,与xml、regex无关。我不止一次地在饭否和本人blog中抱怨,使用上面这足粗笨的方法下载、解析,是最无奈的应用。最便捷的方式,应该是官方提供批量导出程序,只要执行一条数据库查询导出即可实现我们辛辛苦苦半天才能以变通的方式实现的功能。或许是饭否官方的人员都在忙着增强和美化海内吧,饭否自生自长,长时间没有更新,任凭jiwai.de、zuosa等推出一项又一项的新功能。 

扩展

本文的思路,对twitter同样适用。但是twitter越来越慢了。有一段时间好像还不支持查看历史页面。


相关阅读

验证码:BANG1F79A9FAD20225BEA7FE397AXIANGUO e8da37692b5b030cbefb9956e3bdb9cc

Tags: , , , ,

6 Responses to “饭否消息析取之regex vs xml”

  1. October 8th, 2008 at 20:23
    1

    膜拜一下,目前对curl比较感兴趣.
    XML的确强大,而且XML适用广,本人仅用XML做过小database罢了…rex真是强…

    [Reply]

  2. rex
    October 8th, 2008 at 20:30
    2

    我也很喜欢curl,之前写的抓饭程序就是
    masm32界面+perl regex解析+sqlite数据库
    的一个杂凑组合。呵呵。

    [Reply]

  3. 那个谁
    October 9th, 2008 at 13:02
    3

    Why not try XPath?

    [Reply]

  4. rex
    October 9th, 2008 at 13:10
    4

    第一次听说。找到几个链接。有时间研究一下。

    [Reply]

  5. rex
    October 10th, 2008 at 10:46
    5

    多谢那个谁推荐的“XPath”,十分好用,十分强大。正在熟悉。推荐firefox用户安装XPath Checker插件。

    [Reply]

Leave a Comment

如何在本站贴代码?

可以使用任意语言名称代替“python”.