かつて Palm OS や PockePC を搭載した PDA がちょっと流行っていたころ、オフラインブラウズという手法が注目されたことがあった。これは PC 上のコンバータでウェブを巡回、ダウンロードしたページを専用形式に変換、PDA に転送してオフラインで閲覧するというものだ。iSilo や Pluckr、HandStory といったソフトウェアの名称を覚えておられる方も少なくないだろう。中でも iSilo は現在も開発が継続しており、Android や iOS、webOS にも対応している。
このオフラインブラウズというやり方は、携帯端末から直接インターネットにアクセスすることがあたりまえとなった現在においても、通信費やバッテリの節約という面で有効であり、データがすでに手の中にあるためページの読み込みにも時間がかからない。食後にお茶を飲みながらいつものウェブサイトの記事を読むなんて場面に適している。
ただ最近のウェブページは、そのページの中心となる内容以外に余計なものがもうその内容の量よりたくさんくっついていることも多く、そのままではスマートファンなどの画面で読むには障害となる場合も多い。そこで考えられるのが、あらかじめ各サイト専用に書いたスクリプトで各ページから余分な要素を排除したものを用意しておくという方法である。
PHP で impress PC Watch を切り出す
さて私は impress PC Watch を毎日のように読んでいる。impress Watch 系の各記事のページは最近のウェブサイトとしては余計なものがかなり少ない方で、その点とても好感がもてるのだが、記事の内容の前に最近の記事一覧があって邪魔になるので、記事の内容だけを切り出す PHP スクリプトを書いてみることにした。
各記事の URL を得るためのインデックスとしては、RSS が提供されているのでこれを利用する。impress PC Watch の RSS を HTTP_Request2 で取得し、 SimpleXML オブジェクトとして読み込み、各記事の URL を拾っていく。あとはこの URL を HTTP_Request2 に食わせてダウンロード、適当に加工すればいいのだが、ここで問題が発生した。
impress PC Watch の RSS に含まれている各記事の URL は、各記事の URL そのものではなく、リダイレクト用の URL なのだが、HTTP_Request2 でリクエスト先の URL として設定しようとすると、エラーを吐いてスクリプトが終了してしまう。Invalid HTTP URL なのだという。ちょっと納得できないが、しかたないので、getheader() 関数を使ってリダイレクト先の URL を取得し、これを改めて HTTP_Request2 に食わせることにした。
各記事の加工に入るが、ここでは二つのやり方が考えられる。一つはページ全体の構造はそのままに余分なものを削除する方法であり、もう一つは必要な内容だけを切り出して改めてページを生成するという方法だ。ここでは後者を選んだ。PC Watch の各記事には、内容の前後に特定のクラス名を持つ空の div 要素が付いているので、それを手掛かりにして切り出す。あるいは google_ad_section なんちゃらというコメントを利用してもよさそうだ。
PC Watch の各ページは文字コードが UTF-8 だったり Shift_JIS だったりするので、mb_convert_encoding() 関数を通して UTF-8 にそろえておく。また、生成する HTML の中で base 要素の href 属性に http://pc.watch.impress.co.jp/ を入れておく。あとは適当にインデックスを生成しておけばいいだろう。
こうして書き上げたスクリプトを実行、ダウンロードと加工が終わったら、これを携帯端末向けの形式に変換する。iSilo なら専用コンバータの iSiloX を使う。iSiloX には直接クロールする対象としては生成したインデックスだけを指定しておき、そこからリンクを一段階たどるように設定しておけば、各記事が入った iSilo 用のファイルができあがる。
場合によっては何らかのほかの方法で PDF や EPUB に変換することも考えられる。そうすれば、Kindle や Sony Reader のような電子ペーパー端末で読むのに便利かもしれない。
付録:サンプルスクリプト
第一引数にディレクトリを渡して実行するとそこに加工後のファイルを保存します。
#!/usr/bin/php
<?php
$dist_path = preg_replace('/\/$/u', '', $argv[1]);
$dist_article_dir = "pcw.article.dir";
$dist_article_path = "{$dist_path}/{$dist_article_dir}";
$dist_filename = "{$dist_path}/pcw.index.html";
$list_url ="http://rss.rssad.jp/rss/impresswatch/pcwatch.rdf";
$http_proxy = getenv('http_proxy');
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');
if ($http_proxy == ''){
$proxy[0] = '';
$proxy[1] = '';
} else {
$proxy = preg_replace("/^http:\/\//", "", $http_proxy);
$proxy = explode(':', $proxy);
}
function getData($get_url){
try {
global $proxy;
$proxy_host = $proxy[0];
$proxy_port = $proxy[1];
require_once "HTTP/Request2.php";
$req = new HTTP_Request2($get_url);
$req->setHeader('User-Agent','cli/pcwloader.php local-script');
$req->setConfig('proxy_host', $proxy_host);
$req->setConfig('proxy_port', $proxy_port);
$res = $req->send();
$ret['http'] = $res->getStatus();
$ret['body'] = $res->getBody();
$ret['stat'] = TRUE;
} catch (Exception $e){
// var_dump($e);
$ret['stat'] = $e->getMessage();
$ret['http'] = $res->getStatus();
}
return $ret;
}
if (!is_dir($dist_path)){
mkdir($dist_path, 0777, TRUE);
}
if (!is_dir($dist_article_path)){
mkdir($dist_article_path, 0777, TRUE);
} else {
$dir = scandir($dist_article_path);
$dir = array_slice($dir, 2);
foreach ($dir as $file){
unlink($dist_article_path.'/'.$file);
}
}
$list_data = getData($list_url);
if ($list_data['stat'] !== TRUE || $list_data['http'] != '200'){
var_dump($list_data);
exit();
} else {
$rdf = simplexml_load_string($list_data['body']);
var_dump($rdf);
}
$titles = array();
foreach ($rdf->item as $val){
var_dump($val);
$headers = get_headers($val->link, 1);
if (isset($headers['Location'])){
$link = $headers['Location'];
} else {
break;
}
var_dump($link);
if (is_array($link)){
$link = $link[0];
}
if (preg_match("/.+\.impress\..+/", $link)){
$get_data = getData($link);
}
if ($get_data['stat'] !== TRUE || $get_data['http'] != '200'){
var_dump($get_data);
} else {
$file = basename($val->link);
$file = preg_replace("/(.+)(\?.+)/u", "\${1}", $file);
$file = $file . '.html';
$get_body = $get_data['body'];
$get_body = mb_convert_encoding($get_body, 'UTF-8', 'SJIS,JIS,EUC-JP,UTF-8');
$get_body = preg_replace("/\n|\r/u", "\n", $get_body);
if (preg_match("/.+\.impress\..+/", $link)){
$get_body = preg_replace("/^(.+)(<div class=\"ad_contents_start\">.+<div class=\"ad_contents_end\">)(.+)$/uis", "\${2}", $get_body);
$get_body = "<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" /><title>".$val->title."</title><base href=\"http://pc.watch.impress.co.jp/\"><meta http-equiv=\"Content-Style-Type\" content=\"text/css\"><style>\n.social_bookmark {display:none}\n</style></head><body>".$get_body."</div><cite>{$link}</cite></body></html>";
} else {
$get_body = $get_body;
}
file_put_contents("{$dist_article_path}/{$file}", $get_body);
if (preg_match("/.+\.impress\..+/", $link)){
$titles[] = "<li><a href=\"{$dist_article_dir}/{$file}\">{$val->title}</a></li>";
} else {
$titles[] = "<li><a href=\"{$link}\">{$val->title}</a></li>";
}
}
}
var_dump($titles);
$list = '';
foreach ($titles as $val2){
$list = $list . $val2;
}
$index = "<html><head><title>impress PC Watch</title></head><body><h1>impress PC Watch</h1><ul>{$list}</ul></body></html>";
file_put_contents("{$dist_filename}", $index);