【GAS】GoogleAppsScriptでスクレイピングしてスプレッドシートに保存する

# はじめに
趣味で草野球をやっており、Teams（チームスではなくティームズ）で成績管理をしています。

https://teams.one/

ある程度の成績は確認できるのですが、
Teamsにない指標も見れるようにスプレッドシートでも成績管理できるようにしたいなぁと思いました。

この記事では、そのような作業で実践した
GASを使ってWebページをしてスプレッドシートに取得した情報を保存するやり方についてご紹介します。

# この記事の対象読者
* GASでスクレイピングしたい人
* GASでスプレッドシートを操作したい人

# 環境（使うツールとか）
* GoogleAppsScript
* GoogleSpreadSheet

ブラウザで完結するのでPCのOSの違いによりません。
また環境構築をする必要もありません（うれしい）。

# やり方
## Webページをスクレイピングする
スプレッドシートを新規作成し、
拡張機能 > AppsScriptを選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622183/3f0f5ff5-d26e-5962-a52b-f538f3db1bc2.png)


ライブラリの「+」ボタンを選択して、
以下のスクリプトIDを入力する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622183/69c6dc31-f723-e074-1e45-a2db8f593f0c.png)


```
1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw
```
検索を選択すると、Parserライブラリがヒットする。
バージョンとIDは変更せずに追加を選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622183/5ccd92f2-daaf-d264-ae94-e2237a1d49a6.png)


ライブラリにParserが追加されていれば成功
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622183/e8f38508-644c-8202-9d64-8eab978b26ac.png)


myFunctionに以下のコードを追加する

```
  // URLからHTMLを取得する
  // Yahooニュース
  let response = UrlFetchApp.fetch("https://news.yahoo.co.jp/");

  // 文字コードをUTF8に設定する
  let content = response.getContentText("utf-8");


  // HTMLを取得する・・・★
  var newsListHtml = Parser.data(content).from('class="sc-fHCHyC jhKFuK"').to('</li>').iterate();

  console.log(newsListHtml);
```

:::note info
★部分ではiterate関数を使って合致する要素を取得していますが、build関数も使用できます。

2つの関数の違いは次の通りです。
iterate()・・・一致するすべての要素を取得する
build()・・・一致する最初の要素を取得する
:::


実行を選択する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622183/dae2b99a-59cc-03c6-e0b5-692ac6399b5f.png)


以下のようなポップアップが表示されたら「権限を確認」を選択し、
アカウントに対するアプリの操作を許可します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622183/0e144369-6dcd-83cb-3a94-3e326525536d.png)


実行ログにヤフーニュースのニュース一覧のHTMLが表示されていればスクレイピング成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622183/254862a9-50b2-dddd-e3bf-e108c2d1dd38.png)


## データクレンジング
データクレンジングとは人間が見やすくわかりやすい形式に取得したデータを修正することです。
先ほど取得できたデータはHTMLタグが含まれており、非常に見づらい状態です。

これをスプレッドシートに登録した時に見やすくなるように修正していきます。
ここでは取得したニュースへのリンクからURLとタイトルを取り出します。

myFunction関数を次のように修正します。

```
function myFunction() {

  // URLからHTMLを取得する
  // Yahooニュース
  let response = UrlFetchApp.fetch("https://news.yahoo.co.jp/");

  // 文字コードをUTF8に設定する
  let content = response.getContentText("utf-8");


  // ニュース一覧のHTMLを取得する
  var newsListHtml = Parser.data(content).from('class="sc-fHCHyC jhKFuK"').to('</li>').iterate();


  // ----- ここから追加する -----
  // aタグからhref属性と値を取り出すための正規表現を設定する
  const urlRegExp = /https:\/\/.+?(?=")/g;
  const aRegExp = /<a(?: .+?)?>.*?<\/a>/g;
  const ttlRegExp = />(.*?)</;

  // 取得したHTMLからURLとタイトルを取り出す
  newsListHtml.forEach((newsHtml) => {
    
    var url = newsHtml.match(urlRegExp)[0];
    var title = newsHtml.match(aRegExp)[0].match(ttlRegExp)[1];
    console.log([url,title]);

  });
  // ----- ここまで追加する -----

}
```
追加部分では正規表現を使って取得したHTMLからaタグのhref属性と値（タイトル）を抜き出してコンソールに出力しています。

タイトルはspanタグが含まれている場合があるため1つの正規表現だけでは取り出せなかったため、
aタグを取り出してからタグに囲まれた値を取り出すように段階的にタイトルを取得しました。
（もっといいやり方あれば知りたい・・・）

そしてその実行結果が次の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622183/0271d153-befd-51d0-ca5f-2910e37f9bdb.png)


いい感じに取り出せました。
次は取り出した値をスプレッドシートに書き込んでいきます。


## スプレッドシートに書き込む
ここまでで取り出せたデータをスプレッドシートに書き込んでいきます。
スプレッドシートへの書き込み後のイメージは次の通りです。

| リンク | タイトル |
|:-:|:-:|
|  https://news.yahoo.co.jp/news00000000 | ニュースのタイトル1  |
|  https://news.yahoo.co.jp/news11111111 | ニュースのタイトル2  |
|  https://news.yahoo.co.jp/news22222222 | ニュースのタイトル3  |
|  https://news.yahoo.co.jp/news33333333 | ニュースのタイトル4  |
|  https://news.yahoo.co.jp/news44444444 | ニュースのタイトル5  |
|  https://news.yahoo.co.jp/news55555555 | ニュースのタイトル6  |
|  https://news.yahoo.co.jp/news66666666 | ニュースのタイトル7  |


myFunction関数を次のように修正します。
```
function myFunction() {

  // URLからHTMLを取得する
  // Yahooニュース
  let response = UrlFetchApp.fetch("https://news.yahoo.co.jp/");

  // 文字コードをUTF8に設定する
  let content = response.getContentText("utf-8");


  // ニュース一覧のHTMLを取得する
  var newsListHtml = Parser.data(content).from('class="sc-fHCHyC jhKFuK"').to('</li>').iterate();


  // aタグからhref属性と値を取り出すための正規表現を設定する
  const urlRegExp = /https:\/\/.+?(?=")/g;
  const aRegExp = /<a(?: .+?)?>.*?<\/a>/g;
  const ttlRegExp = />(.*?)</;

  // GSSのシートを取得する・・・追加
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName("シート1");

  // 取得したHTMLからURLとタイトルを取り出す
  newsListHtml.forEach((newsHtml, index) => {
    
    var url = newsHtml.match(urlRegExp)[0];
    var title = newsHtml.match(aRegExp)[0].match(ttlRegExp)[1];

    // シートに書き込み・・・追加
    sheet.getRange(index+1, 1).setValue(url);
    sheet.getRange(index+1, 2).setValue(title);

  });


}
```

追加部分ではGASのファイルを作成した時に開いていたスプレッドシートをすべて取得します。
そしてシート名を指定します。

```
SpreadsheetApp.getActiveSpreadsheet();
ss.getSheetByName("シート1");
```


シートへの書き込みは行番号と列番号を指定して書き込みます。
```
sheet.getRange(index+1, 1).setValue(url);
sheet.getRange(index+1, 2).setValue(title);
```
:::note info
getRange関数
　第1引数：行（A列は1列目、B列は2列目、のように対応しています。）
　第2引数：列
:::

実行結果は次の通りです。
・コンソール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622183/5500be6c-8137-b8cf-aede-be22a6e99947.png)
コンソールへの出力はないためGAS標準のログだけ出力されます。

・スプレッドシート
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622183/f24aa43a-d418-6abb-5ddb-eecdd39315e4.png)
見事スプレッドシートへの出力もできました！


# さいごに
この記事では次の2つをご紹介しました。
* GASを使ったWebページのスクレイピング
* GASでスプレッドシートへの書き込み

GASはブラウザで実行できるためOSなど環境によらず使えるところが個人的には好きな点です。
またJavascript由来のプログラミング言語ですのでWebプログラミングをやったことある方は扱いやすいと思います。

今回ご紹介したようにスクレイピングしたデータをスプレッドシートへ保存した場合はぜひ参考にしてみてください。