kw.xsd 驗證),包含多組 <kgroup>,每組含域名 (<kdomain>) 與多個關鍵詞 (<kword>)。<?php
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->load($xmlFile);
if (!$doc->schemaValidate('kw.xsd')) { /* 錯誤處理 */ }
$kwlist = [];
$g = 1;
foreach ($doc->getElementsByTagName('kgroup') as $group) {
$kwlist[$g]['domain'] = trim($group->getElementsByTagName('kdomain')->item(0)->nodeValue);
$w = 1;
foreach ($group->getElementsByTagName('kword') as $kw) {
if ($kw->hasChildNodes()) {
$kwlist[$g]['kword'][$w] = trim($kw->firstChild->nodeValue);
}
$w++;
}
$g++;
}
?>
function fetchBaidu($domain, $keyword) {
$urlw = urlencode(iconv('UTF-8', 'GBK//IGNORE', $keyword)); // 百度歷史編碼
$maxPages = 2; // 抓取前 2 頁 (每頁 50 條 = 前 100 名)
$baiduIds = []; $baiduMus = []; $isRank = 0; $dmWeight = 0;
for ($page = 1; $page <= $maxPages; $page++) {
$pn = ($page - 1) * 50;
$url = "https://www.baidu.com/s?wd={$urlw}&pn={$pn}&rn=50";
$snoopy = new Snoopy();
$snoopy->fetch($url);
$html = iconv('GBK', 'UTF-8//IGNORE', $snoopy->results);
// ... 解析邏輯 ...
}
}
自然結果 (表格 class="result"):
/()?(.*?)<\/a><\/h3>.*?.*? ((\d{4}-\d{1,2}-\d{1,2})|(\d+小时前)|(\d+分钟前)) .*?<\/span>.*?
<\/font><\/td><\/tr><\/table>/is
提取:id(排名), href, title, cache_time。
百度開放平台/應用結果 (mu 屬性):
/(()|())[sS]*?(((.*?)<\/a>)|(<\/div>))/is
提取:id, mu, href, title。
百度推廣 (左上/左下/右側):通過特定 class/id 模式計數。
4. 競爭度權重算法 (get_dm_weight)
function getDmWeight($url, $rank) {
$page = ceil($rank / 10);
$rankWeights = [1=>52, 2=>15, 3=>10, 4=>5, 5=>5, 6=>4, 7=>1, 8=>3, 9=>2, 0=>3]; // 排名權重
$host = parse_url($url, PHP_URL_HOST); // 簡化處理
$path = parse_url($url, PHP_URL_PATH);
$query = parse_url($url, PHP_URL_QUERY);
$levelL = count(explode('/', trim($path, '/'))); // 目錄層級
$levelR = $query ? count(explode('&', $query)) : 0; // 參數數量
// 權重 = 排名權重 * 0.5^(層級深度) * 9 / 10^頁數
return $rankWeights[$rank % 10] * pow(0.5, ($levelL + $levelR - 1)) * 9 / pow(10, $page);
}
直觀含義:排名越前、層級越淺 (首頁/頂級目錄)、頁數越早 → 競爭度越高。僅供參考,非科學指標。
5. 域名匹配判斷
// 匹配目標域名及其子域名
if (preg_match('/:\/\/(w*?\.)?' . preg_quote($domain) . '/i', $url, $match)) {
// 命中:輸出排名、標題、快照、競爭度
$isRank = 1;
}
// 統計百度系佔位
if (preg_match('/:\/\/(w*?\.)?baidu\.com/i', $url)) $baiduIds[] = $rank;
// 統計 mu 佔位
$baiduMus[] = $muId;
關鍵技術挑戰與對策
挑戰 當前方案 局限性 改進方向
百度反爬蟲 Snoopy 無 Cookie 池、無代理 IP、固定 User-Agent 極易被封禁、驗證碼、限流 代理 IP 池 + Cookie 池 + 隨機 UA + 請求頻率控制 + 驗證碼識別服務
頁面結構變更 硬編碼正則強依賴 HTML 結構 百度改版即失效,維護成本極高 DOM 解析器 (DOMDocument/XPath) + 多選器容錯 + 版本檢測告警
編碼轉換 iconv('GBK', 'UTF-8')百度已全面 HTTPS + UTF-8,轉碼可能多餘/報錯 自動檢測編碼 (mb_detect_encoding) 或強制 UTF-8
JS 渲染內容 Snoopy 僅獲取靜態 HTML 無法獲取 AJAX 動態加載的結果 Headless Chrome (Puppeteer/Playwright) / Selenium
性能/併發 串行同步請求,set_time_limit(0) + ob_implicit_flush 慢、阻塞、單進程 異步併發 (Swoole/Workerman/Guzzle Promise) + 隊列調度
法律合規 無 robots.txt 檢查、無授權 法律風險、IP 被永久封禁 遵守 robots.txt、申請官方 API、或使用第三方合規數據服務商
現代化重構建議
// 1. 使用 Guzzle + 異步併發
$client = new GuzzleHttp\Client(['timeout' => 10, 'cookies' => true]);
$promises = [];
foreach ($keywords as $kw) {
$promises[$kw] = $client->getAsync('https://www.baidu.com/s', [
'query' => ['wd' => $kw, 'rn' => 50],
'headers' => ['User-Agent' => '...']
]);
}
$responses = GuzzleHttp\Promise\Utils::unwrap($promises);
// 2. 使用 Symfony DomCrawler 解析
foreach ($responses as $kw => $resp) {
$crawler = new Symfony\Component\DomCrawler\Crawler((string)$resp->getBody());
$crawler->filter('div.result.c-container')->each(function($node) use ($kw, $domain) {
$rank = $node->attr('id'); // 百度現結構可能無 id,需 XPath 計算位置
$title = $node->filter('h3 a')->text();
$url = $node->filter('h3 a')->attr('href');
// ... 域名匹配、數據結構化 ...
});
}
// 3. 結構化存儲 (MySQL/Redis/ES) 而非直接輸出 HTML
// 4. 定時任務調度 (Cron/Swoole Timer) + 鎖防重複
總結
該腳本展示了「配置驅動 → 模擬瀏覽器采集 → 正則解析 → 業務邏輯判斷 → 流式輸出」的經典 SEO 工具鏈路。其核心價值在於排名判斷邏輯 (自然/推廣/mu)、競爭度模型、多域名批量處理流程。但受限於Snoopy 老舊、正則脆弱、無反爬對抗、合規風險,不可直接用於生產環境大規模采集。現代化應遷移至 Headless Chrome + 異步框架 + 結構化解析 + 合規代理池 架構,並優先考慮官方 API 或合規數據服務商。
Copyright © 程式碼之家 All Rights Reserved