home *** CD-ROM | disk | FTP | other *** search
/ CLIX - Fazer Clix Custa Nix / CLIX-CD.cdr / mac / lib / LWP / RobotUA.pm < prev    next >
Text File  |  1997-11-18  |  6KB  |  271 lines

  1. # $Id: RobotUA.pm,v 1.1 1997/11/18 00:33:08 neeri Exp $
  2.  
  3. package LWP::RobotUA;
  4.  
  5. require LWP::UserAgent;
  6. @ISA = qw(LWP::UserAgent);
  7.  
  8. require WWW::RobotRules;
  9. require HTTP::Request;
  10. require HTTP::Response;
  11.  
  12. use Carp ();
  13. use LWP::Debug ();
  14. use HTTP::Status ();
  15. use HTTP::Date qw(time2str);
  16. use strict;
  17.  
  18. =head1 NAME
  19.  
  20. LWP::RobotUA - A class for Web Robots
  21.  
  22. =head1 SYNOPSIS
  23.  
  24.   require LWP::RobotUA;
  25.   $ua = new LWP::RobotUA 'my-robot/0.1', 'me@foo.com';
  26.   $ua->delay(10);  # be very nice, go slowly
  27.   ...
  28.   # just use it just like a normal LWP::UserAgent
  29.   $res = $ua->request($req);
  30.  
  31. =head1 DESCRIPTION
  32.  
  33. This class implements a user agent that is suitable for robot
  34. applications.  Robots should be nice to the servers they visit.  They
  35. should consult the F<robots.txt> file to ensure that they are welcomed
  36. and they should not send too frequent requests.
  37.  
  38. But, before you consider writing a robot take a look at
  39. <URL:http://info.webcrawler.com/mak/projects/robots/robots.html>.
  40.  
  41. When you use a I<LWP::RobotUA> as your user agent, then you do not
  42. really have to think about these things yourself.  Just send requests
  43. as you do when you are using a normal I<LWP::UserAgent> and this
  44. special agent will make sure you are nice.
  45.  
  46. =head1 METHODS
  47.  
  48. The LWP::RobotUA is a sub-class of LWP::UserAgent and implements the
  49. same methods.  The use_alarm() method also desides whether we will
  50. wait if a request is tried too early (if true), or will return an error
  51. response (if false).
  52.  
  53. In addition these methods are provided:
  54.  
  55. =cut
  56.  
  57.  
  58. #
  59. # Additional attributes in addition to those found in LWP::UserAgent:
  60. #
  61. # $self->{'delay'}    Required delay between request to the same
  62. #                     server in minutes.
  63. #
  64. # $self->{'rules'}     A WWW::RobotRules object
  65. #
  66.  
  67.  
  68. =head2 $ua = LWP::RobotUA->new($agent_name, $from, [$rules])
  69.  
  70. Your robot's name and the mail address of the human responsible for
  71. the robot (i.e. you) is required by the constructor.
  72.  
  73. Optionally it allows you to specify the I<WWW::RobotRules> object to
  74. use.
  75.  
  76. =cut
  77.  
  78. sub new
  79. {
  80.     my($class,$name,$from,$rules) = @_;
  81.  
  82.     Carp::croak('LWP::RobotUA name required') unless $name;
  83.     Carp::croak('LWP::RobotUA from address required') unless $from;
  84.  
  85.     my $self = new LWP::UserAgent;
  86.     $self = bless $self, $class;
  87.  
  88.     $self->{'delay'} = 1;   # minutes
  89.     $self->{'agent'} = $name;
  90.     $self->{'from'}  = $from;
  91.  
  92.     if ($rules) {
  93.     $rules->agent($name);
  94.     $self->{'rules'} = $rules;
  95.     } else {
  96.     $self->{'rules'} = new WWW::RobotRules $name;
  97.     }
  98.  
  99.     $self;
  100. }
  101.  
  102.  
  103. =head2 $ua->delay([$minutes])
  104.  
  105. Set the minimum delay between requests to the same server.  The
  106. default is 1 minute.
  107.  
  108. =cut
  109.  
  110. sub delay { shift->_elem('delay', @_); }
  111.  
  112.  
  113. sub agent
  114. {
  115.     my $self = shift;
  116.     my $old = $self->SUPER::agent(@_);
  117.     if (@_) {
  118.     # Changing our name means to start fresh
  119.     $self->{'rules'}->agent($self->{'agent'}); 
  120.     }
  121.     $old;
  122. }
  123.  
  124.  
  125. =head2 $ua->rules([$rules])
  126.  
  127. Set/get which I<WWW::RobotRules> object to use. 
  128.  
  129. =cut
  130.  
  131. sub rules {
  132.     my $self = shift;
  133.     my $old = $self->_elem('rules', @_);
  134.     $self->{'rules'}->agent($self->{'agent'}) if @_;
  135.     $old;
  136. }
  137.  
  138.  
  139. =head2 $ua->no_visits($netloc)
  140.  
  141. Returns the number of documents fetched from this server host.
  142.  
  143. =cut
  144.  
  145. sub no_visits
  146. {
  147.     my($self, $netloc) = @_;
  148.     $self->{'rules'}->no_visits($netloc);
  149. }
  150.  
  151. *host_count = \&no_visits;  # backwards compatibility with LWP-5.02
  152.  
  153.  
  154. =head2 $ua->host_wait($netloc)
  155.  
  156. Returns the number of seconds you must wait before you can make a new
  157. request to this host.
  158.  
  159. =cut
  160.  
  161. sub host_wait
  162. {
  163.     my($self, $netloc) = @_;
  164.     return undef unless defined $netloc;
  165.     my $last = $self->{'rules'}->last_visit($netloc);
  166.     if ($last) {
  167.     my $wait = int($self->{'delay'} * 60 - (time - $last));
  168.     $wait = 0 if $wait < 0;
  169.     return $wait;
  170.     }
  171.     return 0;
  172. }
  173.  
  174.  
  175. sub simple_request
  176. {
  177.     my($self, $request, $arg, $size) = @_;
  178.  
  179.     LWP::Debug::trace('()');
  180.  
  181.     # Do we try to access a new server?
  182.     my $allowed = $self->{'rules'}->allowed($request->url);
  183.  
  184.     if ($allowed < 0) {
  185.     LWP::Debug::debug("Host is not visited before, or robots.txt expired.");
  186.     # fetch "robots.txt"
  187.     my $robot_url = $request->url->clone;
  188.     $robot_url->path("robots.txt");
  189.     $robot_url->params(undef);
  190.     $robot_url->query(undef);
  191.     LWP::Debug::debug("Requesting $robot_url");
  192.  
  193.     # make access to robot.txt legal since this will be a recursive call
  194.     $self->{'rules'}->parse($robot_url, ""); 
  195.  
  196.     my $robot_req = new HTTP::Request 'GET', $robot_url;
  197.     my $robot_res = $self->request($robot_req);
  198.     my $fresh_until = $robot_res->fresh_until;
  199.     if ($robot_res->is_success) {
  200.         LWP::Debug::debug("Parsing robot rules");
  201.         $self->{'rules'}->parse($robot_url, $robot_res->content, 
  202.                     $fresh_until);
  203.     } else {
  204.         LWP::Debug::debug("No robots.txt file found");
  205.         $self->{'rules'}->parse($robot_url, "", $fresh_until);
  206.     }
  207.  
  208.     # recalculate allowed...
  209.     $allowed = $self->{'rules'}->allowed($request->url);
  210.     }
  211.  
  212.     # Check rules
  213.     unless ($allowed) {
  214.     return new HTTP::Response
  215.       &HTTP::Status::RC_FORBIDDEN, 'Forbidden by robots.txt';
  216.     }
  217.  
  218.     my $netloc = $request->url->netloc;
  219.     my $wait = $self->host_wait($netloc);
  220.  
  221.     if ($wait) {
  222.     LWP::Debug::debug("Must wait $wait seconds");
  223.     if ($self->{'use_alarm'}) {
  224.         sleep($wait)
  225.     } else {
  226.         my $res = new HTTP::Response
  227.           &HTTP::Status::RC_SERVICE_UNAVAILABLE, 'Please, slow down';
  228.         $res->header('Retry-After', time2str(time + $wait));
  229.         return $res;
  230.     }
  231.     }
  232.  
  233.     # Perform the request
  234.     my $res = $self->SUPER::simple_request($request, $arg, $size);
  235.  
  236.     $self->{'rules'}->visit($netloc);
  237.  
  238.     $res;
  239. }
  240.  
  241.  
  242. =head2 $ua->as_string
  243.  
  244. Returns a text that describe the state of the UA.
  245. Mainly useful for debugging.
  246.  
  247. =cut
  248.  
  249. sub as_string
  250. {
  251.     my $self = shift;
  252.     my @s;
  253.     push(@s, "Robot: $self->{'agent'} operated by $self->{'from'}  [$self]");
  254.     push(@s, "    Minimum delay: " . int($self->{'delay'}*60) . "s");
  255.     push(@s, "    Will sleep if too early") if $self->{'use_alarm'};
  256.     push(@s, "    Rules = $self->{'rules'}");
  257.     join("\n", @s, '');
  258. }
  259.  
  260. 1;
  261.  
  262. =head1 SEE ALSO
  263.  
  264. L<LWP::UserAgent>, L<WWW::RobotRules>
  265.  
  266. =head1 AUTHOR
  267.  
  268. Gisle Aas E<lt>aas@sn.no>
  269.  
  270. =cut
  271.