萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 判斷網址是否可正常存取的最佳實踐:cURL 完整方案

為什麼需要「最佳」方法?

許多開發者習慣用 file_get_contents() 或 fopen() 直接讀取遠端 URL,但這有嚴重缺陷:

  • **會下載整個內容**,大檔案極耗記憶體與頻寬
  • **無法設定超時**,網路不通時腳本會卡死 default_socket_timeout 秒(預設 60s)
  • **失敗產生 Warning**,干擾日誌,需用 @ 抑制不雅觀
  • **無法區分「連不上」與「回傳 404/500」**,錯誤處理模糊

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

關鍵設計細節解析

1. 雙重超時保護

  • CURLOPT_CONNECTTIMEOUT:TCP 三向交握階段超時,防 DNS 慢、防火牆丟包導致卡住
  • CURLOPT_TIMEOUT:整個請求(含重定向、TLS 握手、傳輸)總超時
  • 建議值:**5-10 秒**;API 健康檢查可設 2-3 秒

2. HEAD 請求 (CURLOPT_NOBODY = true)

  • 只傳輸 Header,不下載 Body,**節省 99%+ 頻寬與時間**
  • 配合 CURLOPT_HEADER = true 仍可在 curl_getinfo 獲取完整標頭資訊

3. 智能重定向處理

  • CURLOPT_FOLLOWLOCATION = true 自動處理 301/302/303/307/308
  • CURLOPT_MAXREDIRS = 5 防止惡意重定向迴圈
  • 最終 URL 可從 $info['url'] 取得

4. 嚴格 vs 寬鬆模式

模式判斷條件適用場景
嚴格 ($strictMode=true)HTTP Code == 200圖片/檔案/靜態資源必須精確存在
寬鬆 ($strictMode=false)200 <= HTTP Code < 400網頁連結檢查、API 端點存活、SEO 爬蟲

5. 完整錯誤資訊回傳

不只回傳 true/false,而是結構化陣列包含:

  • accessible:布林結果
  • http_code:實際 HTTP 狀態碼 (0 表 cURL 層面錯誤)
  • error:人類可讀錯誤訊息
  • info:curl_getinfo() 完整資訊(Content-Type、大小、耗時、最終 URL)

進階:批次並發檢查 (cURL Multi)

若需同時檢查成百上千個 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;
}

總結

**「最佳方法」不是單一函數,而是一套完整策略**:

  1. **用 cURL HEAD 請求** —— 輕量、快速、不下載內容
  2. **雙重超時控制** —— 連線超時 + 總超時,杜絕卡死
  3. **結構化回傳** —— 狀態碼、錯誤詳情、元資料一應俱全
  4. **大量檢查用 curl_multi** —— 並發提升 10-50 倍效率
  5. **嚴格/寬鬆模式可切換** —— 適應不同業務定義

將上述 check_url_accessible() 函數納入專案通用庫,即可專業、穩健地解決所有「網址是否有效」的需求。

copyright © 萬盛學電腦網 all rights reserved