Web::Scraper

若是要提取 html 文件中的某些内容,该怎么做?直接用正则表达式。或者用解析 HTML 的模块 HTML::TokeParser::Simple 或者 HTML::Parser。嫌麻烦?可以试试看 Web::Scraper。比如:

my $res = scraper { process "div.message", message => 'TEXT' }->scrape($content);

文档中展示的用法,非常简练,和 jquery 一样符合直觉的操作方式:

  use URI;
  use Web::Scraper;
 
  my $tweets = scraper {
      process "li.status", "tweets[]" => scraper {
          process ".entry-content", body => 'TEXT';
          process ".entry-date", when => 'TEXT';
          process 'a[rel="bookmark"]', link => '@href';
      };
  };
 
  my $res = $tweets->scrape( URI->new("http://twitter.com/miyagawa") );
 
  for my $tweet (@{$res->{tweets}}) {
      print "$tweet->{body} $tweet->{when} (link: $tweet->{link})\n";
  }

数据驱动测试框架 Test::Base

写测试时,经常会遇到一堆类似的输入和输出要循环迭代同一组功能。简单点的,可以先构造几个数据数组,然后在循环中依次套数据。稍微复杂点看起来就乱哄哄的。

今天看到这个模块,简单易用,条例清晰,扩展方便。

use strict;
use warnings;
use utf8;
use Test::Base;
use URI::Find::UTF8; 
 
filters { raw => 'chomp', uri => 'chomp' };
plan tests => 2 * blocks;
 
run {
    my $block = shift;
 
    my $f = URI::Find::UTF8->new(
        sub {
            my($uri, $orig) = @_;
            is $uri->as_string, $block->uri, "$uri";
            is $orig, $block->raw, "raw path";
        },
    );
    $f->find(\$block->input);
}
 
__DATA__
 
===
--- input
アンサイクロペディアのホームページはhttp://ja.uncyclopedia.info/wiki/メインページ foo bar
--- raw
http://ja.uncyclopedia.info/wiki/メインページ
--- uri
http://ja.uncyclopedia.info/wiki/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8
 
===
--- input
Home page <URL:http://www.google.com> Google
--- raw
http://www.google.com
--- uri
http://www.google.com/

要测试的数据放在 __DATA__ 块内,三个等号标志一个 block。然后三个减号定义一项数据的名字,下接其内容。看程序,use Test::Base; 后,就有 filters, plan, blocks, run 这几个方法可用。

filters 定义提取数据时,要做的休整操作。blocks 在 scalar context 返回定义了多少 block,因为每组 block 将要在 run 里面做两次 is 测试,所以 plan 要做的测试总数为 2 * blocks。接下来 run 定义了一个匿名 subroutine,在其内部接受 Test::Base 传来的 block 对象,然后依次测试,访问 block 数据就直接用定义好的数据名字,清爽。

这里是 cpan 上的连接

Google Ads

Module::Install 中的 clean_files

如果开始一个新模块,一般都是用 module-starter –mi 开始。mi 指 Module::Install,以此作为生成 Mailfile 的后端。为了能使用 make clean 来清空测试过程中留存下来的临时文件,看 Module::Install 的文档。

但文档中没有发现关于 build_requires 之类的使用介绍,而这个指令是用 module-starter 构建后默认自动写好的,这挺奇怪。仔细看文档才发现只列出最常用的。它会使用 Module::Install::* 之类的模块作为扩展。所以最后在 Module/Install/Metadata.pm 里面找到了 build_requires 这个函数,在 Module/Install/Makefile.pm 里找到了 clean_files 这个函数。遗憾的是,除了这两个以外,还有许许多多其他的指令可以用,却没有对应的使用说明文档。所以只能连蒙带猜,尝试在 Makefile.PL 中写上:

clean_files 't/sandbox';

经过测试,确实在生成的 Makefile 中增加了:

clean :: clean_subdirs
...
	- $(RM_RF) \
	  t/sandbox blib
...

这么一来,终于可以在提交更新前 make clean 删掉无用的东西了。

如果不是 make realclean,Module::Install 还会在当前模块下面新建 inc 目录,然后把 Module::Install 自己的主要代码放进去,号称是以丁点的大小增长换取灵活——如果系统的 Module::Install 老旧了,或者你自己需要额外再做些什么的话都行。

PerlChina 会员中心站点恢复

很久以前,不知怎么的,访问的时候报错,看似底层没有正常加载 CDBI 的表关系定义,也不知道怎么解决,当时心情烦躁,很快不了了之。

这次写简历,提到了这个站点,却不得不注明当前维护中,觉得是个心病。于是刚才抽了点时间再看看。花了一个多小时,搞定。可能是 Catalyst 的一个小版本的 Bug 导致的,同时还更新了 Class::DBI 这个模块也升了好几级了。再后来页面能出来了,但一个 Tag 或者 Place 下的人却只有一个,确定 tag.persons 失效,调试发现,Class::DBI::Loader::Relationship 用到的转换英文单复数的 Lingua::EN::Inflect::Number 的模块,把 person 的复数修订为 people. 所以 tag.persons 当然是什么都不返回,改成 tag.people 就好了。

svn status 了下,很久以前的最后更新(大约是一年之前了)都没有 commit,现在看看很多代码都已经过时了。而且还有很多想法没有实现,幻想着能有有时间重新整合下。虽然这个站点访问量不高,不过下线的这段时间有很多人问及,所以不管怎样,还是先挂上来,以后再说。

还有 wiki.perlchina.org 一直是我的心病,自从遭受 Spam 攻击以来,Instiki 一直表现不好,连它自己的站点和 Rails 的 wiki 站点也惨遭毒害,现在的 Instiki 老吃资源然后死掉,用 cron 程序定期启动也快一年了吧,还要定期删除 /tmp 下的 ruby_session 文件。

脏词过滤

公司里接到网监处的通知,对于使用我们公司提供的网站,必须要有文字过滤,避免那些敏感的上不了台面的词汇出现在各种网站上。

我是不同意这种做法的。堵而不疏,连标都治不了。不过既然上头有命令,还是老老实实做一下吧。不想搞得太复杂,只要基本上可以用就行。

思路很简单,先设置脏词列表,然后从指定的目录开始,遇到子目录进去递归,遇到文件并且是文本文件就打开,逐个关键词匹配,找到的话打印清单。用到两个模块,一个 Path::Class 用来处理文件和目录的,一个 File::Slurp,用来直接读入文件内容和输出报告。

做了一次测试,查出来好多都是新闻中提到或者垃圾回复的情况。所以以后如果要扩展的话,还要加上数据库保存检查结果,并通过人工判定,再行处理。此外目前也只是按照 GB2312 来匹配,应该还需要 UTF-8,Big5 等。还有种方式就是在 Apache 中增加一个过滤的 module,实现起来应该不复杂,不过觉着没什么必要,先应付了检查再说。

Image::Seek

如何判断客户上传的两张图片是否重复?大体上可以通过比较 MD5 运算结果可以得知。那如何给出相似的图片呢?有 Image::Seek 模块可以帮我们。

simon 封装了 ImgSeek 。在他自己的图片站点上可以看到例子,http://memories.simon-cozens.org/photo/view/1452?active=similar 。比较好玩,没准哪一天会用得上。

HTML 洗刷刷

webmail 上如何显示 HTML 格式的邮件正文?用 iframe;或格式化后嵌入当前页。

iframe 可以最大限度还原原来的 HTML 页面效果,但存在安全隐患。而且可能需要用户横向移动滚动条,不宜阅读。直接将源代码嵌入当前的显示页面,也存在安全隐患,而且很容易引入 css 而使 webmail 的整体风格破坏。除了 Js 带来的问题,还可能由于 HTML 中引用了外部的“图片”,而暴露邮件账户的真实性 — 垃圾邮件制造者可以通过一个形如静态图片连接的程序,在纪录邮件地址的有效性后,返回一张图片的二进制数据流。所以,Gmail 会提示是否现实外部图片。

仔细参考 Gmail,它是用了后种策略。而且,不光是去掉了 js 和 css ,还对外部图片和外部连接作了处理。 默认,img 标签被去除了 src 属性。所以仅显示占位区。而 a 标签增加了 onclick 事件绑定,这样,点击邮件中的连接不再直接当前 webmail 页面中跳转,而是新开窗口。对于 class 和 style 属性,Gmail 也都作了删除处理。所以 Gmail 给你的并不是原汁原味的 HTML 邮件。

未必一定要原汁原味 – Gmail 是这样处理的,作出这样的决定,就我而言,是不容易的。我总希望能“高保真”。如何取舍?用户关心什么?在乎视觉效果?还是在乎邮件所表达的信息?Gmail 给了我一个很好的启示。

于是,我希望有一种类似的方法处理原始 HTML 数据。当然,可以用 HTML::Parser 来做,不过太过低级别。因为我们还有 HTML::SantizerHTML::ScrubberHTML::Truncate

这三个有所类同,又有所区别。我还是比较习惯于 Scrubber 。记以抛砖引玉。

一些 Perl 模块

Chatbot::Alpha

公司内部要做个简单的工单流转,我突然想到用 MSN 来提示接手人,MSN 的 bot 倒是可以用了,我又想把它弄得聪明些,正好看到这个模块。不过不知道对中文的解析判断如何,anyway,应该是个好的起点。这是个人工智能应答系统。

Class::Meta

通常自己构建对象都用 Class::Accessor ,简单实用,可以快速构建用来存储/读取对象数据的对象方法。也可以自己重载相应的 Accessor 然后对某些数据作验证。Class::Tangram 则是让你自己定义某些对象的数据结构,然后再用这些定义好的 scheme 生成的类来快速构建复杂的对象。而 Class::Meta 则把两者结合起来,加上 Params::Validate ,成为一个通用的,定义对象数据或者类属性,并带有数据合法验证机制的,代码生成工具。这样,我们可以更快的构建项目原型,只需告诉它我想要做什么,而不用考虑如何去做。

Unix::PID

系统管理员比较关心这个。一般大家的做法是用 ps 看当前运行的进程的 pid ,然后 kill 。如果情况比较复杂,比如有一堆进程需要处理,可能需要些个简单的 shell ,sed 后 awk ,想办法弄出 pid 然后再处理。或者有些进程会写个 .pid 的文件,保存着当前运行的 pid 。不过既然有了这个模块,我们应该更抽象地去做这些事情。让它来帮助处理常见的工作。更灵活,也更易扩展。

MIME-Lite-TT-HTML-0.01

看到 cpan 出来了个 MIME-Lite-TT-HTML-Japanese 模块。

之前就有个 MIME-Lite-TT-Japanese 模块,为了解决字符集的问题,不过它 hardcode 为日本的常用字符集,所以在自己的项目中作了个简单的 MIME::Lite::TT::Charset 来完成工作。

这个新的 HTML::Japanese 增加了对邮件中 html 正文和 text 正文的支持。Okey,我就像要一个更为抽象或者通用的模块来处理这些事情。结合之前的经验,大多数国人所用的邮件客户端尚不能很好的支持 utf8 标准字符集,而我又希望项目使用 utf8 来实现,所以除了制定字符集外,我还希望能有字符集转换的功能:项目以及模版都用 utf8 ,发送出去的邮件中,使用转换结果:gb2312。

于是动手自己做了一个,MIME::Lite::TT::HTML,虽然很不好的爬到人家头上去了,不过想来想去也只有这个比较合适。源代码已经上传到 svn.perlchina.org ,也刚刚上传到 cpan。

FSA::Rules

FSA::Rules 这样的模块,名字看上去酷酷的,不过乍一看不知道是干什么用的,所以大都我会忽略过去。不过这是我第二次打开这个模块文档,这回稍加看看,明白了,这是一个可以让你构建状态机的模块,而且是按照规则(Rules)来设定触发某个事件。所以,你所要做的,就是设定这些规则,一般都是条件表达式,每个规则都有一个名字,然后你要写相同名字的事件处理程序,也就是若干 sub 或者说 code ref ,最后 just run。

看起来和 POE 的概念有点相像,不过没有仔细摆弄过 POE 没有发言权。按心里的感觉,这个绝对是个可以派上用场的工具,特别是系统管理维护方面。