Frage
Gibt es einen einfachen Weg, um Dinge cachen bei der Verwendung von urllib2, dass ich über aussehende, oder muss ich meine eigene Rolle?
Lösung
Sie könnten eine Dekorateur Funktion wie:
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)
und eine Funktion entlang der Linien definieren:
@cache
def get_url_src(url):
return urllib.urlopen(url).read()
Dies wird vorausgesetzt, Sie nicht die Aufmerksamkeit auf HTTP-Cache-Kontrollen zahlen, wollen aber nur die Seite für die Dauer der Anwendung zwischenzuspeichern
Andere Tipps
Wenn Sie nichts dagegen nicht auf einem etwas niedrigeren Niveau arbeiten, httplib2 ( https://github.com/ httplib2 / httplib2 ) ist eine ausgezeichnete HTTP-Bibliothek, die Caching-Funktionalität enthält.
Dieses Activestate Python Rezept könnte hilfreich sein: http://code.activestate.com/recipes/491261/
Ich habe immer zwischen der Verwendung von httplib2 gerissen worden, die den Umgang mit HTTP-Caching und Authentifizierung eine solide Arbeit leistet, und urllib2, die in der stdlib ist, hat eine erweiterbare Schnittstelle und unterstützen HTTP-Proxy-Server.
Die Activestate Rezept Cachen Unterstützung urllib2 hinzufügen startet, aber nur in einem sehr primitiven Mode. Es schlägt fehl, für Erweiterbarkeit in Speichermechanismen zu ermöglichen, Hartcodierung der Datei-System-backed Speicher. Es ist auch nicht HTTP-Cache-Header ehren.
In einem Versuch zusammen zu bringen die besten Eigenschaften von httplib2 Caching und urllib2 Erweiterbarkeit, habe ich das Activestate Rezept angepasst meisten die gleichen Caching-Funktionalität zu implementieren, wie in httplib2 gefunden wird. Das Modul ist in jaraco.net als jaraco.net .http.caching . Der Link verweist auf das Modul, wie es zum Zeitpunkt des Schreibens dieses Artikels besteht. Während das Modul zur Zeit ein Teil des größeren jaraco.net Paket ist, hat es keine intra-Paketabhängigkeiten, so fühlen sich um das Modul zu ziehen frei heraus und verwenden Sie es in Ihren eigenen Projekten.
Alternativ, wenn Sie Python 2.6 oder höher haben, können Sie easy_install jaraco.net>=1.3
und dann die CachingHandler mit so etwas wie der Code in caching.quick_test()
nutzen.
"""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'
Beachten Sie, dass jaraco.util.http.caching nicht eine Spezifikation für den Sicherungsspeicher für den Cache liefert, sondern folgt der Schnittstelle von httplib2 verwendet. Aus diesem Grunde kann die httplib2.FileCache direkt mit urllib2 und dem CacheHandler verwendet werden. Auch andere Träger Caches für httplib2 entworfen sollte von dem CacheHandler verwendbar sein.
Ich war auf der Suche nach etwas ähnlichem, und kam über „Rezept 491261: Caching und Drosselung für urllib2“ die geschrieben danivo. Das Problem ist, I wirklich Wenigsten den Caching-Code (eine Menge Doppelarbeit, viele von Dateipfaden manuell verbinden anstatt mit os.path.join verwendet staticmethods, nicht sehr PEP8'sih, und andere Dinge, die ich versuche zu vermeiden)
Der Code ist ein bisschen schöner (meiner Meinung nach sowieso) und ist funktionell sehr ähnlich, mit einigen Zusätzen - vor allem des „Recache“ Verfahren (zB Verwendung sein kann, hier scheint oder im if __name__ == "__main__":
Abschnitt am Ende des Codes).
Die neueste Version kann unter http://github.com/ finden dbr / tvdb_api / Blob / Master / cache.py , und ich werde es hier für die Nachwelt einfügen (mit meinem anwendungsspezifischen Header entfernt):
#!/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()
Dieser Artikel auf Yahoo Developer Network - http://developer.yahoo.com/ python / python-caching.html -. wird beschrieben, wie http Anrufe über urllib entweder Speicher oder Festplatte gemacht zwischenzuspeichern
@dbr: Sie können auch Caching https Antworten hinzufügen müssen mit:
def https_response(self, request, response):
return self.http_response(request,response)