はじめに
この記事は「Ateam LifeDesign Advent Calendar 2023」で完走賞を狙って25記事書いているうちの3日目の記事です。今年も完走目指して頑張るぞ!
GASを使ったスクレイピング
今私が見てる部署では比較的スクレイピングを使う人が多いので参考になるかもな?と思い、実践編の最初にスクレイピングを持ってきました。それでは早速見ていきましょう。
作るもの
こんなシートを用意してA列に入っているURLにアクセスし、取ってきた情報で必要なものをB列に入れていきます。
今回スクレイピングするのは弊社が運営してる結婚式場の紹介サービスであるハナユメというサイトです。結婚式場の詳細が書かれたページのなかから赤枠で囲われている情報を取得してきたいと思います。
準備
今回はCherrioというライブラリを利用したスクレイピングの方法をご紹介します。
まず「ライブラリ」をクリックしたあと、スクリプトIDに
1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0
を入力し、最新のバージョンのCheerioを追加します。
Cherrioの使い方はこちらに詳しく書いてありますがjQueryライクに要素を取ってくることが出来ます。こんな感じで簡単にコンテンツを取ってこれます。
function getContent_(url) {
return UrlFetchApp.fetch(url).getContentText()
}
wikipediaのメインページを取得する方法。
const content = getContent_('https://en.wikipedia.org');
const $ = Cheerio.load(content);
Logger.log($('#mp-right').text());
これでCherrioライブラリが使用できるようになったので、ここからは実際どんなコードを書いて実装していくのか順を追って説明していきます。
GASを書いていく
URLが入ってるA列のセルの中身を取得
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getActiveSheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(1,1, lastRow);
var values = range.getValues().flat().filter(String).map(String);
ここは主に昨日の記事でやった内容です。sheet.getLastRow()
の部分が新しく出てきていますが、この関数はそのシートでデータの入ってる最終行数を返す関数です。サンプルだとURLが5つはいってるのでsheet.getRange(1,1,5)
としても動作しますが、この書き方だとURLをどんどん追加していったときに都度プログラム側を変更する必要があるのであまり美しくないですよね。なので最終行を取ってこれる関数を使って指定をするようにしています。
range.getValues().flat().filter(String).map(String)
の部分は二次元配列をその後の処理で扱いやすいように一次元配列にしています。今回の場合入力されてるのは文字列型なのでString
を指定していますが、数値型であればNumber
を指定しましょう。
取れてきたURLを1つずつ順番にスクレイピングする
for(let i = 0; i < values.length; i++) {
var url = values[i];
if (url) {
var content = UrlFetchApp.fetch(url).getContentText();
var $ = Cheerio.load(content);
var targetText = $('.hallStatus').text();
setInfo[i] = [];
setInfo[i].unshift(targetText.replace(/[\r\n]+/g,""));
Utilities.sleep(3000);
}
}
A列から取ってきたURLのリストは先程の手順で一次元の配列になっているので今回はforで1つずつ見ていきます。URLが空の場合にスクレイピングの処理を通そうとするとエラーが出てしまうので空欄があったときのことを考慮してif
でURLがセルに記載されている場合のみスクレイピング処理を実行するようにします。実際のスクレイピングは上述のサンプルの通りです。なんともめちゃくちゃ簡単・・・感動・・・!
今回取ってきたかったところにはhallStatus
というclassが振られていたので.hallStatus
という指定をしています。もしこれがid="hallStatus"
であれば$('#hallStatus').text()
と指定すればOK。もっと細かな指定もできますが、ここでは割愛。jQueryのDOMの指定方法と同じなのでjQuery DOM操作あれこれがとても参考になると思います。
そのままtargetText
を入れていたのでが改行コードが含まれていたため.replace(/[\r\n]+/g,"")
を追加して改行コードを空文字で置換しています。
そしてスクレイピングはお行儀よくしないとなのでUtilities.sleep(3000);
で3秒間処理を止めています。大量アタックはサイト運営者にご迷惑おかけしちゃうからね。Utilities.sleep(ミリ秒)
という指定の仕方をしてください。
スクレイピングして得られた結果を書き込む
var outputRange = sheet.getRange(1, 2, lastRow);
outputRange.setValues(setInfo);
ここでは取得してきたデータを二次元配列で保持しておいてそれをsetValues
をつかって書き込んでいます。これだけのデータ量であればfor回している中で1セルずつに対してsetValue
しても、最後にまとめてsetValues
しても速度はそこまで変わらないのですが、データ行数が数百レベルになってくると実行結果に明らかな差が出てきます。
GASには1度の処理での実行時間に上限があるのでなるべくまとめてデータをとりだす、まとめてデータを書き込むを身に着けておいたほうがいいので今回はまとめて書き込む方法でサンプルを作成しました。
それではここまでをまとめたものと実際の実行結果を見てみましょう!
完成品がこちら
GASのコード
function myFunction() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getActiveSheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(1,1, lastRow);
var values = range.getValues().flat().filter(String).map(String);
var setInfo = [];
for(let i = 0; i < values.length; i++) {
var url = values[i];
if (url) {
var content = UrlFetchApp.fetch(url).getContentText();
var $ = Cheerio.load(content);
var targetText = $('.hallStatus').text();
setInfo[i] = [];
setInfo[i].unshift(targetText.replace(/[\r\n]+/g,""));
Utilities.sleep(3000);
}
}
var outputRange = sheet.getRange(1, 2, lastRow);
outputRange.setValues(setInfo);
}
実行結果
無事に赤枠で囲っていた部分を取得してB列に書き込めていますね。
最後に
今回はCheerioライブラリを使ってGASでスクレイピングするやりかったについてまとめました。思ったより簡単にできましたね!明日は弊社でもよく使われそうなSlack連携について書いていこうと思います。
くれぐれもスクレイピングは迷惑のない範囲で使いましょうね!