PHP 生成靜態 HTML 頁面完整方案:ob_start 緩衝捕獲、批量生成、分頁處理、目錄自動創建
動態頁靜態化能大幅降低數據庫壓力、提升首屏速度、利於 SEO。本文提供一套**生產可用**的批量生成腳本:模板渲染捕獲、按年月分目錄存儲、分批次生成避免超時、進度顯示與完成提醒。
一、核心原理:輸出緩衝
<?php
// 1. 開啟緩衝
ob_start();
// 2. 執行動態頁邏輯 (包含模板、數據查詢、echo 輸出)
include 'moban.php?id=123'; // 模板文件內部可用 $id 變量
// 3. 獲取緩衝內容並關閉緩衝
$html = ob_get_contents();
ob_end_clean();
// 4. 寫入文件
file_put_contents('html/202401/item_123.html', $html);
優於 file_get_contents('https://domain/moban.php?id=123'):無需 HTTP 開銷、可直接傳遞變量、不受防火牆/域名解析影響。
二、完整批量生成腳本 make.php
<?php
set_time_limit(0); // 無限制執行時間
ini_set('memory_limit', '512M');
require 'config/db.php'; // 數據庫連接 $pdo
require 'Pager.php'; // 分頁類
// 參數:每批次條數 t、起始頁 pg
$pageSize = isset($_GET['t']) ? max(1, (int)$_GET['t']) : 200;
$pageNum = isset($_GET['pg']) ? max(1, (int)$_GET['pg']) : 1;
// 總數
$total = $pdo->query("SELECT COUNT(*) FROM carsell")->fetchColumn();
$pager = new Pager($total, $pageSize);
$pager->setPage($pageNum);
// 當前批次數據
$offset = ($pageNum - 1) * $pageSize;
$stmt = $pdo->prepare("SELECT * FROM carsell ORDER BY id DESC LIMIT ?, ?");
$stmt->execute([$offset, $pageSize]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 開始生成
echo "<h2>第 {$pageNum} 批生成中... (共 {$pager->pageCount} 批)</h2>";
echo "<textarea style='width:100%;height:400px'>";
foreach ($rows as $row) {
$id = $row['id'];
// 1. 捕獲模板輸出
ob_start();
// 模板內可直接使用 $row 變量
include __DIR__ . '/moban.php';
$html = ob_get_clean();
// 2. 目標路徑:html/年月/item_id.html
$ym = date('Ym', strtotime($row['put_date']));
$dir = __DIR__ . "/html/{$ym}";
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
echo "[DIR] Created: {$dir}\n";
}
$file = "{$dir}/item_{$id}.html";
// 3. 寫入
if (file_put_contents($file, $html) !== false) {
echo "[OK] {$file}\n";
} else {
echo "[FAIL] {$file}\n";
}
}
echo "</textarea>";
// 4. 自動跳轉下一批或完成
if ($pageNum < $pager->pageCount) {
$next = $pageNum + 1;
echo "<script>setTimeout(()=>location.href='make.php?t={$pageSize}&pg={$next}', 500);</script>";
} else {
echo "<script>alert('所有 {$total} 條靜態頁生成完成!');</script>";
}
三、模板文件 moban.php 示例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><?= htmlspecialchars($row['title']) ?></title>
</head>
<body>
<h1><?= htmlspecialchars($row['title']) ?></h1>
<p>發布時間: <?= date('Y-m-d H:i', strtotime($row['put_date'])) ?></p>
<div><?= $row['content'] ?></div>
</body>
</html>
四、分頁類 Pager.php 精簡版
<?php
class Pager {
public $total, $pageSize, $pageCount, $currentPage;
public function __construct(int $total, int $pageSize) {
$this->total = $total;
$this->pageSize = $pageSize;
$this->pageCount = ceil($total / $pageSize);
}
public function setPage(int $page) {
$this->currentPage = max(1, min($page, $this->pageCount));
}
}
五、關鍵優化點
| 優化 | 實現 |
| 避免內存爆炸 | 單條處理 + ob_get_clean() 即時釋放,不累積大字符串 |
| 防止腳本超時 | 分批次 (建議 200-500 條/批) + 前端 JS 自動跳轉下一批 |
| 目錄權限 | mkdir($dir, 0755, true) 遞歸創建,生產環境確保 PHP-FPM 用戶有寫權限 |
| 并發生成 | 大數據量可用 pcntl_fork 或 Swoole 多進程并行,配合 Redis 隊列分發 ID |
| 增量更新 | 記錄 last_generate_time,僅重新生成更新過的記錄 |
| CDN 刷新 | 生成後調用 CDN API (如阿里云 RefreshObjectCaches) 預熱/刷新緩存 |
六、替代方案對比
| 方案 | 優點 | 缺點 | 適用場景 |
| ob_start + include (本文) | 零依賴、變量直接可用、性能高 | 模板邏輯與 PHP 混合 | 中小項目、現有 PHP 模板 |
Smarty fetch() | 模板分離、緩存編譯、插件豐富 | 學習成本、額外依賴 | 大型項目、團隊協作 |
| Twig/Blade | 語法優雅、安全(自動轉義)、繼承塊 | 需 Composer、框架綁定 | 現代框架項目 (Laravel/Symfony) |
| 靜態站點生成器 (Hugo/Jekyll/VitePress) | 極快、Markdown 寫作、Git 部署 | 無動態數據庫查詢、學習曲線 | 文檔站、博客、不頻繁更新內容 |
七、部署建議
- 將
make.php 放在**非 Web 可訪問目錄** (如 /data/scripts/),通過 CLI 定時任務觸發:0 2 * * * cd /data/scripts && php make.php t=500 pg=1 >/dev/null 2>&1。
2. 生成目錄 html/ 配置 Nginx 靜態資源服務:location /static/ { alias /data/scripts/html/; expires 30d; }。
3. 監控生成日志、失敗告警、磁盤空間 (預留 20%+)。