Google 官方搜尋 API 為 Custom Search JSON API,提供每日 100 次免費查詢,適合建立站內搜尋、關鍵字排名監控、內容聚合等功能。本文說明申請流程、PHP 整合代碼、配額管理、錯誤處理。
API_KEYCX (搜尋引擎 ID)<?php
namespace App\Service;
class GoogleCustomSearch {
private const ENDPOINT = 'https://www.googleapis.com/customsearch/v1';
private string $apiKey;
private string $cx;
private int $timeout = 10;
public function __construct(string $apiKey, string $cx) {
$this->apiKey = $apiKey;
$this->cx = $cx;
}
/**
* 執行搜尋
* @param string $query 搜尋關鍵字
* @param array $options 額外參數:num(結果數 1-10), start(起始索引 1-91), lr(語言), gl(地區), safe(安全搜尋), dateRestrict(日期限制), fileType(檔案類型), siteSearch(限定網站), siteSearchFilter(i/e)
* @return array ['items' => [], 'totalResults' => int, 'searchTime' => float]
* @throws RuntimeException
*/
public function search(string $query, array $options = []): array {
$params = array_merge([
'key' => $this->apiKey,
'cx' => $this->cx,
'q' => $query,
'num' => 10, // 每頁最多 10 筆
'start' => 1, // 起始索引
], $options);
$url = self::ENDPOINT . '?' . http_build_query($params);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'MyApp/1.0 (+https://example.com)',
CURLOPT_HTTPHEADER => [
'Accept: application/json',
],
]);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($errno) {
throw new RuntimeException("cURL 錯誤 ({$errno}): {$error}");
}
if ($httpCode !== 200) {
$body = $response ?: '無回應內容';
throw new RuntimeException("HTTP {$httpCode}: {$body}");
}
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON 解析失敗: ' . json_last_error_msg());
}
// API 錯誤回應格式
if (isset($data['error'])) {
$err = $data['error'];
throw new RuntimeException("API 錯誤 [{$err['code']}]: {$err['message']} (" . ($err['errors'][0]['reason'] ?? '') . ")");
}
return [
'items' => $data['items'] ?? [],
'totalResults' => (int)($data['searchInformation']['totalResults'] ?? 0),
'searchTime' => (float)($data['searchInformation']['searchTime'] ?? 0),
'queries' => $data['queries'] ?? [],
];
}
/** 分頁搜尋:自動處理 start 參數,最多 10 頁 (100 筆) */
public function searchAll(string $query, int $maxPages = 10): array {
$allItems = [];
for ($page = 1; $page <= $maxPages; $page++) {
$start = ($page - 1) * 10 + 1;
$result = $this->search($query, ['start' => $start, 'num' => 10]);
$allItems = array_merge($allItems, $result['items']);
if (count($result['items']) < 10) break; // 無更多結果
// 避免觸發速率限制
usleep(200000); // 200ms
}
return [
'items' => $allItems,
'total' => count($allItems),
];
}
}
?>
<?php
require 'vendor/autoload.php';
use App\Service\GoogleCustomSearch;
$apiKey = 'YOUR_API_KEY';
$cx = 'YOUR_SEARCH_ENGINE_ID'; // 格式類似 0123456789abcdef:abcdefghijk
$search = new GoogleCustomSearch($apiKey, $cx);
try {
// 基本搜尋
$result = $search->search('PHP 8.2 新特性', [
'num' => 10,
'lr' => 'lang_zh-TW', // 繁體中文
'gl' => 'tw', // 台灣地區
'safe' => 'active', // 安全搜尋
]);
echo "找到 {$result['totalResults']} 筆結果,耗時 {$result['searchTime']} 秒\n";
foreach ($result['items'] as $item) {
echo "標題: {$item['title']}\n";
echo "連結: {$item['link']}\n";
echo "摘要: {$item['snippet']}\n";
echo "---\n";
}
// 限定站內搜尋
$siteResult = $search->search('Laravel 教學', [
'siteSearch' => 'example.com',
'siteSearchFilter' => 'i', // i=包含, e=排除
]);
// 分頁獲取前 50 筆
$all = $search->searchAll('Composer 套件推薦', 5);
echo "共獲取 {$all['total']} 筆\n";
} catch (RuntimeException $e) {
echo "搜尋失敗: {$e->getMessage()}\n";
// 常見錯誤代碼:
// 400: 參數錯誤 (key, cx, q 必填)
// 403: 配額用盡、API 未啟用、金鑰限制
// 429: 請求過於頻繁
| 參數 | 說明 | 範例 |
|---|---|---|
q | 搜尋查詢字串,支援 Google 搜尋運算子 | site:github.com PHP |
num | 每頁結果數 (1-10) | 10 |
start | 起始索引 (1, 11, 21... 最大 91) | 11 (第 2 頁) |
lr | 語言限制 | lang_zh-TW, lang_en |
gl | 地區限制 (國家代碼) | tw, us, hk |
safe | 安全搜尋 | active (過濾), off (關閉) |
dateRestrict | 日期限制 | d7 (7天內), m1 (1個月內), y1 (1年內) |
fileType | 檔案類型 | pdf, doc, ppt |
siteSearch | 限定搜尋網站 | example.com |
siteSearchFilter | i (包含) / e (排除) | i |
| HTTP 代碼 | 錯誤原因 | 處理建議 |
|---|---|---|
| 400 | 參數無效 (缺 key/cx/q、參數格式錯誤) | 檢查請求參數、URL 編碼 |
| 403 | 配額用盡、API 未啟用、API 金鑰限制 (HTTP Referrer、IP、應用限制) | Cloud Console 檢查配額、啟用 API、調整金鑰限制 |
| 429 | 請求過於頻繁 | 指數退避重試、增加請求間隔、申請提高配額 |
| 500/503 | Google 伺服器內部錯誤 | 指數退避重試、記錄日誌稍後再試 |
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class GoogleSearchGuzzle {
private Client $client;
private string $apiKey;
private string $cx;
public function __construct(string $apiKey, string $cx) {
$this->apiKey = $apiKey;
$this->cx = $cx;
$this->client = new Client([
'base_uri' => 'https://www.googleapis.com/',
'timeout' => 10,
'headers' => ['Accept' => 'application/json', 'User-Agent' => 'MyApp/1.0'],
]);
}
public function search(string $query, array $options = []): array {
$params = array_merge(['key' => $this->apiKey, 'cx' => $this->cx, 'q' => $query], $options);
try {
$response = $this->client->get('customsearch/v1', ['query' => $params]);
$data = json_decode($response->getBody()->getContents(), true);
if (isset($data['error'])) throw new RuntimeException($data['error']['message']);
return ['items' => $data['items'] ?? [], 'total' => (int)($data['searchInformation']['totalResults'] ?? 0)];
} catch (RequestException $e) {
throw new RuntimeException("請求失敗: {$e->getMessage()}");
}
}
}
?>
Google Custom Search JSON API 是整合 Google 搜尋功能的標準方式。關鍵點: