顧客フロントSEのIT勉強ブログ

2022 Japan AWS Top Engineer / 2022-23 Japan AWS Certifications Engineer。AWS認定12冠、情報処理試験全冠。顧客フロントSEがなるべく手を動かしながらIT技術を学んでいくブログです。

AWSを使って読書メーターのブログパーツを作ってみた

パソコンからアクセスされている方は、画面右のサイドバーに読書メーター」の棒グラフが表示されていると思います。

読書メーターは読書記録と感想を残せるサイトですが、読書記録をこのブログに連動できないかなーと考え、AWS LambdaやGoogle Chartsを使ってこのパーツを実装してみました。

読書メーターってブログパーツ無いの?」と、このブログに辿りついた方はあまり気軽に試せる方法ではないかもしれませんが、参考までに見ていってください。

処理方式概要

まず読書メーターのマイページのHTMLソースを確認したところ、下記を実行すればJSON形式で読んだページ数が取得できるようでした。

https://bookmeter.com/users/{USER-ID}/summary/graph_data/monthly/page?from={YYYY-MM-DD}

これをこっそり使ってやろう!と思ったのですが、ブログに組み込むとCORS(Cross-Origin Resource Sharing)が有効になっていて、読書メーターのサイトからでないと実行できませんでした。公開APIではないのでそりゃそうですよね・・・

そこでやや大がかりになってしまいますが、次のステップを踏むことにしました。

処理①:Lambdaを使ってマイページをスクレイピングし、必要情報を取得→S3へファイル保存
処理②:ブログに組み込んだJavaScriptでS3からデータ取得し、Google Chartsを起動してグラフ化

Lambda関数を実装する言語はPythonを使うのがベターだと思いますが、直近Javaを勉強していたこともあり、敢えてJavaで実装してみました。

ここからは処理①、②それぞれの実装内容を解説していきます。

処理①:Lambda+Javaによるスクレイピング

IDEとしてEclipseを採用したので、AWS Toolkit for Eclipseというプラグインを使います。
このプラグインにはJavaからAWS操作を行うためのライブラリ(AWS SDK for Java)が入っていたり、EclipseのメニューにAWSアイコンが追加されてLambda関数向けのJavaプロジェクトの枠を簡単に作成できたりします。

また、Javaによるスクレイピングにはjsoupというライブラリを使いました。Toolkitから作ったJavaプロジェクトは、プロジェクト管理(ビルドツール)としてMavenが採用されていますので、pom.xmlに追記してjsoupを取り込みます。

Lambda関数の処理フローは次のような内容です。

STEP1)読書メーターのマイページを開き、必要情報をスクレイピングする
STEP2)S3からファイルを取得して、記載されているデータを配列に格納する
STEP3)古いデータを削除し、スクレイピングで取得した情報に差し替えてS3にファイルをアップロードする

まず、STEP1のスクレイピング部分は実装は下記です。

public String[] scrapingInfo(String url) throws IOException {
// スクレイピング結果を格納する変数
String[] info = new String[2];

// 読書メーターのマイページにGETリクエストを送る
Document doc = Jsoup.connect(url).get();

// マイページのサイドバーにあるプロフィールを取得する
Elements elements = doc.select("dd.bm-details-side__item");

// 必要な要素を順に取得する
for (Element element: elements){
// 読んだ本
if (element.text().contains("冊")){
info[0] = element.text().substring(0,element.text().indexOf("冊"));
}
// 読んだページ
if (element.text().contains("ページ")){
info[1] = element.text().substring(0.element.text().indexOf("ページ"));
}
}
return info;
}

スクレイピングで最新の読んだ冊数とページ数の2つの要素を取得しています。
Lambda関数のメインメソッドとなるhandleRequestメソッドから、このscrapingInfoメソッドを呼び出し、スクレイピング結果をString型の配列として取得します。

次にSTEP2~3で扱うS3ファイルには、過去3週間の読書記録が記載されています。
スクレイピングで過去3週間の冊数・ページ数を一括で取得できればよかったのですが、マイページにはその情報がありませんでした。
そこでスクレイピングを日次で動かし、過去のスクレイピング結果をS3ファイルに残すことで対応しています。
その部分の実装詳細は割愛しますが、AWS SDKに用意されているAmazonS3ClientBuilderクラスを使ってS3バケットにアクセスし、ファイルのGET・PUTを実装しています。

最後に、このLambda関数をEventBridgeを使って毎日1回実行します。

JavaによるLambda関数の開発準備と実装内容は、後日別の記事で詳細をまとめようと思います。

(2022/06/23追記)
Lambda(Java)のスクレイピング処理について実装内容をまとめました。

処理②:Google Chartsによるグラフ化

Google Chartsは、Javascriptsから呼び出すことでWebサイトにグラフを埋め込むことが出来ます。基礎データを渡せば棒グラフ、円グラフ、分布図、バブルチャートなどのグラフ画像が返却される仕組みです。

はてなブログでは管理画面からデザイン > カスタマイズ > サイドバーと辿ると任意のHTMLソースを埋め込むことが出来ます。これを使ってGoogle Chartsを呼び出すJavascriptを埋め込み、ブログを開くたびにS3からデータを取得して最新情報でグラフを描写しています。

ブログに埋め込んだHTMLは次のようなものです。

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"</script>
<scrpit type="text/javascript">
google.charts.load('current', {'package':['corechart']});
google.charts.setOnLoadCallback(drawChart);

function drawChart(){
const csvUrl = {S3に格納したファイルパス};
var arraydata = getCsv(csvUrl);
var data = google.visualization.arrayToDataTable(arraydata);
var options = {
width:300, height: 200,
series: { 0:{targetAxisIndex: 0}, 1:{targetAxisIndex: 1}},
bar: { groupWidth:"85%" },
chartArea:{ top:20, height:"70%", left:30, width:"75%" }
};
var chart = new google.visualizatin.ColumnChart(document.getElementById('chartDiv'));
chart.draw(data, options);
};

function getCsv(url){
var txt = new XMLHttpRequest();
var dat = [];
txt.open ('GET', url, false);
txt.onload = function(){
dat = convertCsvtoArray(txt.responseText);
}
txt.send();
return dat;
};

function convertCsvtoArray(csv){
var result = [['日付', '読書ページ', '読書冊数']];
let line = csv.split("\n");
for (let i = 0; i < line.length; i++){
if (line[i] != ""){
let cell = line[i].split(",");
let goal = [];
goal[0] = cell[1];
goal[1] = parseInt(cell[2]);
goal[2] = parseInt(cell[3]);
result.push(goal);
}
}
return result;
};
</script>
</head>
<body>
<div id="chatDiv" style="text-align;right;", style="width: 350px; height: 200px;"></div>
</body>
</html>

ここからは内容を少し分解して解説しておきます。
まずGoogle Chartsを使うには、<head>に以下のコードを埋め込みます。

// HTMLの4~7行目
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"</script>
<scrpit type="text/javascript">
google.charts.load('current', {'package':['corechart']});
google.charts.setOnLoadCallback(drawChart);
・・・
</script>
  • 1行目はGoogle Chartsのローダー自体を読み込んでいます。
  • 3行目でパッケージ・バージョンを指定してローダーを実行し、Google Chartsのパッケージを読み込んでいます。corechartパッケージによって棒グラフや円グラフなど一般的なグラフを描写できます。
  • 4行目はパッケージがロードされると呼び出されるコールバック関数です。drawChartという主処理を行う関数を呼び出しています。
// HTMLの9~21行目
function drawChart(){
const csvUrl = {S3に格納したファイルパス};
var arraydata = getCsv(csvUrl);
var data = google.visualization.arrayToDataTable(arraydata);
var options = {
width:300, height: 200,
series: { 0:{targetAxisIndex: 0}, 1:{targetAxisIndex: 1}},
bar: { groupWidth:"85%" },
chartArea:{ top:20, height:"70%", left:30, width:"75%" }
};
var chart = new google.visualizatin.ColumnChart(document.getElementById('chartDiv'));
chart.draw(data, options);
};

次にその主処理であるdrawChart関数です。

  • 3行目でgetCsv関数を呼び出し、S3からCSVファイルを取得して必要な情報を配列arraydataに格納します。このCSVファイルは前述のLambdaで作成したファイルです。
  • 4行目では、その配列をgoogle.visualization.arrayToDataTable関数でGoogle Chartsに引き渡すためのフォーマットに変換し、data変数に入れ込みます。
  • 5~10行目で定義しているoptions変数はグラフの見た目を整えるオプションです。グラフの種類ごとに指摘できるオプションが違いますが、今回は縦棒グラフ用のものです。
  • 11~12行目でgoogle.visualizatin.ColumnChartクラスにchartDivというIDをつけて実態化し、dataとoptionsを引数指定してdrawメソッドでグラフを描写します。HTMLのbodyでid="chartDiv"を指定してすることでそのグラフが表示される仕組みです。

getCsv関数とそこから呼び出されるconvertCsvtoArray関数にGoogle Charts固有機能はないので解説は割愛しますが、S3にアップされているCSVファイルは

 20220604, 06/04, 1440, 4

という「日付(yyyymmdd)、日付(mm/dd)、ページ数、冊数」のフォーマットになっているので、必要な項目を読み取って配列に入れ込む処理を実装しています。

まとめ

日々読んだ本・ページ数がブログに自動反映されるようになりました。
個人的にはJava・Lambda・Google Chartsの勉強になりましたし、3週間で1冊も本を読まないと赤青の線で全面が埋め尽くされた面白くないグラフが表示されてしまうので、読書を習慣づけるプレッシャーを作ることもできました(笑)

大した仕組みではないですが、欲しいものを自分で方式検討して実装するのは楽しいですね。

PVアクセスランキング にほんブログ村ブログランキング・にほんブログ村へにほんブログ村