home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 June / PCWorld_2005-06_cd.bin / software / vyzkuste / firewally / firewally.exe / framework-2.3.exe / robotparser.py < prev    next >
Text File  |  2003-12-30  |  10KB  |  293 lines

  1. """ robotparser.py
  2.  
  3.     Copyright (C) 2000  Bastian Kleineidam
  4.  
  5.     You can choose between two licenses when using this package:
  6.     1) GNU GPLv2
  7.     2) PSF license for Python 2.2
  8.  
  9.     The robots.txt Exclusion Protocol is implemented as specified in
  10.     http://info.webcrawler.com/mak/projects/robots/norobots-rfc.html
  11. """
  12. import urlparse,urllib
  13.  
  14. __all__ = ["RobotFileParser"]
  15.  
  16. debug = 0
  17.  
  18. def _debug(msg):
  19.     if debug: print msg
  20.  
  21.  
  22. class RobotFileParser:
  23.     """ This class provides a set of methods to read, parse and answer
  24.     questions about a single robots.txt file.
  25.  
  26.     """
  27.  
  28.     def __init__(self, url=''):
  29.         self.entries = []
  30.         self.default_entry = None
  31.         self.disallow_all = 0
  32.         self.allow_all = 0
  33.         self.set_url(url)
  34.         self.last_checked = 0
  35.  
  36.     def mtime(self):
  37.         """Returns the time the robots.txt file was last fetched.
  38.  
  39.         This is useful for long-running web spiders that need to
  40.         check for new robots.txt files periodically.
  41.  
  42.         """
  43.         return self.last_checked
  44.  
  45.     def modified(self):
  46.         """Sets the time the robots.txt file was last fetched to the
  47.         current time.
  48.  
  49.         """
  50.         import time
  51.         self.last_checked = time.time()
  52.  
  53.     def set_url(self, url):
  54.         """Sets the URL referring to a robots.txt file."""
  55.         self.url = url
  56.         self.host, self.path = urlparse.urlparse(url)[1:3]
  57.  
  58.     def read(self):
  59.         """Reads the robots.txt URL and feeds it to the parser."""
  60.         opener = URLopener()
  61.         f = opener.open(self.url)
  62.         lines = []
  63.         line = f.readline()
  64.         while line:
  65.             lines.append(line.strip())
  66.             line = f.readline()
  67.         self.errcode = opener.errcode
  68.         if self.errcode == 401 or self.errcode == 403:
  69.             self.disallow_all = 1
  70.             _debug("disallow all")
  71.         elif self.errcode >= 400:
  72.             self.allow_all = 1
  73.             _debug("allow all")
  74.         elif self.errcode == 200 and lines:
  75.             _debug("parse lines")
  76.             self.parse(lines)
  77.  
  78.     def _add_entry(self, entry):
  79.         if "*" in entry.useragents:
  80.             # the default entry is considered last
  81.             self.default_entry = entry
  82.         else:
  83.             self.entries.append(entry)
  84.  
  85.     def parse(self, lines):
  86.         """parse the input lines from a robot.txt file.
  87.            We allow that a user-agent: line is not preceded by
  88.            one or more blank lines."""
  89.         state = 0
  90.         linenumber = 0
  91.         entry = Entry()
  92.  
  93.         for line in lines:
  94.             linenumber = linenumber + 1
  95.             if not line:
  96.                 if state==1:
  97.                     _debug("line %d: warning: you should insert"
  98.                            " allow: or disallow: directives below any"
  99.                            " user-agent: line" % linenumber)
  100.                     entry = Entry()
  101.                     state = 0
  102.                 elif state==2:
  103.                     self._add_entry(entry)
  104.                     entry = Entry()
  105.                     state = 0
  106.             # remove optional comment and strip line
  107.             i = line.find('#')
  108.             if i>=0:
  109.                 line = line[:i]
  110.             line = line.strip()
  111.             if not line:
  112.                 continue
  113.             line = line.split(':', 1)
  114.             if len(line) == 2:
  115.                 line[0] = line[0].strip().lower()
  116.                 line[1] = urllib.unquote(line[1].strip())
  117.                 if line[0] == "user-agent":
  118.                     if state==2:
  119.                         _debug("line %d: warning: you should insert a blank"
  120.                                " line before any user-agent"
  121.                                " directive" % linenumber)
  122.                         self._add_entry(entry)
  123.                         entry = Entry()
  124.                     entry.useragents.append(line[1])
  125.                     state = 1
  126.                 elif line[0] == "disallow":
  127.                     if state==0:
  128.                         _debug("line %d: error: you must insert a user-agent:"
  129.                                " directive before this line" % linenumber)
  130.                     else:
  131.                         entry.rulelines.append(RuleLine(line[1], 0))
  132.                         state = 2
  133.                 elif line[0] == "allow":
  134.                     if state==0:
  135.                         _debug("line %d: error: you must insert a user-agent:"
  136.                                " directive before this line" % linenumber)
  137.                     else:
  138.                         entry.rulelines.append(RuleLine(line[1], 1))
  139.                 else:
  140.                     _debug("line %d: warning: unknown key %s" % (linenumber,
  141.                                line[0]))
  142.             else:
  143.                 _debug("line %d: error: malformed line %s"%(linenumber, line))
  144.         if state==2:
  145.             self.entries.append(entry)
  146.         _debug("Parsed rules:\n%s" % str(self))
  147.  
  148.  
  149.     def can_fetch(self, useragent, url):
  150.         """using the parsed robots.txt decide if useragent can fetch url"""
  151.         _debug("Checking robot.txt allowance for:\n  user agent: %s\n  url: %s" %
  152.                (useragent, url))
  153.         if self.disallow_all:
  154.             return False
  155.         if self.allow_all:
  156.             return True
  157.         # search for given user agent matches
  158.         # the first match counts
  159.         url = urllib.quote(urlparse.urlparse(urllib.unquote(url))[2]) or "/"
  160.         for entry in self.entries:
  161.             if entry.applies_to(useragent):
  162.                 return entry.allowance(url)
  163.         # try the default entry last
  164.         if self.default_entry:
  165.             return self.default_entry.allowance(url)
  166.         # agent not found ==> access granted
  167.         return True
  168.  
  169.  
  170.     def __str__(self):
  171.         ret = ""
  172.         for entry in self.entries:
  173.             ret = ret + str(entry) + "\n"
  174.         return ret
  175.  
  176.  
  177. class RuleLine:
  178.     """A rule line is a single "Allow:" (allowance==1) or "Disallow:"
  179.        (allowance==0) followed by a path."""
  180.     def __init__(self, path, allowance):
  181.         if path == '' and not allowance:
  182.             # an empty value means allow all
  183.             allowance = 1
  184.         self.path = urllib.quote(path)
  185.         self.allowance = allowance
  186.  
  187.     def applies_to(self, filename):
  188.         return self.path=="*" or filename.startswith(self.path)
  189.  
  190.     def __str__(self):
  191.         return (self.allowance and "Allow" or "Disallow")+": "+self.path
  192.  
  193.  
  194. class Entry:
  195.     """An entry has one or more user-agents and zero or more rulelines"""
  196.     def __init__(self):
  197.         self.useragents = []
  198.         self.rulelines = []
  199.  
  200.     def __str__(self):
  201.         ret = ""
  202.         for agent in self.useragents:
  203.             ret = ret + "User-agent: "+agent+"\n"
  204.         for line in self.rulelines:
  205.             ret = ret + str(line) + "\n"
  206.         return ret
  207.  
  208.     def applies_to(self, useragent):
  209.         """check if this entry applies to the specified agent"""
  210.         # split the name token and make it lower case
  211.         useragent = useragent.split("/")[0].lower()
  212.         for agent in self.useragents:
  213.             if agent=='*':
  214.                 # we have the catch-all agent
  215.                 return True
  216.             agent = agent.lower()
  217.             if useragent.find(agent) != -1:
  218.                 return True
  219.         return False
  220.  
  221.     def allowance(self, filename):
  222.         """Preconditions:
  223.         - our agent applies to this entry
  224.         - filename is URL decoded"""
  225.         for line in self.rulelines:
  226.             _debug((filename, str(line), line.allowance))
  227.             if line.applies_to(filename):
  228.                 return line.allowance
  229.         return 1
  230.  
  231. class URLopener(urllib.FancyURLopener):
  232.     def __init__(self, *args):
  233.         urllib.FancyURLopener.__init__(self, *args)
  234.         self.errcode = 200
  235.  
  236.     def http_error_default(self, url, fp, errcode, errmsg, headers):
  237.         self.errcode = errcode
  238.         return urllib.FancyURLopener.http_error_default(self, url, fp, errcode,
  239.                                                         errmsg, headers)
  240.  
  241. def _check(a,b):
  242.     if not b:
  243.         ac = "access denied"
  244.     else:
  245.         ac = "access allowed"
  246.     if a!=b:
  247.         print "failed"
  248.     else:
  249.         print "ok (%s)" % ac
  250.     print
  251.  
  252. def _test():
  253.     global debug
  254.     rp = RobotFileParser()
  255.     debug = 1
  256.  
  257.     # robots.txt that exists, gotten to by redirection
  258.     rp.set_url('http://www.musi-cal.com/robots.txt')
  259.     rp.read()
  260.  
  261.     # test for re.escape
  262.     _check(rp.can_fetch('*', 'http://www.musi-cal.com/'), 1)
  263.     # this should match the first rule, which is a disallow
  264.     _check(rp.can_fetch('', 'http://www.musi-cal.com/'), 0)
  265.     # various cherry pickers
  266.     _check(rp.can_fetch('CherryPickerSE',
  267.                        'http://www.musi-cal.com/cgi-bin/event-search'
  268.                        '?city=San+Francisco'), 0)
  269.     _check(rp.can_fetch('CherryPickerSE/1.0',
  270.                        'http://www.musi-cal.com/cgi-bin/event-search'
  271.                        '?city=San+Francisco'), 0)
  272.     _check(rp.can_fetch('CherryPickerSE/1.5',
  273.                        'http://www.musi-cal.com/cgi-bin/event-search'
  274.                        '?city=San+Francisco'), 0)
  275.     # case sensitivity
  276.     _check(rp.can_fetch('ExtractorPro', 'http://www.musi-cal.com/blubba'), 0)
  277.     _check(rp.can_fetch('extractorpro', 'http://www.musi-cal.com/blubba'), 0)
  278.     # substring test
  279.     _check(rp.can_fetch('toolpak/1.1', 'http://www.musi-cal.com/blubba'), 0)
  280.     # tests for catch-all * agent
  281.     _check(rp.can_fetch('spam', 'http://www.musi-cal.com/search'), 0)
  282.     _check(rp.can_fetch('spam', 'http://www.musi-cal.com/Musician/me'), 1)
  283.     _check(rp.can_fetch('spam', 'http://www.musi-cal.com/'), 1)
  284.     _check(rp.can_fetch('spam', 'http://www.musi-cal.com/'), 1)
  285.  
  286.     # robots.txt that does not exist
  287.     rp.set_url('http://www.lycos.com/robots.txt')
  288.     rp.read()
  289.     _check(rp.can_fetch('Mozilla', 'http://www.lycos.com/search'), 1)
  290.  
  291. if __name__ == '__main__':
  292.     _test()
  293.