<div class="wrap">
<h1>防爬虫防护设置</h1>
<form method="post" action="options.php">
<?php settings_fields('anti_scraper_group'); ?>
<table class="form-table">
<tr>
<th scope="row"><label for="block_common_bots">阻止恶意爬虫</label></th>
<td>
<input type="checkbox" id="block_common_bots" name="anti_scraper_options[block_common_bots]"
value="1" <?php checked(1, $options['block_common_bots']); ?>>
<p class="description">仅阻止恶意爬虫(已豁免百度/谷歌/必应等合法搜索引擎)</p>
</td>
</tr>
<tr>
<th scope="row"><label for="block_headless_browsers">阻止无头浏览器</label></th>
<td>
<input type="checkbox" id="block_headless_browsers" name="anti_scraper_options[block_headless_browsers]"
value="1" <?php checked(1, $options['block_headless_browsers']); ?>>
<p class="description">阻止无头浏览器(如Headless Chrome),常用于批量抓取内容</p>
</td>
</tr>
<tr>
<th scope="row"><label for="rate_limit">启用访问频率限制</label></th>
<td>
<input type="checkbox" id="rate_limit" name="anti_scraper_options[rate_limit]"
value="1" <?php checked(1, $options['rate_limit']); ?>>
<p class="description">限制同一IP的访问频率,防止快速批量抓取</p>
</td>
</tr>
<tr>
<th scope="row"><label for="max_requests">最大请求数</label></th>
<td>
<input type="number" id="max_requests" name="anti_scraper_options[max_requests]"
value="<?php echo esc_attr($options['max_requests']); ?>" min="1" max="1000">
<p class="description">在指定时间窗口内允许的最大请求数(建议30-100)</p>
</td>
</tr>
<tr>
<th scope="row"><label for="time_window">时间窗口(秒)</label></th>
<td>
<input type="number" id="time_window" name="anti_scraper_options[time_window]"
value="<?php echo esc_attr($options['time_window']); ?>" min="10" max="3600">
<p class="description">请求计数的时间窗口(建议60-300秒)</p>
</td>
</tr>
<tr>
<th scope="row"><label for="disable_right_click">禁用右键菜单</label></th>
<td>
<input type="checkbox" id="disable_right_click" name="anti_scraper_options[disable_right_click]"
value="1" <?php checked(1, $options['disable_right_click']); ?>>
<p class="description">防止普通用户通过右键复制内容(可被技术手段绕过)</p>
</td>
</tr>
<tr>
<th scope="row"><label for="disable_text_selection">禁用文本选择</label></th>
<td>
<input type="checkbox" id="disable_text_selection" name="anti_scraper_options[disable_text_selection]"
value="1" <?php checked(1, $options['disable_text_selection']); ?>>
<p class="description">防止普通用户选择/复制文本(可被技术手段绕过)</p>
</td>
</tr>
<tr>
<th scope="row"><label for="custom_user_agents">自定义要阻止的User-Agent</label></th>
<td>
<textarea id="custom_user_agents" name="anti_scraper_options[custom_user_agents]"
rows="5" cols="50"><?php echo esc_textarea($options['custom_user_agents']); ?></textarea>
<p class="description">每行一个User-Agent关键词,将阻止包含这些关键词的请求</p>
</td>
</tr>
</table>
<?php submit_button('保存设置'); ?>
</form>
<div style="margin-top:20px; padding:10px; background:#f1f1f1; border-left:4px solid #2196F3;">
<h3>使用提示</h3>
<ul>
<li>该插件仅能防护基础爬虫,无法阻止专业级爬虫(如定制化爬虫)</li>
<li>如需配置可信代理IP段,请修改插件中 <code>anti_scraper_get_user_ip</code> 函数内的 <code>$trusted_proxies</code> 数组</li>
<li>拦截日志可在WordPress后台「工具→站点健康→日志」中查看PHP错误日志</li>
</ul>
</div>
</div>
<?php
}
// 核心防护功能
add_action('init', 'anti_scraper_protect_content');
function anti_scraper_protect_content() {
// 不限制管理员
if (current_user_can('manage_options')) {
return;
}
$options = get_option('anti_scraper_options');
// 1. 阻止恶意爬虫(豁免搜索引擎)
if ($options['block_common_bots']) {
anti_scraper_block_common_bots();
}
// 2. 阻止无头浏览器
if ($options['block_headless_browsers']) {
anti_scraper_block_headless_browsers();
}
// 3. 阻止自定义User-Agent
if (!empty($options['custom_user_agents'])) {
anti_scraper_block_custom_user_agents($options['custom_user_agents']);
}
// 4. 访问频率限制
if ($options['rate_limit']) {
anti_scraper_enforce_rate_limit($options['max_requests'], $options['time_window']);
}
}
// 阻止恶意爬虫(修复:豁免合法搜索引擎)
function anti_scraper_block_common_bots() {
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
// 合法搜索引擎爬虫白名单(核心修复:避免误封SEO)
$whitelist_bots = array(
'BaiduSpider', // 百度
'Googlebot', // 谷歌
'Bingbot', // 必应
'360Spider', // 360搜索
'Sogou Spider', // 搜狗
'YisouSpider', // 神马搜索
'YandexBot', // 俄罗斯Yandex
'DuckDuckBot' // DuckDuckGo
);
// 先检查白名单,命中则直接放行
foreach ($whitelist_bots as $white_bot) {
if (stripos($user_agent, $white_bot) !== false) {
return;
}
}
// 仅拦截恶意爬虫关键词(剔除合法的bot/crawl等)
$malicious_bot_keywords = array(
'scraper', 'grabber', 'harvest', 'parser', 'worm',
'linkchecker', 'webzip', 'teleport', 'httrack', 'offline'
);
foreach ($malicious_bot_keywords as $keyword) {
if (stripos($user_agent, $keyword) !== false) {
anti_scraper_block_request('恶意爬虫关键词匹配: ' . $keyword);
}
}
}
// 阻止无头浏览器
function anti_scraper_block_headless_browsers() {
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$headless_indicators = array(
'HeadlessChrome',
'PhantomJS',
'Selenium',
'webdriver',
'puppeteer'
);
foreach ($headless_indicators as $indicator) {
if (stripos($user_agent, $indicator) !== false) {
anti_scraper_block_request('无头浏览器检测: ' . $indicator);
}
}
// 检查特殊的无头浏览器请求头
if (isset($_SERVER['HTTP_X_SEARCH_ENGINE'])) {
anti_scraper_block_request('可疑请求头: HTTP_X_SEARCH_ENGINE');
}
}
// 阻止自定义User-Agent
function anti_scraper_block_custom_user_agents($custom_agents) {
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$agents = explode("\n", $custom_agents);
foreach ($agents as $agent) {
$agent = trim($agent);
if (!empty($agent) && stripos($user_agent, $agent) !== false) {
anti_scraper_block_request('自定义User-Agent匹配: ' . $agent);
}
}
}
// 实施访问频率限制(修复:简化计数逻辑,防IP伪造)
function anti_scraper_enforce_rate_limit($max_requests, $time_window) {
$ip = anti_scraper_get_user_ip();
$transient_key = 'anti_scraper_rate_limit_' . md5($ip);
// 简化计数逻辑:仅记录请求次数,而非所有时间戳(减少内存占用)
$current_count = (int)get_transient($transient_key);
// 超过限制则拦截(返回429规范状态码,而非403)
if ($current_count >= $max_requests) {
anti_scraper_block_request(
'访问频率超限: IP=' . $ip . ', 限制=' . $max_requests . '/' . $time_window . '秒',
429,
'访问过于频繁,请' . gmdate('i分s秒', $time_window) . '后再试'
);
}
// 计数器递增,过期时间=时间窗口(确保计数周期准确)
set_transient($transient_key, $current_count + 1, $time_window);
}
// 获取用户真实IP(修复:防伪造,仅信任可信代理)
function anti_scraper_get_user_ip() {
// 【重要】修改为你的服务器可信代理IP段(如CDN/反向代理IP)
// 示例:Cloudflare代理IP段、Nginx反向代理IP等
$trusted_proxies = array(
'192.168.1.0/24', // 内网代理示例
'172.16.0.0/12', // 内网代理示例
// '103.21.244.0/22', // Cloudflare示例IP段(按需添加)
);
// 默认取REMOTE_ADDR(服务器直接获取的IP,无法伪造)
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
// 仅当请求来自可信代理时,才读取X-Forwarded-For
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && anti_scraper_is_trusted_proxy($_SERVER['REMOTE_ADDR'], $trusted_proxies)) {
$ip_list = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$real_ip = trim(reset($ip_list)); // 取第一个IP(真实客户端IP)
// 验证IP合法性,防止伪造
if (filter_var($real_ip, FILTER_VALIDATE_IP)) {
$ip = $real_ip;
}
}
// 最终验证IP格式
return filter_var($ip, FILTER_VALIDATE_IP) ?: '0.0.0.0';
}
// 辅助函数:判断IP是否在可信代理段内
function anti_scraper_is_trusted_proxy($ip, $trusted_proxies) {
if (empty($ip) || empty($trusted_proxies)) {
return false;
}
foreach ($trusted_proxies as $cidr) {
list($subnet, $mask) = explode('/', $cidr, 2);
$mask = (int)$mask ?: 32;
$ip_long = ip2long($ip);
$subnet_long = ip2long($subnet);
$mask_long = -1 << (32 - $mask);
if (($ip_long & $mask_long) === ($subnet_long & $mask_long)) {
return true;
}
}
return false;
}
// 阻止请求的通用函数(修复:添加日志、自定义状态码和提示)
function anti_scraper_block_request($reason = '未知原因', $status_code = 403, $message = '403 Forbidden') {
// 1. 记录拦截日志(可在WP后台「工具→站点健康→日志」查看)
$ip = anti_scraper_get_user_ip();
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '未知User-Agent';
$url = $_SERVER['REQUEST_URI'] ?? '未知URL';
error_log(
sprintf(
"[防爬虫插件] 拦截请求 | 时间: %s | IP: %s | URL: %s | User-Agent: %s | 原因: %s",
date('Y-m-d H:i:s'),
$ip,
$url,
$ua,
$reason
)
);
// 2. 返回指定状态码和提示
status_header($status_code);
header('Content-Type: text/html; charset=UTF-8');
echo '<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>' . $message . '</title></head>
<body style="text-align:center; margin-top:100px; font-family:Arial;">
<h1 style="color:#e74c3c;">' . $message . '</h1>
<p>你的请求因违反网站爬虫防护规则被拦截</p>
<p>拦截原因: ' . htmlspecialchars($reason) . '</p>
<p>IP: ' . htmlspecialchars($ip) . '</p>
</body>
</html>';
exit;
}
// 添加前端脚本以禁用右键和文本选择
add_action('wp_footer', 'anti_scraper_add_frontend_scripts');
function anti_scraper_add_frontend_scripts() {
// 不限制管理员
if (current_user_can('manage_options')) {
return;
}
$options = get_option('anti_scraper_options');
$script = '';
// 禁用右键菜单
if ($options['disable_right_click']) {
$script .= "
document.addEventListener('contextmenu', function(e) {
// 允许输入框/文本域右键
var target = e.target;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return;
}
e.preventDefault();
alert('本站禁止右键复制内容!'); // 友好提示
return false;
});
";
}
// 禁用文本选择
if ($options['disable_text_selection']) {
$script .= "
document.addEventListener('selectstart', function(e) {
var target = e.target;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return;
}
e.preventDefault();
return false;
});
document.addEventListener('mousedown', function(e) {
if (e.button === 0) { // 左键
var target = e.target;
if (target.tagName !== 'INPUT' && target.tagName !== 'TEXTAREA' && !target.isContentEditable) {
e.preventDefault();
return false;
}
}
});
";
}
if (!empty($script)) {
echo "<script type='text/javascript'>{$script}</script>";
}
}
// 添加CSS以增强文本选择防护
add_action('wp_head', 'anti_scraper_add_frontend_styles');
function anti_scraper_add_frontend_styles() {
// 不限制管理员
if (current_user_can('manage_options')) {
return;
}
$options = get_option('anti_scraper_options');
if ($options['disable_text_selection']) {
echo "
<style type='text/css'>
body {
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
input, textarea, [contenteditable='true'] {
-webkit-user-select: text !important;
-moz-user-select: text !important;
-ms-user-select: text !important;
user-select: text !important;
}
</style>
";
}
}
// 防止通过RSS Feed抓取完整内容
add_filter('the_content_feed', 'anti_scraper_restrict_feed_content');
function anti_scraper_restrict_feed_content($content) {
$options = get_option('anti_scraper_options');
// 只在启用了内容保护时生效
if ($options['block_common_bots'] || $options['disable_text_selection']) {
// 返回摘要而不是完整内容
return wp_trim_words($content, 50) . '... <p>查看完整内容请访问: <a href="' . get_permalink() . '">' . get_permalink() . '</a></p>';
}
return $content;
}