[{"data":1,"prerenderedAt":631},["ShallowReactive",2],{"content-/mf-table-scraping-bug-fix":3,"all-pages-for-dir":629,"og-image-/mf-table-scraping-bug-fix":630},{"id":4,"title":5,"body":6,"category":609,"description":610,"extension":611,"meta":612,"navigation":613,"ogImage":614,"path":615,"project_name":616,"published":617,"publishedAt":618,"seo":619,"stem":620,"tags":621,"todo":627,"unpublished":617,"updatedAt":614,"__hash__":628},"pages/2026-03/2026-03-23/mf-table-scraping-bug-fix.md","会計サービス テーブルスクレイピングのバグ修正 - 空列・サービス名誤除去・非表示行の3重トラップ",{"type":7,"value":8,"toc":588},"minimark",[9,14,18,21,26,30,33,36,43,119,130,133,136,192,195,197,201,204,207,210,256,259,279,282,291,470,472,476,479,482,485,488,491,497,499,503,513,516,518,522,529,532,543,546,548,551,584],[10,11,13],"h1",{"id":12},"会計サービス-テーブルスクレイピングのバグ修正","会計サービス テーブルスクレイピングのバグ修正",[15,16,17],"p",{},"会計ソフトの明細をスプレッドシートに書き出すChrome拡張で、特定サービス（Square）の明細だけがどうしても取れなかった。原因を追うと、正規表現のマッチ範囲、CSS非表示の判定、フィルタ未適用時のデータ混在という3つのバグが絡み合っていた。最初は明細全取得側だけを直して「なぜ動かない」と首をかしげ、実際に使っているスプレッドシートインポート側が未修正だったことに気づくまで30分ロスした。デバッグの一日を記録する。",[19,20],"hr",{},[22,23,25],"h2",{"id":24},"バグ1-cleanservicenameがsquareを消す","バグ1: cleanServiceNameが「Square」を消す",[27,28,29],"h3",{"id":29},"症状",[15,31,32],{},"スプレッドシートの5番目のシート名が「Square」ではなく「明細」になっていた。",[27,34,35],{"id":35},"原因",[15,37,38,42],{},[39,40,41],"code",{},"cleanServiceName","は口座番号の末尾文字列を除去する正規表現を持っている:",[44,45,50],"pre",{"className":46,"code":47,"language":48,"meta":49,"style":49},"language-js shiki shiki-themes vitesse-light vitesse-light","// 口座番号を除去する意図\nname.replace(/[0-9a-zA-Z\\*_]{5,}$/, '')\n","js","",[39,51,52,61],{"__ignoreMap":49},[53,54,57],"span",{"class":55,"line":56},"line",1,[53,58,60],{"class":59},"sxvE3","// 口座番号を除去する意図\n",[53,62,64,68,72,76,79,83,86,90,94,97,100,104,108,110,113,116],{"class":55,"line":63},2,[53,65,67],{"class":66},"s4oTP","name",[53,69,71],{"class":70},"shFtX",".",[53,73,75],{"class":74},"senZ8","replace",[53,77,78],{"class":70},"(",[53,80,82],{"class":81},"sMJiu","/",[53,84,85],{"class":70},"[",[53,87,89],{"class":88},"snbK4","0-9a-zA-Z",[53,91,93],{"class":92},"svWSF","\\*",[53,95,96],{"class":88},"_",[53,98,99],{"class":70},"]",[53,101,103],{"class":102},"sM54T","{5,}",[53,105,107],{"class":106},"sHkkW","$",[53,109,82],{"class":81},[53,111,112],{"class":70},",",[53,114,115],{"class":81}," ''",[53,117,118],{"class":70},")\n",[15,120,121,122,125,126,129],{},"このパターンは「英数字・アスタリスク・アンダースコアが5文字以上続く末尾」にマッチする。口座番号なら ",[39,123,124],{},"1234567"," や ",[39,127,128],{},"ABCD*1234"," のような文字列を想定していた。ところが「Square」は英字6文字。見事にマッチして空文字になり、フォールバック名の「明細」に落ちていた。",[27,131,132],{"id":132},"修正",[15,134,135],{},"先読みで「数字かアスタリスクを1つ以上含む」条件を追加:",[44,137,139],{"className":46,"code":138,"language":48,"meta":49,"style":49},"name.replace(/(?=.*[\\d\\*])[0-9a-zA-Z\\*_]{5,}$/, '')\n",[39,140,141],{"__ignoreMap":49},[53,142,143,145,147,149,151,153,156,159,162,164,167,169,172,174,176,178,180,182,184,186,188,190],{"class":55,"line":56},[53,144,67],{"class":66},[53,146,71],{"class":70},[53,148,75],{"class":74},[53,150,78],{"class":70},[53,152,82],{"class":81},[53,154,155],{"class":70},"(?=",[53,157,71],{"class":158},"she3A",[53,160,161],{"class":102},"*",[53,163,85],{"class":70},[53,165,166],{"class":158},"\\d",[53,168,93],{"class":92},[53,170,171],{"class":70},"])[",[53,173,89],{"class":88},[53,175,93],{"class":92},[53,177,96],{"class":88},[53,179,99],{"class":70},[53,181,103],{"class":102},[53,183,107],{"class":106},[53,185,82],{"class":81},[53,187,112],{"class":70},[53,189,115],{"class":81},[53,191,118],{"class":70},[15,193,194],{},"純粋な英字だけの文字列にはマッチしなくなる。",[19,196],{},[22,198,200],{"id":199},"バグ2-非表示行の除外が効きすぎる","バグ2: 非表示行の除外が効きすぎる",[27,202,29],{"id":203},"症状-1",[15,205,206],{},"Squareの明細ページでデータが0件になる。DOMにはデータ行が101行あるのに、全て除外されていた。",[27,208,209],{"id":209},"試行錯誤の過程",[211,212,213,229,238],"ol",{},[214,215,216,220,221,224,225,228],"li",{},[217,218,219],"strong",{},"最初の仮説",": 会計サービスがフィルタ時にCSSで行を隠すだけでDOMに残している。",[39,222,223],{},"getVisibleTableRows","を追加して、",[39,226,227],{},"offsetHeight === 0","の行を弾く実装にした",[214,230,231,234,235,237],{},[217,232,233],{},"結果",": 通常のサービスでは動いた。しかしSquareページでは全行が",[39,236,227],{},"になった",[214,239,240,243,244,247,248,251,252,255],{},[217,241,242],{},"DevToolsコンソールで調査",": 行自体は",[39,245,246],{},"display: table-row","、",[39,249,250],{},"visibility: visible","。行は可視状態なのに",[39,253,254],{},"offsetHeight","が0",[27,257,35],{"id":258},"原因-1",[15,260,261,263,264,271,272,275,276,278],{},[39,262,227],{},"になるのは、行自体が非表示なのではなく、",[217,265,266,267,270],{},"テーブルの祖先要素が",[39,268,269],{},"display: none","で隠されている","場合だった。Squareのページでは",[39,273,274],{},"#js-acts-table-tbody","を含むテーブル全体がCSS的に非表示になっていて、別のテーブル要素にデータが表示されていた。",[39,277,254],{},"チェックは祖先の非表示まで拾ってしまう。",[27,280,132],{"id":281},"修正-1",[15,283,284,287,288,290],{},[39,285,286],{},"findDataTable","関数を新設。",[39,289,274],{},"が非表示なら、ページ上の別の表示テーブルを自動検出する方式にした:",[44,292,294],{"className":46,"code":293,"language":48,"meta":49,"style":49},"function findDataTable() {\n  const standard = document.getElementById('js-acts-table-tbody');\n  if (standard && standard.offsetHeight > 0) return standard;\n  // ページ上の表示されているテーブルを探す\n  const tables = document.querySelectorAll('table tbody');\n  return [...tables].find(t => t.offsetHeight > 0 && t !== standard);\n}\n",[39,295,296,311,344,382,388,416,464],{"__ignoreMap":49},[53,297,298,302,305,308],{"class":55,"line":56},[53,299,301],{"class":300},"stQ0i","function",[53,303,304],{"class":74}," findDataTable",[53,306,307],{"class":70},"()",[53,309,310],{"class":70}," {\n",[53,312,313,316,319,322,325,327,330,332,335,339,341],{"class":55,"line":63},[53,314,315],{"class":300},"  const",[53,317,318],{"class":66}," standard",[53,320,321],{"class":70}," =",[53,323,324],{"class":66}," document",[53,326,71],{"class":70},[53,328,329],{"class":74},"getElementById",[53,331,78],{"class":70},[53,333,334],{"class":81},"'",[53,336,338],{"class":337},"sdGka","js-acts-table-tbody",[53,340,334],{"class":81},[53,342,343],{"class":70},");\n",[53,345,347,350,353,356,359,361,363,365,368,371,374,377,379],{"class":55,"line":346},3,[53,348,349],{"class":106},"  if",[53,351,352],{"class":70}," (",[53,354,355],{"class":66},"standard",[53,357,358],{"class":300}," &&",[53,360,318],{"class":66},[53,362,71],{"class":70},[53,364,254],{"class":66},[53,366,367],{"class":70}," >",[53,369,370],{"class":102}," 0",[53,372,373],{"class":70},")",[53,375,376],{"class":106}," return",[53,378,318],{"class":66},[53,380,381],{"class":70},";\n",[53,383,385],{"class":55,"line":384},4,[53,386,387],{"class":59},"  // ページ上の表示されているテーブルを探す\n",[53,389,391,393,396,398,400,402,405,407,409,412,414],{"class":55,"line":390},5,[53,392,315],{"class":300},[53,394,395],{"class":66}," tables",[53,397,321],{"class":70},[53,399,324],{"class":66},[53,401,71],{"class":70},[53,403,404],{"class":74},"querySelectorAll",[53,406,78],{"class":70},[53,408,334],{"class":81},[53,410,411],{"class":337},"table tbody",[53,413,334],{"class":81},[53,415,343],{"class":70},[53,417,419,422,425,428,431,434,436,439,442,445,447,449,451,453,455,457,460,462],{"class":55,"line":418},6,[53,420,421],{"class":106},"  return",[53,423,424],{"class":70}," [...",[53,426,427],{"class":66},"tables",[53,429,430],{"class":70},"].",[53,432,433],{"class":74},"find",[53,435,78],{"class":70},[53,437,438],{"class":66},"t",[53,440,441],{"class":70}," =>",[53,443,444],{"class":66}," t",[53,446,71],{"class":70},[53,448,254],{"class":66},[53,450,367],{"class":70},[53,452,370],{"class":102},[53,454,358],{"class":300},[53,456,444],{"class":66},[53,458,459],{"class":300}," !==",[53,461,318],{"class":66},[53,463,343],{"class":70},[53,465,467],{"class":55,"line":466},7,[53,468,469],{"class":70},"}\n",[19,471],{},[22,473,475],{"id":474},"バグ3-フィルタ未適用で全サービスの明細が混在","バグ3: フィルタ未適用で全サービスの明細が混在",[27,477,29],{"id":478},"症状-2",[15,480,481],{},"Squareのシートに、みずほビジネスWEB・三井住友カード・三井住友銀行の明細が全部入っていた。",[27,483,35],{"id":484},"原因-2",[15,486,487],{},"Squareページのドロップダウンが「全て」の状態（フィルタ未適用）でデータを取得していた。会計サービスはフィルタ未適用だと全サービスの明細をDOMに流し込む。",[27,489,132],{"id":490},"修正-2",[15,492,493,496],{},[39,494,495],{},"waitForPageReady","でフィルタ適用状態を返すようにし、フィルタ未適用（全明細マージ）の場合はスキップする処理を追加した。",[19,498],{},[22,500,502],{"id":501},"一番痛かったミス-修正先の取り違え","一番痛かったミス: 修正先の取り違え",[15,504,505,506,508,509,512],{},"3つのバグ修正を明細全取得のcontent.jsに入れた。動作確認して「直った」と思ったが、実際にスプレッドシートに書き出すのはスプレッドシートインポートの方だった。同じ",[39,507,41],{},"、同じ",[39,510,511],{},"tableToArray","のコードが両方に存在していて、使っていない方だけを修正していた。",[15,514,515],{},"ユーザーから「まだバグってる」と言われて初めて気づいた。拡張機能が2つあって同じロジックが重複している構造自体がバグの温床になっていた。",[19,517],{},[22,519,521],{"id":520},"chrome-devtoolsコンソールによるデバッグ","Chrome DevToolsコンソールによるデバッグ",[15,523,524,525,528],{},"Chrome DevTools MCPが環境のポリシー制限で接続できなかったため、",[217,526,527],{},"ユーザーにDevToolsコンソールでJavaScriptを実行してもらう","方式でデバッグした。",[15,530,531],{},"手順:",[211,533,534,537,540],{},[214,535,536],{},"会計サービスの対象ページでF12→コンソールを開いてもらう",[214,538,539],{},"テーブル構造を調べるスクリプトを渡す（行数、offsetHeight、CSSプロパティ）",[214,541,542],{},"結果をチャットで受け取って原因を絞り込む",[15,544,545],{},"CDP（Chrome DevTools Protocol）が使えない環境でも、コンソールへのJS注入でDOMの状態を細かく調査できる。テーブルの行が「見えているのにoffsetHeightが0」という矛盾した状態は、このデバッグ手法で初めて掴めた。",[19,547],{},[22,549,550],{"id":550},"振り返り",[552,553,554,560,571,577],"ul",{},[214,555,556,559],{},[217,557,558],{},"正規表現は意図しないマッチを引き起こす","。口座番号用のパターンが「Square」という普通の英単語を消した。先読み条件で意図を明示するだけで防げた",[214,561,562,567,568,570],{},[217,563,564,566],{},[39,565,227],{},"は行単体の非表示を意味しない","。祖先要素の",[39,569,269],{},"でも0になる。要素のスタイルだけ見て「表示されている」と判断しても、DOMツリーを遡ると非表示の祖先がいることがある",[214,572,573,576],{},[217,574,575],{},"同じロジックが複数ファイルに散在していると、片方だけ修正して安心してしまう","。コードを重複させないか、修正時に全箇所を検索する癖をつける必要がある",[214,578,579,580,583],{},"この日の後半で、純粋関数を",[39,581,582],{},"lib.js","に抽出してテストを追加するリファクタリングを実施した。重複していたロジックを1箇所に集約して、同じ問題が再発しない構造にした",[585,586,587],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .snbK4, html code.shiki .snbK4{--shiki-default:#A65E2B;--shiki-dark:#A65E2B}html pre.shiki code .svWSF, html code.shiki .svWSF{--shiki-default:#BDA437;--shiki-dark:#BDA437}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .she3A, html code.shiki .she3A{--shiki-default:#5A6AA6;--shiki-dark:#5A6AA6}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}",{"title":49,"searchDepth":63,"depth":63,"links":589},[590,595,601,606,607,608],{"id":24,"depth":63,"text":25,"children":591},[592,593,594],{"id":29,"depth":346,"text":29},{"id":35,"depth":346,"text":35},{"id":132,"depth":346,"text":132},{"id":199,"depth":63,"text":200,"children":596},[597,598,599,600],{"id":203,"depth":346,"text":29},{"id":209,"depth":346,"text":209},{"id":258,"depth":346,"text":35},{"id":281,"depth":346,"text":132},{"id":474,"depth":63,"text":475,"children":602},[603,604,605],{"id":478,"depth":346,"text":29},{"id":484,"depth":346,"text":35},{"id":490,"depth":346,"text":132},{"id":501,"depth":63,"text":502},{"id":520,"depth":63,"text":521},{"id":550,"depth":63,"text":550},"dev","Chrome拡張で会計サービスの明細テーブルを取得する際に遭遇した3つのバグ。tableToArrayの空A列混入、cleanServiceNameがSquareを消す正規表現、offsetHeight===0による非表示テーブル配下の行除外。試行錯誤の過程と解決策を記録","md",{},true,null,"/mf-table-scraping-bug-fix","misc-dev",false,"2026-03-23T00:00:00.000Z",{"title":5,"description":610},"2026-03/2026-03-23/mf-table-scraping-bug-fix",[622,623,624,625,626],"Chrome拡張機能","スクレイピング","正規表現","デバッグ","クラウド会計","memo","RStYmDKYlRt2OkZ23sqy8nxt69JLOMvSGRaFxQ58Epc",[],"https://log.eurekapu.com/og/blog/mf-table-scraping-bug-fix.png?v=2026-03-23T00%3A00%3A00.000Z&title=%E4%BC%9A%E8%A8%88%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0%E3%81%AE%E3%83%90%E3%82%B0%E4%BF%AE%E6%AD%A3%20-%20%E7%A9%BA%E5%88%97%E3%83%BB%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E5%90%8D%E8%AA%A4%E9%99%A4%E5%8E%BB%E3%83%BB%E9%9D%9E%E8%A1%A8%E7%A4%BA%E8%A1%8C%E3%81%AE3%E9%87%8D%E3%83%88%E3%83%A9%E3%83%83%E3%83%97&author=Kei%20Komatsu&sig=7e2ed50e0a6a5c22",1782528820334]