本文目录导读:

在 PHP 爬虫中控制抓取频率是避免被封禁的重要措施,以下是完整的频率控制方案:
基础延时控制
<?php
class Crawler {
private $minDelay = 1; // 最小延时(秒)
private $maxDelay = 3; // 最大延时(秒)
// 简单延时
private function simpleDelay() {
sleep(rand($this->minDelay, $this->maxDelay));
}
// 毫秒级延时
private function microDelay() {
usleep(rand($this->minDelay * 1000000, $this->maxDelay * 1000000));
}
public function fetch($url) {
$content = $this->getContent($url);
$this->simpleDelay(); // 抓取后延时
return $content;
}
}
?>
动态自适应频率控制
<?php
class AdaptiveCrawler {
private $requestInterval; // 当前请求间隔
private $minInterval = 0.5; // 最小间隔(秒)
private $maxInterval = 10; // 最大间隔(秒)
private $successCount = 0; // 连续成功次数
private $failCount = 0; // 连续失败次数
private $lastRequestTime = 0;
private $config;
public function __construct($config = []) {
$this->config = $config;
$this->requestInterval = $this->minInterval;
}
public function fetch($url) {
$this->waitForNextRequest();
$result = $this->performRequest($url);
$this->adjustInterval($result['success']);
$this->lastRequestTime = microtime(true);
return $result['data'];
}
private function waitForNextRequest() {
$elapsed = microtime(true) - $this->lastRequestTime;
$waitTime = $this->requestInterval - $elapsed;
if ($waitTime > 0) {
usleep($waitTime * 1000000);
}
}
private function performRequest($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$data = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$totalTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
curl_close($ch);
$success = ($httpCode >= 200 && $httpCode < 400);
return [
'success' => $success,
'data' => $data,
'time' => $totalTime
];
}
private function adjustInterval($success) {
if ($success) {
$this->successCount++;
$this->failCount = 0;
// 连续成功20次,适当加快速度
if ($this->successCount % 20 == 0 && $this->requestInterval > $this->minInterval) {
$this->requestInterval = max(
$this->minInterval,
$this->requestInterval * 0.8
);
}
} else {
$this->failCount++;
$this->successCount = 0;
// 失败时增加间隔
if ($this->failCount >= 1) {
$this->requestInterval = min(
$this->maxInterval,
$this->requestInterval * 2
);
}
}
}
}
?>
队列 + 令牌桶算法
<?php
class TokenBucket {
private $tokens; // 当前令牌数
private $maxTokens; // 最大令牌数
private $rate; // 令牌生成速率(个/秒)
private $lastRefillTime; // 上次补充时间
public function __construct($rate, $maxTokens = null) {
$this->rate = $rate;
$this->maxTokens = $maxTokens ?: $rate;
$this->tokens = $this->maxTokens;
$this->lastRefillTime = microtime(true);
}
public function consume() {
$this->refill();
if ($this->tokens >= 1) {
$this->tokens--;
return true;
}
return false;
}
private function refill() {
$now = microtime(true);
$delta = $now - $this->lastRefillTime;
$tokensToAdd = $delta * $this->rate;
$this->tokens = min($this->maxTokens, $this->tokens + $tokensToAdd);
$this->lastRefillTime = $now;
}
public function waitAndConsume() {
while (!$this->consume()) {
usleep(500000); // 等待500ms
}
}
}
class QueueCrawler {
private $urlQueue; // URL队列
private $tokenBucket; // 令牌桶
private $isProcessing = false;
public function __construct() {
$this->urlQueue = [];
$this->tokenBucket = new TokenBucket(2); // 每秒2个请求
}
public function addUrl($url) {
$this->urlQueue[] = $url;
$this->processQueue();
}
private function processQueue() {
if ($this->isProcessing || empty($this->urlQueue)) {
return;
}
$this->isProcessing = true;
while (!empty($this->urlQueue)) {
$url = array_shift($this->urlQueue);
// 等待可用的令牌
$this->tokenBucket->waitAndConsume();
// 执行抓取
$this->fetchUrl($url);
// 可选的随机间隔
usleep(rand(100000, 500000)); // 0.1-0.5秒
}
$this->isProcessing = false;
}
private function fetchUrl($url) {
// 抓取逻辑
echo "抓取: {$url} - " . date('H:i:s') . PHP_EOL;
}
}
?>
记忆化频率控制(基于文件缓存)
<?php
class FileBasedRateLimiter {
private $cacheDir;
private $cacheTime = 3600; // 缓存时间(秒)
public function __construct($cacheDir = '/tmp/crawler_cache') {
$this->cacheDir = $cacheDir;
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0755, true);
}
}
public function checkLimit($url, $maxRequests = 10, $timeWindow = 60) {
$urlHash = md5($url);
$cacheFile = $this->cacheDir . '/' . $urlHash . '.json';
$data = $this->readCache($cacheFile);
$now = time();
// 清理过期记录
$data = array_filter($data, function($time) use ($now, $timeWindow) {
return ($now - $time) < $timeWindow;
});
if (count($data) >= $maxRequests) {
return false; // 已达到限制
}
$data[] = $now;
$this->writeCache($cacheFile, $data);
return true;
}
private function readCache($file) {
if (file_exists($file)) {
$content = file_get_contents($file);
return json_decode($content, true) ?: [];
}
return [];
}
private function writeCache($file, $data) {
file_put_contents($file, json_encode($data), LOCK_EX);
}
}
?>
完整示例:带频率控制的爬虫类
<?php
class SmartCrawler {
private $userAgent;
private $delay;
private $jitter;
private $proxies = [];
private $currentProxy = 0;
private $rateLimiter;
private $semaphore; // 信号量(用于多线程)
public function __construct($config = []) {
$this->userAgent = $config['user_agent'] ?? 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)';
$this->delay = $config['delay'] ?? 2;
$this->jitter = $config['jitter'] ?? 0.5;
$this->rateLimiter = new TokenBucket($config['rate'] ?? 2);
// 可选:信号量控制并发
if (isset($config['semaphore_key'])) {
$this->semaphore = sem_get($config['semaphore_key'], 1);
}
}
public function fetch($url, $retry = 3) {
if ($this->semaphore) {
sem_acquire($this->semaphore);
}
// 速率控制
$this->rateLimiter->waitAndConsume();
// 添加随机延迟
$this->randomDelay();
$result = $this->makeRequest($url);
// 重试逻辑
if (!$result['success'] && $retry > 0) {
usleep(1000000); // 1秒后重试
$this->fetch($url, $retry - 1);
}
if ($this->semaphore) {
sem_release($this->semaphore);
}
return $result['data'];
}
private function randomDelay() {
$baseDelay = $this->delay;
$jitterAmount = rand(0, $this->jitter * 1000000) / 1000000;
$totalDelay = $baseDelay + $jitterAmount;
sleep($totalDelay);
}
private function makeRequest($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
// 代理设置
if (!empty($this->proxies)) {
$proxy = $this->getNextProxy();
curl_setopt($ch, CURLOPT_PROXY, $proxy['host']);
curl_setopt($ch, CURLOPT_PROXYPORT, $proxy['port']);
if (isset($proxy['auth'])) {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy['auth']);
}
}
$data = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
// 处理可能的封禁状态码
if (in_array($httpCode, [429, 403, 503])) {
$this->increaseDelay();
return ['success' => false, 'data' => null];
}
return [
'success' => ($httpCode == 200),
'data' => $data,
'http_code' => $httpCode,
'error' => $error
];
}
private function increaseDelay() {
$this->delay = min($this->delay * 2, 30); // 最大30秒
}
private function getNextProxy() {
if (empty($this->proxies)) return null;
$proxy = $this->proxies[$this->currentProxy];
$this->currentProxy = ($this->currentProxy + 1) % count($this->proxies);
return $proxy;
}
public function setProxies($proxies) {
$this->proxies = $proxies;
}
public function resetDelay() {
$this->delay = 2;
}
}
// 使用示例
$crawler = new SmartCrawler([
'delay' => 2, // 基础延迟2秒
'jitter' => 1, // 随机延迟0-1秒
'rate' => 1, // 每秒1个请求
]);
$urls = [
'https://example.com/page1',
'https://example.com/page2',
// ... more URLs
];
foreach ($urls as $url) {
$content = $crawler->fetch($url);
// 处理内容...
}
?>
其他优化建议
// 1. 设置请求头模拟浏览器
$headers = [
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
'Connection: keep-alive',
'DNT: 1'
];
// 2. 缓存已抓取的内容避免重复请求
function getWithCache($url, $cacheTime = 3600) {
$cacheFile = 'cache/' . md5($url);
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $cacheTime) {
return file_get_contents($cacheFile);
}
$content = fetchContent($url);
file_put_contents($cacheFile, $content);
return $content;
}
// 3. 随机化请求时间
function getRandomDelay($url) {
// 基于URL生成确定性随机数
$seed = crc32($url);
mt_srand($seed);
return mt_rand(2, 5); // 2-5秒
}
最佳实践建议
- 遵守robots.txt:先检查目标网站的robots.txt
- 从低速开始:初始频率设低,逐步提高
- 设置请求超时:避免僵死连接消耗资源
- 监控响应状态:检测到429或403时自动降频
- 使用日志记录:记录每次请求的时间和状态
- 分布式爬取:使用队列系统(Redis)实现分布式控制
选择合适的频率控制策略取决于目标网站的性质和你的需求,建议从保守的设置开始,根据实际成功率进行调整。