大きなHTMLをAIで取得したくなったときに考えたこと #LayerX_AI_Agent_ブログリレー

こんにちは。LayerXバクラク事業部でCREをしているuna(@unachang113)です。
こちらはLayerX AI エージェントブログリレー50日目の記事です。前回の記事は矢野目さんのLLMで業務ワークフローを自動生成・最適化する! 〜ワークフロー自動生成・最適化の取り組みについて〜 でした。
今回の記事では大きなHTMLをAIエージェントで扱いたい時にどう解消したかのトライアンドエラーについて話そうと思います。
背景
先日、自分が所属していた申請・経費精算チームでMarkuplintというHTMLのマークアップが正しいか確認するための静的解析ツールを導入しました。
しかし、独自コンポーネントやライブラリで要素をwrapしているHTMLに対し、親子のHTMLの構造の検知が行われないなどの課題がありました。
こちらをAIでうまく解決できないかと思いClaude CodeでAIエージェントを作ってみることにしました。
作ったもの
まずは任意のページのHTMLのソースを取得してMarkuplintを実行しエラーの結果をレポートするAIエージェントを作成しようとしました。
実際に作ったもののプレビュー

Playwright MCPを使ってHTMLを取ってくるだけのシンプルな操作なので簡単だろうと思っていたのですが、以下の問題が発生してしまいました。
起きた問題
任意のページでbrowser_evaluateでHTMLのソースを取得しようとした結果、取得しようとしたHTMLが大きすぎて取得時にClaude Sonnet 4.5の最大トークン数を大幅にオーバーしてしまいました。
[OUTPUT TRUNCATED - exceeded 25000 token limit]
The tool output was truncated. If this MCP server provides pagination or filtering tools, use them to retrieve specific portions of the data. If pagination is not available, inform the user that you are working with truncated output and results may be incomplete.
コーディングエージェントの場合、ファイルが大きい場合は段階的な読み込みを行ったり必要な要素を探索・特定して問題を解決しにいきます。
ただ、今回はMarkuplintを実行するまでが作りたいものだったので、HTMLのソースをまるっと取り扱う事が必要不可欠です。
そこでどうにかできないか色々試行錯誤することにしました。
※取得しようとしたHTMLの特徴
- local環境でのバクラク申請経費精算の明細画面
- SPAの大きなHTML
- inline styleが大量にある
- SVGも大量に存在している
どうやって解決しようとしたか
HTMLソースを取得しようとしたかの試行錯誤は以下です。
HTMLの持ち方について
まず、HTMLを文字列情報としてAIに記憶してもらうのはOutput token limitをオーバーしたこともあり、難しいなと感じました。
そこでHTMLを一度tmpファイルとして保存して、保存したHTMLに対してコマンドでMakruplintを実行することにしました。
1. Playwright MCPをつかってなんとかならないか試した
まずはPlaywright MCPでなんとかできないか考えてみました。
1-1. HTMLを分割して取得する
自分が当初取得しようとしたファイルはHTML上に大量のstyle属性が含まれていたこともあり、styleやscriptなどHTMLに直接関係ない要素を一旦分割して取得する形を試してみました。
処理のフロー(ver.1)
**1. ページ遷移**
- 指定URLにアクセス
**2. HTML分割取得(6パーツを並列で取得)**
- パーツ1: DOCTYPE + <html> + <head>(style/script除外)
- パーツ2: head内の<style>要素のみ
- パーツ3: head内の<script>要素のみ
- パーツ4: <body>(style/script除外)
- パーツ5: body内の<style>要素のみ
- パーツ6: body内の<script>要素のみ
- 各パーツはbrowser_evaluateを並列実行で取得する
**3. HTML結合**
- パーツ1から</head>を削除
- パーツ2(head styles)を追加
- パーツ3(head scripts)を追加
- </head>を追加
- パーツ4から</body>を削除
- パーツ5(body styles)を追加
- パーツ6(body scripts)を追加
- </body></html>を追加
**4. ファイル保存**
- 結合したHTMLを/tmp/playwright-html-split-{timestamp}.htmlに保存
分割の指示の例(一部)
#### パーツ1: DOCTYPE + html/head (style/script除外)
```javascript
() => {
const doctype = document.doctype ? '<!DOCTYPE ' + document.doctype.name + (document.doctype.publicId ? ' PUBLIC "' + document.doctype.publicId + '"' : '') + (document.doctype.systemId ? ' "' + document.doctype.systemId + '"' : '') + '>' : '';
const htmlAttrs = Array.from(document.documentElement.attributes).map(attr => attr.name + '="' + attr.value + '"').join(' ');
const htmlTag = '<html' + (htmlAttrs ? ' ' + htmlAttrs : '') + '>';
const headClone = document.head.cloneNode(true);
headClone.querySelectorAll('style, script').forEach(el => el.remove());
return doctype + htmlTag + headClone.outerHTML;
}
styleは分割して取得するようにしてみましたが、styleの文字数が多すぎて全部取得することができずOutput token limitをまたoverしてしまいました。
2-2. Markuplintの実行に不要な部分を取り除いて取得する+コンテキストを消費しそうな要素を分割取得
2-1で取得しきれなかった要素があったことを踏まえなにか削ったり分割で取得できるものはないか考えることにしました。
MarkuplintはHTMLの静的解析ツールです。要素が閉じているか等のルールはありますが、CSSやscript内の記法に関してはMarkuplintはチェックしてくれません。 <style> が閉じられていないとそもそもHTMLが正しく表示されないため事前に自分のチェックで気づけることや、lintでチェックされないことを踏まえ<style> は削除することにしました。
同様にインラインの<script> に関してもlintでチェックされないことや、外部のスクリプトで自分が行った実装に関係ないケースがあるため削除することにしました。
また、bodyの中身を取得しようとした際にSVGがpath等の指定が長いことが多く、こちらもtokenを多く消費する懸念があったため、一度 <!— SVG_0 —> のようなplaceholderに一度変換してから取得することにしました。
こちらも、HTMLが大きすぎる場合にうまく取得できないケースがあり、Playwright MCPを使って取得する方法と別の手段を考えることにしました。
2. Playwright + Node.jsを使用して取得する
前述の通り、HTMLが大きい場合、不要な要素を削除して分割取得しても取り切れないものがありました。(ex. 明細が100件ある経費精算)
Playwright CLI経由で直接HTMLファイルを保存する動作を別でスクリプトとして用意し、完全にAIの処理の外に置くことでコンテキストの圧迫を防ぎながらHTMLソースを保存することができました。
▼ HTML取得用のコード
import { chromium } from 'playwright'
import { writeFileSync, mkdirSync, existsSync } from 'fs'
import { dirname, resolve } from 'path'
const url = process.argv[2]
const defaultOutput = 'scripts/markuplint-pr-check/temp/page.html'
const outputFile = process.argv[3] || defaultOutput
const authFile = 'scripts/markuplint-pr-check/temp/auth.json'
if (!url) {
console.error('❌ Error: URL is required')
console.error('')
console.error('Usage:')
console.error(' node fetch-html.mjs <url> [output-file]')
console.error('')
console.error('Examples:')
console.error(' node fetch-html.mjs http://localhost:3005/forms')
console.error(' node fetch-html.mjs http://localhost:3005/forms temp/forms.html')
process.exit(1)
}
const absoluteAuthPath = resolve(authFile)
if (!existsSync(absoluteAuthPath)) {
console.error(`❌ Error: Auth state file not found: ${absoluteAuthPath}`)
console.error('')
console.error('Please create auth state file first:')
console.error(` npx playwright open --save-storage=${authFile} http://localhost:3005`)
console.error('')
console.error('Then login manually and close the browser to save the auth state.')
process.exit(1)
}
console.log('🌐 HTML Fetcher\n')
console.log(`📍 Target URL: ${url}`)
console.log(`💾 Output file: ${outputFile}\n`)
;(async () => {
console.log('🔐 Loading auth state...')
const browser = await chromium.launch({
headless: true, // Run in headless mode
})
try {
const context = await browser.newContext({
storageState: absoluteAuthPath,
})
const page = await context.newPage()
console.log(`🌍 Navigating to: ${url}`)
await page.goto(url, {
waitUntil: 'networkidle',
timeout: 30000,
})
console.log(`✅ Page loaded: ${await page.title()}`)
console.log('📸 Capturing HTML...')
const html = await page.evaluate(() => {
const htmlClone = document.documentElement.cloneNode(true)
return htmlClone.outerHTML
})
// Save to file
const absolutePath = resolve(outputFile)
const dir = dirname(absolutePath)
mkdirSync(dir, { recursive: true })
writeFileSync(absolutePath, html, 'utf-8')
console.log(`\n✅ HTML saved successfully!`)
await browser.close()
} catch (error) {
console.error(`❌ Error: ${error.message}`)
await browser.close()
process.exit(1)
}
})()
ただ、こちらの場合、ページ上での操作をAIで自動化することができないため、ページを読み込んだ状態でのHTMLの保存となります。
操作がない場合はこちらが一番確実に取得できる方法だなと感じました。
まとめ
今回はAIを用いてHTMLソースを扱いたい場合の方法を色々考えてみました。
今回試した方法をまとめると以下になります。
| HTMLを分割して取得する | Markuplintの実行に不要な部分を取り除いて取得する+コンテキストを消費しそうな要素を分割取得 | Playwright + Node.jsを使用して取得する | |
|---|---|---|---|
| コンテキスト消費 | △(取得したい要素が多いとOutput token limitを超過する) | △(取得したい要素が多いとOutput token limitを超過する) | ◎(Node.js経由で取得するためコンテキストを消費せずHTMLを取得できる) |
| 操作の自動化 | ◯ | ◯ | ✗(手動で操作する必要がある) |
今回、銀の弾丸になる解決策は見つけられなかったので、AIで扱えるtokenがもっと増えてHTMLも自由に扱えるようになる未来に期待しながら、今後もアクセシビリティ向上に寄与できるAIエージェントについて考えていこうと思います、
Discussion