一括でリンク切れチェックできるGASコードを書いてみた

リンクが有効かどうかを調べるには、リンクをブラウザに貼り付けてEnterキーを押せばわかります。
しかし、調べたいリンクが大量にある場合は、個別に確認するのは面倒ですよね。

そこで今回は、GAS（Google Apps Script）を使ってスプレッドシートにURLを貼り付けるだけで自動でリンク切れチェックができるツールを作成しました。

仕組みはどうでもいいからコードだけ丸々コードをコピーして使いたいという方は、こちらにコードの全容を載せているのでご参照ください。

<details>
    <summary>コード全文はこちら</summary>

    ```js
    function linkChecker() {
      // 現在のシートを取得する変数を定義
      const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
    
      // 現在のシート"sheet"の、2行目以降のA列の値を取得する変数を定義
      const values = sheet.getRange(2, 1, sheet.getLastRow() - 1, 1).getValues();
    
      // 各URLに対して関数checkLink()を実行
      const results = values.map(row => [checkLink(row[0])]);
    
      // 結果を書き込む範囲を指定。引数には、書き込む範囲の左上のセルの行番号、列番号、書き込むデータの行数、列数を指定している。
      sheet.getRange(2, 2, results.length, 1).setValues(results);
    
      function checkLink(url) {
        // urlじゃなければ、'✕'を返す。
        if (!url) {
          return '✕';
        }
          try {
            // A列に記載のURLをスクレイピングする。
            const response = UrlFetchApp.fetch(url, {
              // リダイレクトされるURLの場合、リダイレクト先のURLを参照しない
              followRedirects: false,
    
              // 404などエラーの場合でもレスポンスを返す
              muteHttpExceptions: true
            });
            // HTTPレスポンスのステータスコードの1桁目の数値を取得する変数を定義
            const code = Math.trunc(response.getResponseCode() / 100);
    
            // ステータスコードが200番台なら'〇'を返す。
            if (code === 2) {
              return '〇';
            }
    
            // ステータスコードが300番台なら'〇'を返す。
            else if (code === 3) {
              return getRedirectUrl(url);
            }
            // それ以外なら'×'を返す。
            else {
              return '✕';
            }
          }
         
          // エラーが発生した場合、エラーログを出力し、空文字を返す。
          catch (e) {
            Logger.log(e);
            return '!';
          }
        }
        return '!';
      }
    
      // リダイレクト先のURLを取得する関数を定義
      function getRedirectUrl(url) {
        try {
          // A列に記載のURLをスクレイピングする。
          const response = UrlFetchApp.fetch(url, {
    
            // リダイレクトされるURLの場合、リダイレクト先のURLを参照しない
            followRedirects: false,
    
            // 404などエラーの場合でもレスポンスを返す
            muteHttpExceptions: true
          });
    
          // ヘッダーの値を取得する変数を定義
          const headers = response.getHeaders();
    
          // Locationヘッダーの値が存在する場合、Locationヘッダーの値を返し、存在しなければ空文字を返す。
          return headers['Location'] || '';
        }
    
        // エラーが発生した場合、エラーログを出力し、空文字を返す。
        catch (e) {
          Logger.log(e);
          return '';
        }
      }
    ```　
</details>

## 1. スプレッドシートと変数の準備
スプレッドシートを開き、セルA1に「URL」,セルB1に「判定」と入力して、A列にURLを入力するとB列に自動で〇✕が表示されるようにします。

メニューの「拡張機能」から「App Script」をクリック。

![image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1902958/e0a97a41-d5e1-c76b-6349-2c28b2cdefc7.jpeg)

関数名は何でもいいのですが、わかりやすいように`linkchecker()`にします。

```js
function linkChecker() {
// 現在のシートを取得する変数を定義
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();

// 現在のシート"sheet"の、2行目以降のA列の値を取得する変数を定義
const values = sheet.getRange(2, 1, sheet.getLastRow() - 1, 1).getValues();
}
```

## 2. URLをスクレイピング
```js
function checkLink(url) {
// urlじゃなければ、'✕'を返す。
if (!url) {
return '✕';
}
try {
// A列に記載のURLをスクレイピングする。
const response = UrlFetchApp.fetch(url, {
muteHttpExceptions: true
});

// エラーが発生した場合、エラーログを出力し、空文字を返す。
catch (e) {
Logger.log(e);
return '!';
}
}
```

予期せぬ値が入力されても、プログラムエラーを起こさないようにするために`try-catch`文を用いて処理を記述して行きます。

`UrlFetchApp.fetch`は、外部のAPIやウェブサイトにHTTPリクエストを送るためのメソッドです。これによって、ウェブサイトのmeta情報だったり画像データだったり、いろいろ取得できます。

ちなみに、ウェブサイトから情報を自動的に収集する技術を「**スクレイピング**」といいます。
スクレイピングは、Webサイトに負荷をかける可能性がある行為なので、Webサイトによってはスクレイピングを禁止にしているとこもあるので、注意が必要です。
スクレイピングはWebサイトに負荷をかける可能性があるため、利用規約をよく確認してから実行してください。

`muteHttpExceptions`は`UrlFetchApp.fetch`のオプションであり、これをtrueにしておくことによってエラーを吐かずにhttpレスポンスを返してくれます。

## 3. HTTPステータスコードを取得してリンク切れ判定
```js
function checkLink(url) {
// 省略
try {
// 省略

// HTTPステータスコードの1桁目の数値を取得する変数を定義
const code = Math.trunc(response.getResponseCode() / 100);

// ステータスコードが200番台なら'〇'を返す。
if (code === 2) {
return '〇';
}

// それ以外なら'×'を返す。
else {
return '✕';
}
}

// 省略
}
```
リンクが有効であるかどうかとかを判別するには`response.getResponseCode()`で、**HTTPステータスコード**を取得して確認します。

HTTPステータスコードは、HTTPにおいてWebサーバからのレスポンスの意味を表現する3桁の数字からなるコードです。
最も有名なのが404でしょう。404は、存在しないページを意味します。

ステータスコードの1桁目を取得し、200番台なら有効、300番台ならリダイレクト、400番台なら無効と判定します。

## 4. 判定結果の出力
```js
function linkChecker() {
// 省略

// 各URLに対して関数checkLink()を実行
const results = values.map(row => [checkLink(row[0])]);

// 結果を書き込む範囲を指定。引数には、書き込む範囲の左上のセルの行番号、列番号、書き込むデータの行数、列数を指定しています。
sheet.getRange(2, 2, results.length, 1).setValues(results);

function checkLink(url) {
// 省略
}
}
```

関数checkLink()で得た結果をB列の各セルに出力します。

## 5. リダイレクトURLの取得
単純な〇✕で判断できないのがリダイレクトです。
リダイレクトの場合はB列に**リダイレクト先のURL**を出力するように処理を追加します。

```js
// リダイレクト先のURLを取得する関数を定義
function getRedirectUrl(url) {
try {
const response = UrlFetchApp.fetch(url, {
followRedirects: false,
muteHttpExceptions: true
});

// ヘッダーの値を取得する変数を定義
const headers = response.getHeaders();

// Locationヘッダーの値が存在する場合、Locationヘッダーの値を返し、存在しなければ空文字を返す。
return headers['Location'] || '';
}

catch (e) {
Logger.log(e);
return '';
}
}
```

getHeaders()は文字通り、ヘッダー情報を取得する関数です。
ヘッダー情報から、リダイレクト先の URL を示す`Location`レスポンシブヘッダーを取得します。

そしてgetRedirectUrl()関数を、checkLink()関数に追加します。

```js
function checkLink(url) {
if (!url) {
return '✕';
}
try {
const response = UrlFetchApp.fetch(url, {
followRedirects: false,
muteHttpExceptions: true
});
const code = Math.trunc(response.getResponseCode() / 100);

if (code === 2) {
return '〇';
}

// ステータスコードが300番台ならリダイレクト先のURLを返す。
else if (code === 3) {
return getRedirectUrl(url);
}

else {
return '✕';
}
}
// 省略
}
```

## 動作確認
実際にA列にURLを記載してリンクチェックが正しく動作するか確認します。
![linkchexk.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1902958/a053149e-b61f-ae6f-2311-e9c1d7b7446c.gif)

これで、大量のリンク切れチェックを効率的に行うことができます。ぜひ活用してみてください。