Existe-t-il un moyen facile de mettre en cache des choses lorsque j'utilise urllib2 que je suis trop beau, ou dois-je lancer le mien?Mise en cache dans urllib2?
Répondre
Vous pouvez utiliser une fonction de décorateur tels que:
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)
et définir une fonction le long des lignes de:
@cache
def get_url_src(url):
return urllib.urlopen(url).read()
Ceci suppose que vous ne faites pas attention aux contrôles de cache HTTP, mais je veux juste mettre en cache la page pour la durée de l'application
Cette recette ActiveState Python pourrait être utile: http://code.activestate.com/recipes/491261/
Si vous ne me dérange pas de travailler à un niveau légèrement inférieur, httplib2 (https://github.com/httplib2/httplib2) est une excellente bibliothèque HTTP qui inclut des fonctionnalités de mise en cache.
S'il vous plaît corriger faute de frappe: httlib2 -> httplib2 – tzot
Rappelez-vous juste que vous perdrez le soutien d'autres systèmes (fichiers, ftp, .. .) lors de l'utilisation de httplib2. –
Je cherchais quelque chose de similaire, et je suis tombé sur "Recipe 491261: Caching and throttling for urllib2" qui danivo posté. Le problème est I vraiment n'aime pas le code de mise en cache (beaucoup de duplication, beaucoup de joindre manuellement des chemins de fichiers au lieu d'utiliser os.path.join, utilise staticmethods, PEP8'sih pas très, et d'autres choses que j'essaie d'éviter)
le code est un peu plus agréable (à mon avis de toute façon) et est fonctionnellement la même chose, avec quelques ajouts - principalement la méthode « recache » (par exemple l'utilisation can be seem here, ou dans la section if __name__ == "__main__":
à la fin du code).
La dernière version peut être trouvée à http://github.com/dbr/tvdb_api/blob/master/cache.py, et je vais le coller ici pour la postérité (avec mon application têtes spécifiques supprimés):
#!/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()
Cet article sur Yahoo Developer Network - http://developer.yahoo.com/python/python-caching.html - décrit comment Mettre en mémoire cache les appels http effectués via urllib vers la mémoire ou le disque.
J'ai toujours été confronté à l'utilisation de httplib2, qui gère correctement la mise en cache et l'authentification HTTP, et urllib2, qui se trouve dans stdlib, possède une interface extensible et prend en charge les serveurs proxy HTTP.
Le ActiveState recipe commence à ajouter un support de mise en cache à urllib2, mais seulement de façon très primitive. Il ne permet pas l'extensibilité dans les mécanismes de stockage, codant en dur le stockage sauvegardé par le système de fichiers. Il n'honore pas non plus les en-têtes de cache HTTP. Pour tenter de rassembler les meilleures fonctionnalités de la mise en cache de httplib2 et de l'extensibilité d'urllib2, j'ai adapté la recette ActiveState pour implémenter la plupart des fonctionnalités de mise en cache que l'on trouve dans httplib2. Le module est dans jaraco.net comme jaraco.net.http.caching. Le lien pointe vers le module tel qu'il existe au moment de cette écriture. Bien que ce module fasse actuellement partie du paquet jaraco.net, il n'a pas de dépendances intra-paquet, alors n'hésitez pas à retirer le module et à l'utiliser dans vos propres projets. Alternativement, si vous avez Python 2.6 ou plus tard, vous pouvez easy_install jaraco.net>=1.3
et ensuite utiliser CachingHandler avec quelque chose comme le code 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'
Notez que jaraco.util.http.La mise en cache ne fournit pas de spécification pour le magasin de sauvegarde du cache, mais suit l'interface utilisée par httplib2. Pour cette raison, le httplib2.FileCache peut être utilisé directement avec urllib2 et CacheHandler. De plus, d'autres caches de sauvegarde conçus pour httplib2 devraient être utilisables par CacheHandler.
@dbr: vous devrez peut-être ajouter https également la mise en cache des réponses avec:
def https_response(self, request, response):
return self.http_response(request,response)
J'ai implémenté ceci, mais avec un backend sqlite et une durée de cache personnalisable. Merci :) – Yuvi
@Yuvi Publier quelque part ce serait sympa :) –
Il n'est pas valide de mettre en cache des ressources comme celle-ci, sauf si vous vérifiez leurs en-têtes selon les instructions de la RFC. Ou peut-être proposez-vous cette solution pour quelque chose comme l'assemblage d'une vue unique d'une page Web, et non pour une utilisation sur des rechargements répétés? –