ホーム » 技術 » SeleniumでWebページをスクレイピングする

SeleniumでWebページをスクレイピングする

自分はバッチ大好きなので、何かを毎日チェックしたいときには、なるべくコードで自動化することを考える。たとえば最近MinecraftサーバをPaperMCで立てたのだが、PaperMCは(上流を含めて)開発が活発で頻繁にアップデートされる。これを定期的にチェックして常に最新版にしておきたいのだが、アップデート情報はWebページに記載されているのみの状況だ。具体的には

のページを見に行って、リストの一番上にあるものがアップデートされたものかどうか確認しなければならない。いちいちこれを目でチェックして手動でダウンロードするのは面倒なので避けたいわけだ。こういうときはWebページをバッチで読んで該当部分をparseしてやればよいわけだが、問題は該当ページがJavascriptによる動的生成である場合だ。このようなページはLWP::UserAgentのようなライブラリでは読めない。そこで動的にページ生成して、その結果を読むような仕組みが必要だ。

Seleniumはブラウザ自動化とそれを支えるツール・ライブラリ群のプロジェクトだ。

これと、APIをサポートしたブラウザを組み合わせれば、簡単にページをparseできるようになる。本稿ではAlmaLinux上のPerlでparseできるように環境を構築する手順を示す。

Selenium環境のインストール

まずWebDriverをインストールする。AlmaLinux(CentOS)ではepelに用意されているので、dnfで簡単にインストールできる。

dnf install chromedriver

ブラウザだが、本稿ではコンソール環境・バッチ処理を目指しているのでGUIで利用するフルブラウザは必要ない。そこでChromiumのヘッドレスブラウザをインストールする。

dnf install chromium-headless

最後にPerlのライブラリをインストールする。

dnf install cpanminus
cpanm Selenium::Remote::Driver

これで準備完了だ。

Perlでのparse

まずページのタイトルを取ってくる簡単なコードを示す。

#!/usr/bin/perl

use strict;
use warnings;
use Selenium::Chrome;

my $d = Selenium::Chrome->new(
    extra_capabilities => {
        'goog:chromeOptions' => {
            args => ['headless', 'disable-gpu', 'window-size=1920,1080', 'no-sandbox' ],
            binary => '/usr/lib64/chromium-browser/headless_shell'
        }
    }
);

$d->get('https://north.thco.mp');
print $d->get_title(), "\n";

exit 0;

注意したいのはbinaryで示しているヘッドレスブラウザへのpathだ。これを入れておかないと、デフォルトのChromeのフルブラウザをロードしようとしてエラーになる。またもし別のブラウザに変更したいなら、この部分を書き換えればよい。このコードはごく簡単にページを取ってきてタイトルを表示しているだけだ。

次にPaperMCのダウンロードページから、必要なリンク部分を取り出すコードを示す。

#!/usr/bin/perl

use strict;
use warnings;
use Selenium::Chrome;

my $d = Selenium::Chrome->new(
    extra_capabilities => {
        'goog:chromeOptions' => {
            args => ['headless', 'disable-gpu', 'window-size=1920,1080', 'no-sandbox' ],
            binary => '/usr/lib64/chromium-browser/headless_shell'
        }
    }
);

$d->set_implicit_wait_timeout(5000);
$d->get('https://papermc.io/downloads');
my $a = $d->find_element_by_xpath('/html/body/main/div/div/div');
my $c = $a->children('.//a[@href]', 'xpath');
my $url = ${$c}[0]->get_attribute('href');
print "$url\n";

exit 0;

implicit_wait_timeoutは適当な値を入れておく。これをしないと処理が追い付かないのかfind_elementに失敗することが多くなる。

parseだが、欲しい部分を抽出するにはWeb Elementsに馴染む必要がある。今回自分は初めて触るので完全に理解できていないが、Chromeのデバッグモードで該当部分を見に行くとxpathがなんとなく分かるのでそれを頼りにコードを書くとよいようだ。ここでは、リスト表示されている各バージョンへのリンクリストを配列で取得し、その最初(一番上)の要素が欲しいリンクなのでそれを取るようにしている。最終的に得られたリンクはpathのみなので、prefixを補完してやればダウンロードに使えるようになる。なおparseにあたってはエラーが出がちなので例外処理を入れてやった方がよい。

このコードに肉付けして、ダウンロード済みのコードと比較するとか、実際にcurlなどでダウンロードすれば完成だ。