2024年1月15日 星期一

C# 以 WebView2 代替 WebBrowser 的簡單筆記

 


元旦過後,用了十幾年的UDN人氣統計程式突然無法讀取網頁資料(403: Forbidden),一時不知什麼道理,又有點懶得理會,擺了幾天到週末,想起好幾天沒有看到報數,癮又犯了😅,才開始下工夫檢討。

引發 403 的原因不少,一勞永逸不大可能,只能見招拆招。十幾年前剛開始,用一個叫做 HtmlAgilityPack 的程式庫去抓網頁,幾年後可能缺乏保養,無法應對日新月異的外在環境,於是改用 Visual Studio 內建瀏覽器。這玩意本身基於老舊的 IE,微軟早已停止維護,或許又到了找尋替代品的時機。

當然,403 的確引發一番遐思。由於我的目的只是要讀取個別文章的瀏覽及回應次數,最好只接收純文字 (HTML) ,圖片、廣告等無謂負荷,能免則免。當初選擇 HtmlAgilityPack,正因為它能做到這點。用內建瀏覽器一開始做不到,後來發現可以改用 jQuery 方式查詢,節省不少頻寬。然而這種方式可能因深度連結 (Deep Link) 被禁止而得到 403。若是如此,就得另闢蹊徑。

暫且不管,先找尋內建瀏覽器的替代品,結果找到兩組分別基於 Chromium 和 Edge 的瀏覽器元件,稱為 CefSharp 和 WebView2。 Edge 也是基於 Chromium 的產品,所以直覺先選擇 CefSharp。不料此物背後元件眾多,噸位嚇人,雖然功能完整,對我這支小程式來說,卻實屬大而無當。

WebView2 則是微軟建議的元件,有望取代舊版內建瀏覽器。它本身不大,符合袖珍程式需要。其實並不真的小,而是 Edge 早已存在系統之中,肥碩的部分無感也就無痛。當然此說僅限於 Windows,若需要移植到 Mac 之類,就是另一回事了。

Windows Learn 當中有篇文章介紹如何安裝,相當複雜,然而實際用上的不多。以下摘要說明,免得暈頭轉向。
  1. 下載並安裝 Microsoft Edge Insider (preview) Channel,建議 Canary 版本,目的是取得 WebView2 SDK(每個開發環境只要做一次)。
  2. 下載並安裝 Microsoft.Web.WebView2 SDK NuGet package。這個動作,每個用到 WebView2 的專案都要做一次,以便在專案中使用 WebView2 元件。
至於安裝 WebView2Samples 並非必要,但若你和我一樣,是大姑娘上花轎頭一回,參考範例是很有用的。若是像我這支袖珍程式,在 GettingStartedGuides 找個範例就夠了;想做較複雜功能,再看 SampleApps。

雖然 WebView2 已經開發好幾年,也有機會取代舊版,但似乎著重於功能彈性,並不怎麼簡單直觀。譬如說,真正開工前,有些必要前置動作,不做不行,卻很扎眼。

        public YourForm()
        {
            InitializeComponent();
            InitializeAsync();    // 務必實施的步驟
        }

        async void InitializeAsync()     // 注意紅字
        {
            await webView.EnsureCoreWebView2Async(null);    // 初始化需要時間
        }

        // 必須等到這個 event 發生, 才可以開始操作 webView
        private void webView_CoreWebView2InitializationCompleted(object sender,                                                                                    CoreWebView2InitializationCompletedEventArgs e)
        {
            webView.CoreWebView2.Navigate( URL );    // 載入 URL 所指定的網頁
        }
        private async void webView_NavigationCompleted(object sender,
                                                   CoreWebView2NavigationCompletedEventArgs e)
        {
            // 網頁完全載入, 可以提取其中的 HTML, 但方法不太直觀
            string html = await 
                      webView.ExecuteScriptAsync("document.documentElement.outerHTML;");

            // 取出的 HTML 包含 escape 碼, 需要處理
            html = Regex.Unescape(html);
            // 而且前後還用雙引號 (") 包起來, 也得拿掉
            html = html.Remove(0, 1);
            html = html.Remove(html.Length - 1, 1);
            // 現在才是原先由舊版 WebBrowser 直接可以取得並後續處理的 HTML
        }

目前對微軟來說, WebView2 還是 preview 版。希望將來成為正式版的時候,能把操作步驟再簡化直觀。畢竟很多人如我,用 C# 開發環境只是想快快搞定一些小事,迂迴曲折並不討人喜歡。不過話說回來,以 JavaScript 操作 DOM 雖然有點拐彎抹角,但功能擴展的可能性卻大大增加,端看需求而定。

        // 利用 JavaScript 操作自動登入的例子,必須視實際狀況調整
        // 目的在展示 C# 與 JavaScript 如何整合,雖然醜得很,應該有更好方式
        private async void ProcessLogin( string MyUserName, string MyPassword )
        {
            string jsCode = "let inputs = " +
                                "document.getElementById('loginFm').getElementsByTagName('input');"+
                            "for (let i=0; i<inputs.length;i++) " +
                            "{ " +
                                "let inp=inputs[i]; " +
                                "let _id_ = inp.getAttribute('id'); " +
                                "if (_id_=='login_username') " +
                                   "inp.setAttribute('value', '"+ MyUserName + "'); " +
                                "else if (_id_=='login_password') " +
                                   "inp.setAttribute('value', '" + MyPassword +"'); " +
                            "} " +
                            "document.getElementById('btn_submit').click();";

            await webView.CoreWebView2.ExecuteScriptAsync(jsCode);
        }

大致可以確定,得到 403 的第一個原因,乃是瀏覽器太過老舊,可能被認為不安全而拒絕。證據之一是部落格首頁,無深度連結之可言,更無安全顧慮,也一樣讀不到。至於深度連結本身,同樣是被拒的原因。用新版連續執行若干次之後,又出現 403,這回理由就說得很直白了。如此一來要怎麼改進,又是另一課題。確實也有些想法,等有空再說罷。
 


附帶一提,與此同時,發願將原 UDN 部落格的讀者留言交流搬到 Blogger,因為早期很多留言或對話相當精彩。本來想由「部落格文章匯出檔」直接讀取,卻發現其中只有讀者迴響,沒有作者答覆😦。很奇怪的事,卻無可奈何,只好由部落格網站抓。剛開始用 jQuery,跑幾百篇後同樣撞到 403,結果也是改用 WebView2 解決,姑為之記。
 


 
 

沒有留言:

張貼留言