自建全文搜尋引擎成本極高(爬蟲、索引、排序、儲存)。替代方案:元搜尋引擎 —— 代理用戶請求至成熟引擎(Google),解析回應,重構呈現。核心流程:
訪問 https://www.google.com/search?q=關鍵字,關鍵參數:
q:查詢字串(需 urlencode)hl:介面語言(zh-TW, en)gl:地區(tw, us)num:每頁結果數(預設 10,最大 100)start:分頁偏移(0, 10, 20...)反爬蟲機制:User-Agent 檢查、IP 速率限制、驗證碼、動態渲染(JavaScript 生成結果)。簡單 file_get_contents 易被封鎖,生產級需使用 cURL + 完整 Header + Cookie 池 + 代理 IP + 延遲/隨機間隔。
search.html<form method="get" action="search.php">
<input type="text" name="q" placeholder="輸入搜尋關鍵字" required>
<input type="hidden" name="hl" value="zh-TW">
<button type="submit">搜尋</button>
</form>
search.php<?php
function googleSearch(string $query, int $start = 0, string $hl = 'zh-TW'): array {
$url = 'https://www.google.com/search';
$params = [
'q' => $query,
'hl' => $hl,
'start' => $start,
'num' => 10,
];
$ch = curl_init($url . '?' . http_build_query($params));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
CURLOPT_HTTPHEADER => [
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language: zh-TW,zh;q=0.9,en;q=0.8',
'Accept-Encoding: gzip, deflate, br',
'Connection: keep-alive',
'Upgrade-Insecure-Requests: 1',
],
CURLOPT_COOKIEJAR => '/tmp/google_cookies.txt',
CURLOPT_COOKIEFILE => '/tmp/google_cookies.txt',
CURLOPT_ENCODING => 'gzip, deflate',
]);
$html = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($httpCode !== 200 || $error) {
throw new RuntimeException("Google 請求失敗: HTTP $httpCode, $error");
}
return parseGoogleResults($html);
}
function parseGoogleResults(string $html): array {
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->loadHTML('' . $html); // 強制 UTF-8
$xpath = new DOMXPath($dom);
$results = [];
// Google 結果容器選擇器(需隨版本更新調整)
$nodes = $xpath->query('//div[@class="g"]'); // 經典結構
// 現代結構可能為 //div[@data-hveid] 或 //div[contains(@class, "MjjYud")]
foreach ($nodes as $node) {
$titleNode = $xpath->query('.//h3', $node)->item(0);
$linkNode = $xpath->query('.//a[@href]', $node)->item(0);
$snippetNode = $xpath->query('.//div[@class="VwiC3b"] | .//span[@class="st"] | .//div[contains(@class, "aCOpRe")]', $node)->item(0);
if (!$titleNode || !$linkNode) continue;
$title = trim($titleNode->textContent);
$url = $linkNode->getAttribute('href');
$snippet = $snippetNode ? trim($snippetNode->textContent) : '';
// 過濾 Google 內部連結
if (strpos($url, '/url?q=') === 0) {
parse_str(parse_url($url, PHP_URL_QUERY), $qs);
$url = $qs['q'] ?? $url;
}
if (strpos($url, 'google.com') !== false) continue;
$results[] = [
'title' => $title,
'url' => $url,
'snippet' => $snippet,
];
}
return $results;
}
// 主程式
try {
$query = $_GET['q'] ?? '';
$start = (int)($_GET['start'] ?? 0);
if ($query) {
$results = googleSearch($query, $start);
}
} catch (Exception $e) {
$error = $e->getMessage();
}
?>
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>搜尋結果</title></head><body>
<form method="get"><input name="q" value="<?= htmlspecialchars($query) ?>"><button>搜尋</button></form>
<?php if (!empty($error)): ?><div class="error"><?= $error ?></div><?php endif; ?>
<?php if (!empty($results)): ?>
<ol>
<?php foreach ($results as $i => $r): ?>
<li>
<h3><a href="<?= $r['url'] ?>" target="_blank" rel="noopener"><?= htmlspecialchars($r['title']) ?></a></h3>
<p><?= htmlspecialchars($r['snippet']) ?></p>
<cite><?= $r['url'] ?></cite>
</li>
<?php endforeach; ?>
</ol>
<nav>
<a href="?q=<?= urlencode($query) ?>&start=<?= $start-10 ?>" <?= $start==0?'style="visibility:hidden"':'' ?>>上一頁</a>
<a href="?q=<?= urlencode($query) ?>&start=<?= $start+10 ?>">下一頁</a>
</nav>
<?php endif; ?>
</body></html>
| 挑戰 | 解決方案 |
|---|---|
| Google HTML 結構頻繁變動 | 建立多組 XPath 選擇器備援;監控 Google 官方部落格/SEO 社群變更;引入 Simple HTML DOM Parser 容錯解析 |
| IP 被封鎖/驗證碼 | 代理 IP 池輪換;請求間隔隨機化 (2-10秒);模擬真實瀏覽器指紋;遇驗證碼觸發人工/第三方打碼服務 |
| 動態渲染 (AJAX/JS) | 使用 Headless Chrome (Puppeteer/PHP-WebDriver) 執行 JS 後再解析;或分析 XHR 介面直接呼叫內部 API |
| 法律/條款風險 | 遵守 robots.txt、Terms of Service;添加 User-Agent 標識;提供移除請求管道;考慮使用官方 Custom Search JSON API (免費額度 100 次/日) |
| 效能瓶頸 | 結果快取 (Redis, 1-24小時);非同步預抓熱門查詢;前端 AJAX 分頁載入 |
大規模抓取搜尋引擎結果可能違反服務條款、著作權法、反爬蟲法(如美國 CFAA、歐盟 GDPR)。商業用途強烈建議使用官方 API 或自建索引。本文代碼僅供學習 HTTP 協議、HTML 解析、XPath 技術之用。