搜尋功能起始於簡單的 HTML 表單,提交關鍵字至處理腳本 search.php。首要任務是獲取並驗證輸入:
<?php
$keyword = trim($_POST['keyword'] ?? '');
if (empty($keyword)) {
exit('關鍵字不能為空');
}
// 後續處理...
?>
早期方案採用 opendir()/readdir() 遞歸掃描目錄樹,讀取每個文件內容進行字符串匹配。核心代碼結構:
function searchFiles($dir, $keyword, &$results) {
$handle = opendir($dir);
while (false !== ($file = readdir($handle))) {
if ($file === '.' || $file === '..') continue;
$path = "$dir/$file";
if (is_dir($path)) {
searchFiles($path, $keyword, $results); // 遞歸進入子目錄
} else {
// 處理文件匹配邏輯
}
}
closedir($handle);
}
僅處理 HTML/PHP 等文本文件,排除圖片、二進制文件及搜尋腳本本身:
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!in_array($ext, ['html','htm','php','txt','xml'])) continue;
if ($file === 'search.php') continue;
使用正則提取 <body> 內容,再以 strip_tags() 移除標籤:
$content = file_get_contents($path);
if (preg_match('/<body[^>]*>(.+)<\/body>/is', $content, $m)) {
$text = strip_tags($m[1]);
} else {
$text = strip_tags($content);
}
從 <title> 標籤獲取標題,作為搜尋結果展示:
$title = '無標題';
if (preg_match('/<title>(.+)<\/title>/i', $content, $m)) {
$title = trim($m[1]);
}
使用 stripos() 或 preg_match() 進行匹配,將符合條件的文件路徑與標題存入結果數組:
if (stripos($text, $keyword) !== false) {
$results[] = ['path' => $path, 'title' => $title];
}
大量文件遍歷極易觸發 max_execution_time 限制,需在腳本開頭設置:
set_time_limit(600); // 10 分鐘
將結果渲染為可點擊鏈接,支援新視窗打開:
foreach ($results as $item) {
echo "<a href='{$item['path']}' target='_blank'>{$item['title']}</a><br>";
}
統一轉換為 UTF-8 進行匹配,避免亂碼:
$content = mb_convert_encoding($content, 'UTF-8', 'auto');
上述方案僅適合中小型站點(文件數 < 5,000)。大型站點必須引入索引機制:
| 方案 | 原理 | 適用場景 | 複雜度 |
|---|---|---|---|
| 文件遍歷 | 實時掃描文件系統 | 小型靜態站、臨時需求 | 低 |
| 數據庫全文索引 | MySQL FULLTEXT / PostgreSQL tsvector | 動態內容管理系統(CMS) | 中 |
| 專用搜尋引擎 | Elasticsearch、Meilisearch、Algolia | 大規模、高併發、需分詞/同義詞/權重 | 高 |
| 倒排索引自建 | 定時任務構建詞→文檔映射表 | 中型靜態站、不想引入重組件 | 中高 |
詞 → [文檔ID, 詞頻, 位置] 映射 → 序列化存儲(Redis/文件/SQLite)。preg_quote())。站內搜尋是用戶體驗的關鍵環節。從簡單的文件遍歷起步,隨著業務增長平滑遷移至專業搜尋引擎,是架構演進的必經之路。選型時以「滿足當前業務、預留擴展空間、控制運維成本」為原則,切勿過度設計。