許多開發者習慣用 file_get_contents() 或 fopen() 直接讀取遠端 URL,但這有嚴重缺陷:
default_socket_timeout 秒(預設 60s)@ 抑制不雅觀cURL + HEAD 請求 + 精確超時控制 是解決上述所有問題的標準答案。
<?php
/**
* 檢查 URL 是否可正常存取 (HTTP 2xx/3xx)
*
* @param string $url 目標網址
* @param int $timeout 總超時秒數 (連線+傳輸)
* @param bool $strictMode true=只接受200, false=接受2xx/3xx
* @return array ['accessible' => bool, 'http_code' => int, 'error' => string|null, 'info' => array]
*/
function check_url_accessible(string $url, int $timeout = 10, bool $strictMode = false): array
{
// 1. 基本 URL 格式預檢
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return ['accessible' => false, 'http_code' => 0, 'error' => 'Invalid URL format', 'info' => []];
}
// 2. 初始化 cURL
$ch = curl_init($url);
$options = [
CURLOPT_NOBODY => true, // HEAD 請求,不下載 Body
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true, // 自動追蹤重定向
CURLOPT_MAXREDIRS => 5, // 限制重定向次數防迴圈
CURLOPT_CONNECTTIMEOUT => $timeout, // 連線階段超時
CURLOPT_TIMEOUT => $timeout, // 整體執行超時
CURLOPT_SSL_VERIFYPEER => false, // 正式環境請設 true + CURLOPT_CAINFO
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP URL Checker/1.0)',
CURLOPT_HEADER => true, // 包含 Header 以便除錯 (HEAD 請求本身無 Body)
];
curl_setopt_array($ch, $options);
// 3. 執行並捕獲錯誤
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
curl_close($ch);
// 4. 判斷邏輯
if ($errno !== 0) {
// cURL 層面錯誤:DNS 失敗、連線拒絕、超時、SSL 驗證失敗等
return [
'accessible' => false,
'http_code' => 0,
'error' => "cURL Error [$errno]: $error",
'info' => $info,
];
}
$httpCode = (int)($info['http_code'] ?? 0);
$accessible = $strictMode ? ($httpCode === 200) : ($httpCode >= 200 && $httpCode < 400);
return [
'accessible' => $accessible,
'http_code' => $httpCode,
'error' => $accessible ? null : "HTTP $httpCode",
'info' => [
'content_type' => $info['content_type'] ?? 'unknown',
'content_length' => $info['content_length_download'] ?? -1,
'total_time' => $info['total_time'] ?? 0,
'final_url' => $info['url'] ?? $url, // 重定向後最終 URL
],
];
}
// --- 使用示範 ---
$urls = [
'https://www.google.com',
'https://httpbin.org/status/404',
'https://httpbin.org/status/500',
'https://this-domain-definitely-not-exist-12345.com',
];
foreach ($urls as $url) {
$result = check_url_accessible($url, 5, false); // 寬鬆模式,5秒超時
$status = $result['accessible'] ? '✅ 可存取' : '❌ 不可存取';
echo "[$status] $url => HTTP {$result['http_code']} ({$result['error'] ?? 'OK'}) 耗時: {$result['info']['total_time']}s\n";
}
?>
[✅ 可存取] https://www.google.com => HTTP 200 (OK) 耗時: 0.23s
[❌ 不可存取] https://httpbin.org/status/404 => HTTP 404 (HTTP 404) 耗時: 0.18s
[❌ 不可存取] https://httpbin.org/status/500 => HTTP 500 (HTTP 500) 耗時: 0.21s
[❌ 不可存取] https://this-domain-definitely-not-exist-12345.com => HTTP 0 (cURL Error [6]: Could not resolve host) 耗時: 5.00s
CURLOPT_CONNECTTIMEOUT:TCP 三向交握階段超時,防 DNS 慢、防火牆丟包導致卡住CURLOPT_TIMEOUT:整個請求(含重定向、TLS 握手、傳輸)總超時CURLOPT_NOBODY = true)CURLOPT_HEADER = true 仍可在 curl_getinfo 獲取完整標頭資訊CURLOPT_FOLLOWLOCATION = true 自動處理 301/302/303/307/308CURLOPT_MAXREDIRS = 5 防止惡意重定向迴圈$info['url'] 取得| 模式 | 判斷條件 | 適用場景 |
|---|---|---|
嚴格 ($strictMode=true) | HTTP Code == 200 | 圖片/檔案/靜態資源必須精確存在 |
寬鬆 ($strictMode=false) | 200 <= HTTP Code < 400 | 網頁連結檢查、API 端點存活、SEO 爬蟲 |
不只回傳 true/false,而是結構化陣列包含:
accessible:布林結果http_code:實際 HTTP 狀態碼 (0 表 cURL 層面錯誤)error:人類可讀錯誤訊息info:curl_getinfo() 完整資訊(Content-Type、大小、耗時、最終 URL)若需同時檢查成百上千個 URL,**單執行緒循環極慢**。請使用 curl_multi_* 並發:
function check_urls_concurrent(array $urls, int $timeout = 5, int $maxConcurrent = 20): array
{
$mh = curl_multi_init();
$handles = [];
$results = [];
// 初始化所有 handle
foreach ($urls as $i => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_CONNECTTIMEOUT => $timeout,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_USERAGENT => 'PHP Multi Checker',
]);
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
// 並發執行循環
$running = null;
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh, 0.1); // 等待活動,降低 CPU 佔用
} while ($running > 0);
// 收集結果
foreach ($handles as $i => $ch) {
$errno = curl_errno($ch);
$info = curl_getinfo($ch);
$code = (int)($info['http_code'] ?? 0);
$results[$urls[$i]] = [
'accessible' => ($errno === 0 && $code >= 200 && $code < 400),
'http_code' => $code,
'error' => $errno ? curl_error($ch) : null,
'total_time' => $info['total_time'] ?? 0,
];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $results;
}
**「最佳方法」不是單一函數,而是一套完整策略**:
將上述 check_url_accessible() 函數納入專案通用庫,即可專業、穩健地解決所有「網址是否有效」的需求。