root/scrapy/contrib/downloadermiddleware/httpcache.py @ 1835:87eb02202c2b

Revision 1835:87eb02202c2b, 6.5 kB (checked in by Pablo Hoffman <pablo@…>, 10 months ago)

replaced remaining uses of log.msg() 'domain' argument to use 'spider' instead

Line 
1from __future__ import with_statement
2
3import errno
4import os
5import hashlib
6import datetime
7import cPickle as pickle
8from scrapy.xlib.pydispatch import dispatcher
9
10from scrapy.core import signals
11from scrapy import log
12from scrapy.http import Headers
13from scrapy.core.exceptions import NotConfigured, IgnoreRequest
14from scrapy.core.downloader.responsetypes import responsetypes
15from scrapy.utils.request import request_fingerprint
16from scrapy.utils.http import headers_dict_to_raw, headers_raw_to_dict
17from scrapy.utils.httpobj import urlparse_cached
18from scrapy.conf import settings
19
20
21class HttpCacheMiddleware(object):
22    def __init__(self):
23        if not settings['HTTPCACHE_DIR']:
24            raise NotConfigured
25        self.cache = Cache(settings['HTTPCACHE_DIR'], sectorize=settings.getbool('HTTPCACHE_SECTORIZE'))
26        self.ignore_missing = settings.getbool('HTTPCACHE_IGNORE_MISSING')
27        dispatcher.connect(self.open_domain, signal=signals.spider_opened)
28
29    def open_domain(self, spider):
30        self.cache.open_domain(spider.domain_name)
31
32    def process_request(self, request, spider):
33        if not is_cacheable(request):
34            return
35
36        key = request_fingerprint(request)
37        domain = spider.domain_name
38
39        try:
40            response = self.cache.retrieve_response(domain, key)
41        except:
42            log.msg("Corrupt cache for %s" % request.url, log.WARNING)
43            response = False
44
45        if response:
46            return response
47        elif self.ignore_missing:
48            raise IgnoreRequest("Ignored request not in cache: %s" % request)
49
50    def process_response(self, request, response, spider):
51        if is_cacheable(request):
52            key = request_fingerprint(request)
53            self.cache.store(spider.domain_name, key, request, response)
54
55        return response
56
57
58def is_cacheable(request):
59    return urlparse_cached(request).scheme in ['http', 'https']
60
61
62class Cache(object):
63    DOMAIN_SECTORDIR = 'data'
64    DOMAIN_LINKDIR = 'domains'
65
66    def __init__(self, cachedir, sectorize=False):
67        self.cachedir = cachedir
68        self.sectorize = sectorize
69
70        self.baselinkpath = os.path.join(self.cachedir, self.DOMAIN_LINKDIR)
71        if not os.path.exists(self.baselinkpath):
72            os.makedirs(self.baselinkpath)
73
74        self.basesectorpath = os.path.join(self.cachedir, self.DOMAIN_SECTORDIR)
75        if not os.path.exists(self.basesectorpath):
76            os.makedirs(self.basesectorpath)
77
78    def domainsectorpath(self, domain):
79        sector = hashlib.sha1(domain).hexdigest()[0]
80        return os.path.join(self.basesectorpath, sector, domain)
81
82    def domainlinkpath(self, domain):
83        return os.path.join(self.baselinkpath, domain)
84
85    def requestpath(self, domain, key):
86        linkpath = self.domainlinkpath(domain)
87        return os.path.join(linkpath, key[0:2], key)
88
89    def open_domain(self, domain):
90        if domain:
91            linkpath = self.domainlinkpath(domain)
92            if self.sectorize:
93                sectorpath = self.domainsectorpath(domain)
94                if not os.path.exists(sectorpath):
95                    os.makedirs(sectorpath)
96                if not os.path.exists(linkpath):
97                    try:
98                        os.symlink(sectorpath, linkpath)
99                    except:
100                        os.makedirs(linkpath) # windows filesystem
101            else:
102                if not os.path.exists(linkpath):
103                    os.makedirs(linkpath)
104
105    def read_meta(self, domain, key):
106        """Return the metadata dictionary (possibly empty) if the entry is
107        cached, None otherwise.
108        """
109        requestpath = self.requestpath(domain, key)
110        try:
111            with open(os.path.join(requestpath, 'pickled_meta'), 'r') as f:
112                metadata = pickle.load(f)
113        except IOError, e:
114            if e.errno != errno.ENOENT:
115                raise
116            return None
117        expiration_secs = settings.getint('HTTPCACHE_EXPIRATION_SECS')
118        if expiration_secs >= 0:
119            expiration_date = metadata['timestamp'] + datetime.timedelta(seconds=expiration_secs)
120            if datetime.datetime.utcnow() > expiration_date:
121                log.msg('dropping old cached response from %s' % metadata['timestamp'], \
122                    level=log.DEBUG, domain=domain)
123                return None
124        return metadata
125
126    def retrieve_response(self, domain, key):
127        """
128        Return response dictionary if request has correspondent cache record;
129        return None if not.
130        """
131        metadata = self.read_meta(domain, key)
132        if metadata is None:
133            return None # not cached
134
135        requestpath = self.requestpath(domain, key)
136        responsebody = responseheaders = None
137        with open(os.path.join(requestpath, 'response_body')) as f:
138            responsebody = f.read()
139        with open(os.path.join(requestpath, 'response_headers')) as f:
140            responseheaders = f.read()
141
142        url = metadata['url']
143        headers = Headers(headers_raw_to_dict(responseheaders))
144        status = metadata['status']
145
146        respcls = responsetypes.from_args(headers=headers, url=url)
147        response = respcls(url=url, headers=headers, status=status, body=responsebody)
148        response.meta['cached'] = True
149        response.flags.append('cached')
150        return response
151
152    def store(self, domain, key, request, response):
153        requestpath = self.requestpath(domain, key)
154        if not os.path.exists(requestpath):
155            os.makedirs(requestpath)
156
157        metadata = {
158                'url':request.url,
159                'method': request.method,
160                'status': response.status,
161                'domain': domain,
162                'timestamp': datetime.datetime.utcnow(),
163            }
164
165        # metadata
166        with open(os.path.join(requestpath, 'meta_data'), 'w') as f:
167            f.write(repr(metadata))
168        # pickled metadata (to recover without using eval)
169        with open(os.path.join(requestpath, 'pickled_meta'), 'w') as f:
170            pickle.dump(metadata, f)
171        # response
172        with open(os.path.join(requestpath, 'response_headers'), 'w') as f:
173            f.write(headers_dict_to_raw(response.headers))
174        with open(os.path.join(requestpath, 'response_body'), 'w') as f:
175            f.write(response.body)
176        # request
177        with open(os.path.join(requestpath, 'request_headers'), 'w') as f:
178            f.write(headers_dict_to_raw(request.headers))
179        if request.body:
180            with open(os.path.join(requestpath, 'request_body'), 'w') as f:
181                f.write(request.body)
Note: See TracBrowser for help on using the browser.