網頁快照(Web Snapshot)技術常用於搜尋引擎索引、離線閱讀、內容備份、跨域內容代理等場景。本文介紹一個功能完整的 snap 類別,支援快取過期控制、HTML 內容清理(移除 Script)、相對 URL 自動轉絕對路徑,並提供詳細除錯日誌。
<?php
class snap {
// 核心屬性
private $dir; // 快取目錄
private $log; // 除錯日誌
private $contents; // 最終輸出內容
private $filename; // 快取檔案路徑
private $host; // 目標站點基礎 URL
private $name; // 快取檔名(MD5)
private $data_ts; // 快取檔修改時間
private $ttl; // 快取存活秒數
private $url; // 目標 URL
private $ts; // 時間差
public function __construct() {
$this->log = "New snap() object instantiated.
\n";
$this->dir = dirname(__FILE__) . '/';
}
// ... 核心方法詳見下文
}
?>
public function fetch($url = '', $ttl = 10) {
$this->log .= "--------------------------------
fetch() called
\n";
$this->log .= "url: {$url}
\n";
if (empty($url)) {
$this->log .= "OOPS: You need to pass a URL!
\n";
return false;
}
// 解析目標主機
$hosts = parse_url($url);
$this->host = $hosts['scheme'] . '://' . $hosts['host'] . '/';
$this->ttl = $ttl;
$this->url = $url;
$this->name = md5($this->url);
$this->filename = $this->dir . $this->name;
$this->log .= "Filename: {$this->filename}
\n";
$this->getFile_ts(); // 取得快取檔時間
$this->file_get_content(); // 讀取內容(快取或遠端)
return true;
}
private function file_get_content() {
ob_start();
$this->ts = time() - $this->data_ts;
// 快取存在且未過期 → 讀取本地快取
if ($this->data_ts !== 0 && $this->ts <= $this->ttl) {
$this->log .= "cache has expired
\n"; // 註:原碼邏輯文字與條件相反,實際為「未過期」
@readfile($this->filename);
$this->contents = ob_get_contents();
ob_end_clean();
}
// 快取不存在或已過期 → 抓取遠端並更新快取
else {
$this->log .= "cache hasn't expired
\n"; // 同上,實際為「需更新」
@readfile($this->url);
$this->contents = ob_get_contents();
ob_end_clean();
$this->saveToCache();
}
return true;
}
⚠️ 注意:原始碼的日誌訊息與邏輯判斷文字相反,實際執行流程正確。建議修正日誌文字避免誤導。
private function saveToCache() {
$this->log .= "saveToCache() called
\n";
if (!$fp = @fopen($this->filename, 'w')) {
$this->log .= "Could not open {$this->filename}
\n";
return false;
}
// 核心處理:相對路徑轉絕對 + 移除 Script 標籤
$this->contents = $this->formaturl($this->contents, $this->host);
$this->contents = preg_replace("''si", '', $this->contents);
if (!@fwrite($fp, $this->contents)) {
$this->log .= "Could not write to {$this->filename}
\n";
fclose($fp);
return false;
}
fclose($fp);
return true;
}
formaturl() 與 lIIIIl()(建議重命名為 convertToAbsoluteUrl())協同工作:
img[src]、link[href]、a[href] 標籤(雙引號/單引號)。/ 開頭 → 網域根目錄絕對路徑../ 開頭 → 相對上層目錄,遞迴處理./ 開頭 → 相對當前目錄mailto:、javascript: → 保持不變<?php
require_once dirname(__FILE__) . '/snap.class.php';
$h = new snap();
$h->fetch($_GET['url']);
// echo $h->log; // 除錯用
echo $h->contents;
?>
| 面向 | 原始做法 | 建議改良 |
|---|---|---|
| HTTP 客戶端 | readfile() + 輸出緩衝 | Guzzle / cURL,支援標頭、逾時、重試、代理 |
| HTML 解析 | 正則表達式 | DOMDocument / Masterminds\HTML5 / Symfony DomCrawler |
| 快取儲存 | 本地檔案系統 | Redis / Memcached / 資料庫,支援分散式部署 |
| 快取策略 | 固定 TTL | 支援 ETag/Last-Modified 驗證、快取標籤、主動清除 |
| 安全性 | 無防護 | SSRF 防護(封鎖內網 IP)、內容型別驗證、大小限制 |
| 編碼處理 | 假設單一編碼 | 自動偵測 charset(mb_detect_encoding + iconv) |
snap 類別展示了網頁快照的核心邏輯:抓取 → 處理相對路徑 → 移除腳本 → 快取 → 輸出。雖然正則解析 HTML 有脆弱性,但對於簡單場景仍具參考價值。現代專案建議採用成熟的 HTTP 客戶端與 HTML 解析器,並建立完善的快取與安全防護機制。