Playwrightで作成する高機能リンクチェッカーの作り方

リンクチェッカーとは

リンクチェッカーはWebページ内に存在するリンク(URL)が正しく動作しているかを確認するためのツールです。

リンク切れになっているとユーザー体験やSEO(検索エンジン最適化)に悪影響を及ぼします。

そのため、Webサイトを作成したら必ずリンクチェッカーでリンクが正しく動作しているかチェックすることが望ましいです。

リンクチェッカーはW3C Link CheckerやChrome拡張機能のLink Checkerが有名です。

しかし、いずれも自身がチェックしたいWebサイトに合わせた細かいカスタマイズができないというデメリットがあります。

Playwrightで作成する高機能リンクチェッカー

Playwrightを利用すればリンクチェッカーを簡単に自作することができます。

作り方はまず以下のコマンドでPlaywrightをインストールします。

mkdir my-link-checker; cd my-link-checker; touch link-checker.mjs; npm init playwright@latest

初期設定のための質問が表示されますが、すべてEnterキーを押してデフォルト設定で良いです。

Playwrightがインストールされたら「npx playwright test」を実行してみてください。

テストが実行されれば、Playwrightのインストールは完了しています。

$ npx playwright test

Running 6 tests using 4 workers
  6 passed (11.0s)

リンクチェッカー用のコードを書く

link-checker.mjsにリンクチェッカー用の以下のコードを書きます。

link-checker.mjs
import { chromium } from 'playwright'

// コマンドライン引数からURLを取得
const url = process.argv[2] ?? 'https://example.com/'

// 画像はリクエストしない
const abortImagesRequest = async (page) => {
  await page.route('**/*', (route) => {
    if (route.request().resourceType() === 'image') {
      route.abort()
    } else {
      route.continue()
    }
  })
}

if (!url || !/^https?:\/\/.+/.test(url)) {
  console.error('❌ 有効なURLを引数として指定してください。')
  process.exit(1)
}

;(async () => {
  const browser = await chromium.launch()
  const context = await browser.newContext()
  const page = await context.newPage()

  // ページを開く
  await abortImagesRequest(page)
  await page.goto(url)

  // ページ内のすべてのリンクを取得
  const links = await page.$$eval('a[href]', (anchors) =>
    anchors
    .filter((anchor) => !anchor.href.startsWith('mailto:'))
    .filter((anchor) => !anchor.href.startsWith('javascript:'))
    .map((anchor) => anchor.href.replace(/#.*$/, ''))
  )
  const uniqueLinks = [...new Set(links)]
  const uniqueLinksLen = uniqueLinks.length
  console.log(`🔗 リンクの総数: ${uniqueLinksLen}`)
  let count = 0

  // 各リンクのステータスコードをチェック
  for (const link of uniqueLinks) {
    const context = await browser.newContext()
    const page = await context.newPage()

    await abortImagesRequest(page)
    count++

    try {
      const response = await page.goto(link, {
        waitUntil: 'domcontentloaded',
        timeout: 5000
      })
      const status = response.status() ?? 'No Response'

      if (status < 400) {
        console.log(`✅ [${count}/${uniqueLinksLen}] ${status}: ${link}`)
      } else {
        console.error(`❌ [${count}/${uniqueLinksLen}] ${status}: ${link}`)
      }
    } catch (error) {
      console.error(`❌ [${count}/${uniqueLinksLen}] エラー: ${link} (${error.message.substring(0,99)})`)
    }
  }

  await browser.close()
})()

Playwrightは「import { chromium } from 'playwright'」で読み込めば使えるので、要約すると以下の手順の処理でリンクをチェックしています。

  1. process.argv[2] により「node link-checker.mjs [チェックするURL]」コマンドによるリンクチェックが実行できるようにする
  2. 「import { chromium } from 'playwright'」でPlaywright (chromium)が使えるようにする
  3. 「page.goto(url)」で指定したURLにアクセスする
  4. 「page.$$eval('a[href]'」で対象のリンクのURL一覧を取得する
  5. リンクのURL一覧を1つずつ「page.goto」でアクセスして、ステータスコードから有効なリンクかチェックする

timeoutはデフォルトの30秒から5秒に変更しています。

ステータスコードが200でも開くのに5秒以上もかかるのは問題があるので、開くのが遅いページも検出できるようにしています。

リンクのURL一覧は重複を除外してあるので、同じURLをチェックする無駄な処理は発生しません。

Playwrightリンクチェッカーの使い方

link-checker.mjsが書かれていれば以下のコマンドで実行できます。

node link-checker.mjs [チェックするURL]
package.json
{
  "scripts": {
    "test": "node link-checker.mjs http://iwb.jp/"
  }
}

リンクチェックするURLが常に同じなら、package.jsonに保存すれば「npm test」コマンドで実行できるので便利です。

試しに「node link-checker.mjs https://iwb.jp/」を実行すると下図のような結果になります。

「/error-link/」のURLは404の確認用に一時的に追加した仮のページです。

Playwrightで作成する高機能リンクチェッカーの作り方

Playwrightリンクチェッカーのカスタマイズ

Playwrightはpage.screenshotでスクリーンショット画像を保存できます。

JavaScript
await page.screenshot({ path: 'screenshot.png' });

ほかにもブラウザ操作などができるので、一般的なリンクチェッカーと比べて様々なチェック項目をコードに追記することで実装できます。

通常のリンクチェッカーではステータスコードだけで判断しているため、例えば404ページを表示しているが、ステータスコードは200を返している場合はエラーとして検出できません。

しかし、Playwrightで作ったリンクチェッカーなら、titleが404のものになっているかで判定することもできます。

JavaScript
console.log(/お探しのページは見つかりませんでした。/.test(await page.title()))

このようにPlaywrightのリンクチェッカーなら高機能のリンクチェッカーを作れます。

Webサイトを制作されている場合はプロジェクトディレクトリに追加しておくことをオススメします。