sironekotoroの日記

Perl で楽をしたい

Perl から Selenium を使う

色々あって

Twitter がデザイン変更して、スクレイピングが失敗するようになり、またDOM解析して修正かー・・・とか思ったら、SPA 化かなんかで全然 DOM の把握ができず、スクレイピングどころではないってなって、あー!!

ってことで、以前から気になっていた Selenium を使ってみます。

大体、Twitter が提供している API でちゃんと全部情報が取れないのが悪い。センシティブと判定された tweet とか取れないんだよなー

Perl 側の準備

ためらうことなく cpanm

$ cpanm Selenium::Remote::Driver

Perl のモジュールインストールが終わったか確認

$ perl -e -MSelenium::Remote::Driver

これは、ワンライナーという Perl の書き方。モジュールを呼び出しただけのコード。ここでエラーが出なければok

引数の説明はこんな感じ。

  • -e 引数をPerlのコードとして解釈して実行する
  • -M モジュールを呼び出す

上記のワンライナーをコードに起こすとこう。この1行。

use Selenium::Remote::Driver;

ここで use できないとエラーが出る。エラーが出ない、ってことはとりあえずインストールはできている。

ワンライナーは短くかけるので、ギュッとまとめるとこう。

$ perl -eMSelenium::Remote::Driver

この perl -eMモジュール名 ってワンライナーでインストールの成否を見たりするのはワンライナーの定番。

Mac 側の準備

Perl (とか他のプログラム言語)側からブラウザを操作するためのドライバをインストールする。

$ brew install geckodriver
$ brew cask install chromedriver

brew の formula がインストールされたか確認

まずは Chrome のドライバを実行

$ chromedriver
Starting ChromeDriver 83.0.4103.39 (ccbf011cb2d2b19b506d844400483861342c20cd-refs/branch-heads/4103@{#416}) on port 9515
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.

こちらは大丈夫そう。CTRL-C で閉じる。

次は Firefox 用のドライバ。Gecko ってのは Firefox のHTML描画エンジンなのだけど、最近はあまり名前聞かなかったんで懐かしくなった。

$ geckodriver

応答ないので CTRL-C で閉じる・・・大丈夫かなぁ。

Google のトップページを表示してタイトル取得して終了するだけ

初めてなので、基礎からやっていく。まずは Google のトップページを出して、タイトルを取得するだけ。

#!/usr/bin/env perl
use strict;
use warnings;

# Selenium::Web::Driver と一緒にインストールされる Chrome 用のドライバを呼び出す
use Selenium::Chrome;

# ドライバのインスタンスを起動する。初期化みたいなもの。
my $driver = Selenium::Chrome->new;

# Google のトップページに移動する。Get は Perl入学式 第5回でやったアレです。メソッドってやつです。
$driver->get('https://www.google.com');

# 移動した後のページのタイトルを表示する
print $driver->get_title() . "\n";  # Google

# ドライバを終了する
$driver->shutdown_binary();

Chrome が立ち上がって、Googleのトップページを表示して自動的に終了する。コンソールにはページのタイトルが表示されている。

f:id:sironekotoro:20200606133157p:plain

次は Firefox。説明は省略。

use Selenium::Firefox;
my $driver = Selenium::Firefox->new;
$driver->get('https://www.google.com');
print $driver->get_title() . "\n";   # Google
$driver->shutdown_binary();

ドライバが何も表示しなかったんで不安だったけど、ちゃんと動いた。よかった。

なお、Firefox の場合は終了時にポートの情報などが表示される。

Killing Driver PID 46947 listening on port 49580...

f:id:sironekotoro:20200606133531p:plain

Yahoo.co.jp からニュースを持ってくる

sironekotoro.hateblo.jp

これの Selenium 版をやってみます

#!/usr/bin/env perl
use strict;
use warnings;

use Selenium::Chrome;

# utf8 で出力する
binmode STDOUT, ":utf8";

my $driver = Selenium::Chrome->new;

# Yahooのトップページに移動する
$driver->get('https://www.yahoo.co.jp/');

# ニュース一覧のDOMを特定する
my $news
    = $driver->find_element_by_xpath(
    '/html/body/div/div/main/div[2]/div[1]/article/div/section/div/div[1]/ul'
    );

# ニュース一覧のDOMから、個別のニュースの要素を取得する
my $articles = $news->children( './li//h1/span', 'xpath' );

# 1記事ずつタイトルを取得する
for my $article ( @{$articles} ) {
    print $article->get_text() . "\n";
}


# ニュース一覧のDOMから、個別のニュースのURLを取得する
my $aTags = $news->children( './li//a[@href]', 'xpath' );

# 1記事ずつURLを取得する
for my $aTag ( @{$aTags} ) {
    print $aTag->get_attribute('href') . "\n";
}


sleep 3;

# WebDriver を終了する
$driver->shutdown_binary();
給付金委託 元電通社員に委任
激しい雨 東京や埼玉竜巻注意
習主席の国賓来日 年内見送り
緊急宣言 再指定に消極的な訳
加首相 デモ参加し片膝をつく
夜は高架下 困窮の留学生帰国
ヤクルト スアレスの陰性発表
鬼滅に続く?若手集うジャンプ
https://news.yahoo.co.jp/pickup/6361779
https://news.yahoo.co.jp/pickup/6361787
https://news.yahoo.co.jp/pickup/6361782
https://news.yahoo.co.jp/pickup/6361778
https://news.yahoo.co.jp/pickup/6361780
https://news.yahoo.co.jp/pickup/6361781
https://news.yahoo.co.jp/pickup/6361783
https://news.yahoo.co.jp/pickup/6361762

うまく取れたんだけど、結構時間がかかる・・・20 〜 30 秒くらい。

あと、タイトルとURLはもう少しスマートな取り方があると思う・・・

Twitter にログインしてみる

さて、本命。

$username_or_email$password を変更して実行

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;

use Selenium::Chrome;

my $username_or_email = 'hogehoge';
my $password          = 'fugafuga';

my $driver = Selenium::Chrome->new;

# Twitter のログイン画面に移動する
$driver->get('https://www.twitter.com/login');

# name 属性でユーザー名の入力欄の要素を特定する
my $username = $driver->find_element_by_name('session[username_or_email]');
# 特定した入力欄にユーザー名を入力する
$username->send_keys($username_or_email);

sleep 1;  # 1秒待つ

# パスワードも、ユーザー名と同様に処理
my $psw = $driver->find_element_by_name('session[password]');
$psw->send_keys($password);

sleep 1;  # 1秒待つ

# ログインボタン には name 属性がないので、div タグと role 属性で要素を特定する
my $button = $driver->find_element('//div[@role="button"]');

# ボタンをクリックする
$button->click();

sleep 5;  # 5秒待つ


# WebDriver を終了する。ブラウザも閉じられる。
$driver->shutdown_binary();

うちの環境ではうまくいきました。

これで、あとはソースを解析することができればー、なのですが本日はここまで。

補足

Twitter側にはこのような通知が出ます。

f:id:sironekotoro:20200607112315p:plain

あまりやりすぎると「不正アクセスされてる!」って判断されちゃうかな。

参考にしたサイト

www.selenium.dev

qiita.com

aiacademy.jp

www.seleniumqref.com

Perl の情報は少ないんですが、他の言語のリファレンスが大変参考になりました。