sironekotoroの日記

Perl と Mac の初心者の備忘録

HTTP::TinyでBasic認証を超える

昨日今日とやっていた「AWSをはじめよう」ではWordPressのサイトを立てて、そこの管理者ページにApacheBasic認証をかけます。

で、読み終えた後にBasic認証越しの情報ってPerlは取れるんかなー?と思って、ググったら既にやってた方がいて、コードまで載ってた。ありがたい。

サンプルのコードのURLと認証情報書き換えただけでいけた。

HTTP::Tinyでやってみる

同じことを標準モジュールのHTTP::Tinyでもできるんかなー?と思ってやってみたらできたのであげる。

ちょっとだけ詰まったのは、base64エンコードするときに、第2引数に空文字を入れないと改行が入っちゃうということ。

HTTP::TinyMIME::Base64もコアモジュールなので大体は大丈夫なはず。

文字列連結にsprintfとか使っちゃってるのは明らかにお仕事の影響ですね・・・

追記

・・・!?できた。

これだとbase64エンコード云々もいらないじゃーん

#! /usr/bin/env perl
use strict;
use warnings;
use HTTP::Tiny;

my $user = 'user';
my $pass = 'pass';
my $url
    = sprintf( 'https://%s:%s' . '@認証後のURL',
    $user, $pass );

my $res = HTTP::Tiny->new->get($url);

print $res->{content} if $res->{success};

AWSをはじめよう! で詰まったところ

ってことで、2019年09月最初の連休はAWSに入門しております。

booth.pm

この本は2018年10月08日に技術書典5で頒布されたものですが、約1年後のこの連休に試してみたところ、ちょっと詰まったところがあったので残しておきます。

技術書、特にITがらみはサービスの進化も早く、頻繁にデザインが変わるので、出た瞬間からすごい勢いで古くなってしまうという宿命があります。つらい。

この本はAWSの管理画面の画像も多くわかりやすいのですが、一部は既にAWS側のデザインや名称が異なっています。

とはいえ、ある程度は推測ができるものでそれほど迷うことはありませんでした。

詰まったところ 「6.3 画像をS3に保存する」

この第6章ではAWSのEC2上にWordPressを立てます。

本の通りに立てたところ、以下の構成になりました。

  • PHP Version 7.2.19
  • WordPress 5.2.3 (Twenty Nineteen テーマ)

3節では画像の保存先をAmazon S3にするということで、プラグインを使う方法が書かれていました。プラグインは2つです。

  1. Amazon Web Service プラグイン
  2. WP Offload S3 Lite プラグイン

この2番目のプラグインプラグインの検索で引っかからなくなっています。色々ググってみたんですが、うちのググりぢからなくて、解決まで時間がかかりました。

ということで、最終的には

  1. Amazon Web Service プラグイン は使わない
  2. WP Offload S3 Lite プラグインの代わりに WP Offload Media Lite プラグインを使う

ということで落ち着きました。

WP Offload Media Lite

名前の通りWP Offload S3 Liteプラグインの機能拡張版です。S3以外のクラウドストレージへの保存にも対応しています。

このWP Offload Media LiteプラグインはS3に保存する際のアクセスキーIDとシークレットアクセスキーIDを独自に保存が可能です。このため、このプラグインでS3に保存するだけであればAmazon Web Serviceプラグインを削除しても画像のアップロードは可能でした。

  1. WordPressの管理画面でWP Offload Media Liteを検索し、「今すぐインストール」ボタンでインストールする

    f:id:sironekotoro:20190915155220p:plain

  2. インストールが完了したら、「有効化」ボタンを押す

    f:id:sironekotoro:20190915155310p:plain

  3. プラグイン一覧の画面で有効化し、「Settings」のリンクをクリックする

    f:id:sironekotoro:20190915155456p:plain

  4. 以下のような画面が出てくるので、Access Key IDとSecret Access Keyを入力する

    f:id:sironekotoro:20190915155931p:plain

    1. もし、以下のような画面が出た場合には、既に設定があるということなので、Back のリンクから Offload Media Liteの設定ページに移動する

      f:id:sironekotoro:20190915160124p:plain

    2. 移動した先のページの AmazonS3 の右にある Change のリンクをクリックすると 4. の画面に移動する f:id:sironekotoro:20190915160309p:plain

  5. バケットの指定をしていない場合には以下の画面が出るので、バケット名を入力する

    f:id:sironekotoro:20190915160758p:plain

  6. ・・・ただし、ここでAmazon S3の制限に気をつける必要がある

    docs.aws.amazon.com

    バケット名は必ず、Amazon S3 内の既存バケット名の中で一意になるようにします。

    つまり、本にある通り東京リージョンではstart-aws-wordpress-bucketという名称のバケットを作ることができない。うちは後ろに-sironekotoroってつけて回避。

  7. httpsにする

    ここは本と同じ

    f:id:sironekotoro:20190915161506p:plain

これで、WordPressにあげた画像をS3に保存することができました。めでたしめでたし。

おまけ

PHP触るの初めてかもなー、ってことで、ググりながら書いた初めてのPHP

<?php

header("Content-type: text/plain");

for ( $i = 1; $i <= 100; $i++){

    if ($i % 15 == 0){
        echo "fizzbuzz\n";
    }elseif ($i % 3 == 0){
        echo "fizz\n";
    }elseif($i % 5 == 0){
        echo "buzz\n";
    }else{
        echo "$i\n";
    }
}

?>

Perl入学式 in 東京第5回でピザ会でのお題「WebアプリからLINEに定型メッセージ送信」

Perl入学式 in 東京では各回の講義終了後にピザ会(ピザ&ジュース代は参加者負担)を開催しており、そこで受講者さんと雑談などをしております。

その中で、id:xtetsuji さんが課題が出し、志願者がコードを書いてその場で発表、という試みを行っています。

今回のお題:WebアプリからLINEに定型メッセージ送信

  • Mojolicious::Lite でボタンが並んでいるページを作る
  • そのボタンには「帰宅」「横になる」「寝る」などの状態が書かれている
  • そのボタンを押すと、IFTTT の WebHookを経由してLINEに「帰宅」「横になる」「寝る」などのメッセージが投稿される
  • ボタンを押した後は「送信しました」というページが表示される
  • LINEの家族チャットなどに送信できると便利なのではないか?

というものでした。

しかし

当方、LINE使っていないでござる・・・

ということで、当日は作りかけだったTwitterのアプリからAPIKeyを流用してでっち上げ。

ボタンを押すと、メッセージがtwitter APIを利用してtwitterに投稿される、というものです。

Perl入学式 第5回の講義資料の最後に掲載した「落ち穂拾い」にあるGETとPOSTを分けたコードになっています。

ボタンを押すと、TwitterAPI経由で定型メッセージをtweetする君

クリックするとコードが出てきます

ぶぶー!レギュレーション違反です!!

LINEじゃなくてtwitterだし、IFTTTじゃなくてtwitterAPIだけど、ボタンを押すとメッセージが飛ぶという結果自体は同じだし・・・

しかし、やっぱりレギュレーション満たしたコード書いてみたいですよね!

というわけで、LINEに登録。

IFTTT

ifttt.com

まずはIFTTTについて情報あつめ。

www.atmarkit.co.jp

うんうん、大体わかった(フラグ

Continue with Google or Facebook からGoogleでログイン。

  1. ログイン後、右上のユーザーのアイコンをクリックして Create を選択

  2. IF + THEN THIS THAT の + をクリック

  3. たくさんのアイコンが出てくるので、検索窓に web と入力。出てきた Web Hook をクリック

    f:id:sironekotoro:20190901160306p:plain

  4. Connect Webhooks 画面のConnect ボタンをクリック

  5. Receive a web request をクリック

    f:id:sironekotoro:20190901160311p:plain

  6. Event Name は適当に・・・send_message とかにしとこう。ほんで、 Create trigger ボタンをクリック

    f:id:sironekotoro:20190901160315p:plain

  7. おぉ、+ のところが Web Hook のマークになった。では次の + のマークのところをクリック。

    f:id:sironekotoro:20190901160317p:plain

  8. 検索窓に今度は LINE と入力して、出てきた LINEの緑のアイコンをクリック

    f:id:sironekotoro:20190901160321p:plain

  9. Connectボタンを押すと、LINEのメアドとパスワード入力画面が出るのでそれを入力

  10. 同意して連携する、をクリック

    f:id:sironekotoro:20190901160324p:plain

  11. Send message という緑のボタンが出てくるのでそれをクリック

    f:id:sironekotoro:20190901160328p:plain

  12. Recipient という画面が出てくる・・・とりあえず、何も変更せずに Create action をクリック

    f:id:sironekotoro:20190901160333p:plain

  13. 確認画面が出てくるので Finish をクリック

    f:id:sironekotoro:20190901160339p:plain

  14. 次に出てきた画面で、Web Hook のアイコンをクリック

    f:id:sironekotoro:20190901160324p:plain

  15. 画面右上にある Documentationをクリック

    f:id:sironekotoro:20190901160343p:plainf:id:sironekotoro:20190901160348p:plain

  16. 画面下部にある curl -X POST https://maker.ifttt.com/trigger//with/key/YOUR_KEY をターミナルから実行

  17. LINE Notify にメッセージが届く

届いたメッセージは

[IFTTT] Value 1:
Value 2:
Value 3:

ふむ。とりあえず、URLにアクセスすると、LINEに何か通知が届く、というところまではできた。

IFTTTのAPI調整

このDocumentationのページ、薄い四角のところは実は文字が入力可能だったりする。

{event} に 6. で決めたEvebt Name である send_message を入力、Value1 , 2, 3 にそれぞれ対応する何か適当な文字を入れて、画面下部にあるTest it!を使うと・・・おぉ、ちゃんと文字が入ったメッセージが届いた!

f:id:sironekotoro:20190901160511p:plain

あ、画像からはKEYは消してます。

ということで、このURLをPerlで組み立て、そしてMojolicious::Liteのボタンを押した時にURLに送信するようにすれば、レギュレーションは満たせそう。

ちなみに、このDocumentationページから入力が可能ってのがわからなくて、2時間くらい試行錯誤したよ!

というわけで、出来上がったコードがこれです。第5回はサブルーチンもやったので、IFTTTにリクエスト投げるところはサブルーチンにしてみました。

ボタンを押すと、IFTTT API経由で定型メッセージをLINEに流す君

クリックするとコードが出てきます gist.github.com

注意

このコードをインターネットに置いた場合、誰彼構わずボタンが押しまくられるだろうことは想像に難くないです。

ただ、自分の手元のパソコンで利用するだけであれば十分でしょう。

IFTTTと連携したレシピをPerlから制御することができる、というのは日々の暮らしがちょっと楽になる素晴らしい技術だと思います。

また、IFTTT経由ではなく、直接LINEのAPIを使ってみるのも良いかもしれません。

developers.line.biz

ぜひ、試行錯誤して日々の煩わしい業務や暮らしを楽にしてください!

試行錯誤の後

Perl入学式 2019 in東京 第5回 お疲れ様でした

受講された方、サポーターの方、気温と湿度の高い中、お疲れ様でした。 講師をやったジャージの人です。

Perl入学式第5回です。Webアプリに関するスライドも大改修を施しました。その影響でいくつか表記の抜けや不整合があり、お手数とご迷惑をおかけしました。すみません。

スライドの修正は完了しており、近々反映予定です。スライドの修正が完了しました。

コードの追加や削除、編集をGitHubのdiffを使うことで、わかりやすく提示できたと考えています。

講義に利用したスライドはMarkdown形式で公開しています。復習に使ってください。スライドの最後の方に「落ち穂拾い」として追加のコンテンツもあります。チャレンジしてみてください。

github.com

スライド中にある練習問題の解答例を掲載しています。TMTOWTDI(There's More Than One Way To Do It.(やり方は何通りもある))の一つとして、参考にしてください。

github.com

問題の意味がわからない、とか、このような解答例はどうだろう?という方はSlackのPerl入学式チャンネル(招待フォーム)やtwitterハッシュタグ #Perl入学式 をつけて聞いてみてください。 応答速度、監視頻度などの面からSlackの方をお勧めします。

Perl入学式を終えた方へ

一昨年、同じようなエントリ書いていたんで、リンク貼っておきます。

sironekotoro.hateblo.jp

以下、講義中に言及したオススメ。

また、これまでの第5回 Webアプリ編 の自分のエントリを並べておきます。

2019年後半のPerl入学式

従来、Perl入学式 in東京 は年に2回、同じ内容を繰り返す受講スケジュールでやってきました。

が、今年後半の構成はちょっと変わる可能性があります。

(変わらない可能性もある)

connpassやTwitter、Slackでの告知があると思いますので、興味のある方は続報をお待ちください。

YAPC::Japan ?????

海外からの情報なんですが、来年の3月末くらいにPerlのお祭り YAPC::Japanが開催されるようです。公式発表を楽しみに待ちましょう。

YAPCPerlの話はもちろん、様々なシステムやサービスの構成、ベストプラクティスや新しい知見を得られる場です。参加を強くお勧めします。受講した方とお会いできるのを楽しみにしてます。

perlcon.eu

(動画の18分あたり)

懇親会などで出た話の元ネタやリンクなど

今回の懇親会のお題については別エントリで。

謎のカタツムリ

my @hoge = @_;

行末のセミコロンまで含めると、さらにカタツムリ度が増す感

PerlからGoogleの感情分析を使ってみる

お盆明けから妙に気分が沈んだり、なんかおかしいなーって感じですが、みなさんお元気でしょうか。うちはダメです。

で、ほぼ毎日twitterでつぶやいているツイ廃としては、過去に呟いた内容をもとに、いつからおかしくなったのか?ってのを調べてみようと思ったのです。

今回はGoogleの感情分析APIを使ってみるところまでやってみました。暗く沈んでる時期の自分のtweet漁るのは怖さがある・・・

しかし、いつもの通りPerl用のライブラリやSDK(Software Development Kit)は提供されていない・・・けど、WebAPIであればリクエストを投げて受け取るだけなので安心(安心?

JSONでPOSTして、JSONで受け取る、ということなのでSDKなくてもなんとかなりそうですね。

ただ、APIを利用するためにGoogle CloudのAPI_KEYの取得とクレジットカード登録の必要があり、そこはハードルが高いかもしれません。

うちの場合、昔、BigQuery試してみた時にクレカを登録していました。

参考にしたところ

Google Cloud Natural Language API 公式サイト

cloud.google.com

GoogleAPI使うための認証情報の扱いとか。特に環境変数の設定のあたり

cloud.google.com

公式サイトのほか、こちらのページが大変参考になりました。実装はPHPですが、curl をつかってリクエストを投げてくれるところがあって、Perlで書くにあたって大変助かりました。

www.casleyconsulting.co.jp

macOS用のパッケージ管理ソフト brew cask をつかってgoogle-cloud-sdkを入れる際に参考にしました。

qiita.com

動かすまで

  1. google-cloud-sdk をインストール

    $ brew cask install google-cloud-sdk

    $ brew cask info google-cloud-sdk

  2. 秘密鍵のファイルをローカルにおいて、パスを通したりする

  3. 早く動かしたいので、コマンドラインAPI_KEYを表示させて、それをスプリプトに直接入れることにする

    $ gcloud auth application-default print-access-token

  4. PerlのWebクライアントでAPIを叩いて、 返ってくるエラーを見ながらGCPの権限をつけたり、コードを直したりする

    • Furl ではうまく動いたので、標準に寄せるべく HTTP::Tiny で書き直してたらハマった・・・

    • ハマった原因は、headers と書くところを header と書いていたからなのでした

  5. うごいたー!

こんな感じです。あー、参考にならない書き方ー

あと、やはり環境構築が鬼門だなぁという感じ。

コード

でも、コードがあればなんとかなりそうなので、コードを置いておきますね。

gist.github.com

PerlでWebから情報を持ってくるときのツール

まず注意

昨今のWebではhttps化が進んでいます。https の s はSecureの s です。安全な通信をするためにデータを暗号化/復号するのですが、そのためのモジュールが別途必要です。

運が良ければ、すでにインストールされているかも・・・

Perl入学式で構築した手元のMsys32(Windows7上に構築)だと

  • のちに出てくる HTTP::Tiny のサンプルスクリプトを試したところ、httpのサイト(例:http://www.hatena.ne.jp/)はアクセスできるが、httpsのサイト(例:https://www.yahoo.co.jp)にPerlスクリプトからアクセスできず。

  • エラーメッセージによると、 Net::SSLeay が無いとのこと。

  • cpanm Net::SSLeay

    • インストール失敗
    • エラーメッセージによると、MSYS側にSSL環境がなさそう
  • pacman -Syu

    • MSYS側の更新がなくなるまで複数回実行&再起動
    • エラー変わらず
  • pacman -S openssl

    • f:id:sironekotoro:20190728112219p:plain
    • MSYSのopensslのインストールはできたが、 cpanm Net::SSLeayは失敗
  • pacman -S perl-Net-SSLeay

    • cpanm じゃなくて、MSYSのパッケージマネージャ(pacman)で入れてみることにする
    • f:id:sironekotoro:20190728114636p:plain
    • インストールはされたが、httpsのサイトへのアクセスは失敗する・・・が、エラーメッセージが変わった!一歩前進!
    • こんどは IO::Socket::SSLがないだとー
  • cpanm IO::Socket::SSL

    • 大文字小文字を厳密に見てくるので注意
    • こっちはなんか時間切れとか出てくる。きー!
  • pacman -S perl-IO-Socket-SSL

    • インストールできた
  • うごいた!

こんな感じでした。ここまでが長い。でも、動いてしまえば「許そう、全てを・・・」って気分になりませんか。うちはなります。

HTTPクライアント編

HTTPクライアントというのは、Chromeとか、Firefoxとかそういったブラウザです。HTTPサーバーにアクセスし、情報を得るものです。

プログラムからもHTTPサーバにアクセスして情報を得ることができます。

以下はPerlでのHTTPクライアントの紹介です。

HTML::Tiny

perldoc.jp

Perl 5.13.9以降であればコアモジュールとしてPerl本体と一緒にインストールされるモジュール。なので、普通にPerlをインストールすれば大概の環境では使えるはず。

$ corelist HTTP::Tiny

Data for 2018-11-29
HTTP::Tiny was first released with perl v5.13.9

HTTP::Tiny のページにあるサンプルをPerl入学式にある範囲で書き直したものです。あと、対象を日本のYahoo Japanに変えています。

$response->{content} とか、リファレンス回をやった人だと見覚えがあるのではないでしょうか。ハッシュリファレンスですね。

use strict;
use warnings;
use HTTP::Tiny;

# Yahooのurlを変数に入れる
my $yahoo_url = 'http://www.yahoo.co.jp';

# HTTP::TinyでYahooにアクセスして、getでデータを持ってくる
my $response = HTTP::Tiny->new->get($yahoo_url);

# データを持ってくるのに成功したかどうかを判別
if ( $response->{success} ) {
    # 成功してたら、HTTPのステータスコードを表示。200ならok
    print "$response->{status} $response->{reason}\n";
}
# 失敗してたら、プログラム終了
else {
    print "Failed!\n";
    exit();
}

print $response->{content}; # ヤフーのHTMLが表示される

LWP::Simple

perldoc.jp

ここからは cpanm でのインストールが必要なモジュール。LWP というのは ibwww-perl の略称で、PerlをつかってWebにアクセスするときの基本的なモジュールです。その機能最小版です。

use strict;
use warnings;
use LWP::Simple;

# Yahooのurlを変数に入れる
my $yahoo_url = 'http://www.yahoo.co.jp';

# getでデータを持ってくる
my $content = get($yahoo_url);

# データ持ってくるのに成功したら表示する
if ($content) {
    print $content;    # ヤフーのHTMLが表示される
}

HTTP::Tinyと大体同じですね。うちのコメントも早くも簡素になってきました。

LWP

perldoc.jp

上記 LWP::Simple の大元である、HTTPアクセスに必要なことの全てが詰まったフルセット版です。 ユーザーエージェントの変更やPOSTでのデータ送信など、HTTP通信を使った機能が詰まっています。

use strict;
use warnings;
use LWP::UserAgent;

# Yahooのurlを変数に入れる
my $yahoo_url = 'http://www.yahoo.co.jp';

# LWP::UserAgentのインスタンスを生成する
my $ua  = LWP::UserAgent->new();
# HTTP::Requestのインスタンスを生成するついでに、GETでアクセスしに行くページも設定する
my $req = HTTP::Request->new( GET => $yahoo_url );

# uaがGETでyahooにデータを取りに行く
my $res = $ua->request($req);

# ページの取得に成功してたら内容を表示する
if ( $res->is_success ) {
    print $res->content;  # ヤフーのHTMLが表示される
}

Furl

perldoc.jp

tokuhiromさん作成の「早い」ライブラリ。

Furlはもう一つのHTTPクライアントライブラリです。LWPはPerl5のデファクトスタンダードな HTTPクライアントですが、クリティカルなジョブでは遅すぎますし、週末のハッキングには 複雑過ぎます。Furlはこれらの問題を解決します。楽しんで下さい!

use strict;
use warnings;
use Furl;

# Yahooのurlを変数に入れる
my $yahoo_url = 'http://www.yahoo.co.jp';

# Furlのインスタンスを生成するついでに、GETでアクセスしに行くページも設定する
my $res = Furl->new->get($yahoo_url);

# ページの取得に成功してたら内容を表示する
if ( $res->is_success ) {
    print $res->content;
}

Webスクレイピング

ここまで紹介したものは、Webサーバーにアクセスして、そこから情報を取得するものでした。しかし、ほとんどの用途では、Webの情報が全て必要なわけではなく、ごく一部のみ必要だったりします。

そういった情報を都度、正規表現などを使って取り出してもいいですが、もっと楽はできないものでしょうか?そんなプログラマの三大美徳「怠惰」を実現させるツール、ありそうですよね。

Webサーバから返ってきたデータのうち、必要な部分だけ取り出す、こそげとる、それがスクレイピングと言われるツールです。

ここからはPerlスクレイピングモジュールを使ってYahooニュースからトピック一覧だけ取り出してみます。

Web::Scraper

metacpan.org

うちはこのツールをPerl CPANモジュールガイド という本で知って、ほぼこれをメインに使っています。要素の指定にXPathCSSセレクタを利用することができます。

この本には他にもPerlのモジュールがたくさん載っているので、やや古いとはいえオススメです。

use strict;
use warnings;

# 日本語をターミナルで表示(出力)するときに警告が出ないよう書いておく
binmode STDOUT, ':encoding(UTF-8)';

use Web::Scraper;
use URI;

my $yahoo_news_url = 'https://news.yahoo.co.jp/topics';

my $uri = URI->new($yahoo_news_url);

# どの要素を取得するかを設定する変数
my $scraper = scraper {

    # タイトルを取得するスクレイパー
    # Xpath形式で指定
    process '/html/head/title', 'page_title' => 'TEXT';

    # ニュースタイトル一覧を取得するスクレイパー
    # CSSセレクタ形式で指定
    process 'li.topicsListItem > a ', 'news_titles[]' => 'TEXT';
};

# ここでスクレイピングが実行される
my $res = $scraper->scrape($uri);

# ここから結果表示
print $res->{page_title} . "\n";    # タイトルの表示

for my $title ( @{ $res->{news_titles} } ) {
    print $title . "\n";    # ニュースのタイトルの表示
}

Web::Query

metacpan.org

Web::Query is a yet another scraping framework, have a jQuery like interface.

Javascriptのライブラリ、JQueryに似た方法で要素を指定してスクレイピンすることができるモジュールです。実はJQuery使ったことないので、わからない・・・

でも、直感的に要素を指定できる感じで良いですね。

use strict;
use warnings;

# 日本語をターミナルで表示(出力)するときに警告が出ないよう書いておく
binmode STDOUT, ':encoding(UTF-8)';

use Web::Query;

my $yahoo_news_url = 'https://news.yahoo.co.jp/topics';

# topicsListItem 属性をもつ li タグの下にある a タグを持ってきて、
# a タグのテキスト要素を表示する
wq($yahoo_news_url)->find('li.topicsListItem > a')->each(
    sub {
        print $_->text . "\n";
    }
);

HTML::TreeBuilder

metacpan.org

スクレイピングツールのくくりに入れましたが、HTML::TreeBuilderはHTMLをパース(解析)するためのツールです。

正確なHTMLは<html>の中に<head><body>タグがあり、その中にさらに複数のタグが配置されています。

これを、<html>という太い幹から<head><body>という枝が生え、さらにその枝から別の枝が生え・・・という木構造で解析していきます。

本来は「特定の枝の先にだけ、何らかの処理を行う」ということをしたかったのですが、ちょっと今の自分ではわかりませんでしたわ・・・

use strict;
use warnings;

# 日本語をターミナルで表示(出力)するときに警告が出ないよう書いておく
binmode STDOUT, ':encoding(UTF-8)';

use HTML::TreeBuilder;

my $yahoo_url = 'https://news.yahoo.co.jp/topics';

# new_from_urlメソッドは裏でLWP::UserAgent使っているので、
# LWP入れてないと動かない
my $tree = HTML::TreeBuilder->new_from_url($yahoo_url);
$tree->eof();

foreach $a ( $tree->find("a") ) {

    # news の a タグには data-ual-gotocontent って属性が付与されてた
    # ところで、ual って何だろう・・・urlならわかるんだけど
    my $data_ual_gotocontent = $a->attr('data-ual-gotocontent') || next;

    if ( $data_ual_gotocontent eq 'true' ) {
        print $a->as_text . "\n";
    }
}

閑話休題

WWW::Mechanize

perldoc.jp

一部の人には刺さるかもしれません。私も刺さった人です。

今まで紹介したものは、Webサーバから情報を持ってくるものと、Webページから特定のデータを抜き出す(スクレイピングする)ものでした。

ここで紹介する WWW::Mechanize 、通称Mechメック は、Webサーバからデータを受け取り、そのあとにリンクをたどったり、入力欄に文字を入れてボタンを押す、なんてことができます。

以下は、Yahooの検索ページの入力欄に「Perl入学式」という文字を入力して、その検索結果を得るスクリプトです。

use strict;
use warnings;
use utf8;

# 日本語をターミナルで表示(出力)するときに警告が出ないよう書いておく
binmode STDOUT, ':encoding(UTF-8)';

use WWW::Mechanize;

# Yahooの検索ページのURL
my $yahoo_search_url = 'https://search.yahoo.co.jp/';

# WWW::Mechanizeのインスタンスを生成する
my $mech = WWW::Mechanize->new();

# Yahooの検索ページのHTMLをGETで取得する
$mech->get($yahoo_search_url);

# 取得したHTMLを解析し、フォームの入力欄に文字を入れて、
# そのフォームのボタン(検索ボタン)を押す
$mech->submit_form(
    # 最初のフォーム
    form_number => 0,

    # name属性が p の入力欄に 検索対象の文字を入れる
    fields => { p => 'Perl入学式' },
);

# 結果を表示する
print $mech->{content};

どうでしょう、ずらっとHTMLが表示されて、その中にかすかに検索結果らしきものが見えますね・・・これを整形したい・・・と思いませんか?

うちは思います。

ということで、ここで、先に登場したスクレイピングツールを使います。WWW::MechanizeでYahoo検索した結果から、タイトルとリンクをWeb::Scraperで取り出してみます。

use strict;
use warnings;
use utf8;

# 日本語をターミナルで表示(出力)するときに警告が出ないよう書いておく
binmode STDOUT, ':encoding(UTF-8)';

use WWW::Mechanize;

my $yahoo_search_url = 'https://search.yahoo.co.jp/';

my $mech = WWW::Mechanize->new();

$mech->get($yahoo_search_url);

$mech->submit_form(
    form_number => 0,
    fields      => { p => 'Perl入学式' },
);

my $mech_result = $mech->{content};

# -----ここまでが WWW::Mechanize の部分

# -----ここからは Web::Scraper の部分

use Web::Scraper;

my $scraper = scraper {

# 検索結果を取得するスクレイパーを用意しているところ
# Xpath形式で指定 //と書くことで前方の要素を省略する
# ここでは、
# id が web 要素のdivタグを撮ってきて、その下(直下とは限らない)にある liタグを持ってくる
# という内容
    process '//div[@id="web"]//li', 'search_result[]' => scraper {

    # さらに、その取得条件からタイトルとリンクを分けて取得し、
    # ハッシュで格納する
        process '//a', 'title' => 'TEXT';
        process '//a', 'url'   => '@href';
    };

};

# ここでスクレイピングが実行される
# スクレイプ対象にはmechで検索した後のページを入れる
my $res = $scraper->scrape( $mech_result );

# ここから結果表示
for my $result ( @{ $res->{search_result} } ) {
    print '-------' . "\n";
    print $result->{title} . "\n";
    print $result->{url} . "\n";
    print '-------' . "\n";
}

Web API

さっきので終わらせようと思ったんだけど、ふと、昨今のWebからのデータ取得をいうならWeb APIは外せないのでは?と思い至りました。遅い。

Web APIは簡単にいうと、プログラムが使いやすいように整形されたデータのやり取り、というところでしょうか。詳細はググったり調べたりでお願いします。

もちろん、上記のHTTP::TinyとJSON::PPモジュールを利用することでWeb APIで提供されているデータの取得と利用が容易になります。どちらもPerlのコアモジュールです(5.13.9 以降)。

ここでは、無料で利用できるAPIとして、Livedoorの天気予報APIを利用します。

weather.livedoor.com

地域は東京地方、今日(2019年8月4日)の最高気温を調べてみます。

HTTP::Tiny + JSON::PP

use strict;
use warnings;
use HTTP::Tiny;

# Livedoor天気APIのURLを変数に入れる
my $livedoor_weather_url = 'http://weather.livedoor.com/forecast/webservice/json/v1?city=130010';

# HTTP::TinyでAPIにアクセスして、getでデータを持ってくる
my $response = HTTP::Tiny->new->get($livedoor_weather_url);

# データを持ってくるのに成功したかどうかを判別
if ( $response->{success} ) {
    # 成功してたら、HTTPのステータスコードを表示。200ならok
    print "$response->{status} $response->{reason}\n";
}
# 失敗してたら、プログラム終了
else {
    print "Failed!\n";
    exit();
}

# 取得したデータはJSON形式なので、Perlで操作しやすいように変換する。

use JSON::PP;

my $decoded_response = decode_json($response->{content});

# どんなデータ構造になっているかわからなくなったら、Data::Dumperを使おう!
# use Data::Dumper;
# print Dumper $decoded_response;

# 最高気温
print $decoded_response->{forecasts}->[0]->{temperature}->{max}->{celsius}; # 34

34度!ヤダもう・・・

ところで、my $livedoor_weather_url = 'http://weather.livedoor.com/forecast/webservice/json/v1?city=130010'; の末尾の数字を 270000 に変えることで、大阪の最高気温を出したりすることができます。

ここを変数にすると、他の地域の気温を見たいときに楽ですね。

さらに、その数字を標準入力から入れられると楽かも。

さらにさらに、Webアプリから地域を選べるようにすれば・・・となります。

gist.github.com

そんな、Webアプリからの入出力を学べる Perl入学式 2019 第5回 in東京 お待ちしております!

perl-entrance-tokyo.connpass.com

ojo

Perl入学式 in沖縄の id:AnaTofuZ さんから教えてもらったのがこのツール。

metacpan.org

cpanm ojo でインストールして、コマンドラインから1行、ワンライナーです。

$ perl -Mojo -E'my $content = j g("http://weather.livedoor.com/forecast/webservice/json/v1?city=130010")->{content}->{asset}->{content};say $content->{forecasts}->[0]->{temperature}->{max}->{celsius}'

ということで

PerlでWebにアクセスしてデータ集める方法でした。

Perl入学式 in 東京第4回でピザ会でのお題「双六」

Perl入学式 in 東京第4回でピザ会でのお題「双六」

今回の id:xtetsuji さんからのお題は双六で「あがり」までの平均何回サイコロを振れば良いか?を計算するものでした。 問題のレギュレーションは既にサポーターの id:nishiru3 さんが書いてくれております。 @nishiru3++

nishiru3.hatenablog.com

うちはこんな感じで解いてみました。平均6.3回振ればゴールにたどり着けそう、って感じですかね・・・

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

my $number_of_trials      = 1000000;    # 試行回数。
                                        # 100回とか10万回とか変えられるよう、変数に入れておく。
my $number_of_dice_rolled = 0;          # サイコロを振った合計回数を格納する変数。


for ( 1 .. $number_of_trials ) {        # 試行回数分繰り返す

    my $sum = 0;             # 1試行あたりのサイコロの目の合計を初期化
    while ( $sum < 10 ) {    # 目の合計(双六のマスの位置)が10以下の時は繰り返す
        $number_of_dice_rolled++;   # サイコロを振った回数 +1 する
        $sum += int( rand(6) + 1 ); # サイコロを振る(1〜6の目が出る)

        # サイコロの目の合計(双六のマスの位置)が3の倍数、かつ、
        # サイコロの目の合計が10未満の時は「振り出しに戻る」
        if ( $sum % 3 == 0 && $sum < 10 ) {
            $sum = 0;
        }
    }
}

print $number_of_dice_rolled / $number_of_trials;

今回のピザ会は盛況だった

他にも

とネタが多くて、一通り書いてからブログ公開だとしばらくかかりそうだなーってことで、先にかいた双六から公開しまする。