首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

ASP.NET 站點採集實戰:icbase.com ViewState 動態參數處理完整方案

     編輯:PHP 綜合知識

核心難點:ASP.NET WebForms 三大動態參數

icbase.com 采用 ASP.NET WebForms,列表頁翻頁需攜帶 __VIEWSTATE、__PREVIOUSPAGE、__EVENTVALIDATION 三個隱藏字段,且每次請求值都不同,必須動態提取並攜帶。

完整採集流程

1. HTTP 客戶端(復用通用封裝)

<?php
function curl_get($url, $data = [], $header = [], $timeout = 15, $port = 80, $referer = '', $proxy = '') {
    $ch = curl_init();
    if (!empty($data)) {
        $data = is_array($data) ? http_build_query($data) : $data;
        $url .= (strpos($url, '?') ? '&' : '?') . $data;
    }
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => $timeout,
        CURLOPT_PORT           => $port,
        CURLOPT_HTTPHEADER     => $header,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_REFERER        => $referer,
        CURLOPT_PROXY          => $proxy,
        CURLOPT_SSL_VERIFYPEER => false,
    ]);
    $r['result'] = curl_exec($ch);
    $r['error']  = curl_error($ch);
    curl_close($ch);
    return $r;
}

function curl_post($url, $data = [], $header = [], $timeout = 5, $port = 80) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $data,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => $timeout,
        CURLOPT_PORT           => $port,
        CURLOPT_HTTPHEADER     => $header,
        CURLOPT_SSL_VERIFYPEER => false,
    ]);
    $r['result'] = curl_exec($ch);
    $r['error']  = curl_error($ch);
    curl_close($ch);
    return $r;
}
?>

2. 列表頁獲取:首次 GET 取 Token,後續 POST 翻頁

<?php
function getListHtml($keywords, $page = 1) {
    if ($page < 1) return false;
    
    // 第 1 頁:GET 獲取初始 Token
    if ($page == 1) {
        $result = curl_get('https://www.icbase.com/ProResult.aspx', ['ProKey' => $keywords]);
        if (isset($result['error'])) return false;
        $html = $result['result'];
        
        // 提取三大 Token 並定義為常量(進程級緩存)
        if (!defined('__VIEWSTATE') && preg_match('/__VIEWSTATE"\s+value="([^"]+)"/isU', $html, $m)) {
            define('__VIEWSTATE', $m[1]);
        } else { return false; }
        
        if (!defined('__PREVIOUSPAGE') && preg_match('/__PREVIOUSPAGE"\s+value="([^"]+)"/isU', $html, $m)) {
            define('__PREVIOUSPAGE', $m[1]);
        } else { return false; }
        
        if (!defined('__EVENTVALIDATION') && preg_match('/__EVENTVALIDATION"\s+value="([^"]+)"/isU', $html, $m)) {
            define('__EVENTVALIDATION', $m[1]);
        } else { return false; }
        
        return $html;
    }
    
    // 第 2+ 頁:POST 提交翻頁
    $postData = [
        '__EVENTTARGET'       => 'pager',
        '__EVENTARGUMENT'     => (string)$page,
        '__VIEWSTATE'         => __VIEWSTATE,
        '__PREVIOUSPAGE'      => __PREVIOUSPAGE,
        '__EVENTVALIDATION'   => __EVENTVALIDATION,
    ];
    
    $result = curl_post('https://www.icbase.com/ProResult.aspx?ProKey=' . $keywords, $postData);
    return $result['result'] ?? false;
}
?>

3. 列表解析:提取詳情鏈接與下一頁頁碼

<?php
function getListHref($html) {
    // <a href='...' target="_blank"><img .../>
    $pattern = "/<a\s+href='([^']+)'\s+target=\"_blank\"\s*>\s*<img[^>]+\/>/isU";
    return preg_match_all($pattern, $html, $m) ? $m[1] : [];
}

function getListNextPage($html) {
    // <div id="Pager">...<a href="javascript:__doPostBack('Pager','N')">></a>
    $pattern = '/<div\s+id="Pager"[^>]*>.+<a\s+href="javascript:__doPostBack\(\'Pager\',\'(\d+)\'\)">><\/a>/isU';
    return preg_match($pattern, $html, $m) ? (int)$m[1] : -1;
}

function getListHrefAll($keywords) {
    $hrefs = [];
    $html = getListHtml($keywords);
    $hrefs = array_merge($hrefs, getListHref($html));
    while (($next = getListNextPage($html)) > 0) {
        $html = getListHtml($keywords, $next);
        $hrefs = array_merge($hrefs, getListHref($html));
    }
    return $hrefs;
}
?>

4. 詳情頁解析:表格結構化提取

<?php
function getDetail($url, $isUrl = 1) {
    $host = 'www.icbase.com';
    if ($isUrl) {
        $result = curl_get('https://' . $host . '/' . ltrim($url, '/'));
        $html = $result['result'] ?? '';
    } else {
        $html = $url;
    }
    
    $result = [
        'mfg_part' => '', 'mfg_name' => '', 'para' => [],
        'desc' => '', 'pdf_url' => '', 'sup_stock' => '',
        'price' => [], 'img_url' => '', 'page_url' => '',
    ];
    
    // 型號
    if (preg_match('/<td>產品型號<\/td><td>([^<]+)</isU', $html, $m)) {
        $result['mfg_part'] = trim($m[1]);
    } else { return []; } // 無核心字段視為失敗
    
    // 廠商
    if (preg_match('/<td>廠商<\/td>\s*<td>(.+)<\/td>/isU', $html, $m)) {
        $result['mfg_name'] = trim($m[1]);
    }
    
    // 參數表(特殊表格結構:標題行 + 數據行交替)
    if (preg_match('/<tr\s+style="background-color:#E9E9E9;[^"]*">(.+)<\/tr><\/table>/isU', $html, $m)) {
        if (preg_match_all('/<td>(.+)<\/td>/isU', $m[1], $mm)) {
            $count = count($mm[1]);
            $half = $count / 2;
            for ($k = 0; $k < $half; $k++) {
                $key = trim($mm[1][$k]);
                $val = trim($mm[1][$half + $k]);
                if ($key === '描述') {
                    $result['desc'] = $val;
                } else {
                    $result['para'][$key] = $val;
                }
            }
        }
    }
    
    // PDF、庫存、價格、圖片等類似正則...
    // 價格表:<tr><td...>N+</td><td...>價格</td></tr>
    if (preg_match_all('/<tr><td[^>]+>(\d+)\+<\/td><td[^>]+>[^\d]*([\d.]+)<\/td><\/tr>/isU', $html, $m)) {
        foreach ($m[1] as $k => $v) {
            $result['price'][$v] = '¥' . $m[2][$k];
        }
    }
    
    if ($isUrl) $result['page_url'] = 'https://' . $host . '/' . ltrim($url, '/');
    return $result;
}
?>

5. 主入口

<?php
function getData($keywords) {
    $hrefs = getListHrefAll($keywords);
    $data = [];
    foreach ($hrefs as $href) {
        $data[] = getDetail($href);
        usleep(300000); // 0.3s 延遲
    }
    return $data;
}

// 測試
$keywords = trim($_GET['keywords'] ?? 'STM32');
$result = getData($keywords);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
?>

ASP.NET 採集核心模式總結

特徵應對策略
__VIEWSTATE 等隱藏字段首次 GET 解析提取,定義為常量/緩存,後續 POST 固定攜帶
__doPostBack 翻頁解析分頁鏈接中的 __EVENTARGUMENT 作為頁碼參數
EventValidation 驗證必須攜帶最新 __EVENTVALIDATION,否則 403/500
ViewState 加密/壓縮若啟用加密需模擬機器金鑰生成,極其複雜,建議改用 Selenium
UpdatePanel 部分刷新分析 AJAX 請求(__ASYNCPOST=true),直接調用後端接口

進階優化建議

  • Token 失效重試:POST 返回錯誤時,重新 GET 首頁刷新 Token 再試
  • 並發控制:單進程串行足夠,如需加速可多進程 + 共享內存緩存 Token
  • 異常 HTML 兜底:正則失敗時保存原始 HTML 供人工分析
  • 遷移到 Headless:若參數邏輯極其複雜(如動態加密),直接用 Puppeteer/Playwright 模擬瀏覽器操作
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved