寫爬蟲抓取網頁內容已算開發人員的基本功,說穿了不值兩毛錢,不外乎模擬瀏覽器發出 HttpRequst 取回 HTML,再設法解析內容取出想要的資訊。 各語言幾乎都有發送 HttpRequest 的程式庫函式,.NET 有 WebClient/HttpClient、Python 有 reuqests 模組、PowerShell 有 Invoke-WebRequest,工具類則有 PostMan、cUrl、Headless Chrome,選擇很多。 至於解析內容,從用 Headless Chrome 將其轉為完整 DOM (甚至 JavaScript 部分也照跑)進行存取、轉為 XML 文件做結構化查詢,到最簡單粗暴但有效的絕招 - 用 Regular Expression 挖資料,都能達成目標。最近處理排程作業要抓網頁內容,我開始試著用 PowerShell Invoke-WebRequest + .NET Regex 解題,因網頁結構還算簡單,倒也都能手到擒來。直到幾前天遇上一個 WebForm PostBack 式的查詢網頁,就是那種最傳統的 WebForm,把查詢邏輯寫在 Button1_Click(object sender, EventArgs e) 伺服器端 C# 事件的玩法,發現一些眉角。

用個範例展示,假設有一個 DropDownList 下拉選單列舉分類,按下 Button PostBack 回去,伺服器端依選取項目傳回該分類的庫存數字:

程式碼如下:

<%@Page Language="C#"%>
<script runat="server">
    void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            ddlCategory.Items.Clear();
            ddlCategory.Items.AddRange(
                Qtys.Keys.Select(o => new ListItem(o)).ToArray());
        }
    }

    static Dictionary<string, int> Qtys = new Dictionary<string, int>()
    {
        ["Notebook"] = 255,
        ["Monitor"] = 1024,
        ["Keybd/Mouse"] = 32767
    };

    protected void btnSend_Click(object sender, EventArgs e)
    {
        var catg = ddlCategory.Text;
        lblMessage.Text =
            "Category = " + catg + ", Qty = " + Qtys[catg];

    }
</script>

<html>
<body>
<Form runat="server">
	<asp:DropDownList id="ddlCategory" runat="server"></asp:DropDownList>
    <asp:Button ID="btnSend" runat="server" Text="Show Qty" OnClick="btnSend_Click" />
	<br />
	<asp:Label ID="lblMessage" runat="server" Text=""></asp:Label>
</Form>
</body>
</html>

依據直覺,我用 Invoke-WebRequest 發出 POST 請求,將 Content-Type 設為 application/x-www-form-urlencoded,將下拉選單及按鈕結果組成 ddlCategory=Monitor&btnSend=Show+Qty 當傳送內容送出:

不料,ASP.NET WebForm 傳回如 GET 時看到的全新網頁,看到 HTML 出現 ViewStateEvent Validation,這才想起這問題之前遇過 - 抓取 ASP.NET WebForm 網頁 PostBack 結果,解法不難,先發一次 GET 取回 __VIEWSTATE、__EVENTVALIDATION 兩個 <input type="hidden" > 加入送出參數即可,當年用 WebClient 與 HttpClient 示範,今天就來補上 PowerShell 版吧:

$html = (Invoke-WebRequest -Method GET -Uri http://localhost/aspnet/mywebform.aspx).Content
$m = [System.Text.RegularExpressions.Regex]::Match($html, "(?ms)__VIEWSTATE`" value=`"(?<v>\S+?)`".+__EVENTVALIDATION`" value=`"(?<e>\S+?)`"")
$form = @{
    __VIEWSTATE = $m.Groups["v"].Value
    __EVENTVALIDATION = $m.Groups["e"].Value
    ddlCategory = "Monitor"
    btnSend = "Show Qty"
}
$html = (Invoke-WebRequest -Method POST `
    -ContentType application/x-www-form-urlencoded `
    -Uri http://localhost/aspnet/mywebform.aspx `
    -Body $form).Content
[System.Text.RegularExpressions.Regex]::Match($html, "lblMessage`">(?<v>.+?)</span>").Groups["v"].Value

測試成功!

這次還多學到一個技巧,表單內容可用 $form = @ 宣告直接當成 -Body 的參數,Invoke-WebRequest 將會自動轉成 paramA=valueA&paramB=valueB 還會加上 UrlEncode,之前都傻傻自己搞,學會這招省事不少。

Get postback result of ASP.NET WebForm with PowerShell script.


Comments

Be the first to post a comment

Post a comment