簡単なGoogle Chrome 拡張機能を作ってみた

e-how-to-google-chrome-ext プログラミング

※当ブログでは商品・サービスのリンク先にプロモーションを含みます。ご了承ください。

諸用で必要になったので、特定の文言を抽出するChromeの拡張機能を作りました。マーケットに出すものなどではなく、完全に自分用のものとなります。

初めてのGoogle Chrome拡張機能づくりです。

やりたいこと

諸用でWebにある膨大なテキストの中から、特定の文言を抽出する必要がありました。今まで手作業で抽出していたのですが、そろそろ自動化したいと思い、いろいろ考えた結果、Chrome拡張がよさそうということで作ってみました。

ちなみにChrome拡張で使う言語はjsとなっています。

抽出したい文言があるサイト上で拡張ボタンを押すと、文言が抽出され、そのサイトのコメント欄にその抽出した文言をコメントする。みたいなことをやりました。

もし拡張ボタンを押して何かをする拡張機能を作りたい!ということでしたら、以下の内容は少し役立つかなと思います。

必要なファイル

  • manifest.json
  • background.js
  • content.js

それぞれ何をしているか解説します。

manifest.json

拡張機能のバージョン情報などの設定を書くファイルになります。

{
  "name": "App Name", // アプリ名
  "version": "1.0.0", // アプリのバージョン(適当)
  "manifest_version": 3, // 最新であるV3を使う
  "description": "description", // アプリの説明文
  "icons": { // 拡張機能のアイコン、それぞれn*nのサイズで作成する
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "action": { // 右上の拡張機能ボタンを使うために必要
    "default_icon": "icons/icon16.png" // 拡張機能ボタンのアイコン
  },
  "permission": ["activateTab", "scripting"], // アプリに渡す権限
  "background": { // ボタンクリック時に走らせたいスクリプト
    "service_worker": "background.js"
  }
}

background.js

拡張機能ボタンを押された時に発動するスクリプトを書きます。

chromeが提供しているAPIのonClickedイベントにリスナーを追加します。

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: {tabId: tab.id},
    files: ['content.js'] // 実行するスクリプトを指定
  });
});

こうすることで、拡張ボタンクリック時にcontent.jsを実行してくれます。

content.js

いよいよメインの処理を書いていきます。

// (1) serialize document and parse
const serializer = new XMLSerializer();
const documentCopy = serializer.serializeToString(document);
const parser = new DOMParser();
const doc = parser.parseFromString(documentCopy, 'text/html');

const parent = doc.getElementById('main');
const child = doc.querySelector('#main div.table.conf.line');

// (2) remove unwanted
parent.removeChild(child);

// (3) extract
const text = parent.textContent;
const regex = /https:\/\/hogehoge.com\/hoge\/.*?(\d{1,})/g;
const found = text.match(regex);

const result = [];
found.forEach((value) => {
  const reg = /https:\/\/hogehoge.com\/hoge\//g;

  result.push(value.replace(reg, ''));
});

// (4) 
if (result.length > 0) {
  const labelLink = document.querySelector('a.comment-container');
  labelLink.click();

  // (5) fill in comment
  document.forms['hogehoge-form'].elements['hogehoge-input'].defaultValue = result;

  const registerLabel = document.querySelector('#hogehoge-comment');
  registerLabel.click();

  const closeLink = document.querySelector('a.hogehoge-close');
  setTimeout(() => closeLink.click(), 500);
}

何をしているのか、ざっと説明をします。

// (1) serialize document and parse
const serializer = new XMLSerializer();
const documentCopy = serializer.serializeToString(document);
const parser = new DOMParser();
const doc = parser.parseFromString(documentCopy, 'text/html');

const parent = doc.getElementById('main');
const child = doc.querySelector('#main div.table.conf.line');

// (2) remove unwanted
parent.removeChild(child);

対象ページのすべてのテキストを取ってきて、そこから対象の文言を取りたかったのですが、

対象ページの特定の箇所の文言だけは取りたくなかったので、その要素だけremoveChildしています。

(1)のserializerparserでは、DOMをstringに変換して、それをまたDOMに変換しています。直接DOMをremoveChildしてしまうと、ページでその部分が削除されてしまうので、このようにしてあります。

詳しくは、以降のハマったポイントで説明します。

// (3) extract
const text = parent.textContent;
const regex = /https:\/\/hogehoge.com\/hoge\/.*?(\d{1,})/g;
const found = text.match(regex);

const result = [];
found.forEach((value) => {
  const reg = /https:\/\/hogehoge.com\/hoge\//g;

  result.push(value.replace(reg, ''));
});

ここでは、取得した文言の中から特定の文言だけを抽出し、抽出した文言をさらに整形してresult配列に入れています。

// (4) 
if (result.length > 0) {
  const labelLink = document.querySelector('a.comment-container');
  labelLink.click();

  // (5) fill in comment
  document.forms['hogehoge-form'].elements['hogehoge-input'].defaultValue = result;

  const registerLabel = document.querySelector('#hogehoge-comment');
  registerLabel.click();

  const closeLink = document.querySelector('a.hogehoge-close');
  setTimeout(() => closeLink.click(), 500);
}

そして、最後にもし対象の文言が存在していたら、ページのコメント欄を開いて、resultを入力し投稿ボタンを押して、コメント欄を閉じるといった操作をしています。

ハマりポイント

DOMではなく、文言のコピーがほしかった

// (1) serialize document and parse の箇所になるのですが、最初はシリアライズ&パースせずに直接DOMを触っていました。

そうするとremoveChildでページ上の項目が消えてしまうので、困っていました。

データだけ取りたい・・と色々調べてたのですが、シリアライズでstringに変換して、それを対象ページとは関係のないDOMにひとまず変換することで、

サイトと紐づいてないDOMのデータとして抽出することができ、removeChildもできるようになりました。

DOMとは、Document Object Modelの略で、HTML/XML文書を扱うためのAPI。

https://www.javadrive.jp/javascript/dom/index1.html

正規表現

.*とか、意味がわからなかったのですが、いざ使ってみて少しはわかるようになったかな・・

/https:\/\/hogehoge.com\/hoge\/.*?(\d{1,})/g

こちらは、https://hogehoge.com/hoge/ABCD123みたいなものを取得したい場合の正規表現になります。

上記の正規表現の意味としては、先頭https://hogehoge.com/hoge/で始まり、最後が1文字以上の数字で終わるような文字列というものです。

.*?は最短マッチと呼ばれるもので、条件にあう最短の部分にマッチします。

正規表現説明正規表現の例マッチする例
.任意の1文字A.AABA, ANA
*直前の文字が0個以上A*AA, AAA, AAAAA
?直前の文字が0個または1個A?AA, AAA
.*?直前の任意の文字(=直前のなんでもいい文字)が0回以上繰り返すA.*?AABA, ABNA

書いててもいまいちわからないので、.*?は最短マッチなんだな〜と覚えています。

最後の/gは、オプションフラグといって、いろいろ指定できるのですが、今回は連続で検索するgを付与し、すべてのマッチする文言を抽出します。

inputタグにvalueがリアルタイムに反映されない

document.forms['hogehoge-form'].elements['hogehoge-input'].defaultValue = result;

とやるとinputのテキストボックスに指定した値が動的に入るはずなのですが、何度やっても入らなくて。

悩んだ末に、とりあえず投稿ボタンを押してみようと思ってボタンクリックのコードを入れたら無事内容がコメントできました。反映されないのが謎すぎますが。

ウィンドウ閉じるボタンが押せない

投稿ボタンを押したあとに小さいウィンドウを閉じるための閉じるボタンがあるのですが、それをクリックしても閉じず・・。

悩んだ末、setTimeoutを使って遅延を入れてクリックしてみたところボタンが押せました。投稿中のあとにすぐ閉じるボタンを押すコードを入れると競合してしまうのか、、?

非同期に実行してみたらできました。コールスタックが空になってからタスクキューを実行しているので、競合しない・・?

まとめ

簡単なChrome拡張機能を作ってみました。できると嬉しいですねっっ!

正規表現とDOMの操作に関しても勉強になりました〜!:)

にほんブログ村に参加しております!よろしければクリックお願いします 🙂

にほんブログ村 IT技術ブログへ
にほんブログ村

コメント

タイトルとURLをコピーしました