Có cách nào dễ dàng để lưu trữ mọi thứ khi sử dụng urllib2 mà tôi đang tìm kiếm quá mức hay tôi phải tự cuộn?Caching trong urllib2?
Trả lời
Bạn có thể sử dụng một chức năng trang trí như:
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)
và xác định một chức năng dọc theo dòng:
@cache
def get_url_src(url):
return urllib.urlopen(url).read()
này được giả sử bạn đang không chú ý đến HTTP cache Controls, nhưng chỉ muốn cache trang trong khoảng thời gian của ứng dụng
ActiveState này Python công thức có thể hữu ích: http://code.activestate.com/recipes/491261/
Nếu bạn không nhớ làm việc ở một mức độ thấp hơn một chút, httplib2 (https://github.com/httplib2/httplib2) là một thư viện HTTP tuyệt vời bao gồm chức năng bộ nhớ đệm.
Hãy sửa lỗi đánh máy: httlib2 -> httplib2 – tzot
Chỉ cần nhớ rằng bạn sẽ mất sự ủng hộ cho các chương trình khác (file, ftp, .. .) khi sử dụng httplib2. –
Tôi đang tìm kiếm thứ gì đó tương tự và đã xem qua "Recipe 491261: Caching and throttling for urllib2" mà danivo đã đăng. Vấn đề là tôi thực sự không thích mã bộ nhớ đệm (rất nhiều sao chép, rất nhiều tham gia thủ công của đường dẫn tệp thay vì sử dụng os.path.join, sử dụng staticmethods, không rất PEP8'sih, và những thứ khác mà tôi cố gắng tránh)
Mã này đẹp hơn một chút (theo ý kiến của tôi) và có chức năng giống nhau, với một vài bổ sung - chủ yếu là phương pháp "recache" (ví dụ sử dụng can be seem here hoặc trong phần if __name__ == "__main__":
ở cuối mã).
Phiên bản mới nhất có thể được tìm thấy tại http://github.com/dbr/tvdb_api/blob/master/cache.py, và tôi sẽ dán nó ở đây cho hậu thế (với ứng dụng của tôi tiêu đề cụ thể loại bỏ):
#!/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()
Bài viết này trên Yahoo Developer Network - http://developer.yahoo.com/python/python-caching.html - mô tả cách các cuộc gọi cache http được thực hiện thông qua urllib tới bộ nhớ hoặc đĩa.
Tôi luôn bị rách giữa việc sử dụng httplib2, một công cụ vững chắc trong việc xử lý bộ nhớ đệm HTTP và xác thực, và urllib2, trong stdlib, có giao diện mở rộng và hỗ trợ máy chủ HTTP Proxy.
ActiveState recipe bắt đầu thêm hỗ trợ bộ nhớ đệm vào urllib2, nhưng chỉ theo kiểu rất nguyên thủy. Nó không cho phép mở rộng trong các cơ chế lưu trữ, mã hóa cứng kho lưu trữ tập tin được hỗ trợ bởi hệ thống. Nó cũng không tôn trọng tiêu đề bộ nhớ cache HTTP. Trong một nỗ lực để tập hợp các tính năng tốt nhất của bộ nhớ đệm httplib2 và khả năng mở rộng urllib2, tôi đã điều chỉnh công thức ActiveState để thực hiện hầu hết các chức năng bộ nhớ đệm giống như được tìm thấy trong httplib2. Mô-đun nằm trong jaraco.net là jaraco.net.http.caching. Liên kết trỏ đến mô-đun khi nó tồn tại tại thời điểm viết bài này. Mặc dù mô-đun đó hiện là một phần của gói jaraco.net lớn hơn, nhưng nó không có phụ thuộc vào gói nội bộ, vì vậy, hãy kéo module ra và sử dụng nó trong các dự án của riêng bạn.
Hoặc, nếu bạn có Python 2.6 hoặc mới hơn, bạn có thể easy_install jaraco.net>=1.3
và sau đó sử dụng CachingHandler với một cái gì đó giống như mã trong 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'
Lưu ý rằng jaraco.util.http.bộ nhớ đệm không cung cấp một đặc điểm kỹ thuật cho các cửa hàng sao lưu cho bộ nhớ cache, nhưng thay vì theo giao diện được sử dụng bởi httplib2. Vì lý do này, httplib2.FileCache có thể được sử dụng trực tiếp với urllib2 và CacheHandler. Ngoài ra, các bộ đệm sao lưu khác được thiết kế cho httplib2 nên có thể sử dụng được bởi CacheHandler.
@dbr: bạn có thể cần thêm cũng https phản ứng bộ nhớ đệm với:
def https_response(self, request, response):
return self.http_response(request,response)
Tôi đã thực hiện điều này, nhưng với phần phụ trợ sqlite và thời lượng bộ nhớ cache có thể tùy chỉnh. Cảm ơn bạn :) – Yuvi
@Yuvi Đăng bài này ở đâu đó sẽ tốt đẹp :) –
Nó không hợp lệ để lưu trữ tài nguyên như thế này trừ khi bạn kiểm tra tiêu đề của họ theo hướng dẫn trong RFC. Hoặc có lẽ bạn đang đề xuất giải pháp này cho một cái gì đó giống như lắp ráp một cái nhìn duy nhất của một trang web, và không cho sử dụng trên tải lại lặp đi lặp lại? –