頁的核心技術(shù))
1. 項目概述當LLM需要“看見”瀏覽器想象一下你讓一個語言模型去幫你完成一些網(wǎng)頁操作比如“幫我在電商網(wǎng)站上找到最便宜的無線耳機并加入購物車”。對于人類來說這很簡單打開網(wǎng)頁眼睛一掃找到搜索框、商品列表、價格標簽、購物車按鈕然后點擊。但對于一個純粹處理文本的LLM來說它面對的是一個由成千上萬行HTML、CSS和JavaScript代碼構(gòu)成的、結(jié)構(gòu)復雜的“黑箱”。它無法“看見”網(wǎng)頁的視覺布局無法理解一個div在屏幕上呈現(xiàn)為一個按鈕還是一個廣告橫幅。這就是browser-use這類瀏覽器Agent所要解決的核心問題為LLM構(gòu)建一雙能“看懂”網(wǎng)頁的“眼睛”和一雙能“操作”網(wǎng)頁的“手”。browser-use在GitHub上獲得了超過86k顆星這個驚人的數(shù)字背后反映的是社區(qū)對構(gòu)建實用、可靠的AI Agent的強烈需求。它不是一個簡單的“瀏覽器自動化工具”而是一套精心設計的處理管線Processing Pipeline專門負責將網(wǎng)頁的原始文檔對象模型DOM轉(zhuǎn)換、過濾、壓縮成LLM能夠理解和處理的“語言”。這個過程我們稱之為DOM處理管線。它的目標是在信息保真度和處理效率之間找到最佳平衡點既要讓LLM獲得足夠決策的信息又要避免因上下文過長Context Length而導致的成本飆升或性能下降。簡單來說browser-use的工作流可以概括為獲取原始DOM - 理解與抽象 - 壓縮與表征 - 交付給LLM決策 - 執(zhí)行動作并觀察結(jié)果。本文將深入拆解這個管線中的每一個核心環(huán)節(jié)看看一個頂流的開源項目是如何巧妙地解決“讓LLM看懂網(wǎng)頁”這個復雜問題的。無論你是AI應用開發(fā)者、對Agent技術(shù)感興趣的研究者還是希望了解前沿工程實踐的工程師這篇拆解都能為你提供扎實的、可直接借鑒的洞見。2. DOM處理管線的核心架構(gòu)與設計哲學2.1 為什么原始DOM對LLM是“天書”要理解browser-use的設計首先得明白原始DOM為什么不適合直接喂給LLM。一個現(xiàn)代網(wǎng)頁的DOM樹可能包含數(shù)萬個節(jié)點對應著HTML中的每一個標簽、屬性、文本節(jié)點。直接將其以文本形式例如通過document.documentElement.outerHTML dump出來可能會產(chǎn)生一個超過10萬token的龐然大物。這不僅會瞬間耗盡大多數(shù)LLM的上下文窗口例如GPT-4 Turbo的128K token也很容易被占滿更重要的是這些信息中充斥著大量對任務無關(guān)的“噪聲”。這些噪聲包括渲染無關(guān)的節(jié)點大量的div、span僅用于布局和樣式控制沒有直接的語義或交互意義。腳本與樣式內(nèi)容內(nèi)聯(lián)的script和style標簽內(nèi)容對理解頁面功能和可操作元素幫助有限但token消耗巨大。隱藏元素通過CSS設置為display: none或visibility: hidden的元素用戶不可見通常也不應被操作。重復的樣板代碼頁頭、頁腳、導航欄等通用結(jié)構(gòu)在每個頁面都可能重復出現(xiàn)。如果讓LLM直接閱讀這份“天書”它就像被扔進了一個堆滿雜亂零件的倉庫并被要求“找到那個紅色的螺絲刀”。效率低下且容易出錯。因此DOM處理管線的首要任務就是“降噪”和“結(jié)構(gòu)化”。2.2 browser-use管線的整體設計思路browser-use的管線設計遵循一個清晰的層次化策略可以類比為一個信息過濾與提煉的漏斗。第一層可訪問性過濾與基礎清理。這一步基于一個關(guān)鍵假設用戶只能與可見且可交互的頁面元素進行交互。因此管線首先會利用瀏覽器提供的API如Chrome DevTools Protocol或Playwright等自動化庫來篩選出當前在視口中可見、且未被禁用disabled屬性為false的元素。同時會剝離掉script、style、注釋等對交互決策無用的節(jié)點。這一步大幅削減了初始數(shù)據(jù)量。第二層語義增強與屬性提取。僅僅有標簽名和基礎屬性是不夠的。一個button它的文本內(nèi)容是“提交”還是“取消”一個input它是用來輸入郵箱的還是搜索的這一步會為每個候選元素提取豐富的語義屬性這些屬性是LLM決策的關(guān)鍵依據(jù)通常包括innerText元素的可見文本這是理解其功能最重要的信號。aria-label/aria-labelledby專門為可訪問性設計的標簽通常包含精確的描述。placeholder對于輸入框提示文本至關(guān)重要。type對于輸入框和按鈕類型text,submit,checkbox等定義了其行為。roleARIA角色明確說明了元素的用途button,link,textbox等。以及id,name,class等可用于唯一或分類標識的屬性。第三層空間結(jié)構(gòu)與層次關(guān)系編碼。LLM需要理解元素之間的相對位置關(guān)系。一個在“登錄表單”內(nèi)的輸入框和一個在“頁頭搜索欄”內(nèi)的輸入框意義完全不同。browser-use會采用一種緊湊的方式編碼元素的層級路徑例如使用CSS選擇器片段或XPath索引以及在視口中的近似坐標或使用基于DOM順序的索引。這有助于LLM建立頁面的“心理地圖”。第四層壓縮與表征格式化。這是將處理后的信息“翻譯”成LLM友好格式的最后一步。browser-use不會將整個處理后的DOM樹以XML或HTML格式發(fā)送。相反它會將每個候選元素及其關(guān)鍵屬性格式化成一個結(jié)構(gòu)化的文本描述。一種常見的格式是線性列表或簡化的樹形描述例如[1] button: idsubmit-btn, text登錄, rolebutton, located inside form#loginForm [2] input: typetext, nameusername, placeholder請輸入郵箱, roletextbox, follows [1] [3] link: text忘記密碼, href/forgot-password, rolelink, near [2]同時項目可能會引入更高級的壓縮策略比如將視覺上相鄰的、功能相似的元素如一個表單內(nèi)的所有輸入項進行分組用一個更高層級的描述來代表從而進一步節(jié)省token。第五層與LLM的交互協(xié)議。處理后的DOM表征會與用戶的指令、歷史操作記錄一起構(gòu)成發(fā)送給LLM的提示詞Prompt。LLM的輸出被嚴格約束為一種可解析的動作指令比如CLICK [idsubmit-btn]或TYPE [nameusername] “userexample.com”。browser-use再解析這個指令通過瀏覽器自動化驅(qū)動執(zhí)行。這個分層管線的設計哲學是逐步提煉保留精華。每一層都丟棄一些信息但目標是丟棄對當前任務最不重要的信息最終得到一個高信噪比、LLM可消化、且足以支撐準確決策的頁面摘要。3. 核心環(huán)節(jié)一DOM的獲取、過濾與語義增強3.1 高效獲取“活性”DOMbrowser-use通常不直接解析原始的HTML字符串因為那代表的是初始頁面源碼而非經(jīng)過JavaScript動態(tài)修改后的當前狀態(tài)。它依賴于成熟的瀏覽器自動化工具如Playwright或Puppeteer。這些工具提供了訪問“實時DOM”的能力。一個關(guān)鍵的操作是執(zhí)行JavaScript來獲取和過濾DOM。例如通過page.evaluate()注入一段腳本在瀏覽器上下文內(nèi)部執(zhí)行。這段腳本的核心任務是遍歷DOM樹從document.body或document.documentElement開始。應用可見性過濾器使用element.checkVisibility()API現(xiàn)代瀏覽器或計算樣式getComputedStyle(element).display ! ‘none’且visibility ! ‘hidden’來判斷元素是否可見。應用交互性過濾器檢查元素是否disabled以及元素類型是否可交互如button,input,a,select等或具有role”button”等ARIA角色。視口裁剪通過element.getBoundingClientRect()判斷元素是否在當前視口內(nèi)或至少部分可見。這對于長頁面尤其重要可以忽略屏幕外的內(nèi)容。實操心得element.checkVisibility()是一個更強大的API它考慮了CSS樣式、祖先元素可見性、內(nèi)容可見性content-visibility等多種因素比手動計算樣式更準確。但在較舊的瀏覽器環(huán)境中可能需要回退方案。3.2 關(guān)鍵語義屬性的提取策略對于過濾后留下的每個元素需要提取一組標準化的屬性集。這個屬性集的設計直接影響LLM的判斷能力。browser-use通常會定義一個優(yōu)先級邏輯來獲取元素的“最佳描述文本”// 偽代碼獲取元素描述文本的優(yōu)先級 function getElementDescription(element) { // 1. 首選 aria-label (明確的無障礙標簽) if (element.ariaLabel?.trim()) return element.ariaLabel; // 2. 其次 innerText (可見文本) const text element.innerText?.trim(); if (text text.length 100) return text; // 避免過長文本塊 // 3. 對于輸入框placeholder是關(guān)鍵 if (element.tagName ‘INPUT’ element.placeholder?.trim()) return 輸入框: ${element.placeholder}; // 4. 使用 alt 屬性圖片 if (element.tagName ‘IMG’ element.alt?.trim()) return 圖片: ${element.alt}; // 5. 回退到 title 屬性或生成一個基于標簽和id的通用描述 if (element.title?.trim()) return element.title; return ${element.tagName.toLowerCase()}${element.id ? ‘#’ element.id : element.className ? ‘.’ element.className.split(‘ ‘)[0] : ‘’}; }除了文本描述以下屬性幾乎總是被收集標識符id,name。它們是唯一選擇器的最佳來源。交互類型tagName,type(button,submit,text,checkbox),role。狀態(tài)checked(復選框/單選框),value(輸入框當前值)。位置線索通過element.getBoundingClientRect()得到的x, y, width, height或計算其在DOM樹中的索引路徑。注意事項innerText的提取需要謹慎。一個包含大量子元素的div可能會返回巨量的、拼接在一起的文本這可能是無意義的。通常需要設置一個長度閾值或者只提取直接文本子節(jié)點textContentof direct children。對于列表、表格等結(jié)構(gòu)化數(shù)據(jù)可能需要特殊的處理邏輯來保持其結(jié)構(gòu)性。3.3 處理動態(tài)內(nèi)容與iframe的挑戰(zhàn)現(xiàn)代網(wǎng)頁大量使用動態(tài)加載和iframe。browser-use的管線必須應對這些挑戰(zhàn)。動態(tài)內(nèi)容頁面加載后通過Ajax或WebSocket更新的內(nèi)容必須能被捕獲。一種常見策略是定期重新運行DOM提取流程或者在檢測到頁面主要區(qū)域發(fā)生變化通過MutationObserver時觸發(fā)。但這需要平衡實時性和性能開銷。iframeiframe是一個獨立的文檔上下文。browser-use需要能夠識別并切換到iframe內(nèi)部去獲取其DOM。這要求自動化工具支持page.frame()之類的API。在表征時需要明確標注元素來自哪個iframe例如[in iframe#payment] button: text”確認支付”。這一階段的輸出是一個由“富語義元素對象”組成的列表每個對象都包含了足夠LLM理解其“是什么”和“能做什么”的信息。但這還不是最終形態(tài)數(shù)據(jù)量可能仍然很大。4. 核心環(huán)節(jié)二DOM的結(jié)構(gòu)化壓縮與表征4.1 從樹形結(jié)構(gòu)到線性化表征原始的DOM是樹形結(jié)構(gòu)但LLM處理的是線性序列的token。如何將樹形關(guān)系有效地編碼進線性序列是一個關(guān)鍵問題。browser-use通常采用以下幾種策略或其組合縮進列表法用縮進來表示層級關(guān)系。這是最直觀的方法。- body - div#header - a.logo: text首頁 - form#search - input[typetext]: placeholder搜索... - button: text搜索 - div#main - h1: text商品列表 - div.product: text商品A 價格100 - div.product: text商品B 價格200這種方法保留了清晰的父子關(guān)系但當層級很深時會占用較多橫向空間token。路徑前綴法為每個元素賦予一個基于其在樹中位置的唯一路徑標識。[0] body [0-0] div#header [0-0-0] a.logo: text首頁 [0-0-1] form#search [0-0-1-0] input: placeholder搜索... [0-0-1-1] button: text搜索 [0-1] div#main [0-1-0] h1: text商品列表在后續(xù)LLM輸出動作指令時可以引用這個路徑ID如CLICK [0-0-1-1]。這種方式非常緊湊但丟失了元素類型的直接信息LLM需要額外記憶“路徑ID到元素描述”的映射。扁平列表關(guān)系描述法browser-use更傾向于使用的方法。它將所有元素放在一個扁平列表中每個元素有唯一索引如[1],[2]然后在元素的描述中通過自然語言說明其位置關(guān)系。[1] link: text首頁, idlogo, located at top-left corner. [2] form: idsearch, contains input and button for search. [3] input: typetext, placeholder搜索..., inside [2]. [4] button: text搜索, inside [2], right after [3]. [5] heading: text商品列表, level1, below the header. [6] product item: text商品A 價格100, below [5]. [7] product item: text商品B 價格200, below [6].這種方法在token使用和可讀性之間取得了很好的平衡。LLM可以輕松地理解“inside [2]”和“below [5]”所表達的空間和邏輯關(guān)系。4.2 智能分組與信息聚合對于高度重復或相似的元素分組是極佳的壓縮手段。例如一個商品列表頁可能有20個結(jié)構(gòu)完全相同的div class”product”每個里面包含圖片、標題、價格、按鈕。與其枚舉20次不如進行抽象[8-27] product list (20 items): - Each item has: image, title, price, Add to Cart button. - Example item [8]: titleWireless Headphone A, price$99.99. - Example item [9]: titleBluetooth Speaker B, price$59.99. ... - You can refer to specific item by its index (8 to 27).這樣我們用一小段描述概括了20個元素的核心特征和模式節(jié)省了數(shù)百個token。LLM在需要操作特定商品時仍然可以通過索引來指定如CLICK [8] button。實現(xiàn)分組需要算法識別具有相似HTML結(jié)構(gòu)、CSS類和視覺布局的元素。4.3 視覺與布局線索的融合純文本描述有時無法區(qū)分緊密相鄰的元素。一些更先進的Agent會嘗試融入簡單的視覺線索。例如在元素描述中加入其屏幕坐標的簡化表示[3] input: placeholderEmail, (area: top:200-220px, left:300-500px) [4] input: placeholderPassword, (area: top:230-250px, left:300-500px) [5] button: textSign In, (area: top:280-310px, left:400-450px)或者使用方向詞進行強化“位于頁面中央的登錄框”、“右上角的用戶頭像”。這些信息對于LLM理解“哪個輸入框在上面哪個在下面”非常有幫助能減少歧義。這一階段的輸出是一個高度精煉、結(jié)構(gòu)化、富含語義和關(guān)系信息的頁面摘要文本。它可能只有原始DOM token數(shù)量的5%-20%但卻包含了完成大多數(shù)交互任務所需的90%以上的關(guān)鍵信息。5. 核心環(huán)節(jié)三與LLM的協(xié)同工作流與動作執(zhí)行5.1 提示詞工程構(gòu)建有效的上下文處理后的DOM摘要不會單獨發(fā)送給LLM。它被嵌入到一個結(jié)構(gòu)化的提示詞模板中這個模板定義了Agent的角色、任務、操作規(guī)范和上下文。一個典型的提示詞結(jié)構(gòu)如下你是一個網(wǎng)頁瀏覽助手。你的目標是通過操作網(wǎng)頁元素來完成用戶指令。 當前頁面摘要如下 [此處插入DOM摘要] 你可以執(zhí)行以下操作 - CLICK [元素索引或描述]點擊一個元素。 - TYPE [元素索引或描述] [文本]向輸入框輸入文本。 - SCROLL [方向] [可選像素數(shù)]滾動頁面。 - WAIT [秒數(shù)]等待一段時間。 - NAVIGATE [URL]導航到新頁面。 - EXTRACT [信息描述]從頁面中提取并返回信息。 歷史操作 [此前步驟的記錄用于維持會話一致性] 當前用戶指令”[用戶的具體指令例如登錄到example.com用戶名為test密碼為123456]” 請根據(jù)頁面摘要規(guī)劃并輸出下一步要執(zhí)行的操作。只輸出操作指令不要有其他解釋。這個提示詞做了幾件關(guān)鍵事明確角色和約束讓LLM進入“瀏覽器Agent”的角色并限定其輸出格式。提供決策依據(jù)DOM摘要是LLM的“視覺輸入”。定義動作空間告訴LLM它能做什么Click, Type等避免其天馬行空。提供短期記憶歷史操作幫助LLM理解當前任務進展到了哪一步。清晰的任務目標用戶指令是最終導向。5.2 LLM的決策與輸出解析LLM接收到提示詞后會輸出類似TYPE [3] “testexample.com”的指令。這里有一個關(guān)鍵點LLM如何引用元素通過索引如[3]。這是最精確、無歧義的方式要求DOM摘要中的元素索引是穩(wěn)定且唯一的。通過描述如[input placeholder”Email”]。這更接近人類語言但需要解析器能穩(wěn)健地匹配描述。通常系統(tǒng)會結(jié)合兩者優(yōu)先使用索引當索引不明確或需要泛化操作時如“點擊所有同意條款的復選框”使用描述性匹配。browser-use需要一個穩(wěn)健的解析器來將LLM的自然語言或結(jié)構(gòu)化指令輸出轉(zhuǎn)換為具體的、可執(zhí)行的瀏覽器自動化命令。這個解析器要能處理一定的模糊性和LLM可能犯的格式錯誤。5.3 動作執(zhí)行與狀態(tài)更新解析出指令后Agent通過Playwright等庫執(zhí)行操作例如# 偽代碼 if action ‘CLICK’: selector convert_index_to_css_selector(element_index) # 例如將索引[3]轉(zhuǎn)換為‘#email-input’ await page.click(selector) elif action ‘TYPE’: selector convert_index_to_css_selector(element_index) await page.fill(selector, text)執(zhí)行后頁面狀態(tài)發(fā)生變化。Agent需要重新感知頁面即重新啟動DOM處理管線獲取新的頁面摘要。這個“感知-決策-執(zhí)行-再感知”的循環(huán)構(gòu)成了Agent與環(huán)境的交互回路。新的DOM摘要會和之前的操作歷史一起構(gòu)成下一個循環(huán)的提示詞輸入。實操心得在動作執(zhí)行后加入一個短暫的固定等待如0.5-1秒或智能等待等待某個特定元素出現(xiàn)或消失是至關(guān)重要的。這給了頁面足夠的時間完成JavaScript渲染或網(wǎng)絡請求避免在頁面未穩(wěn)定時就進行下一次感知導致決策基于過時信息。這是實踐中Agent穩(wěn)定性的一個關(guān)鍵技巧。6. 常見問題、挑戰(zhàn)與優(yōu)化策略實錄6.1 元素定位失敗最頭疼的問題即使經(jīng)過精心處理LLM指示點擊[button: text”Submit”]但執(zhí)行時依然可能失敗。原因和解決方案如下問題1頁面動態(tài)變化導致索引失效。上一次感知到的元素[3]在執(zhí)行動作后頁面刷新或DOM更新[3]可能指向了完全不同的元素。策略避免過度依賴絕對索引。在每次動作執(zhí)行后、下一次感知前重新建立完整的元素索引映射。或者在指令中更多地使用基于穩(wěn)定屬性的描述如id、name或獨特的text并在解析時進行實時匹配。問題2選擇器不夠健壯。將[button: text”Submit”]簡單轉(zhuǎn)換為button:has-text(“Submit”)可能匹配到多個按鈕或因為文本大小寫、空格差異而匹配失敗。策略使用更健壯的匹配邏輯。例如文本匹配使用模糊匹配包含關(guān)系或計算相似度并結(jié)合其他屬性如role、鄰近元素進行精確定位。優(yōu)先使用id選擇器它是唯一性最高的。問題3元素被遮擋或不在視口。Playwright的點擊操作默認會滾動到元素并確保其可操作但極端情況下可能失敗。策略在執(zhí)行操作前可以顯式調(diào)用page.waitForSelector(selector, state’visible’, timeout5000)來等待元素。對于復雜場景可以引入重試機制。6.2 LLM的“幻覺”與指令遵循LLM可能會輸出不符合規(guī)范的指令或者對頁面摘要產(chǎn)生誤解“幻覺”。問題1輸出非指定格式。LLM可能輸出“我應該先點擊用戶名輸入框”而不是CLICK [3]。策略在提示詞中強烈強調(diào)輸出格式并使用結(jié)構(gòu)化輸出技術(shù)如要求LLM輸出JSON或在提示詞末尾提供更嚴格的格式示例。也可以在解析層增加一個輕量級的LLM或規(guī)則引擎對不規(guī)范輸出進行糾正。問題2誤解頁面能力。LLM可能試圖執(zhí)行一個頁面上不存在的操作例如要求“拖拽滑塊”但你的動作空間里根本沒有DRAG操作。策略在提示詞中清晰界定動作空間邊界。如果LLM反復請求邊界外操作可以在系統(tǒng)層面回復“該操作暫不支持請嘗試其他方式”。6.3 性能與成本的權(quán)衡DOM處理管線的每一步都有開銷。過于復雜的過濾和壓縮算法會增加延遲發(fā)送過長的上下文給LLM會增加成本和響應時間。優(yōu)化策略1分級壓縮。不是所有任務都需要完整的頁面摘要。對于“點擊登錄按鈕”這樣的簡單任務也許只需要提取所有按鈕和鏈接就夠了。可以根據(jù)用戶指令的復雜度動態(tài)調(diào)整DOM處理的“粒度”。優(yōu)化策略2緩存與差分更新。如果兩次感知之間頁面只有局部變化如彈窗出現(xiàn)可以只處理變化的部分而不是重新處理整個DOM樹。這需要比較DOM快照實現(xiàn)起來復雜但對性能提升顯著。優(yōu)化策略3LLM調(diào)用優(yōu)化。使用更小、更快的模型來處理簡單的、模式化的決策例如從三個明顯的按鈕中選一個而只在需要復雜推理時調(diào)用大模型如GPT-4。這種“大小模型協(xié)同”的架構(gòu)是降低成本的有效途徑。6.4 處理復雜交互與多步任務對于“找到最便宜的耳機加入購物車”這樣的任務它包含多個子步驟導航、搜索、排序、篩選、比較、點擊。Agent需要具備任務分解和狀態(tài)管理能力。策略browser-use這類框架通常會與一個高層任務規(guī)劃器結(jié)合。這個規(guī)劃器可能是一個更強大的LLM負責將用戶指令分解為一系列原子操作子目標然后由本文描述的“感知-執(zhí)行”循環(huán)來逐一完成。規(guī)劃器還需要處理子任務失敗的情況并制定備用計劃如“如果按價格排序失敗則嘗試提取所有價格后自行計算”。構(gòu)建一個真正魯棒的瀏覽器AgentDOM處理管線是基石但它只是整個系統(tǒng)的一部分。如何讓LLM更好地理解這個“提煉過的世界”如何設計更有效的交互協(xié)議如何處理異常和邊緣情況這些都是工程上持續(xù)挑戰(zhàn)。browser-use的86k星正是因為它為這個復雜問題提供了一個相對完整、可擴展且效果出色的開源解決方案為整個社區(qū)搭建了一個極高的起點。在實際項目中你可以直接使用它也可以深入其源碼借鑒它的管線設計思想來構(gòu)建更適合自己特定場景的網(wǎng)頁“眼睛”和“手”。