深柳堂

  • Tag:
    标签集合
  • Suggest:

WordPress 更新到2.6.5 停掉了UTW这一伟大的插件...

饭否消息析取之regex vs xml

October 8th, 2008

页内导航:

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

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

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

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的快捷方式。这里使用完整的路径,以便体现其一般性。),观察可见,有用的饭否消息在这个框架里面:(代码较长,阅读请点击展开)

 
<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抓饭程序。)

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程序。
 

?View Code PYTHON
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

饭否私信格式分析

May 31st, 2008

URL

饭否私信分为两种,一种是我收到的私信,一种是我发出的私信。

  • 我收到的私信:http://fanfou.com/privatemsg/p.(1-N)
  • 我发出的私信:http://fanfou.com/privatemsg/sent/p.(1-N)

上面的地址中不含饭否ID;需要cookie验证。

结束标志

通过cookie验证后,可以使用数字获得对应页码的私信内容。什么时候是结束呢?假如您的收件箱有1000条私信,每页显示20条,那么当你您输入http://fanfou.com/privatemsg/p.51时,就得不到任何有效的内容了。作为程序,它是寻找如下标志:

<ol class="wa">\s*</ol>

好友列表

在页面代码中,每页都有一个“向XXX发送私信”的combox列表,条目以昵称+ID组成。如果你的好友很多的话(500+),每条好友(昵称+ID)需要20字节(估算)的话,20*500=10K,大约需要多抓取10K的字节量。

收件箱饭否私信的结构

收到的私信分为两种,一种是有回复信息的(回复原文:…),一种是没有回复的。先从简单的入手,看没有回复的。
所有的私信都在<ol class=”wa”>…</ol>之内,以<li></li>分隔

例如下面这一条,就是一则很规范的私信(与发件人相关的信息都使以正则式表示):

<li>
	<a href="/[^"]+" title="[^"]+" class="avatar"><img src="[^"]+" alt="[^"]+" /></a>
	来自<a href="[^"]+">[^"]+</a><span class="content">没法比较啊,你得说个具体的值,比如100条以下的算少,1000条以上的算多……</span>
	<span class="stamp time" title="2008-05-30 17:25">15 小时前</span> 
	<span class="op">
		<a href="/privatemsg.reply/583520">回复</a> 
		<a href="/privatemsg.del/583520" class="post_act">删除</a></span>
</li>

其中,需要记录的信息有:

  • 发件人名字;
  • 发件人ID;
  • 私信内容;
  • 时间;
  • 私信ID;(便于作删除、回复处理)。

根据以上需求,将上面的私信代码作处理:

<li>
	<a href="/([^"]+)" title="([^"]+)" class="avatar"><img src="[^"]+" alt="[^"]+" /></a>
	来自<a href="[^"]+">[^"]+</a><span class="content">([^<]+)</span>
	<span class="stamp time" title="([^"]+)">[^<]+</span> 
	<span class="op">
		<a href="/privatemsg.reply/(\d+)">回复</a>
		<a href="/privatemsg.del/\d+" class="post_act">删除</a>
	</span>
</li>

从而得到:

$1=fanfou ID;
$2=fanfou name;
$3=private msg;
$4=msg time;
$5=msg ID;

再看一下包含“回复原文”的私信的结构(部分内容已作正则处理):

<li>
	<a href="/([^"]+)" title="([^"]+)" class="avatar"><img src="[^"]+" alt="[^"]+" /></a>
	来自<a href="[^"]+">[^"]+</a><span class="content">([^<]+)</span>
	<span class="stamp time" title="([^"]+)">[^<]+</span> 
	<span class="op">
		<a href="/privatemsg.reply/(\d+)">回复</a> 
		<a href="/privatemsg.del/\d+" class="post_act">删除</a>
	</span>
	<p class="pm-parent">回复原文: 有兴趣就有动力</p>					
</li>

与前者相比,只是多了<p class=”pm-parent”>.*?</p>这一段。这是不足为虑的。只要整体加上?这个强有力的正则符号,就能与上面的代码片段归纳到一起。两者结合合的代码如下:

<li>
	<a href="/([^"]+)" title="([^"]+)" class="avatar"><img src="[^"]+" alt="[^"]+" /></a>
	来自<a href="[^"]+">[^"]+</a><span class="content">([^<]+)</span>
	<span class="stamp time" title="([^"]+)">[^<]+</span> 
	<span class="op">
		<a href="/privatemsg.reply/(\d+)">回复</a> 
		<a href="/privatemsg.del/\d+" class="post_act">删除</a></span>
	(?:<p class="pm-parent">([^<]+)</p>)?
</li>

得到的变量为:

$1=fanfou ID;
$2=fanfou name;
$3=private msg;
$4=msg time;
$5=msg ID;
$6=parent msg;#回复原文。

发件箱饭否私信的结构

抄代码:

<li>
	<a href="/([^"]+)" title="([^"]+)" class="avatar"><img src="[^"]+" alt="[^"]+" /></a>
	发给<a href="[^"]+">[^"]+</a><span class="content">海内的像片是真的。</span>
	<span class="stamp time" title="2008-05-28 20:24">2008-05-28 20:24</span>
	<span class="op"><a href="/privatemsg.del/576827" class="post_act">删除</a></span>
</li>

这与“我收到的私信”的结构完全一致,只是将原来的“来自”改为“发给”而已。

不出意外,带有“回复原文”的“我收到的私信”的结构是这样的:

<li>
	<a href="/([^"]+)" title="([^"]+)" class="avatar"><img src="[^"]+" alt="[^"]+" /></a>
	发给<a href="[^"]+">[^"]+</a><span class="content">在自述部分显示的那个网上。</span>
	<span class="stamp time" title="2008-05-29 17:00">2008-05-29 17:00</span>
	<span class="op"><a href="/privatemsg.del/579918" class="post_act">删除</a></span>
	<p class="pm-parent">回复原文: 我也要试一试。</p>					
</li>

我们从饭否私信代码上得到的信息就这些。遗憾的是,饭否私信中,关于“回复原文”是以明文内容形式出现,而不是以原私信ID的形式出现。后期处理时通过搜索功能解决此问题并非不能,只是如果饭否官方能够再将此功能完善的话,会省整理者不少力气。

饭否的私信源码分析完毕。至于如何读写cookie,如何写代码,如何以数据库的形式来管理下载的数据,是第二阶段的事情了。待我一一实现。

windows下的正则式工具介绍之一:RegexBuddy

April 30th, 2008

RegexBuddy

俗话说,工欲善其事,必先利其器。关于windows下的正则表达式工具,这里推荐的是:RegexBuddy和PowerGREP。在linux下,也有好用的正则表达式工具,例如grep的兄弟们,只不过是都是基于命令行的。而这两款windows下的小工具,其突出特点是可视化,允许尝试和预览,极大地方便了使用者。

RegexBuddy:网址是http://www.regexbuddy.com。在编写正则式时,它提供可视化的支持、提示、调试方面的便利;在使用正则表达式时,它无私地将正则式转换为多种语言的字串,还提供了代码输出功能。正则式助手,该称号名副其实。

基本界面

正则式的基本功能无外乎搜索和替换。在本文中,我们使用匹配Email的正则式,代码如下:

\b[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b

此时,RegexBuddy的界面如图:

regex001

在点击“Explain Token”时,能对当前的正则式片断的作用作出详细解释,例如当你把光标移动到\b上再点击Explain Token,就会激活帮助文档,自动定位到Word Boundaries这一段。

如果想对刚才编写的这条正则式进行测试和验证,可以点击“Test”进行测试。这时,在下边的文本框输入所需要匹配测试的文字,例如dog@animals.com,匹配结果就以黄色背景色标出。在本例中,你或许没有得到正确的匹配,呵呵,那是正常的。为什么?答案见文章结尾。

regex002

拷贝粘贴

RegexBuddy能把正则式以多种字符串格式拷贝出来。还是刚才那条正则式,根据需要,它可以被拷贝为:

'\\b[A-Z0-9._%-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b'    
"\\b[A-Z0-9._%-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b"     
'/\\b[A-Z0-9._%-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b/i'

具体的选项在copy菜单里,如下图所示。你不必为单双引号、正反斜线操心了。

regex004_menu

在paste菜单项中也有类似应用,不赘述。

正则式->代码

如果你想把刚才编写好的正则式应用在程序中,这里还有一个选项:Use,界面见下图:

regex004_use

在上面的match和replace之间点击,其代码也相应自动调整;当选取不同的language时,代码也会相应调整。它支持的语言格式为:

  • C#
  • Delphi(NET/Win32)
  • Java/JavaScript/ECMAScript
  • PCRE
  • PHP
  • Perl
  • Python
  • RealBasic
  • Ruby
  • VB

另外,它还有function选项,分别用以实现下述功能:

  • If/else branch whethe the regex matches (part of) a string. If/else验证正则式是否匹配字串(的一部分)。 最常用的功能。
  • If/else branch whethe the regex matches a string entirely. If/else验证正则式是否匹配整条字串。
  • Get the part of a string matched by the regex. 取得字串中与正则式匹配的部分。Get the part of a string matched by a capturing group. 取得字串中所匹配的捕获组。这一条我也是刚刚知道,很有用哟。
  • Get an array of all regex matches in a string.将字串中所有的匹配保存到数组中。
  • Iterate over all matches in a string。列出字串中所有的匹配项。(例如,在使用正则式’\w’来匹配字串’abc’时,本function列出的内容为’a',’b',’c’.)。单词iterate的含义是重复
  • Comment with RegexBuddy’s regex tree. RegexBuddy的正则树的注释。

文本分割split

如果需要处理的文本是以某种分隔符隔开的,而该种分隔符恰好又能使用正则式描述,(例如html标签),此时regexbuddy的split功能就可以大显身手了。我随便打开了一个饭否网页,对其源代码中的消息部分(<div id=“stream”>与</div>之内)的文本进行了处理,使用如下正则式删除了所有的尖括号内容,只留下普通文本。

使用的正则式为:

<[^>]+>

软件界面以及运行结果请见下图。



结尾:

关于本文开头提出的小问题,细心的你或许一下子就能看出答案了!见下图:

regex003

只要选中Case insensitive选项中OK啦!如果你没有找到,或许是因为该软件是英文的,一时间您没有注意到该选项;或者您对正则式还不太熟悉。

软件下载

上文已经提到,其官网为www.regexbuddy.com,可以去下载其最新版试用。该软件为商业软件

  • 如果你偶然路过,尝新而已,那只需下载试用版即可;
  • 如果你觉得好用、准备常用、手有余钱、非正版不用,不妨花美金购买
  • 如果你喜欢它,同时你认为优秀的网络资源是应该和朋友免费分享的,从而想获得该软件的全功能免费版,好吧,我也成全你,请在本文后留言(附邮箱),我会把这个小东西的链接发给你(最新版为3.1.1,我手头的全功能版为3.1.0,也足够用了)。

下篇文章将要介绍另一款regex工具:PowerGREP,敬请期待。

本文为rex.zhasm原创,原文地址在http://iregex.org/blog/y2008/m04/d30/regexbuddyregexbuddy.html,可以在遵循CC协议的条件下转载。

[老贴整理]如何使用正则式从英文句子里提取词根

April 25th, 2008

以前在chinaunix回答过这样一个问题,用到了正则表达式(而且我认为正则式解决此类问题是最合适的。)

学英语的一些例句,每句都有若干词根相同的词,例如 She swears to wear the pearls that appear to be pears. 但是每句的词根都未必相同;我希望把这些包含词根的词都标记出来,请问如何写?

这里说的词根不是原本词根的定义,只是一组字母序列,比如

9. The dust in the industrial zone frustrated the industrious man.

词根是dust或ust

10. The just budget judge just justifies the adjustment of justice.

词根是dust

11. I used to abuse the unusual usage, but now I’m not used to doing so.

词根是use,有变形

12. The lace placed in the palace is replaced first, and displaced later.

词根是lace

13. I paced in the peaceful spacecraft.

词根是pace

14. Sir, your bird stirred my girlfriend’s birthday party.

词根是ir

如果您对此问题感兴趣,请独立思考后再继续阅读本站提供的解决方法。

Read the rest of this entry »

  • 通配天下

    • 正则
        正则式gtalk群:regex[at]zhasm[dot]com
        站长gtalk:rex[at]zhasm[dot]com
      --------------------------------------------------------------
      ◆匹配文本,管它字母数字一次多次懒惰贪婪行首行尾,火眼金睛疏不漏  
      ◆替换字串,任尔局部全局单行多行前瞻回顾大写小写,偷梁换柱度陈仓
      --------------------------------------------------------------