コードを書きたい

コードが書きたかった

広島工業大学休講botを作った

はじめに

大学がオンラインの体制に変わって約2ヶ月が経とうとしています。

しかしながらオンラインの授業体制という状況でも大学の授業は休講になる授業もあるらしく、そこで、休講情報をTwitter上で手早く迅速にキャッチできるようにという理由で、今回、広島工業大学の休講botを作りました。

以下リンクになります。

 

作り始めようとした経緯

もともと、休講情報をTwitterに情報共有したいなという理由で作りたかったという意思はありました。このプロジェクト自体も、今年に入ってすぐに立ち上げたもので最初はPHPを利用して自分のスキルをあげたいという目的のもと作ることを決意しました。

最初は以下のスライドを参考にしていました。

speakerdeck.com

つまづいたところ

この休講botですが、今年のはじめにプロジェクトを立ち上げたのに完成したのは半年もかかった7月の中旬になります。

なぜこんなにも時間がかかってしまったのかというのを書いていきたいと思います。

使用するライブラリの影響

プロジェクトを立ち上げようとした際、このプロジェクトに欠かせない構成として、学校のポータルサイトから休講情報をクローリング する必要がありました。

そこでPHPでクローリング をするために必要なライブラリを探して、実際に導入し、まずはポータルサイトにログインするところから始めようとしました。

最初に使ったライブラリというのがこちらの記事に書いてあるGoutteというライブラリです。

qiita.com

qiita.com

こちらのライブラリを自分のプロジェクトに導入してみて使ってみようと思ったのですが、なぜかこのライブラリでは広島工業大学ポータルサイトにログイン自体ができなかったのです。

問題を探ろうにも自分の力ではなにもできず、このプロジェクトは放置されておりました。

休講情報を取得する方法について

後述するのですが、このプロジェクトは現在、上記のGoutteというライブラリではなく、php-webdriverというライブラリを活用して運用されています。

github.com

このライブラリを使い、ポータルサイトにログインして、いざ、休講情報をとりだすことには成功しましたが、id属性で指定されているtableの文字列を全て抽出してしまうため、全ての休講情報をとりだしてしまい、ひとつひとつの休講情報をとりだすことができませんでした。

string(118) "工学部 電子情報工学科 3年次 2020/07/20(月) 1・2時限 アナログ電子回路 オンライン授業"
string(119) "工学部 建築工学科 3年次 2020/07/20(月) 7・8時限 建築設備B 橋本 俊二 オンライン授業" 

 このように情報を取得したかったのですが…

string(296) "学部 学科 開講年次 日付 時限 授業名 備考
工学部 電子情報工学科 3年次 2020/07/20(月) 1・2時限 アナログ電子回路 オンライン授業
工学部 建築工学科 3年次 2020/07/20(月) 7・8時限 建築設備B 橋本 俊二 オンライン授業" 

 このように情報が取得されてしまいます。

最初の”学部 学科 開講年次 日付 時限 授業名 備考”なんて文字列は取得されたくないし、このままツイートされてしまうとTwitterの140文字制限に大いに関わってくる問題になってしまう可能性などが懸念されます。

そこで利用したのがXpathで取得したい情報を取得するという方法でした。

Xpath自体は初めて知ったことだったのですが、調べてみるとそんなに難しいことでもなく、初めて知った僕でも20分あれば全然理解できるような内容でした。

このwebdriverというライブラリではXpathを利用してクローリング はできるのですが、実際にこのライブラリにあったXpathの書き方をしなければならないというのが少し難しかった印象を受けます。

実際に書いたコード

<?php
require_once(__DIR__ . '/vendor/autoload.php');
require_once(__DIR__ . '/config.php');

use Facebook\WebDriver;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Chrome\ChromeDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Abraham\TwitterOAuth\TwitterOAuth;

class Crawling {
    public function access()
    {
        // ダウンロードしたchromedriverのパスを指定
        $driverPath = realpath("/usr/local/bin/chromedriver");
        putenv("webdriver.chrome.driver=" . $driverPath);

        // Chromeを起動するときのオプション指定用
        $options = new ChromeOptions();

        // ヘッドレスで起動するように指定
        $options->addArguments(['--headless']);

        $caps = DesiredCapabilities::chrome();
        $caps->setCapability(ChromeOptions::CAPABILITY, $options);

        $driver = ChromeDriver::start($caps);

        $driver->get('ポータルサイトのログインページURL');

        // テキストボックス入力
        $driver->findElement(WebDriverBy::id('ログイン用のIDinput要素のID属性'))->sendKeys(LOGIN_ID);

        $driver->findElement(WebDriverBy::id('ログイン用のパスワードinput要素のID属性'))->sendKeys(LOGIN_PASSWORD);

        // ボタン押下
        $driver->findElement(WebDriverBy::id('ログインボタンのID属性'))->click();
        
        $driver->get('ポータルサイトの休講情報を掲載しているページURL');

        $twitter = new TwitterOAuth(APIKey, APISecretKey, AccessToken, AccessTokenSecret);

        $results = $driver->findElements(WebDriverBy::xpath("(//table[@id='休講情報を掲載しているtable要素のID属性'])/tbody/tr[1 < position()]"));
        foreach ($results as $result) {
            $twitter->post(
                "statuses/update",
                array("status" => $result->getText())
            );
        }

        if($twitter->getLastHttpCode() == 200) {
            // ツイート成功
            print "tweeted\n";
        } else {
            // ツイート失敗
            print "tweet failed\n";
        }

        // ブラウザを閉じる
        $driver->close();
    }
}

$c = new Crawling();
$c->access();

たったこれだけです。

一応、サイトのURLやID属性などは隠しておきます。見たい方は後述のGitHubから見てみてください。

        $driver->get('ポータルサイトのログインページURL');

        // テキストボックス入力
        $driver->findElement(WebDriverBy::id('ログイン用のIDinput要素のID属性'))->sendKeys(LOGIN_ID);

        $driver->findElement(WebDriverBy::id('ログイン用のパスワードinput要素のID属性'))->sendKeys(LOGIN_PASSWORD);

        // ボタン押下
        $driver->findElement(WebDriverBy::id('ログインボタンのID属性'))->click();

このコードで大学のポータルサイトにログインするための処理を書いています。

        $results = $driver->findElements(WebDriverBy::xpath("(//table[@id='休講情報を掲載しているtable要素のID属性'])/tbody/tr[1 < position()]"));
        foreach ($results as $result) {
            $twitter->post(
                "statuses/update",
                array("status" => $result->getText())
            );
        }

このコードは休講情報を一行ごとに取得してTwitterにポストする処理を書いています。

Xpathのところがすこしだけ苦労しました。

要するにこんな感じ…

疑問点

これまでにこのbotをつかって休講情報を流してみましたが何点か疑問に残る点があります。

TwitterAPIがうまく動かない

これはツイートを送信する際におこることなのですが、TwitterAPIで休講情報をポストする際、なぜか401エラーが帰ってきてしまうことがあります。原因は不明ですが、時間をおくとなぜか送信ができるようになっていたりして、不具合として考えられます。早くなんとかしたいと考えています。

休講情報がない場合に実行させるとどうなるのか

夏休みなど授業がないときにこのプログラムを実行させるとどうなるのかいまだにわかっておらず、空のツイートがされてしまう可能性もあり、タイムラインに意味のないツイートを流してしまう恐れがあります。いずれ、定期実行をしたいと考えているので、はやくなんとかしないといけない作業の一つでもあります。(たぶんすぐにできると思う)

おわりに

このプロジェクトはまだまだ改善の余地がたくさんありますし、実際にいまでも仮想環境から手作業でphpを実行させてツイートをしています。

早くサーバーに移行させて定期的に実行させるようにしたいと考えています。

そして、このプロジェクトに関して、先駆者であった先輩や、Discordで相談に乗ってくださった社会人の方々、ライブラリを提供してくださっている方々に感謝を込めておわりとさせていただきます。協力してくださってありがとうございました。

GitHubはこちらです。PR待ってます。

github.com