如何检测虚假用户(crawlers)和cURL

时间:2012-09-04 05:59:57

标签: php curl spam-prevention

其他一些网站使用cURL和假http引用来复制我的网站内容。 我们有没有办法检测cURL或不是真正的网络浏览器?

6 个答案:

答案 0 :(得分:96)

没有神奇的解决方案可以避免自动抓取。人类可以做到的一切,机器人也可以做到。只有解决方案才能让工作变得更加困难,只有强大的技术极客才能尝试通过它们。

几年前我也遇到了麻烦,我的第一个建议是,如果你有时间,自己做一个爬虫(我假设一个"爬行器"是抓住你网站的人),这就是这个主题最好的学校。通过抓取几个网站,我学会了不同类型的保护,通过关联它们,我一直很有效率。

我给你一些你可能尝试的保护措施的例子。


每个IP会话数

如果用户每分钟使用50个新会话,您可以认为此用户可能是不处理Cookie的抓取工具。当然,curl可以完美地管理cookie,但是如果你将它与每个会话的访问计数器结合起来(稍后解释),或者如果你的爬虫是一个有cookie的noobie,它可能是有效的。

很难想象同一个共享连接中的50个人会同时在您的网站上获得(当然这取决于您的流量,这取决于您)。如果发生这种情况,您可以锁定网站的页面,直到填写验证码。

想法:

1)你创建了2个表:1表示保存禁止的ips,1表示保存ip和会话

create table if not exists sessions_per_ip (
  ip int unsigned,
  session_id varchar(32),
  creation timestamp default current_timestamp,
  primary key(ip, session_id)
);

create table if not exists banned_ips (
  ip int unsigned,
  creation timestamp default current_timestamp,
  primary key(ip)
);

2)在脚本开头,删除两个表中过旧的条目

3)接下来你检查你的用户的ip是否被禁止(你将标志设置为true)

4)如果没有,你可以计算他的ip会话数量

5)如果他有太多会话,你将它插入禁止的表并设置一个标志

6)如果尚未插入会话,则将ip插入每个ip表的会话

我写了一个代码示例,以更好的方式展示我的想法。

<?php

try
{

    // Some configuration (small values for demo)
    $max_sessions = 5; // 5 sessions/ip simultaneousely allowed
    $check_duration = 30; // 30 secs max lifetime of an ip on the sessions_per_ip table
    $lock_duration = 60; // time to lock your website for this ip if max_sessions is reached

    // Mysql connection
    require_once("config.php");
    $dbh = new PDO("mysql:host={$host};dbname={$base}", $user, $password);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Delete old entries in tables
    $query = "delete from sessions_per_ip where timestampdiff(second, creation, now()) > {$check_duration}";
    $dbh->exec($query);

    $query = "delete from banned_ips where timestampdiff(second, creation, now()) > {$lock_duration}";
    $dbh->exec($query);

    // Get useful info attached to our user...
    session_start();
    $ip = ip2long($_SERVER['REMOTE_ADDR']);
    $session_id = session_id();

    // Check if IP is already banned
    $banned = false;
    $count = $dbh->query("select count(*) from banned_ips where ip = '{$ip}'")->fetchColumn();
    if ($count > 0)
    {
        $banned = true;
    }
    else
    {
        // Count entries in our db for this ip
        $query = "select count(*)  from sessions_per_ip where ip = '{$ip}'";
        $count = $dbh->query($query)->fetchColumn();
        if ($count >= $max_sessions)
        {
            // Lock website for this ip
            $query = "insert ignore into banned_ips ( ip ) values ( '{$ip}' )";
            $dbh->exec($query);
            $banned = true;
        }

        // Insert a new entry on our db if user's session is not already recorded
        $query = "insert ignore into sessions_per_ip ( ip, session_id ) values ('{$ip}', '{$session_id}')";
        $dbh->exec($query);
    }

    // At this point you have a $banned if your user is banned or not.
    // The following code will allow us to test it...

    // We do not display anything now because we'll play with sessions :
    // to make the demo more readable I prefer going step by step like
    // this.
    ob_start();

    // Displays your current sessions
    echo "Your current sessions keys are : <br/>";
    $query = "select session_id from sessions_per_ip where ip = '{$ip}'";
    foreach ($dbh->query($query) as $row) {
        echo "{$row['session_id']}<br/>";
    }

    // Display and handle a way to create new sessions
    echo str_repeat('<br/>', 2);
    echo '<a href="' . basename(__FILE__) . '?new=1">Create a new session / reload</a>';
    if (isset($_GET['new']))
    {
        session_regenerate_id();
        session_destroy();
        header("Location: " . basename(__FILE__));
        die();
    }

    // Display if you're banned or not
    echo str_repeat('<br/>', 2);
    if ($banned)
    {
        echo '<span style="color:red;">You are banned: wait 60secs to be unbanned... a captcha must be more friendly of course!</span>';
        echo '<br/>';
        echo '<img src="http://4.bp.blogspot.com/-PezlYVgEEvg/TadW2e4OyHI/AAAAAAAAAAg/QHZPVQcBNeg/s1600/feu-rouge.png" />';
    }
    else
    {
        echo '<span style="color:blue;">You are not banned!</span>';
        echo '<br/>';
        echo '<img src="http://identityspecialist.files.wordpress.com/2010/06/traffic_light_green.png" />';
    }
    ob_end_flush();
}
catch (PDOException $e)
{
    /*echo*/ $e->getMessage();
}

?>

访问计数器

如果您的用户使用相同的Cookie抓取您的网页,您将能够使用他的会话来阻止它。这个想法非常简单:您的用户可能在60秒内访问60页吗?

想法:

  1. 在用户会话中创建一个数组,它将包含visit time()s。
  2. 删除此阵列中超过X秒的访问次数
  3. 为实际访问添加新条目
  4. 计算此数组中的条目
  5. 如果用户访问了Y页
  6. ,请将其禁止

    示例代码:

    <?php
    
    $visit_counter_pages = 5; // maximum number of pages to load
    $visit_counter_secs = 10; // maximum amount of time before cleaning visits
    
    session_start();
    
    // initialize an array for our visit counter
    if (array_key_exists('visit_counter', $_SESSION) == false)
    {
        $_SESSION['visit_counter'] = array();
    }
    
    // clean old visits
    foreach ($_SESSION['visit_counter'] as $key => $time)
    {
        if ((time() - $time) > $visit_counter_secs) {
            unset($_SESSION['visit_counter'][$key]);
        }
    }
    
    // we add the current visit into our array
    $_SESSION['visit_counter'][] = time();
    
    // check if user has reached limit of visited pages
    $banned = false;
    if (count($_SESSION['visit_counter']) > $visit_counter_pages)
    {
        // puts ip of our user on the same "banned table" as earlier...
        $banned = true;
    }
    
    // At this point you have a $banned if your user is banned or not.
    // The following code will allow us to test it...
    
    echo '<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>';
    
    // Display counter
    $count = count($_SESSION['visit_counter']);
    echo "You visited {$count} pages.";
    echo str_repeat('<br/>', 2);
    
    echo <<< EOT
    
    <a id="reload" href="#">Reload</a>
    
    <script type="text/javascript">
    
      $('#reload').click(function(e) {
        e.preventDefault();
        window.location.reload();
      });
    
    </script>
    
    EOT;
    
    echo str_repeat('<br/>', 2);
    
    // Display if you're banned or not
    echo str_repeat('<br/>', 2);
    if ($banned)
    {
        echo '<span style="color:red;">You are banned! Wait for a short while (10 secs in this demo)...</span>';
        echo '<br/>';
        echo '<img src="http://4.bp.blogspot.com/-PezlYVgEEvg/TadW2e4OyHI/AAAAAAAAAAg/QHZPVQcBNeg/s1600/feu-rouge.png" />';
    }
    else
    {
        echo '<span style="color:blue;">You are not banned!</span>';
        echo '<br/>';
        echo '<img src="http://identityspecialist.files.wordpress.com/2010/06/traffic_light_green.png" />';
    }
    ?>
    

    要下载的图像

    当爬虫需要做他的肮脏工作时,这是为了获取大量数据,并且在最短的时间内完成。这就是他们不在页面上下载图像的原因;它需要太多的带宽并使爬行速度变慢。

    这个想法(我认为最优雅,最容易实现)使用mod_rewrite隐藏.jpg / .png / ...图像文件中的代码。此图像应该在您要保护的每个页面上可用:它可能是您的徽标网站,但您将选择一个小尺寸的图像(因为此图像不得缓存)。

    想法:

    1 /将这些行添加到.htaccess

    RewriteEngine On
    RewriteBase /tests/anticrawl/
    RewriteRule ^logo\.jpg$ logo.php
    

    2 /使用安全性

    创建logo.php
    <?php
    
    // start session and reset counter
    session_start();
    $_SESSION['no_logo_count'] = 0;
    
    // forces image to reload next time
    header("Cache-Control: no-store, no-cache, must-revalidate");
    
    // displays image
    header("Content-type: image/jpg");
    readfile("logo.jpg");
    die();
    

    3 /在您需要添加安全性的每个页面上增加no_logo_count,并检查它是否达到了您的限制。

    示例代码:

    <?php
    
    $no_logo_limit = 5; // number of allowd pages without logo
    
    // start session and initialize
    session_start();
    if (array_key_exists('no_logo_count', $_SESSION) == false)
    {
        $_SESSION['no_logo_count'] = 0;
    }
    else
    {
        $_SESSION['no_logo_count']++;
    }
    
    // check if user has reached limit of "undownloaded image"
    $banned = false;
    if ($_SESSION['no_logo_count'] >= $no_logo_limit)
    {
        // puts ip of our user on the same "banned table" as earlier...
        $banned = true;
    }
    
    // At this point you have a $banned if your user is banned or not.
    // The following code will allow us to test it...
    
    echo '<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>';
    
    // Display counter
    echo "You did not loaded image {$_SESSION['no_logo_count']} times.";
    echo str_repeat('<br/>', 2);
    
    // Display "reload" link
    echo <<< EOT
    
    <a id="reload" href="#">Reload</a>
    
    <script type="text/javascript">
    
      $('#reload').click(function(e) {
        e.preventDefault();
        window.location.reload();
      });
    
    </script>
    
    EOT;
    
    echo str_repeat('<br/>', 2);
    
    // Display "show image" link : note that we're using .jpg file
    echo <<< EOT
    
    <div id="image_container">
        <a id="image_load" href="#">Load image</a>
    </div>
    <br/>
    
    <script type="text/javascript">
    
      // On your implementation, you'llO of course use <img src="logo.jpg" />
      $('#image_load').click(function(e) {
        e.preventDefault();
        $('#image_load').html('<img src="logo.jpg" />');
      });
    
    </script>
    
    EOT;
    
    // Display if you're banned or not
    echo str_repeat('<br/>', 2);
    if ($banned)
    {
        echo '<span style="color:red;">You are banned: click on "load image" and reload...</span>';
        echo '<br/>';
        echo '<img src="http://4.bp.blogspot.com/-PezlYVgEEvg/TadW2e4OyHI/AAAAAAAAAAg/QHZPVQcBNeg/s1600/feu-rouge.png" />';
    }
    else
    {
        echo '<span style="color:blue;">You are not banned!</span>';
        echo '<br/>';
        echo '<img src="http://identityspecialist.files.wordpress.com/2010/06/traffic_light_green.png" />';
    }
    ?>
    

    Cookie检查

    您可以在javascript端创建Cookie,以检查您的用户是否确实解释了javascript(例如,使用Curl的抓取工具)。

    这个想法非常简单:这与图像检查大致相同。

    1. 将$ _SESSION值设置为1并在每次访问中增加它
    2. 如果确实存在cookie(在JavaScript中设置),请将会话值设置为0
    3. 如果此值达到限制,请禁止您的用户
    4. 代码:

      <?php
      
      $no_cookie_limit = 5; // number of allowd pages without cookie set check
      
      // Start session and reset counter
      session_start();
      
      if (array_key_exists('cookie_check_count', $_SESSION) == false)
      {
          $_SESSION['cookie_check_count'] = 0;
      }
      
      // Initializes cookie (note: rename it to a more discrete name of course) or check cookie value
      if ((array_key_exists('cookie_check', $_COOKIE) == false) || ($_COOKIE['cookie_check'] != 42))
      {
          // Cookie does not exist or is incorrect...
          $_SESSION['cookie_check_count']++;
      }
      else
      {
          // Cookie is properly set so we reset counter
          $_SESSION['cookie_check_count'] = 0;
      }
      
      // Check if user has reached limit of "cookie check"
      $banned = false;
      if ($_SESSION['cookie_check_count'] >= $no_cookie_limit)
      {
          // puts ip of our user on the same "banned table" as earlier...
          $banned = true;
      }
      
      // At this point you have a $banned if your user is banned or not.
      // The following code will allow us to test it...
      
      echo '<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>';
      
      // Display counter
      echo "Cookie check failed {$_SESSION['cookie_check_count']} times.";
      echo str_repeat('<br/>', 2);
      
      // Display "reload" link
      echo <<< EOT
      
      <br/>
      <a id="reload" href="#">Reload</a>
      <br/>
      
      <script type="text/javascript">
      
        $('#reload').click(function(e) {
          e.preventDefault();
          window.location.reload();
        });
      
      </script>
      
      EOT;
      
      // Display "set cookie" link
      echo <<< EOT
      
      <br/>
      <a id="cookie_link" href="#">Set cookie</a>
      <br/>
      
      <script type="text/javascript">
      
        // On your implementation, you'll of course put the cookie set on a $(document).ready()
        $('#cookie_link').click(function(e) {
          e.preventDefault();
          var expires = new Date();
          expires.setTime(new Date().getTime() + 3600000);
          document.cookie="cookie_check=42;expires=" + expires.toGMTString();
        });
      
      </script>
      EOT;
      
      
      // Display "unset cookie" link
      echo <<< EOT
      
      <br/>
      <a id="unset_cookie" href="#">Unset cookie</a>
      <br/>
      
      <script type="text/javascript">
      
        // On your implementation, you'll of course put the cookie set on a $(document).ready()
        $('#unset_cookie').click(function(e) {
          e.preventDefault();
          document.cookie="cookie_check=;expires=Thu, 01 Jan 1970 00:00:01 GMT";
        });
      
      </script>
      EOT;
      
      // Display if you're banned or not
      echo str_repeat('<br/>', 2);
      if ($banned)
      {
          echo '<span style="color:red;">You are banned: click on "Set cookie" and reload...</span>';
          echo '<br/>';
          echo '<img src="http://4.bp.blogspot.com/-PezlYVgEEvg/TadW2e4OyHI/AAAAAAAAAAg/QHZPVQcBNeg/s1600/feu-rouge.png" />';
      }
      else
      {
          echo '<span style="color:blue;">You are not banned!</span>';
          echo '<br/>';
          echo '<img src="http://identityspecialist.files.wordpress.com/2010/06/traffic_light_green.png" />';
      }
      

      代理保护

      关于我们可能在网络上找到的不同类型代理的一些说法:

      • “普通”代理显示有关用户连接的信息(特别是他的IP)
      • 匿名代理不显示IP,但提供有关标头上代理使用情况的信息。
      • 高级匿名代理不显示用户IP,也不显示浏览器可能不发送的任何信息。

      很容易找到连接任何网站的代理,但很难找到高匿名代理。

      如果您的用户位于代理后面,某些$ _SERVER变量可能包含密钥(详尽列表来自this question):

      • CLIENT_IP
      • 转发
      • FORWARDED_FOR
      • FORWARDED_FOR_IP
      • HTTP_CLIENT_IP
      • HTTP_FORWARDED
      • HTTP_FORWARDED_FOR
      • HTTP_FORWARDED_FOR_IP
      • HTTP_PC_REMOTE_ADDR
      • HTTP_PROXY_CONNECTION&#39;
      • HTTP_VIA
      • HTTP_X_FORWARDED
      • HTTP_X_FORWARDED_FOR
      • HTTP_X_FORWARDED_FOR_IP
      • HTTP_X_IMFORWARDS
      • HTTP_XROXY_CONNECTION
      • VIA
      • X_FORWARDED
      • X_FORWARDED_FOR

      如果您在$_SERVER变量上检测到其中一个键,则可以为反爬行证券提供不同的行为(下限等)。


      结论

      有很多方法可以检测您网站上的滥用行为,因此您肯定会找到解决方案。但是你需要确切地知道你的网站是如何被使用的,所以你的证券不会对你的正常情况产生侵略性。用户。

答案 1 :(得分:2)

记住:HTTP并不神奇。每个HTTP请求都会发送一组已定义的标头;如果这些标题是由网络浏览器发送的,它们也可以由任何程序发送 - 包括cURL(和libcurl)。

有些人认为这是一个诅咒,但另一方面,这是一种祝福,因为它极大地简化了Web应用程序的功能测试。

更新:正如unr3al011正确地注意到的那样,curl不会执行JavaScript,所以理论上可以创建一个页面,当抓取器查看时它的行为会有所不同(例如,设置和稍后,通过JS检查特定的cookie意味着。)

尽管如此,这仍然是一个非常脆弱的防守。页面的数据仍然必须从服务器中获取 - 这个HTTP请求(以及它的总是 HTTP请求)可以通过curl模拟。检查this answer,了解如何打败这种防御。

...我甚至没有提到一些抓取器 能够执行JavaScript。 )

答案 2 :(得分:0)

避免虚假引用者的方法是跟踪用户

您可以通过以下一种或多种方法跟踪用户:

  1. 使用一些特殊代码(例如:访问过的最后一个网址,时间戳)在浏览器客户端中保存一个cookie,并在服务器的每个响应中对其进行验证。

  2. 与之前相同,但使用会话而非显式Cookie

  3. 对于cookie,您应该添加加密安全性,如。

    [Cookie]
    url => http://someurl/
    hash => dsafdshfdslajfd
    

    hash以这种方式在PHP中计算

    $url = $_COOKIE['url'];
    $hash = $_COOKIE['hash'];
    $secret = 'This is a fixed secret in the code of your application';
    
    $isValidCookie = (hash('algo', $secret . $url) === $hash);
    
    $isValidReferer = $isValidCookie & ($_SERVER['HTTP_REFERER'] === $url)
    

答案 3 :(得分:0)

您可以通过以下方法检测cURL-Useragent。但是请注意,用户可以覆盖useragent,无论如何默认设置可以通过以下方式识别:

help_patch_cmd()

答案 4 :(得分:-1)

正如有些人提到的,cURL无法执行JavaScritp(据我所知),所以你可以尝试设置一些像raina77ow建议的东西,但那不会为其他抓取器/下载者而烦恼。

我建议您尝试构建bot trap,以便处理可以执行JavaScript的抓取器/下载器。

我不知道任何一种完全阻止这种情况的解决方案,所以我最好的建议是尝试多种解决方案:

1)只允许已知的用户代理,例如.htaccess文件中的所有主流浏览器

2)设置robots.txt以防止机器人

3)为不遵守robots.txt文件的机器人设置机器人陷阱

答案 5 :(得分:-3)

将其作为.htaccess文件放入根文件夹中。它可能有所帮助。我在一个虚拟主机提供商网站上找到了它,但不知道这意味着什么:)

SetEnvIf User-Agent ^Teleport graber   
SetEnvIf User-Agent ^w3m graber    
SetEnvIf User-Agent ^Offline graber   
SetEnvIf User-Agent Downloader graber  
SetEnvIf User-Agent snake graber  
SetEnvIf User-Agent Xenu graber   
Deny from env=graber