Вопрос

Есть ли простой способ кэширования вещей при использовании urllib2, который я упускаю из виду, или мне придется использовать свой собственный?

Это было полезно?

Решение

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

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-кешем, а просто хотите кэшировать страницу на время работы приложения.

Другие советы

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

Этот рецепт ActiveState Python может быть полезен:http://code.activestate.com/recipes/491261/

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

А Рецепт ActiveState начинает добавлять поддержку кэширования в 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.caching не предоставляет спецификацию резервного хранилища для кеша, а вместо этого следует интерфейсу, используемому httplib2.По этой причине httplib2.FileCache можно использовать напрямую с urllib2 и CacheHandler.Кроме того, CacheHandler должен использовать другие резервные кэши, разработанные для httplib2.

Искал что-то подобное и наткнулся «Рецепт 491261:Кэширование и регулирование для urllib2" который опубликовал Даниво.Проблема в том, что я Действительно не нравится код кеширования (много дублирования, множество ручных объединений путей к файлам вместо использования os.path.join, использование статических методов, не очень PEP8 и другие вещи, которых я стараюсь избегать)

Код немного лучше (во всяком случае, на мой взгляд) и функционально практически такой же, с некоторыми дополнениями - в основном методом "перекэширования" (пример использования можно посмотреть здесь, или в 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()

Эта статья в сети разработчиков Yahoo — http://developer.yahoo.com/python/python-caching.html - описывает, как кэшировать http-вызовы, сделанные через urllib, в памяти или на диске.

@дбр:вам может потребоваться добавить также кэширование ответов https с помощью:

def https_response(self, request, response):
    return self.http_response(request,response)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top