2008-09-29 10 views
13

Có cách nào dễ dàng để lưu trữ mọi thứ khi sử dụng urllib2 mà tôi đang tìm kiếm quá mức hay tôi phải tự cuộn?Caching trong urllib2?

Trả lời

7

Bạn có thể sử dụng một chức năng trang trí như:

class cache(object): 
    def __init__(self, fun): 
     self.fun = fun 
     self.cache = {} 

    def __call__(self, *args, **kwargs): 
     key = str(args) + str(kwargs) 
     try: 
      return self.cache[key] 
     except KeyError: 
      self.cache[key] = rval = self.fun(*args, **kwargs) 
      return rval 
     except TypeError: # incase key isn't a valid key - don't cache 
      return self.fun(*args, **kwargs) 

và xác định một chức năng dọc theo dòng:

@cache 
def get_url_src(url): 
    return urllib.urlopen(url).read() 

này được giả sử bạn đang không chú ý đến HTTP cache Controls, nhưng chỉ muốn cache trang trong khoảng thời gian của ứng dụng

+0

Tôi đã thực hiện điều này, nhưng với phần phụ trợ sqlite và thời lượng bộ nhớ cache có thể tùy chỉnh. Cảm ơn bạn :) – Yuvi

+0

@Yuvi Đăng bài này ở đâu đó sẽ tốt đẹp :) –

+4

Nó không hợp lệ để lưu trữ tài nguyên như thế này trừ khi bạn kiểm tra tiêu đề của họ theo hướng dẫn trong RFC. Hoặc có lẽ bạn đang đề xuất giải pháp này cho một cái gì đó giống như lắp ráp một cái nhìn duy nhất của một trang web, và không cho sử dụng trên tải lại lặp đi lặp lại? –

8

Nếu bạn không nhớ làm việc ở một mức độ thấp hơn một chút, httplib2 (https://github.com/httplib2/httplib2) là một thư viện HTTP tuyệt vời bao gồm chức năng bộ nhớ đệm.

+1

Hãy sửa lỗi đánh máy: httlib2 -> httplib2 – tzot

+2

Chỉ cần nhớ rằng bạn sẽ mất sự ủng hộ cho các chương trình khác (file, ftp, .. .) khi sử dụng httplib2. –

3

Tôi đang tìm kiếm thứ gì đó tương tự và đã xem qua "Recipe 491261: Caching and throttling for urllib2" mà danivo đã đăng. Vấn đề là tôi thực sự không thích mã bộ nhớ đệm (rất nhiều sao chép, rất nhiều tham gia thủ công của đường dẫn tệp thay vì sử dụng os.path.join, sử dụng staticmethods, không rất PEP8'sih, và những thứ khác mà tôi cố gắng tránh)

Mã này đẹp hơn một chút (theo ý kiến ​​của tôi) và có chức năng giống nhau, với một vài bổ sung - chủ yếu là phương pháp "recache" (ví dụ sử dụng can be seem here hoặc trong phần if __name__ == "__main__": ở cuối mã).

Phiên bản mới nhất có thể được tìm thấy tại http://github.com/dbr/tvdb_api/blob/master/cache.py, và tôi sẽ dán nó ở đây cho hậu thế (với ứng dụng của tôi tiêu đề cụ thể loại bỏ):

#!/usr/bin/env python 
""" 
urllib2 caching handler 
Modified from http://code.activestate.com/recipes/491261/ by dbr 
""" 

import os 
import time 
import httplib 
import urllib2 
import StringIO 
from hashlib import md5 

def calculate_cache_path(cache_location, url): 
    """Checks if [cache_location]/[hash_of_url].headers and .body exist 
    """ 
    thumb = md5(url).hexdigest() 
    header = os.path.join(cache_location, thumb + ".headers") 
    body = os.path.join(cache_location, thumb + ".body") 
    return header, body 

def check_cache_time(path, max_age): 
    """Checks if a file has been created/modified in the [last max_age] seconds. 
    False means the file is too old (or doesn't exist), True means it is 
    up-to-date and valid""" 
    if not os.path.isfile(path): 
     return False 
    cache_modified_time = os.stat(path).st_mtime 
    time_now = time.time() 
    if cache_modified_time < time_now - max_age: 
     # Cache is old 
     return False 
    else: 
     return True 

def exists_in_cache(cache_location, url, max_age): 
    """Returns if header AND body cache file exist (and are up-to-date)""" 
    hpath, bpath = calculate_cache_path(cache_location, url) 
    if os.path.exists(hpath) and os.path.exists(bpath): 
     return(
      check_cache_time(hpath, max_age) 
      and check_cache_time(bpath, max_age) 
     ) 
    else: 
     # File does not exist 
     return False 

def store_in_cache(cache_location, url, response): 
    """Tries to store response in cache.""" 
    hpath, bpath = calculate_cache_path(cache_location, url) 
    try: 
     outf = open(hpath, "w") 
     headers = str(response.info()) 
     outf.write(headers) 
     outf.close() 

     outf = open(bpath, "w") 
     outf.write(response.read()) 
     outf.close() 
    except IOError: 
     return True 
    else: 
     return False 

class CacheHandler(urllib2.BaseHandler): 
    """Stores responses in a persistant on-disk cache. 

    If a subsequent GET request is made for the same URL, the stored 
    response is returned, saving time, resources and bandwidth 
    """ 
    def __init__(self, cache_location, max_age = 21600): 
     """The location of the cache directory""" 
     self.max_age = max_age 
     self.cache_location = cache_location 
     if not os.path.exists(self.cache_location): 
      os.mkdir(self.cache_location) 

    def default_open(self, request): 
     """Handles GET requests, if the response is cached it returns it 
     """ 
     if request.get_method() is not "GET": 
      return None # let the next handler try to handle the request 

     if exists_in_cache(
      self.cache_location, request.get_full_url(), self.max_age 
     ): 
      return CachedResponse(
       self.cache_location, 
       request.get_full_url(), 
       set_cache_header = True 
      ) 
     else: 
      return None 

    def http_response(self, request, response): 
     """Gets a HTTP response, if it was a GET request and the status code 
     starts with 2 (200 OK etc) it caches it and returns a CachedResponse 
     """ 
     if (request.get_method() == "GET" 
      and str(response.code).startswith("2") 
     ): 
      if 'x-local-cache' not in response.info(): 
       # Response is not cached 
       set_cache_header = store_in_cache(
        self.cache_location, 
        request.get_full_url(), 
        response 
       ) 
      else: 
       set_cache_header = True 
      #end if x-cache in response 

      return CachedResponse(
       self.cache_location, 
       request.get_full_url(), 
       set_cache_header = set_cache_header 
      ) 
     else: 
      return response 

class CachedResponse(StringIO.StringIO): 
    """An urllib2.response-like object for cached responses. 

    To determine if a response is cached or coming directly from 
    the network, check the x-local-cache header rather than the object type. 
    """ 
    def __init__(self, cache_location, url, set_cache_header=True): 
     self.cache_location = cache_location 
     hpath, bpath = calculate_cache_path(cache_location, url) 

     StringIO.StringIO.__init__(self, file(bpath).read()) 

     self.url  = url 
     self.code = 200 
     self.msg  = "OK" 
     headerbuf = file(hpath).read() 
     if set_cache_header: 
      headerbuf += "x-local-cache: %s\r\n" % (bpath) 
     self.headers = httplib.HTTPMessage(StringIO.StringIO(headerbuf)) 

    def info(self): 
     """Returns headers 
     """ 
     return self.headers 

    def geturl(self): 
     """Returns original URL 
     """ 
     return self.url 

    def recache(self): 
     new_request = urllib2.urlopen(self.url) 
     set_cache_header = store_in_cache(
      self.cache_location, 
      new_request.url, 
      new_request 
     ) 
     CachedResponse.__init__(self, self.cache_location, self.url, True) 


if __name__ == "__main__": 
    def main(): 
     """Quick test/example of CacheHandler""" 
     opener = urllib2.build_opener(CacheHandler("/tmp/")) 
     response = opener.open("http://google.com") 
     print response.headers 
     print "Response:", response.read() 

     response.recache() 
     print response.headers 
     print "After recache:", response.read() 
    main() 
5

Tôi luôn bị rách giữa việc sử dụng httplib2, một công cụ vững chắc trong việc xử lý bộ nhớ đệm HTTP và xác thực, và urllib2, trong stdlib, có giao diện mở rộng và hỗ trợ máy chủ HTTP Proxy.

ActiveState recipe bắt đầu thêm hỗ trợ bộ nhớ đệm vào urllib2, nhưng chỉ theo kiểu rất nguyên thủy. Nó không cho phép mở rộng trong các cơ chế lưu trữ, mã hóa cứng kho lưu trữ tập tin được hỗ trợ bởi hệ thống. Nó cũng không tôn trọng tiêu đề bộ nhớ cache HTTP. Trong một nỗ lực để tập hợp các tính năng tốt nhất của bộ nhớ đệm httplib2 và khả năng mở rộng urllib2, tôi đã điều chỉnh công thức ActiveState để thực hiện hầu hết các chức năng bộ nhớ đệm giống như được tìm thấy trong httplib2. Mô-đun nằm trong jaraco.net là jaraco.net.http.caching. Liên kết trỏ đến mô-đun khi nó tồn tại tại thời điểm viết bài này. Mặc dù mô-đun đó hiện là một phần của gói jaraco.net lớn hơn, nhưng nó không có phụ thuộc vào gói nội bộ, vì vậy, hãy kéo module ra và sử dụng nó trong các dự án của riêng bạn.

Hoặc, nếu bạn có Python 2.6 hoặc mới hơn, bạn có thể easy_install jaraco.net>=1.3 và sau đó sử dụng CachingHandler với một cái gì đó giống như mã trong caching.quick_test().

"""Quick test/example of CacheHandler""" 
import logging 
import urllib2 
from httplib2 import FileCache 
from jaraco.net.http.caching import CacheHandler 

logging.basicConfig(level=logging.DEBUG) 
store = FileCache(".cache") 
opener = urllib2.build_opener(CacheHandler(store)) 
urllib2.install_opener(opener) 
response = opener.open("http://www.google.com/") 
print response.headers 
print "Response:", response.read()[:100], '...\n' 

response.reload(store) 
print response.headers 
print "After reload:", response.read()[:100], '...\n' 

Lưu ý rằng jaraco.util.http.bộ nhớ đệm không cung cấp một đặc điểm kỹ thuật cho các cửa hàng sao lưu cho bộ nhớ cache, nhưng thay vì theo giao diện được sử dụng bởi httplib2. Vì lý do này, httplib2.FileCache có thể được sử dụng trực tiếp với urllib2 và CacheHandler. Ngoài ra, các bộ đệm sao lưu khác được thiết kế cho httplib2 nên có thể sử dụng được bởi CacheHandler.

1

@dbr: bạn có thể cần thêm cũng https phản ứng bộ nhớ đệm với:

def https_response(self, request, response): 
    return self.http_response(request,response)