• #Chrome拡張機能
  • #スクレイピング
  • #正規表現
  • #デバッグ
  • #MF
開発misc-devメモ

MFテーブルスクレイピングのバグ修正

会計ソフトの明細をスプレッドシートに書き出すChrome拡張で、特定サービス(Square)の明細だけがどうしても取れなかった。原因を追うと、正規表現のマッチ範囲、CSS非表示の判定、フィルタ未適用時のデータ混在という3つのバグが絡み合っていた。最初はMF_明細全取得側だけを直して「なぜ動かない」と首をかしげ、実際に使っているMF_スプレッドシートインポート側が未修正だったことに気づくまで30分ロスした。デバッグの一日を記録する。


バグ1: cleanServiceNameが「Square」を消す

症状

スプレッドシートの5番目のシート名が「Square」ではなく「明細」になっていた。

原因

cleanServiceNameは口座番号の末尾文字列を除去する正規表現を持っている:

// 口座番号を除去する意図
name.replace(/[0-9a-zA-Z\*_]{5,}$/, '')

このパターンは「英数字・アスタリスク・アンダースコアが5文字以上続く末尾」にマッチする。口座番号なら 1234567ABCD*1234 のような文字列を想定していた。ところが「Square」は英字6文字。見事にマッチして空文字になり、フォールバック名の「明細」に落ちていた。

修正

先読みで「数字かアスタリスクを1つ以上含む」条件を追加:

name.replace(/(?=.*[\d\*])[0-9a-zA-Z\*_]{5,}$/, '')

純粋な英字だけの文字列にはマッチしなくなる。


バグ2: 非表示行の除外が効きすぎる

症状

Squareの明細ページでデータが0件になる。DOMにはデータ行が101行あるのに、全て除外されていた。

試行錯誤の過程

  1. 最初の仮説: MFがフィルタ時にCSSで行を隠すだけでDOMに残している。getVisibleTableRowsを追加して、offsetHeight === 0の行を弾く実装にした
  2. 結果: 通常のサービスでは動いた。しかしSquareページでは全行がoffsetHeight === 0になった
  3. DevToolsコンソールで調査: 行自体はdisplay: table-rowvisibility: visible。行は可視状態なのにoffsetHeightが0

原因

offsetHeight === 0になるのは、行自体が非表示なのではなく、テーブルの祖先要素がdisplay: noneで隠されている場合だった。Squareのページでは#js-acts-table-tbodyを含むテーブル全体がCSS的に非表示になっていて、別のテーブル要素にデータが表示されていた。offsetHeightチェックは祖先の非表示まで拾ってしまう。

修正

findDataTable関数を新設。#js-acts-table-tbodyが非表示なら、ページ上の別の表示テーブルを自動検出する方式にした:

function findDataTable() {
  const standard = document.getElementById('js-acts-table-tbody');
  if (standard && standard.offsetHeight > 0) return standard;
  // ページ上の表示されているテーブルを探す
  const tables = document.querySelectorAll('table tbody');
  return [...tables].find(t => t.offsetHeight > 0 && t !== standard);
}

バグ3: フィルタ未適用で全サービスの明細が混在

症状

Squareのシートに、みずほビジネスWEB・三井住友カード・三井住友銀行の明細が全部入っていた。

原因

Squareページのドロップダウンが「全て」の状態(フィルタ未適用)でデータを取得していた。MFはフィルタ未適用だと全サービスの明細をDOMに流し込む。

修正

waitForPageReadyでフィルタ適用状態を返すようにし、フィルタ未適用(全明細マージ)の場合はスキップする処理を追加した。


一番痛かったミス: 修正先の取り違え

3つのバグ修正をMF_明細全取得のcontent.jsに入れた。動作確認して「直った」と思ったが、実際にスプレッドシートに書き出すのはMF_スプレッドシートインポートの方だった。同じcleanServiceName、同じtableToArrayのコードが両方に存在していて、使っていない方だけを修正していた。

ユーザーから「まだバグってる」と言われて初めて気づいた。拡張機能が2つあって同じロジックが重複している構造自体がバグの温床になっていた。


Chrome DevToolsコンソールによるデバッグ

Chrome DevTools MCPが環境のポリシー制限で接続できなかったため、ユーザーにDevToolsコンソールでJavaScriptを実行してもらう方式でデバッグした。

手順:

  1. MFの対象ページでF12→コンソールを開いてもらう
  2. テーブル構造を調べるスクリプトを渡す(行数、offsetHeight、CSSプロパティ)
  3. 結果をチャットで受け取って原因を絞り込む

CDP(Chrome DevTools Protocol)が使えない環境でも、コンソールへのJS注入でDOMの状態を細かく調査できる。テーブルの行が「見えているのにoffsetHeightが0」という矛盾した状態は、このデバッグ手法で初めて掴めた。


振り返り

  • 正規表現は意図しないマッチを引き起こす。口座番号用のパターンが「Square」という普通の英単語を消した。先読み条件で意図を明示するだけで防げた
  • offsetHeight === 0は行単体の非表示を意味しない。祖先要素のdisplay: noneでも0になる。要素のスタイルだけ見て「表示されている」と判断しても、DOMツリーを遡ると非表示の祖先がいることがある
  • 同じロジックが複数ファイルに散在していると、片方だけ修正して安心してしまう。コードを重複させないか、修正時に全箇所を検索する癖をつける必要がある
  • この日の後半で、純粋関数をlib.jsに抽出してテストを追加するリファクタリングを実施した。重複していたロジックを1箇所に集約して、同じ問題が再発しない構造にした