在日常開發中我們經常可能會遇到這樣一些情景:需要使用一些自動化的手段來幫助我們測試;在擷取某些網頁的資料的時候,由于目标網頁的資料是動态的,使用傳統的資料擷取手段無法有效的抓取;需要時不時的抓取某個網頁儲存為截圖作為快照,以用于諸如競品監測分析等;諸如以上的場景可能在以前我們比較難以實作,或者現有的工具或多或少存在着某些不足。今天就來介紹一款 Go 語言寫的基于 DevTools Protocol 的工具,叫 Rod 。
Rod的Github位址:https://github.com/go-rod/rod
Rod 是什麼?
Rod 是一款直接基于 DevTools Protocol 的進階 DevTools 驅動程式,它是為了網頁自動化和抓取而設計的,Rod 還給使用者暴露了一些底層接口,以便如果缺失某些功能的話,使用者可以直接調用這些接口發送控制請求給浏覽器。
Rod 能做什麼?
既然是基于 DevTools Protocol 協定的,那基本 DevTools 能實作的功能大部分都能實作。比如浏覽器頁面控制、視窗控制、事件監聽,根據選擇器擷取網頁元素,模拟滑鼠移動點選,頁面事件監聽、觸發,網頁注入腳本、樣式表,網頁内容修改,網頁内容擷取,執行腳本,Cookie 擷取設定,頁面截圖,會話複用,頁面監控等等功能。
Rod 的工作原理是什麼?
Rod 在被調用後會嘗試使用 WebSocket 去連接配接 DevTools,如果沒有找到的話,會基于不同的系統嘗試去一些預設的路徑尋找本地浏覽器,找到就打開,沒有找到的話會嘗試去下載下傳一個再啟動。
然後 Rod 使用 JSON-RPC 去和 DevTools 互動,以便控制浏覽器。當需要控制某個具體的頁面的時候,Rod 會給這個頁面注入一個js的助手腳本,Rod 使用這個腳本來完成頁面内容的擷取,控制等。
其他同類工具存在什麼問題?
chromedp 由于需要使用複雜的類 DSL 的任務來處理主邏輯,導緻發生問題時很難通過了解代碼來解決,也很難去追蹤問題。chromedp 也無法處理 iframe,不支援影子 DOM,當程式崩潰時,會留下僵屍程序等。
selenium 由于是基于 webdriver protocol 的,功能上相對更少,比如沒法處理關閉的影子 DOM,沒辦法儲存網頁為 PDF,沒有對性能等工具的支援,由于依賴于浏覽器驅動等也使得其變得難以使用和維護。雖然 selenium 宣稱是支援跨浏覽器的,但實際上在很多浏覽器上都或多或少存在這樣或那樣的問題。
puppeteer 需要去處理複雜的 promise/async/await 問題,但在進行自動化測試的時候,更多想要同步的擷取到狀态之後再進行後續操作,對于 QA 來說不太友好。
cypress 對關閉的影子 DOM 限制很大,對跨域 iframe 來說也不太安全。
案例一:定時對某個網頁截圖
就像前面說的,有時候我們需要對某個競品網頁保持長期的關注,需要定時的儲存快照來做分析,這時候我們就可以使用 rod 來友善的完成這個需求。
這裡我寫了一個小方法,每3秒(測試用,實際環境可以調整為半天、一天等頻率)請求一次 gocn 這個網站,然後等頁面加載完成後,将整個頁面截圖并按截圖時間作為檔案名儲存到本地。
import ( "github.com/go-rod/rod" "strconv" "time")func monitorGocn() { // 建立一個浏覽器的執行個體 browser := rod.New().Connect() url := "https://gocn.vip" // 指定要擷取的網頁 page := browser.Page(url) // 指定視窗大小,避免截圖時截不完整 page.Window(0, 0, 1024, 768) // 指定截圖間隔時間,我這裡是測試,就設定了3秒 interval := time.Duration(3) * time.Second for { select { case nowTimestamp := time.Now().Unix() nowTimestampStr := strconv.Itoa(int(nowTimestamp)) // 重定向頁面,後面的參數無實意,隻是為了確定頁面重新整理 page.Navigate(url + "/?__t__=" + nowTimestampStr) // 等待頁面加載完成 wait := page.WaitRequestIdle() wait() file := "screenshot/" + nowTimestampStr + ".jpg" // 整個頁面截圖并儲存 page.ScreenshotFullPage(file) } }}
截圖的結果:
截圖效果如下:
案例二:自動簽到某度貼吧
這個案例主要是以自動簽到某度貼吧為例展示自動化操作,我們複用了使用者的登入資訊,設定一些需要登入的貼吧的數組,每天自動打開每個貼吧的首頁,點選簽到按鈕進行簽到。
對簽到按鈕進行元素審查,很容易得到對應的元素選擇器。
然後我們編寫代碼:
import ( "github.com/go-rod/rod" "github.com/go-rod/rod/lib/launcher" "time")func signInBa() { // 以NewUserMode形式啟動,這樣可以複用會話資訊 // 當然你也可以以普通模式啟動,自己調用程式登入 url := launcher.NewUserMode().Launch() browser := rod.New().ControlURL(url).Connect() // 先打開某度貼吧首頁,友善複用 page := browser.Page("https://tieba.example.com/") // 設定視窗大小 page.Window(0, 0, 1024, 768) // 等待頁面加載完成 wait := page.WaitRequestIdle() wait() // 這裡是需要簽到的貼吧的名稱 kws := []string{"php", "java", "javascript"} for _, kw := range kws { // 組裝貼吧位址 url := "https://tieba.example.com/f?kw=" + kw // 前往某個貼吧首頁 page.Navigate(url) wait = page.WaitRequestIdle() wait() // 點選簽到按鈕 page.Element(".j_signbtn").Click() // 休息10秒再繼續 time.Sleep(time.Duration(10) * time.Second) }}
執行後就可以看到如下效果:
以上兩個案例也隻是抛磚引玉,充分利用 Rod 能完成的功能遠比這些強大,同時支援在 Docker 中啟動浏覽器,也就是說可以在伺服器上去使用 Rod 的所有功能,寫好的程式直接扔伺服器上就可以等他定時執行,而且支援監控,也能自己處理錯誤來傳回。
總之,用好 Rod 能使你的工作效率大大提升,也能為你完成各種定制化的自動化功能和進階抓取資料需求。