(.*?)<\/div>/s')[0] ?? '';
// 入庫(PDO 預處理)
$stmt = $pdo->prepare('INSERT INTO articles (title, content, source_url) VALUES (?, ?, ?)');
$stmt->execute([$title, $content, $url]);
sleep(1); // 禮貌延遲
}
?>
圖片/檔案本地化下載
function downloadAsset($url, $saveDir) {
if (empty($url)) return false;
$ext = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
if (!in_array($ext, ['jpg','jpeg','png','gif','webp','swf','mp4'])) return false;
$filename = md5($url) . '.' . $ext;
$path = $saveDir . '/' . $filename;
if (file_exists($path)) return $filename; // 已下載
$data = @file_get_contents($url);
if ($data === false) return false;
file_put_contents($path, $data);
return $filename;
}
實戰避坑指南(血淚經驗)
| 坑點 | 現象 | 解決方案 |
| 防盜鏈 | 圖片/頁面返回 403 | 設定 Referer 標頭、使用代理 IP、或放棄該站 |
| 編碼不一致 | 中文亂碼 | mb_detect_encoding() + iconv() 統一轉 UTF-8 |
| 動態渲染 | 內容在 JS 中、AJAX 載入 | 分析 API 介面、或用 Headless Chrome (Puppeteer/PHP-WebDriver) |
| 頻率限制/IP 封禁 | 連續請求失敗 | 代理池輪換、隨機延遲、模擬真實瀏覽器指紋 |
| 頁面結構變更 | 正則失效 | 改用 DOM+XPath、加入選擇器健壯性檢查、監控告警 |
| 重複采集 | 資料庫主鍵衝突 | URL 去重表、內容指紋 (simhash)、INSERT IGNORE/ON DUPLICATE KEY UPDATE |
| 超時/網路抖動 | 腳本中斷 | 重試機制 (指數退避)、隊列化 (Redis/RabbitMQ) + Worker |
| 大量資料記憶體溢出 | PHP Fatal Error | 生成器 yield、分批處理、unset() 釋放大變數 |
架構升級建議
- 隊列化:生產者 (列表解析) → Redis List/RabbitMQ → 消費者 (詳情解析/入庫)
- 去重服務:Bloom Filter + Redis Set + 資料庫唯一索引三層防線
- 監控告警:成功率、耗時、錯誤率、入庫量 Grafana 看板
- 分散式:多機器協同,共享隊列與去重集合
法律與道德邊界
- 遵守
robots.txt、網站服務條款
- 控制頻率,不影響目標站點正常服務
- 僅采集公開資料,不繞過登入/驗證
- 尊重版權,必要時聯繫授權