【Chrome Extension】ポップアップから簡易スクレイピング

# はじめに

- 特定のページからちょこっと情報を取得したいけど、NodeJSやPythonでがっつりやるのは大袈裟過ぎる
- 非エンジニアさん向けのスクレイピングツールを作りたいけど、相手のPCに動作環境がない

こんなときはChrome Extensionとかいかがでしょうか？
Chrome Extensionなら、Google Chrome（動作環境）とExtensionの入ったフォルダさえあれば動きます。

本記事では、Chrome Extensionで簡易的なスクレイピングを行う方法を紹介します。
コード一式は[こちら](https://github.com/turara/chrome-extension-popup-parser-sample "chrome-extension-popup-parser-sample")。
![PopupParser.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/9b38bb54-acb2-98a7-6316-2a5ee2106860.gif)

# コード

以下の順番で実装していきます。

1. Chrome Extensionでポップアップを表示する
2. ポップアップ内のボタンを押してバックグラウンドでタブを開く
3. 開いたタブの内容を取得してポップアップ内に表示
4. 表示した内容をクリップボードにコピー

用途としては「管理画面の入力に必要な情報を特定のページから取得して参照する」などを想定してます。

## ブラウザアクションを追加してポップアップを有効にする

manifest.jsonで、browser_actionを有効にします。

```json-doc
// manifest.json
{
  ...
  "browser_action": {
    // ツールバーにエクステンションを表示する設定 + 表示するアイコンのパス
    "default_icon": "icon128.png",
    // ツールバーのアイコンをクリックしたときにポップアップを表示する設定
    "default_popup": "popup/popup.html"
  }
}
```

## ポップアップから新しいタブをバックグラウンドで開く

ポイントは2つ

- chrome.tabs APIを使用できるように、permissionsにtabsをセットする
- タブを開いた時点でポップアップが閉じないように、chrome.tabs.createメソッドのactiveオプションにfalseを渡す

```json-doc
// manifest.json
{ 
  ...
  // chrome.tabs.createなどのメソッドを使用できるように設定
  "permissions: ["tabs"]
}
```

```javascript
// popup.js
async function loadNewTab(url, timeoutSeconds = 5) {
  return new Promise((resolve, reject) => {
    let targetTabId = null
  
    const listener = (tabId, changedProps) => {
      if (tabId != targetTabId || changedProps.status != 'complete') {
        return
      }
      chrome.tabs.onUpdated.removeListener(listener)
      resolve(tabId)
    }
    chrome.tabs.onUpdated.addListener(listener)
          
    // ポップアップが閉じないように、active: falseを設定する
    chrome.tabs.create({ url, active: false }, (tab) => {
      // 開いたtabのidをスコープの変数にセットする
      targetTabId = tab.id
    })
   
    setTimeout(() => {
      reject(new Error('Time out...'))
    }, timeoutSeconds * 1000)
  })
}
```

loadNewTabメソッドをpopup.htmlに設置したボタンのイベントリスナー内で呼び出します。
Promiseで包んでasyncにしていますが、普通にコールバック内で結果を受けるようにしても問題ありません。

## 新しく開いたタブから情報を取得してポップアップに表示する

ここでは例として、英語版Wikipediaのトップページから本日の記事のテキストを取得します。

ポイントは3つ

- permissionsに情報を取得したいURLを指定する（ホストだけでもOKのようです）
- chrome.tabs.executeScriptの第一引数にtabIdを渡すことで、非アクティブのタブでスクリプトを実行可能
- chrome.tabs.executeScriptのコールバックでスクリプトの結果を取得可能

```json-doc
// manifest.json
{
  ...
  "permissions": [
     "tabs",
     "https://en.wikipedia.org/wiki/Main_Page"
   ]
```

```javascript
// popup.js
async function parseWikipedia(tabId) {
  return new Promise((resolve, reject) => {
    chrome.tabs.executeScript(
      tabId,
      { file: 'parser/parseWikipedia.js' },
      (results) => {
        if (results[0] === null) {
          reject(new Error('Failed to parse wikipedia.'))
        } else {
          resolve(results[0])
        }
      }
    )
  })
}

// parseWikipedia.js
;(() => {
  function parse() {
    // Get text from "From today's featured article"
    return document.querySelector('#mp-tfa p').textContent
  }
   
  try {
    return parse()
  } catch (e) {
    console.log(e)
    return null
  }
})()
```

chrome.tabs.executeScriptに渡すスクリプトでは、結果もしくはnullを返すようにしています。
スクリプト内でキャッチできない例外が発生した場合は、results[0]にはundefinedが入るようです。

## ポップアップ内に表示した結果をクリップボードにコピーする

```javascript
// 結果は、<textarea id="content"></textarea>のtextContentに格納されている前提
function copyResult() {
  document.querySelector('#content').select()
  document.execCommand('copy')
}
```

# 終わりに

定期的に実行するほどでもないけど、たまにスクレイピングしたいみたいなときにも便利かと思います。
ちょっとスクレイピングしたいなってときも、ボタンとURL、ページ固有のスクリプトを追加するだけで済みます。是非使ってみてください！

コード一式は[こちら](https://github.com/turara/chrome-extension-popup-parser-sample "chrome-extension-popup-parser-sample")（再掲）。