引發 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 當中有篇文章介紹如何安裝,相當複雜,然而實際用上的不多。以下摘要說明,免得暈頭轉向。
- 下載並安裝 Microsoft Edge Insider (preview) Channel,建議 Canary 版本,目的是取得 WebView2 SDK(每個開發環境只要做一次)。
- 下載並安裝 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 = " +
{
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);
}
"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 解決,姑為之記。
沒有留言:
張貼留言