Есть ли простой способ кэшировать вещи при использовании urllib2, что я пересматриваю, или мне нужно сворачивать самостоятельно? Рецепт PythonКэширование в 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 Cache, но просто хотите кэшировать страницу в течение всего срока действия приложения.
Это ActiveState может быть полезным: http://code.activestate.com/recipes/491261/
Если вы не против работать на несколько более низком уровне, httplib2 (https://github.com/httplib2/httplib2) является отличной библиотекой HTTP, которая включает в себя функцию кэширования.
Я искал что-то похожее и натолкнулся на "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()
Эта статья на Yahoo Developer Network - http://developer.yahoo.com/python/python-caching.html - описывает, как кешировать http-вызовы, выполненные через urllib либо на память, либо на диск.
Я всегда был разорван между использованием 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.
@dbr: Вы, возможно, потребуется также добавить HTTPS кэширование ответов с:
def https_response(self, request, response):
return self.http_response(request,response)
Пожалуйста исправить опечатку: httlib2 -> httplib2 – tzot 2008-09-29 16:28:31
Просто помните, что вы потеряете поддержку для других схем (файл, FTP, .. .) при использовании httplib2. – 2010-11-08 18:33:12