在開發友鏈檢測、鏈接驗證、內容聚合等功能時,需要判斷遠程 URL 是否真實可訪問。本文詳細對比三種主流方法:file_get_contents、cURL、get_headers,分析其優缺點、適用場景及最佳實踐。
<?php
$url = 'https://www.example.com';
// 基礎用法
if (@file_get_contents($url)) {
echo 'URL 可訪問';
} else {
echo 'URL 不可訪問';
}
// 進階:設置超時上下文
$context = stream_context_create([
'http' => [
'timeout' => 10, // 10秒超時
'method' => 'HEAD', // 僅請求頭部,不下載內容
'user_agent' => 'PHP URL Checker 1.0'
]
]);
$content = @file_get_contents($url, false, $context);
if ($content !== false) {
echo '可訪問';
} else {
echo '不可訪問';
}
?>
<?php
function checkUrlCurl($url, $timeout = 10) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true, // 僅獲取頭部 (HEAD 請求)
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout, // 總超時
CURLOPT_CONNECTTIMEOUT => 5, // 連接超時
CURLOPT_FOLLOWLOCATION => true, // 跟隨重定向
CURLOPT_MAXREDIRS => 5, // 最大重定向次數
CURLOPT_USERAGENT => 'PHP URL Checker 1.0',
CURLOPT_SSL_VERIFYPEER => false, // 開發環境可關閉 SSL 驗證
CURLOPT_HEADER => true // 包含頭部信息
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return [
'accessible' => $httpCode >= 200 && $httpCode < 400,
'http_code' => $httpCode,
'error' => $error,
'response_time' => curl_getinfo($ch, CURLINFO_TOTAL_TIME)
];
}
// 使用示例
$result = checkUrlCurl('https://www.google.com');
if ($result['accessible']) {
echo "可訪問 (HTTP {$result['http_code']})";
} else {
echo "不可訪問: {$result['error']}";
}
?>
<?php
function checkUrlHeaders($url, $timeout = 10) {
// 設置流超時
$ctx = stream_context_create([
'http' => ['timeout' => $timeout]
]);
$headers = @get_headers($url, 1, $ctx);
if ($headers === false) return ['accessible' => false, 'error' => '連接失敗'];
// 解析狀態碼
$statusLine = $headers[0] ?? '';
preg_match('/HTTP\/\d\.\d\s+(\d+)/', $statusLine, $matches);
$httpCode = isset($matches[1]) ? (int)$matches[1] : 0;
return [
'accessible' => $httpCode >= 200 && $httpCode < 400,
'http_code' => $httpCode,
'headers' => $headers
];
}
?>
| 特性 | file_get_contents | cURL | get_headers |
|---|---|---|---|
| 代碼複雜度 | 低 | 中 | 低 |
| 請求方式控制 | 支持 (需 context) | 完全控制 | 僅 HEAD/GET |
| 超時控制 | 支持 (需 context) | 精確到毫秒 | 支持 (需 context) |
| 重定向處理 | 自動跟隨 | 可配置策略 | 自動跟隨 |
| SSL/TLS 支持 | 基礎支持 | 完整支持 | 基礎支持 |
| 代理支持 | 需 context | 完整支持 | 需 context |
| 錯誤信息詳盡度 | 低 | 高 | 中 |
| 性能(HEAD請求) | 中 | 高 | 高 |
| 依賴擴展 | 無 (核心) | 需 curl 擴展 | 無 (核心) |
| 內存佔用 | 較高(下載內容) | 可控(不下載body) | 低(僅頭部) |
<?php
class UrlChecker {
private $timeout = 10;
private $userAgent = 'UrlChecker/1.0';
public function __construct($timeout = 10) {
$this->timeout = $timeout;
}
// 推薦使用 cURL(功能最全)
public function check($url) {
if (!function_exists('curl_init')) {
return $this->checkWithGetHeaders($url);
}
return $this->checkWithCurl($url);
}
private function checkWithCurl($url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_USERAGENT => $this->userAgent,
CURLOPT_SSL_VERIFYPEER => true, // 生產環境建議開啟
]);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
$time = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
curl_close($ch);
return [
'url' => $url,
'accessible' => $httpCode >= 200 && $httpCode < 400,
'http_code' => $httpCode,
'response_time' => round($time * 1000, 2), // 毫秒
'error' => $error ?: null,
'method' => 'curl'
];
}
private function checkWithGetHeaders($url) {
$ctx = stream_context_create(['http' => ['timeout' => $this->timeout]]);
$headers = @get_headers($url, 1, $ctx);
if ($headers === false) {
return ['url' => $url, 'accessible' => false, 'error' => 'Connection failed', 'method' => 'get_headers'];
}
$statusLine = $headers[0] ?? '';
preg_match('/HTTP\/\d\.\d\s+(\d+)/', $statusLine, $matches);
$httpCode = isset($matches[1]) ? (int)$matches[1] : 0;
return [
'url' => $url,
'accessible' => $httpCode >= 200 && $httpCode < 400,
'http_code' => $httpCode,
'method' => 'get_headers'
];
}
}
// 批量檢測示例
$checker = new UrlChecker(5);
$urls = [
'https://www.google.com',
'https://www.github.com',
'https://nonexistent-domain-12345.com'
];
foreach ($urls as $url) {
$result = $checker->check($url);
$status = $result['accessible'] ? '✅ 可訪問' : '❌ 不可訪問';
echo "{$status} [{$result['http_code']}] {$url} ({$result['response_time']}ms)\n";
}
?>
// php.ini 設置
allow_url_fopen = On
// 或運行時檢查
if (ini_get('allow_url_fopen')) {
// 使用 file_get_contents
} else {
// 回退到 cURL
}
// 開發環境臨時關閉(生產嚴禁)
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// 正確做法:配置 CA 證書包
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem');
// 使用 curl_multi 並發請求
function checkUrlsConcurrent($urls, $concurrency = 10) {
$mh = curl_multi_init();
$handles = [];
$results = [];
foreach ($urls as $i => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_FOLLOWLOCATION => true,
]);
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
$running = null;
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh);
} while ($running > 0);
foreach ($handles as $i => $ch) {
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$results[$urls[$i]] = [
'accessible' => $httpCode >= 200 && $httpCode < 400,
'http_code' => $httpCode
];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $results;
}
| 範圍 | 含義 | 判定為可訪問 |
|---|---|---|
| 200-299 | 成功 | ✅ 是 |
| 300-399 | 重定向 | ✅ 是 (最終目標可達) |
| 400-499 | 客戶端錯誤 | ❌ 否 (404, 403, 429等) |
| 500-599 | 服務端錯誤 | ❌ 否 (暫時性可能重試) |
綜合來看,cURL 是功能最完整、生產環境最推薦的方案。若環境受限無法安裝擴展,get_headers 是不錯的備選。實際項目中建議封裝統一檢測類,支持多種後端自動降級,並結合緩存機制避免重複檢測同一 URL。