RSS极大的方便了我们及时跟踪页面的最新变化,可惜不是所有的地方都提供了RSS。Google Reader虽然提供了为没有RSS的页面生成RSS的功能,但是只能处理英文网页,对于中文或日语网页,与及阻止了Google爬虫的网页就无能为力了,例如:

Generated feed for “http://www.zju.edu.cn/”
from http://www.zju.edu.cn/ Google feed by Google
* Google was not able to access this page to check for updates. This page may be unavailable or have other restrictions that prevent Google from getting updates.

于是自己写了一个简单的脚本,自己为这些页面生成一个RSS。

#!/usr/bin/perl
# wafeed.pl

use AnyDBM_File;
use DBM_Filter;
use Encode qw(decode_utf8);
use LWP::Simple qw(get);
use XML::FeedPP;

$config = $ARGV[0] || 'config.pl';
require $config;

$time = time;
if (-e $rssfile) {
    $feed = new XML::FeedPP::RSS($rssfile, utf8_flag => 1);
} else {
    $feed = new XML::FeedPP::RSS;
}
$feed->title($title);
$feed->link($link);
$feed->pubDate($time);
$feed->description($description);

dbmopen(%history, $dmbfile, 0666);
(tied %history)->Filter_Push('utf8');
while (($key, $cfg) = each %config) {
    $value = get($cfg->{'link'});
    $value = $cfg->{'handler'}($value) if defined $cfg->{'hand'};
    $value = decode_utf8($value);
    if ($value !~ /^\s*$/ && $value ne $history{$key}) {
        $history{$key} = $value;
        $feed->add_item(
            title => $cfg->{'title'},
            link => $cfg->{'link'},
            pubDate => $time,
            description => $value);
    }
}
dbmclose %history;

$feed->sort_item();
$feed->limit_item($itemnum);
$feed->to_file($rssfile);

程序中需要处理字符串和编码。perl中字符串有两种形式:octets是8位序列,可以看作是java.lang.Byte[];string是utf8编码的字符串,可以看作是java.lang.String。LWP::Simple中的get将以octets字符串的形式返回给定url的页面内容。通过decode_utf8可以将octets转为utf8的string。

程序中将每个页面的最新内容保存在哈希%history(实际上是一个DBM)中,以判断内容是否发生变化。由于DBM只能处理octets,如果使用utf-8的string,将出现“Wide character in null operation at …”的警告,汉字将不能正确存取。所以我们需要在存储(STORE)时调用encode_utf8,取值(FETCH)时调用decode_utf8,而这些在DBM_Filter::utf8中已经实现好了,所以只要简单的Filter_Push(‘utf8′)就可以了。

这里使用XML::FeedPP模块处理RSS,这个模块同时还能处理RDF和Atom,此外能够处理RSS的模块还有XML::RSS等。需要注意new中的utf8_flag => 1必不可少,否则utf8编码的汉字将被当作ISO-8859-1的拉丁字母,生成的RSS中原有的item将变成是乱码。to_file有一个可选的参数$encoding,默认值就是utf8。

# config.pl
use utf8;
use Encode qw(decode encode_utf8);

$dmbfile = 'wafeed';
$rssfile = 'wafeed.rss';

$title = 'wafeed';
$link = 'http://zjuicpc.watashi.ws/watashi/wafeed.rss';
$description = "warashi's rss reverse proxy";
$itemnum = 10;

%config = (
    'upload' => {title => 'ZJUICPC-HOST uploadz', link => 'http://zjuicpc.watashi.ws/uploadz/'},
    'zojnews' => {title => 'ZOJ News', link => 'http://acm.zju.edu.cn/onlinejudge/index.jsp'},
    'maikaze' => {title => '舞風ホームページ', link => 'http://maikaze.com/'},
    'tohokinemakan' => {title => '東方活動写真館', link => 'http://tohokinemakan.jp/'});

$config{'zojnews'}->{'handler'} = sub {
    return $1 if $_[0] =~ m#<div[^>]*id="content_body"[^>]*>([\s\S]*?)</div>#i;
};

$config{'maikaze'}->{'handler'} = sub {
    return "<pre>$1</pre>" if $_[0] =~ m#<div[^>]*id="update"[^>]*>[\s\S]*?<textarea[^>]*>([\s\S]*?)</textarea>#i;
};

$config{'tohokinemakan'}->{'handler'} = sub {
    if ($_[0] =~ m#<div[^>]*id="update"[^>]*>[\s\S]*?<textarea[^>]*>([\s\S]*?)</textarea>#i) {
        return encode_utf8(decode('shift_JIS', "<pre>$1</pre>"));
    }
}

config.pl中指定了DBM和RSS文件的路径,RSS中channel的$title, $link和$description,rss中的最大item数。%config中还有所需跟踪页面的信息,包括item的title和link,还有一个可选的handler用于从get的返回结果中分离出所关心的部分。

perl程序中的字符串常量默认被当作octets处理,而在use utf8将改变这一规则。如果没有use utf8,就需要把代码中的日语字符串’…’改成decode_utf8(‘…’)避免乱码。由于tohokinemakan.jp的网页是shift_JIS编码的,不能直接处理,所以还要通过decode(‘shift_JIS’, ‘…’)转化为utf8的string。

最后修改crontab,每隔一段时间运行一次wafeed.pl更新RSS的内容,再用Google Reader订阅所生成的RSS就可以实现对这几个页面的跟踪了。

7 Responses to “用perl生成RSS”
  1. quark says:

    ym. 你是在这台VPS上面运行wafeed.pl的吗?
    Yahoo 有一个服务,好像叫 Pipes ,可以生成自定义的 RSS,可视化编程界面,有点像你之前发的 A+B。

    不在意流量的话吧WP设置成全文输出吧 -,-

  2.  
Leave a Reply