Published on Blog Developer's Cookbook.
Printer friendly version of http://blog.bulknews.net/cookbook/blosxom/trackback/tb_discovery.html.
Trackback Auto Discovery
by miyagawa at Sun, 07 Dec 2003 22:40
Trackback をサポートしているBlog エントリには、"このエントリの Trackback URL" という表記がしてあります。これをコピーすれば Trackback Ping を打つ URL がわかるというわけですが、はっきりいってメンドウです。こういうものは人手でやるのではなくて、プログラムから自動でわかるようにした方が便利です。というわけで、今回は Trackback Ping URL を自動検出する方法について解説します。
ある Blog に対する Trackback Ping URL は、Trackback 仕様書 に Trackback Auto-Discovery として記述されています。
Trackback Auto-Discovery は、RDF (Resource Description Format) と呼ばれるフレームワークを利用して実装されています。RDF は、Web サイトのメタデータを Machine Readable にするという"セマンティックWeb" のベースとなる、メタデータ記述フォーマットです。
各 Blog に関連する Trackback Ping URL を、RDF データ内に記述し、 HTML 内に埋めこみます。サンプルを見た方が理解が早いでしょう。このエントリの RDF データは以下のようになります。
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
<rdf:Description
rdf:about="http://blog.bulknews.net/cookbook/blosxom/trackback/tb_discovery.html"
trackback:ping="http://blog.bulknews.net/cookbook/trackback/trackback_tb_discovery"
dc:title="Trackback Auto Discovery"
dc:identifier="http://blog.bulknews.net/cookbook/blosxom/trackback/tb_discovery.html" />
</rdf:RDF>
このように rdf タグ内に Description という単一要素タグで、Blog エントリのメタデータについて記述しています。ここでは先頭の xmlns 宣言で、RDF 内に 3 つの名前空間を定義しています。
rdf | http://www.w3.org/1999/02/22-rdf-syntax-ns# | Resource Description Format |
dc | http://purl.org/dc/elements/1.1/ | Dublin Core 1.1 |
trackback | http://madskills.com/public/xml/rss/module/trackback/ | Trackback Ping |
RDF に trackback モジュールをインポートし、
trackback:ping="http://blog.bulknews.net/cookbook/trackback/trackback_tb_discovery"
の部分で、Ping URL を指定しています。ちなみに dc:title
にはエントリのタイトル、dc:identifier
にはエントリの Permalink を指定しています。rdf:about
については、意味的にいろいろな指定方法が考えられますが、ここでは "Trackback Ping URL についてのメタデータ" であると考え、Trackback Ping URL を指定しています。
このような RDF タグを、Blog ツールのテンプレートなどを編集して HTML 内に埋めこみます(*1)。ただ、XHTML 内にこうした RDF タグを埋めこんだ場合、User-Agent によっては表示に不具合を起こす場合があるので、この RDF を HTML エスケープするのが一般的なようです(*2)。
RSS Auto Discovery では HTML の link タグに URL を指定していたことと比べてみると面白いですね。
サンプルコード
こうして埋めこまれた Trackback Ping URL を自動的に検出するスクリプトを書くと、List 1 のようになります。このスクリプトは Trackback 仕様書 に記述されていたサンプルコードを基に、MovableType の Bookmarklet 部で使用されているものを参考にして記述しています(*3)。
my $url = shift;
my $find_all = shift;
このスクリプトは、Blog エントリ(あるいはインデクスページ) の URL を渡すと、関連した Trackback Ping URL を表示するスクリプトになっています。引数は Blog エントリの URL と、複数の Ping URL があった場合、それをすべて表示するかどうかのフラグになります。
my @pings = discover_tb($url, $find_all);
for my $ping (@pings) {
print "$ping->{url}\n($ping->{title})\n";
}
discover_tb
で Ping URL を検出し、見つかれば Ping URL をタイトルとともに表示します。
sub discover_tb {
my($url, $find_all) = @_;
my $html = get($url) or die "Can't GET $url\n";
URL を LWP::Simple を使用して HTTP GET します。取得できなければエラー終了です。
# no-anchor version for matching
(my $url_no_anchor = $url) =~ s/#.*$//;
$url
のアンカー部分を削除した $url_no_anchor
を用意します。例えばエントリの URL が http://example.com/1234.html#more
等だった場合に、このアンカー部がない URL でマッチした場合にも、検出するためです。
while ($html =~ m!(<rdf:RDF.*?</rdf:RDF>)!sg) {
my $rdf = $1;
my $ping_url;
my($permalink) = $rdf =~ m!dc:identifier="(.+?)"!;
HTML から RDF 部分をマッチさせ、さらにそこから dc:identifier
を拾って Permalink となる URL を抜き出します。
# Check if $pemalink matches to my $url
next unless $find_all || $permalink eq $url
|| $permalink eq $url_no_anchor;
Permalink が、引数となる URL と同じかどうかを比較します。これがマッチしない場合には、対象でないので次のマッチを処理します。$find_all
(スクリプトの第2引数) が指定されているときは、Permalink にマッチするかどうかに関わらずすべての Trackback Ping URL を抜き出します。
# find Trackback URLs, fallback to rdf:about
if ($rdf =~ m!trackback:ping="(.+?)"!) {
$ping_url = $1;
} elsif ($rdf =~ m!about="(.+?)"!) {
$ping_url = $1;
}
trackback:ping
属性を抜き出して Ping URL を取得します。RDF内に trackback モジュールを宣言していない場合には、rdf:about
で代用します。
my($title) = $rdf =~ m!dc:title="(.+?)"!;
push @items, { title => $title, url => $ping_url };
last unless $find_all;
dc:title
でタイトル文字列を取得します。タイトルと Ping URL をハッシュリファレンスにして @items
に格納します。$find_all
がある場合は、次の RDF も処理しますが、そうでない場合は1個見つかれば OK なので、ループを抜けます。
実行例
この Blog の最初のエントリで実行してみます。
% ./tb_discovery.pl http://blog.bulknews.net/cookbook/blosxom/common/readme_first.html
http://blog.bulknews.net/cookbook/trackback/common_readme_first
(About This Blog (Blog Developer's Cookbook))
のように、エントリのタイトルと Ping URL が出力されます。インデクスページに対して実行すると、
% ./tb_discovery.pl http://blog.bulknews.net/cookbook/
のように何もマッチしませんが、2番目の引数に 1 を渡せば、
% ./tb_discovery.pl http://blog.bulknews.net/cookbook/ 1
http://blog.bulknews.net/cookbook/trackback/trackback_tb_rss
(Trackback Ping 一覧を RSS 出力する)
http://blog.bulknews.net/cookbook/trackback/trackback_tb_receive
(Trackback Ping を受信する)
http://blog.bulknews.net/cookbook/trackback/trackback_tb_send
(Trackback Ping を送信する)
http://blog.bulknews.net/cookbook/trackback/common_wiki_created
(WikiWikiWeb Created)
http://blog.bulknews.net/cookbook/trackback/rss_rss2js
(RSS feed を JavaScript で HTML に埋め込む)
http://blog.bulknews.net/cookbook/trackback/rss_rss2htmlmail
(RSS をメールで送信する (HTML メール編))
http://blog.bulknews.net/cookbook/trackback/rss_genfeed
(genfeed - 汎用 RSS ジェネレータ)
http://blog.bulknews.net/cookbook/trackback/opml_blogrolling2opml
(BlogRolling を利用して RSS 巡回先を管理 (OPML編))
http://blog.bulknews.net/cookbook/trackback/rss_blogrolling2rss
(BlogRolling を利用して RSS 巡回先を管理)
http://blog.bulknews.net/cookbook/trackback/rss_rss2email
(RSS をメールで送信する)
のようにそのページ内に含まれるすべての Ping URL が取得できます(*4)。
Hack the Hacks
Net::TrackBack では、discover
メソッドで Trackback Ping URL の検出を行うことができます。ただし、このメソッドでは URL の抽出のみしか行うことはできず、この例のように dc:title
も一緒に取得することはできません。
Listings
#!/usr/local/bin/perl -w
# tb_discovery - Trackback Auto Discovery
use strict;
use LWP::Simple;
my $url = shift;
my $find_all = shift;
my @pings = discover_tb($url, $find_all);
for my $ping (@pings) {
print "$ping->{url}\n($ping->{title})\n";
}
sub discover_tb {
my($url, $find_all) = @_;
my $html = get($url) or die "Can't GET $url\n";
# no-anchor version for matching
(my $url_no_anchor = $url) =~ s/#.*$//;
my @items;
while ($html =~ m!(<rdf:RDF.*?</rdf:RDF>)!sg) {
my $rdf = $1;
my $ping_url;
my($permalink) = $rdf =~ m!dc:identifier="(.+?)"!;
# Check if $pemalink matches to my $url
next unless $find_all || $permalink eq $url
|| $permalink eq $url_no_anchor;
# find Trackback URLs, fallback to rdf:about
if ($rdf =~ m!trackback:ping="(.+?)"!) {
$ping_url = $1;
} elsif ($rdf =~ m!about="(.+?)"!) {
$ping_url = $1;
}
my($title) = $rdf =~ m!dc:title="(.+?)"!;
push @items, { title => $title, url => $ping_url };
last unless $find_all;
}
return @items;
}
Copyright©2002-2003 Tatsuhiko Miyagawa