針對 ASP.NET WebForms 頁面(ReportByDateOfPivot.aspx),本文完整記錄從靜態 HTML 解析失敗、分析 __VIEWSTATE/__EVENTVALIDATION 動態生成機制、模擬 POST 請求攜帶完整表單欄位、處理分頁參數 __EVENTARGUMENT 的全過程,並提供可直接運行的 Java 代碼範例。
Jsoup.connect(url).get() 只能拿到初始頁面,分頁按鈕觸發 __doPostBack 需要攜帶完整 ViewState 與 EventValidation。body 字串,導致分頁失敗、資料為空。#__VIEWSTATE, #__EVENTVALIDATION。__EVENTTARGET(分頁控制項 ID)、__EVENTARGUMENT(頁碼)、__VIEWSTATE、__EVENTVALIDATION 及所有 ctl00$... 欄位。User-Agent, Content-Type: application/x-www-form-urlencoded, Referer。import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
public class LnPriceCrawler {
private static final String BASE_URL =
"https://www.lnprice.gov.cn/wjjc/jgjc/ReportByDateOfPivot.aspx" +
"?PriceBureauMainType_Id=101&YM=201502&DP=28";
public static void main(String[] args) throws Exception {
for (int page = 1; page <= 3; page++) {
List prices = fetchPage(page);
System.out.println("Page " + page + ": " + prices);
}
}
static List fetchPage(int pageNo) throws Exception {
// 1. 首次 GET 取得初始 Token
Document initDoc = Jsoup.connect(BASE_URL)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.get();
String viewState = initDoc.select("#__VIEWSTATE").val();
String eventValidation = initDoc.select("#__EVENTVALIDATION").val();
// 2. 構造 POST Body
String body = buildPostBody(pageNo, viewState, eventValidation);
// 3. 發送 POST
String html = postHtml(body);
// 4. 解析表格
Document doc = Jsoup.parse(html);
Elements rows = doc.select("#ctl00_cphMainFrame_Table1 tr");
List result = new ArrayList<>();
for (int i = 1; i < rows.size(); i++) { // 跳過表頭
Element row = rows.get(i);
String price = row.select("td").get(5).text(); // 第 6 欄
result.add(price);
}
return result;
}
static String buildPostBody(int pageNo, String viewState, String eventValidation) {
// 必須 URLEncode 每個欄位值
return "__EVENTTARGET=ctl00$cphMainFrame$aspnetpager1" +
"&__EVENTARGUMENT=" + pageNo +
"&__VIEWSTATE=" + encode(viewState) +
"&__EVENTVALIDATION=" + encode(eventValidation) +
"&ctl00$cphMainFrame$ddlYear=2015" +
"&ctl00$cphMainFrame$ddlMonth=02" +
"&ctl00$cphMainFrame$ddlTimePoint=28" +
"&ctl00$cphMainFrame$ScriptManager1=ctl00$cphMainFrame$UpdatePanel1|ctl00$cphMainFrame$aspnetpager1" +
"&__ASYNCPOST=true";
}
static String postHtml(String body) throws IOException {
URL url = new URL(BASE_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
conn.setRequestProperty("Referer", BASE_URL);
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write(body.getBytes("UTF-8"));
}
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line);
return sb.toString();
}
}
static String encode(String s) {
try { return java.net.URLEncoder.encode(s, "UTF-8"); }
catch (UnsupportedEncodingException e) { throw new RuntimeException(e); }
}
}
body 差異。403/500,檢查 __VIEWSTATE 是否過期、Cookie 是否需維持 Session。| 坑點 | 症狀 | 解法 |
|---|---|---|
| 硬編碼 ViewState | 第一頁正常,第二頁失效 | 每次請求前動態解析更新 |
| 缺少 __ASYNCPOST=true | 伺服器拒絕或返回完整頁面而非片段 | 加上該參數標識局部更新 |
| Content-Type 錯誤 | 伺服器無法解析表單 | 明確設定 application/x-www-form-urlencoded |
| 分頁控制項 ID 錯誤 | __EVENTTARGET 不匹配 | 從頁面原始碼搜尋 aspnetpager 確認 ClientID |
ASP.NET WebForms 爬蟲的核心在於「維持 ViewState/EventValidation 的同步更新」與「精確模擬 __doPostBack 表單結構」。採用 Jsoup 解析配合 HttpURLConnection 發送 POST,並在每次迴圈前刷新 Token,即可穩定抓取分頁資料。建議將上述邏輯封裝為可複用的 WebFormCrawler 基類,應對同類站點更高效。