首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 抓取網頁標題、描述、關鍵字與正文:cURL + 正則完整採集器

     編輯:PHP 相關知識

前言

建立搜尋引擎、內容聚合、SEO 分析工具時,常需從任意 URL 擷取 <title>、<meta name="description">、<meta name="keywords"> 及 <body> 正文。本文提供一個健壯的 getPageContent() 函數,涵蓋 cURL 設定、編碼偵測、HTML 清洗、錯誤處理與效能優化。

核心採集函數

<?php
/**
 * 從 URL 擷取頁面核心 SEO 資訊與正文
 *
 * @param string $url 目標網址
 * @param int    $timeout 連線逾時秒數
 * @return array [
 *   'title'       => string,
 *   'description' => string,
 *   'keywords'    => string,
 *   'body_text'   => string,  // 已移除 script/style/tag 的純文字
 *   'html'        => string,  // 原始 HTML
 *   'http_code'   => int,
 *   'charset'     => string,
 *   'error'       => string|null
 * ]
 */
function getPageContent(string $url, int $timeout = 10): array {
    $result = [
        'title' => '', 'description' => '', 'keywords' => '',
        'body_text' => '', 'html' => '', 'http_code' => 0,
        'charset' => 'UTF-8', 'error' => null,
    ];

    // 1. 初始化 cURL
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,      // 跟隨 3xx 重導向
        CURLOPT_MAXREDIRS      => 5,
        CURLOPT_TIMEOUT        => $timeout,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_USERAGENT      => 'Mozilla/5.0 (compatible; PHPBot/1.0; +https://example.com/bot)',
        CURLOPT_SSL_VERIFYPEER => false,     // 生產環境建議 true + CA 憑證
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_ENCODING       => 'gzip,deflate', // 自動解壓縮
        CURLOPT_HEADER         => true,      // 需要回應標頭以偵測編碼
    ]);

    $response = curl_exec($ch);
    $errno = curl_errno($ch);
    $error = curl_error($ch);
    $info = curl_getinfo($ch);
    curl_close($ch);

    if ($errno) {
        $result['error'] = "cURL 錯誤 ($errno): $error";
        $result['http_code'] = 0;
        return $result;
    }

    $result['http_code'] = $info['http_code'] ?? 0;
    if ($result['http_code'] < 200 || $result['http_code'] >= 400) {
        $result['error'] = "HTTP {$result['http_code']}";
        return $result;
    }

    // 2. 分離標頭與主體
    $header_size = $info['header_size'];
    $headers = substr($response, 0, $header_size);
    $html = substr($response, $header_size);

    // 3. 偵測編碼:優先 Content-Type 標頭,其次 meta charset
    $charset = 'UTF-8';
    if (preg_match('/charset=([^\s;]+)/i', $headers, $m)) {
        $charset = strtoupper(trim($m[1]));
    } elseif (preg_match('/<meta[^>]+charset=["\']?([^"\'\s>]+)/i', $html, $m)) {
        $charset = strtoupper(trim($m[1]));
    }
    $result['charset'] = $charset;

    // 4. 轉為 UTF-8 內部處理
    if ($charset !== 'UTF-8') {
        $html = @iconv($charset, 'UTF-8//IGNORE', $html);
        if ($html === false) {
            $result['error'] = "編碼轉換失敗: $charset → UTF-8";
            return $result;
        }
    }

    $result['html'] = $html;

    // 5. 擷取 title
    if (preg_match('/<title[^>]*>(.*?)<\/title>/is', $html, $m)) {
        $result['title'] = trim(strip_tags($m[1]));
    }

    // 6. 擷取 meta description
    if (preg_match('/<meta[^>]+name=["\']description["\'][^>]+content=["\']([^"\']+)["\']/i', $html, $m)) {
        $result['description'] = trim($m[1]);
    } elseif (preg_match('/<meta[^>]+content=["\']([^"\']+)["\'][^>]+name=["\']description["\']/i', $html, $m)) {
        $result['description'] = trim($m[1]);
    }

    // 7. 擷取 meta keywords
    if (preg_match('/<meta[^>]+name=["\']keywords["\'][^>]+content=["\']([^"\']+)["\']/i', $html, $m)) {
        $result['keywords'] = trim($m[1]);
    } elseif (preg_match('/<meta[^>]+content=["\']([^"\']+)["\'][^>]+name=["\']keywords["\']/i', $html, $m)) {
        $result['keywords'] = trim($m[1]);
    }

    // 8. 擷取 body 並清洗為純文字
    if (preg_match('/<body[^>]*>(.*?)<\/body>/is', $html, $m)) {
        $body = $m[1];
        // 移除 script、style、noscript、iframe、svg 等非內容標籤
        $body = preg_replace('/<(script|style|noscript|iframe|svg)[^>]*>.*?<\/\1>/is', '', $body);
        // 移除 HTML 註解
        $body = preg_replace('/<!--.*?-->/s', '', $body);
        // 移除所有標籤,保留文字
        $body = strip_tags($body);
        // 解碼 HTML 實體
        $body = html_entity_decode($body, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        // 正規化空白
        $body = preg_replace('/\s+/u', ' ', $body);
        $result['body_text'] = trim($body);
    }

    return $result;
}

// === 使用範例 ===
$url = 'https://www.php.net/';
$data = getPageContent($url);

if ($data['error']) {
    echo "採集失敗: {$data['error']}\n";
} else {
    echo "標題: {$data['title']}\n";
    echo "描述: {$data['description']}\n";
    echo "關鍵字: {$data['keywords']}\n";
    echo "正文長度: " . mb_strlen($data['body_text']) . " 字元\n";
    echo "編碼: {$data['charset']}\n";
    echo "HTTP: {$data['http_code']}\n";
}
?>

關鍵技術細節

1. cURL 設定最佳實踐

  • CURLOPT_ENCODING => 'gzip,deflate':自動處理壓縮回應,節省頻寬。
  • CURLOPT_HEADER => true:取得原始標頭以精準偵測 Content-Type: charset=...。
  • CURLOPT_USERAGENT:模擬瀏覽器,避免被簡單反爬蟲攔截。
  • CURLOPT_SSL_VERIFYPEER => false:開發測試用;生產請設為 true 並配置 CURLOPT_CAINFO。

2. 編碼偵測優先序

  1. HTTP 標頭 Content-Type: text/html; charset=GBK
  2. HTML <meta charset="GBK"> 或 <meta http-equiv="Content-Type" content="text/html; charset=GBK">
  3. 3. 預設 UTF-8

使用 iconv(..., '//IGNORE') 忽略無法轉換字元,避免中斷。

3. 正則模式防禦性寫法

  • /is 修飾符:i 忽略大小寫、s (PCRE_DOTALL) 讓 . 匹配換行。
  • 雙重模式應對屬性順序不同:name...content 與 content...name。
  • 非貪婪 .*? 避免跨標籤匹配。

4. 正文清洗管線

原始 body HTML
  → 移除 script/style/noscript/iframe/svg (含內容)
  → 移除 HTML 註解
  → strip_tags() 移除所有標籤
  → html_entity_decode() 解碼 &nbsp; &lt; 等
  → 正規化空白 (\n\r\t 多空白 → 單空格)
  → trim()

效能與擴充建議

  1. 並發採集:使用 curl_multi_* 或 GuzzleHttp\Pool 並發請求,單機可達 50-200 QPS。
  2. 快取層:對同一 URL 在短時間內 (如 1 小時) 僅採集一次,儲存 Redis/Memcached。
  3. Robots.txt 遵守:採集前檢查 robots.txt,尊重 Crawl-delay、Disallow。
  4. 內容提取升級:對於複雜版面,改用 DOMDocument + XPath 或 Masterminds\HTML5 解析器,比正則更健壯。
  5. 語言偵測:整合 Text_LanguageDetect 或 cld3 自動識別頁面語言。

常見錯誤與排查

現象原因解決
中文亂碼編碼偵測失敗或 iconv 不支援該編碼加入 mb_detect_encoding 兜底、安裝 iconv 完整版
取不到 title/description頁面用 JS 動態渲染 (SPA)需改用 Headless Chrome (Puppeteer/Playwright) 渲染後再擷取
cURL 60 錯誤SSL 憑證驗證失敗設定 CURLOPT_CAINFO 指向 cacert.pem
超時目標伺服器回應慢或封鎖調大 timeout、使用代理 IP 池
正文含大量垃圾文字strip_tags 保留了 script/style 內容先用正則移除 script/style 標籤及內容,再 strip_tags

總結

getPageContent() 以 cURL + 正則實現輕量、無依賴的網頁核心資訊擷取。關鍵在於 標頭+Meta 雙重編碼偵測、防禦性正則匹配屬性順序、分層清洗管線產出純文字。對於大規模、高併發、JS 渲染頁面場景,建議遷移至 Guzzle + 佇列 + Headless Chrome 架構,但核心擷取邏輯 (編碼、清洗、欄位提取) 仍完全適用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved