Skip to content

anti-scraper.php

Latest

Choose a tag to compare

@smmnn smmnn released this 22 Dec 09:03
68400f0
1, 'rate_limit' => 1, 'max_requests' => 30, 'time_window' => 60, 'disable_right_click' => 1, 'disable_text_selection' => 1, 'block_headless_browsers' => 1, 'custom_user_agents' => "example-bot\nbad-bot" ); // 仅在无配置时添加,避免覆盖已有设置 if (!get_option('anti_scraper_options')) { add_option('anti_scraper_options', $default_options); } } // 插件停用时的操作 register_deactivation_hook(__FILE__, 'anti_scraper_deactivate'); function anti_scraper_deactivate() { // 保留配置供下次使用,如需清理可取消注释 // delete_option('anti_scraper_options'); } // 添加管理菜单(中文) add_action('admin_menu', 'anti_scraper_add_admin_menu'); function anti_scraper_add_admin_menu() { add_options_page( '防爬虫设置', '防爬虫防护', 'manage_options', 'anti-scraper', 'anti_scraper_render_settings_page' ); } // 注册设置 add_action('admin_init', 'anti_scraper_register_settings'); function anti_scraper_register_settings() { register_setting('anti_scraper_group', 'anti_scraper_options', 'anti_scraper_sanitize_options'); } // 验证和清理设置 function anti_scraper_sanitize_options($input) { $output = array(); // 布尔值处理 $output['block_common_bots'] = isset($input['block_common_bots']) ? 1 : 0; $output['rate_limit'] = isset($input['rate_limit']) ? 1 : 0; $output['disable_right_click'] = isset($input['disable_right_click']) ? 1 : 0; $output['disable_text_selection'] = isset($input['disable_text_selection']) ? 1 : 0; $output['block_headless_browsers'] = isset($input['block_headless_browsers']) ? 1 : 0; // 数值范围限制 $output['max_requests'] = isset($input['max_requests']) ? intval($input['max_requests']) : 30; $output['max_requests'] = max(1, min(1000, $output['max_requests'])); $output['time_window'] = isset($input['time_window']) ? intval($input['time_window']) : 60; $output['time_window'] = max(10, min(3600, $output['time_window'])); // 文本域清洗 $output['custom_user_agents'] = isset($input['custom_user_agents']) ? sanitize_textarea_field($input['custom_user_agents']) : ''; return $output; } // 渲染设置页面(中文) function anti_scraper_render_settings_page() { if (!current_user_can('manage_options')) { wp_die(__('你没有足够的权限访问此页面。')); } $options = get_option('anti_scraper_options'); ?>
<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;

}