2008-09-29 3 views
13

Есть ли простой способ кэшировать вещи при использовании urllib2, что я пересматриваю, или мне нужно сворачивать самостоятельно? Рецепт PythonКэширование в urllib2?

ответ

7

Вы можете использовать функцию декоратора, такие как:

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) 

и определить функцию вдоль линий:

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

Это предполагает, что вы не обращая внимания на управление HTTP Cache, но просто хотите кэшировать страницу в течение всего срока действия приложения.

8

Если вы не против работать на несколько более низком уровне, httplib2 (https://github.com/httplib2/httplib2) является отличной библиотекой HTTP, которая включает в себя функцию кэширования.

+1

Пожалуйста исправить опечатку: httlib2 -> httplib2 – tzot 2008-09-29 16:28:31

+2

Просто помните, что вы потеряете поддержку для других схем (файл, FTP, .. .) при использовании httplib2. – 2010-11-08 18:33:12

3

Я искал что-то похожее и натолкнулся на "Recipe 491261: Caching and throttling for urllib2", который отправил Даниво. Проблема в том, что я действительно не нравится код кеширования (много дублирования, много ручного объединения путей к файлам вместо использования os.path.join, использует staticmethods, не очень PEP8'sih и другие вещи, которые я стараюсь избегать)

код немного лучше (на мой взгляд, во всяком случае) и функционально то же самое, с некоторыми дополнениями - в основном метод «кэширования» (пример использования can be seem here, или в разделе if __name__ == "__main__": в конце кода).

Последняя версия может быть найден в http://github.com/dbr/tvdb_api/blob/master/cache.py, и я буду вставлять его здесь для потомков (с моим приложением конкретные заголовки удалены):

#!/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

Я всегда был разорван между использованием httplib2, который выполняет прочную работу по обработке кеширования и аутентификации HTTP, а urllib2, который находится в stdlib, имеет расширяемый интерфейс и поддерживает HTTP-прокси-серверы.

ActiveState recipe начинает добавлять поддержку кеширования urllib2, но только в очень примитивном стиле. Он не позволяет расширяться в механизмах хранения, жестко кодируя хранилище с файловой системой. Он также не соблюдает заголовки кеша HTTP.

В попытке объединить лучшие функции кеширования httplib2 и расширяемости urllib2, я адаптировал рецепт ActiveState для реализации большинства тех же функций кеширования, что и в httplib2. Модуль находится в jaraco.net как jaraco.net.http.caching. Ссылка указывает на модуль, который существует на момент написания этой статьи. Хотя этот модуль в настоящее время является частью более крупного пакета jaraco.net, он не имеет зависимостей внутри пакета, поэтому не стесняйтесь вытаскивать модуль и использовать его в своих собственных проектах.

В качестве альтернативы, если у вас есть Python 2.6 или новее, вы можете easy_install jaraco.net>=1.3, а затем использовать CachingHandler с чем-то вроде кода в 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' 

Примечание: jaraco.util.http.кэширование не предоставляет спецификации хранилища резервных копий для кеша, а вместо этого следует интерфейсу, используемому httplib2. По этой причине httplib2.FileCache можно использовать напрямую с urllib2 и CacheHandler. Кроме того, CacheHandler должен использовать другие резервные кэши, предназначенные для httplib2.

1

@dbr: Вы, возможно, потребуется также добавить HTTPS кэширование ответов с:

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