icbase.com 采用 ASP.NET WebForms,列表頁翻頁需攜帶 __VIEWSTATE、__PREVIOUSPAGE、__EVENTVALIDATION 三個隱藏字段,且每次請求值都不同,必須動態提取並攜帶。
<?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;
}
?>
<?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;
}
?>
<?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;
}
?>
<?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;
}
?>
<?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);
?>
| 特徵 | 應對策略 |
|---|---|
| __VIEWSTATE 等隱藏字段 | 首次 GET 解析提取,定義為常量/緩存,後續 POST 固定攜帶 |
| __doPostBack 翻頁 | 解析分頁鏈接中的 __EVENTARGUMENT 作為頁碼參數 |
| EventValidation 驗證 | 必須攜帶最新 __EVENTVALIDATION,否則 403/500 |
| ViewState 加密/壓縮 | 若啟用加密需模擬機器金鑰生成,極其複雜,建議改用 Selenium |
| UpdatePanel 部分刷新 | 分析 AJAX 請求(__ASYNCPOST=true),直接調用後端接口 |