ここ数年、Web発の出版物が日本でも目立って増えてきました。昨年のJEPA電子出版アワードの大賞をピースオブケイクのnoteが取ったことに象徴されるように、日本でもようやくWebがかつて雑誌の担っていた「告知」の役割を果たし始めているということだと思います。コミックに関してはもうずいぶん長くそれが続いているのですが、それ以外のジャンルにもどんどん波及し始めたというのがここ数年の新しい動きです。
さて、そうなってくると、技術サイドとしてもWebサイトからデータを取得し、電子書籍データや最終的には紙の出版物を作るためのルートを整備しなければ、という気分になってきます。ということでここ最近はWebスクレイピングの技術を学び始めているのですが、試行錯誤してどうにか指定したURL内&特定タグ範囲の画像をまとめてダウンロードできるようになったのでメモ代わりに置いておきます。なお、「特定タグ範囲」が今回キモです。サイドバーとかに大量に並ぶアイコン類の画像はあっても困るので。ブログ等で各エントリー部分の画像だけを抽出したいわけです。
#!/usr/bin/perl
#use strict;
#指定したURL内、指定タグ範囲内の画像をダウンロードして保存
use utf8;
use Encode qw/encode decode/;
use File::Basename qw/basename dirname/;
use LWP::UserAgent;
use HTML::TreeBuilder::XPath;
use HTML::Selector::XPath 'selector_to_xpath';
use XML::LibXML;
#処理対象のURLを取得
my $targetURL = $ARGV[0];
$targetURL = decode('UTF-8', $targetURL);
#出力先フォルダ指定
my $exportFolderPath = $ARGV[1];
$exportFolderPath = decode('UTF-8', $exportFolderPath);
#WebサイトからHTMLテキスト取得
my $ua = LWP::UserAgent->new;
my $response = $ua->get($targetURL)->content;
$response = decode('UTF-8', $response);
#取得したHTMLをまずHTML::TreeBuilder::XPathでパース、指定部分だけHTMLとして抜き出し(この場合はclass名「post」で囲まれた範囲を指定している)
my $tree = HTML::TreeBuilder::XPath->new;
$tree->parse($response);
my @htmlPart = $tree->findnodes(selector_to_xpath('.post'));
my $htmlString = $htmlPart[0]->as_HTML;
$htmlString = encode('UTF-8', $htmlString);
#HTMLをXML::LibXMLで再度パース
my $parser = XML::LibXML->new();
$parser->no_network(1);
my $dom = $parser->parse_html_string($htmlString);
#imgタグのsrc属性の値をリスト化
my @imageUrls;
foreach my $imageTagNode($dom->findnodes(selector_to_xpath('.post img'))){
my $imageURL = $imageTagNode->findvalue('@src');
#画像以外をはじいてリストにpush
push (@imageUrls, $imageURL) if ($imageURL =~ /(jpg|jpeg|png|gif)$/i);
}
#画像ダウンロード実行
foreach $imageUrl (@imageUrls){
& downloadImage($imageUrl);
}
exit;
sub downloadImage {
#ダウンロード対象のURLを取得
$targetImageFileURL = $_[0];
#ファイル名取得
my $exportFilename = basename $targetImageFileURL;
#出力
system "curl " . $targetImageFileURL . ">" . $exportFolderPath . "/" . $exportFilename;
}
ターミナルで
perl このスクリプトのパス 処理対象のURL 画像保存先フォルダ
の順で指定すれば画像を一括でダウンロードします。
最初にHTMLパーサーモジュールでパースし、その後XMLパーサーモジュールで再度パースしています。いきなりXMLパーサーモジュールにHTMLを食わせたらエラーで動かなかったためです。Validでない構文の読み込みには(オプションあるとは言え)弱いということなのでしょう。まあ仕方ない。なお今回初めて使ったモジュール、HTML::Selector::XPathがスゴい便利です。CSSの指定構文で書けばXPathに内部変換してくれるとか素晴らしい。
今回は抽出テストにこのブログ(システムはWordPress)の過去エントリを使ったので措定範囲が「.post」ですが、ここは抜き出し対象のサイトごとに書き換えが必要になる感じです。変数化して引数で指定できるようにしても良かったけどどういう絞り込み条件があるかわからんのでとりあえずはコレで。
さて次は本文テキストだぜ・・・。