Google Apps Script: GASでスクレイピング
まとめ
- スクレイピングの問題点やリスクを十分に認識し、利用する場面や実装内容を検討する必要がある
- HTTPリクエストの送信には、UrlFetchAppクラスを用いる
- HTTPレスポンスの解釈には、正規表現を用いるのが手
- 複雑な要件では、HTTPヘッダーの処理が必要
私たちOrchestra Canvas Tokyoは、都内を中心に活動するアマチュア・オーケストラです。
次回は2025年7月にシューマンの交響曲第2番。
初めての方も、そうでない方も、お気軽にお越しください!詳しくはチケット販売ページまで。
背景
当団(Orchestra Canvas Tokyo)では、Google Apps Script(GAS)を用いて団内ツールを開発しています。
スクレイピングの簡単な自動化として、GASを用いる機会がありました。
それに向けた知見として、GASでスクレイピングを実装する際の知見についてまとめます。
スクレイピングのリスク
スクレイピングは大きなリスクを孕む行為です。
利用規約にて明確に禁止されているサイトは少なくありません。
また、実装によっては相手方のサイトに大きな負担をかけてしまいます。
最悪の場合、違法となりうる 行為です。
今回の記事では詳しく触れませんが、規約の確認、設計の確認、リクエスト間隔の制御など、適切な前提のもと実装する必要があります。
HTTPメッセージの解剖
これから記事中で頻出する単語について、その親子関係を整理します。
- HTTPメッセージ
- リクエスト
- メソッド e.g.)
GET
、POST
- ヘッダー e.g.)
Content-Type: application/json
- ボディ
- メソッド e.g.)
- レスポンス
- ステータス
- ヘッダー
- ボディ
- リクエスト
冗長さ回避のため、各単語冒頭の「HTTP」を省略していますが、以降は適宜HTTP付きで表記していきます。
クッキーの実体
クッキーは、特別なHTTPヘッダーとしてやり取りされています。
要所をかいつまむと、次のようなやり取りになっています。
# HTTPレスポンス内のヘッダー
Set-Cookie: hoge=fuga
Set-Cookie: fuga=hoge
# HTTPリクエスト内のヘッダー
Cookie: hoge=fuga; fuga=hoge
GASでは、Cookieをよきに取り扱ってくれる組み込み関数はなさそうです。
HTTPヘッダーのやり取りとして、自力で記述する必要があります。
スクレイピングの全体を俯瞰
スクレイピングで頻出かつ複雑なユースケースは、「認証付きページへのアクセス」でしょうか。
実装に主眼を置いた、認証付きページへのアクセス手順は次のようになります。
手順を分解して整理すると、次の要素で成り立っていることが分かります。
- HTTPリクエストの送信
- GETリクエスト
- POSTリクエスト
- HTTPヘッダー付き(Cookie付き)リクエスト
- HTTPレスポンスの解釈
- HTMLの解釈
- 付与されたHTTPヘッダー(Cookie)の発見
これらいずれも、GASで問題なく実装できます。
順に述べていきます。
HTTPリクエストの送信
UrlFetchAppクラスを用います。
GETリクエスト
最も基本的なリクエストです。
実装例を示します。
// 固定URLへのリクエスト
const response1 = UrlFetchApp.fetch('https://example.com/');
// 動的なクエリパラメータを有する場合のリクエスト
const url = new URL('https://example.com/');
url.params.append('hoge', 'fuga');
const response2 = UrlFetchApp.fetch(url.toString()); // https://example.com/?hoge=fuga へリクエストされる
クエリパラメータの他でも、URL操作にはURLインターフェースを用いると便利です。
POSTリクエスト
GETリクエストから、HTTPメソッドおよびHTTPボディーを指定する必要があります。
// 固定URLへのリクエスト
const response1 = UrlFetchApp.fetch('https://example.com/', {
method: 'post',
payload: {
'hoge': 'fuga'
}
});
// JSONを用いる場合のリクエスト
const data = {
'hoge': 'fuga'
}
const response2 = UrlFetchApp.fetch('https://example.com/', {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
});
payload
にJavaScriptオブジェクトを指定した場合、フォームデータとして扱われるようです。
contentType
は自動的にapplication/x-www-form-urlencoded
かmultipart/form-data
とされるようです[1]。
HTTPヘッダー付き(Cookie付き)リクエスト
これまで示した例にある、fetch()
関数の第2引数に指定します。
前述したように、Cookieの実体はHTTPヘッダーです。
Cookie付きリクエストも応用として記述できます。
// 単純なHTTPヘッダー付きリクエスト
const response1 = UrlFetchApp.fetch('https://example.com/', {
header: {
'X-Hoge': 'fuga'
}
});
// クッキー付きのリクエスト
const cookies = [
'hoge=fuga',
'fuga=hoge'
];
const cookieHeaderValue = cookies.join('; ')
// 「Cookie: hoge=fuga; fuga=hoge」ヘッダー付きでリクエストされる
const response2 = UrlFetchApp.fetch('https://example.com/', {
header: {
'Cookie': cookieHeaderValue
}
});
HTTPレスポンスの解釈
後半戦です。
この調整こそがスクレイピングの山場です!
HTMLの解釈
大半のケースでは正規表現で十分でしょう。
正規表現の実際は、それぞれのHTMLに大きく依存します。
ここでは、よく使うCSRFトークンを取得する例を示します。
実際には、テストツールを使って一意かつ再現性のある値が取得できるか、検証していくことになると思います。
const response = = UrlFetchApp.fetch('https://example.com/');
const body = response.getContentText();
// CSRFトークンを取得する
// e.g.) <input type="csrfToken">hoge</input>
// -> csrfToken = hoge
const csrfToken = body.match(/<input.+name="csrfToken".*>(.+)</)[1];
より厳密に、あるいは可読性高く記述するために、HTMLの構文解釈を必要とするのであれば、XmlServiceクラスが有用かもしれません(未検証)。
付与されたHTTPヘッダー(Cookie)の発見
getAllHeaders()
メソッドを用いて記述します。
類似のメソッドとしてgetHeaders()
がありますが、複数の値をもつのヘッダーをうまく取り扱うことができません。
Cookieの処理などで困ってしまいますので、シンプルなケースを除いてはgetAllHeaders()
の利用が好ましいでしょう。
const response = = UrlFetchApp.fetch('https://example.com/');
const headers = response.getAllHeaders();
// 単純なHTTPヘッダーの取得
const hoge = headers['hoge'];
// Cookieの取得
// e.g.) cookies = ['hoge=fuga', 'fuga=hoge']
const cookies = headers['Set-Cookie'];
おわりに
最後までお読みいただきありがとうございました!
Webアプリを構成する技術、HTTPメッセージ、Cookie、セッション――
スクレイピングの設計・実装を通して、これらの解像度を上げることができ、気分が良いものです!
次回演奏会のご案内
Orchestra Canvas Tokyoは、都内を中心に活動するアマチュアオーケストラです。
日々の癒しに、新たなひらめきのきっかけに——
オーケストラの演奏会はいかがでしょうか?
初めての方も大歓迎!
ご来場をお待ちしています。
Orchestra Canvas Tokyo
第14回定期演奏会2025年7月12日(土)
練馬区立練馬文化センター 大ホール
シューマン / 交響曲第2番 ほか詳細はチケット販売ページにて
Discussion