Python: оптимальный алгоритм, позволяющий избежать загрузки неизмененных страниц при сканировании
Я пишу сканеру, который регулярно проверяет список новостных сайтов на наличие новых статей. Я читал о различных подходах для избежания ненужных загрузок страниц, в основном идентифицировал 5 элементов заголовка, которые могут быть полезны, чтобы определить, изменилась страница или нет:
- Статус HTTP
- ETAG
- Last_modified (объединить с запросом If-Modified-Since)
- Истекает
- Content-Length.
Отличный http://feedparser.org/, кажется, реализует некоторые из этих подходов.
Я ищу оптимальный код на Python (или любой подобный язык), который принимает такого рода решения. Имейте в виду, что информация заголовка всегда предоставляется сервером.
Это может быть что-то вроде:
def shouldDonwload(url,prev_etag,prev_lastmod,prev_expires, prev_content_length):
#retrieve the headers, do the magic here and return the decision
return decision
2 ответа
Единственное, что вам нужно проверить, прежде чем сделать запрос Expires
, If-Modified-Since
это не то, что сервер отправляет вам, а то, что вы отправляете на сервер.
Что вы хотите сделать, это HTTP GET
с If-Modified-Since
заголовок, указывающий, когда вы в последний раз получили ресурс. Если вы вернетесь код состояния 304
а не обычный 200
с тех пор ресурс не был изменен, и вам следует использовать сохраненную копию (новая копия не будет отправлена).
Кроме того, вы должны сохранить Expires
заголовок с последнего раза, когда вы получили документ, а не выдавать GET
вообще, если ваша сохраненная копия документа не истек.
Перевод этого в Python оставлен в качестве упражнения, но добавить добавление If-Modified-Since
заголовок к запросу, чтобы сохранить Expires
заголовок из ответа, и проверить код состояния из ответа.
Вы должны были бы передать в заголовке shouldDownload
(или результат urlopen
):
def shouldDownload(url, headers, prev_etag, prev_lastmod, prev_expires, prev_content_length):
return (prev_content_length != headers.get("content-length") || prev_lastmod != headers.get("If-Modified-Since") || prev_expires != headers.get("Expires") || prev_etag != headers.get("ETAG"))
# or the optimistic way:
# return prev_content_length == headers.get("content-length") and prev_lastmod == headers.get("If-Modified-Since") and prev_expires = headers.get("Expires") and prev_etag = headers.get("ETAG")
Сделайте это, когда откроете URL:
# my urllib2 is a little fuzzy but I believe `urlopen()` doesn't
# read the whole file until `.read()` is called, and you can still
# get the headers with `.headers`. Worst case is you may have to
# `read(50)` or so to get them.
s = urllib2.urlopen(MYURL)
try:
if shouldDownload(s.headers):
source = s.read()
# do stuff with source
else:
continue
# except HTTPError, etc if you need to
finally:
s.close()