Использование urllibs
(или urllibs2
) и желание того, что я хочу, безнадежно.
Любое решение?
Python - поиск в потоке ответов HTTP
Ответ 1
Я не уверен, как работает реализация С#, но поскольку интернет-потоки, как правило, не доступны для поиска, я предполагаю, что он загружает все данные в локальный файл или объект в памяти и ищет внутри него. Эквивалент Python этого будет заключаться в том, чтобы предлагать Abafei и записывать данные в файл или StringIO и искать оттуда.
Однако, если, как указывает ваш комментарий к ответам Abafei, вы хотите получить только определенную часть файла (вместо того, чтобы искать назад и вперед через возвращаемые данные), есть еще одна возможность. urllib2
может использоваться для извлечения определенного раздела (или "диапазона" на языке HTTP) веб-страницы при условии, что сервер поддерживает это поведение.
Заголовок range
Когда вы отправляете запрос на сервер, параметры запроса указываются в разных заголовках. Одним из них является заголовок range
, определенный в разделе раздела 14.35 RFC2616 (спецификация, определяющая HTTP/1.1). Этот заголовок позволяет делать такие вещи, как получение всех данных, начиная с 10 000-го байта, или данных между байтами 1000 и 1500.
Поддержка сервера
Нет необходимости поддерживать сервер для поддержки поиска диапазона. Некоторые серверы вернут заголовок Accept-Ranges
(раздел 14.5 RFC2616) вместе с ответом на отчет, если они поддерживают диапазоны или нет. Это можно проверить с помощью запроса HEAD. Однако нет особой необходимости в этом; если сервер не поддерживает диапазоны, он вернет всю страницу, и мы сможем затем извлечь нужную часть данных в Python, как и раньше.
Проверка возврата диапазона
Если сервер возвращает диапазон, он должен отправить заголовок Content-Range
(раздел 14.16 RFC2616) вместе с ответом. Если это присутствует в заголовках ответа, мы знаем, что диапазон был возвращен; если его нет, вся страница была возвращена.
Реализация с помощью urllib2
urllib2
позволяет нам добавлять заголовки к запросу, что позволяет нам запрашивать сервер для диапазона, а не всей страницы. Следующий script принимает URL-адрес, начальную позицию и (необязательно) длину в командной строке и пытается получить заданный раздел страницы.
import sys
import urllib2
# Check command line arguments.
if len(sys.argv) < 3:
sys.stderr.write("Usage: %s url start [length]\n" % sys.argv[0])
sys.exit(1)
# Create a request for the given URL.
request = urllib2.Request(sys.argv[1])
# Add the header to specify the range to download.
if len(sys.argv) > 3:
start, length = map(int, sys.argv[2:])
request.add_header("range", "bytes=%d-%d" % (start, start + length - 1))
else:
request.add_header("range", "bytes=%s-" % sys.argv[2])
# Try to get the response. This will raise a urllib2.URLError if there is a
# problem (e.g., invalid URL).
response = urllib2.urlopen(request)
# If a content-range header is present, partial retrieval worked.
if "content-range" in response.headers:
print "Partial retrieval successful."
# The header contains the string 'bytes', followed by a space, then the
# range in the format 'start-end', followed by a slash and then the total
# size of the page (or an asterix if the total size is unknown). Lets get
# the range and total size from this.
range, total = response.headers['content-range'].split(' ')[-1].split('/')
# Print a message giving the range information.
if total == '*':
print "Bytes %s of an unknown total were retrieved." % range
else:
print "Bytes %s of a total of %s were retrieved." % (range, total)
# No header, so partial retrieval was unsuccessful.
else:
print "Unable to use partial retrieval."
# And for good measure, lets check how much data we downloaded.
data = response.read()
print "Retrieved data size: %d bytes" % len(data)
Используя это, я могу получить последние 2000 байт домашней страницы Python:
[email protected]:~$ python retrieverange.py http://www.python.org/ 17387
Partial retrieval successful.
Bytes 17387-19386 of a total of 19387 were retrieved.
Retrieved data size: 2000 bytes
Или 400 байт с середины главной страницы:
[email protected]:~$ python retrieverange.py http://www.python.org/ 6000 400
Partial retrieval successful.
Bytes 6000-6399 of a total of 19387 were retrieved.
Retrieved data size: 400 bytes
Однако главная страница Google не поддерживает диапазоны:
[email protected]:~$ python retrieverange.py http://www.google.com/ 1000 500
Unable to use partial retrieval.
Retrieved data size: 9621 bytes
В этом случае необходимо было бы извлечь интересующие данные в Python до дальнейшей обработки.
Ответ 2
Лучше всего просто записать данные в файл (или даже в строку, используя StringIO) и искать в этом файле (или строке).
Ответ 3
См.
Python ищет удаленный файл, используя HTTP
Решение основано на поддержке диапазона HTTP, как определено в RFC 2616.
Ответ 4
Я не нашел никаких существующих реализаций файлового интерфейса с URL-адресами seek() для HTTP, поэтому я перевел свою собственную простую версию: https://github.com/valgur/pyhttpio. Это зависит от urllib.request
, но, вероятно, может быть легко изменено для использования requests
, если это необходимо.
Полный код:
import cgi
import time
import urllib.request
from io import IOBase
from sys import stderr
class SeekableHTTPFile(IOBase):
def __init__(self, url, name=None, repeat_time=-1, debug=False):
"""Allow a file accessible via HTTP to be used like a local file by utilities
that use `seek()` to read arbitrary parts of the file, such as `ZipFile`.
Seeking is done via the 'range: bytes=xx-yy' HTTP header.
Parameters
----------
url : str
A HTTP or HTTPS URL
name : str, optional
The filename of the file.
Will be filled from the Content-Disposition header if not provided.
repeat_time : int, optional
In case of HTTP errors wait `repeat_time` seconds before trying again.
Negative value or `None` disables retrying and simply passes on the exception (the default).
"""
super().__init__()
self.url = url
self.name = name
self.repeat_time = repeat_time
self.debug = debug
self._pos = 0
self._seekable = True
with self._urlopen() as f:
if self.debug:
print(f.getheaders())
self.content_length = int(f.getheader("Content-Length", -1))
if self.content_length < 0:
self._seekable = False
if f.getheader("Accept-Ranges", "none").lower() != "bytes":
self._seekable = False
if name is None:
header = f.getheader("Content-Disposition")
if header:
value, params = cgi.parse_header(header)
self.name = params["filename"]
def seek(self, offset, whence=0):
if not self.seekable():
raise OSError
if whence == 0:
self._pos = 0
elif whence == 1:
pass
elif whence == 2:
self._pos = self.content_length
self._pos += offset
return self._pos
def seekable(self, *args, **kwargs):
return self._seekable
def readable(self, *args, **kwargs):
return not self.closed
def writable(self, *args, **kwargs):
return False
def read(self, amt=-1):
if self._pos >= self.content_length:
return b""
if amt < 0:
end = self.content_length - 1
else:
end = min(self._pos + amt - 1, self.content_length - 1)
byte_range = (self._pos, end)
self._pos = end + 1
with self._urlopen(byte_range) as f:
return f.read()
def readall(self):
return self.read(-1)
def tell(self):
return self._pos
def __getattribute__(self, item):
attr = object.__getattribute__(self, item)
if not object.__getattribute__(self, "debug"):
return attr
if hasattr(attr, '__call__'):
def trace(*args, **kwargs):
a = ", ".join(map(str, args))
if kwargs:
a += ", ".join(["{}={}".format(k, v) for k, v in kwargs.items()])
print("Calling: {}({})".format(item, a))
return attr(*args, **kwargs)
return trace
else:
return attr
def _urlopen(self, byte_range=None):
header = {}
if byte_range:
header = {"range": "bytes={}-{}".format(*byte_range)}
while True:
try:
r = urllib.request.Request(self.url, headers=header)
return urllib.request.urlopen(r)
except urllib.error.HTTPError as e:
if self.repeat_time is None or self.repeat_time < 0:
raise
print("Server responded with " + str(e), file=stderr)
print("Sleeping for {} seconds before trying again".format(self.repeat_time), file=stderr)
time.sleep(self.repeat_time)
Потенциальный пример использования:
url = "https://www.python.org/ftp/python/3.5.0/python-3.5.0-embed-amd64.zip"
f = SeekableHTTPFile(url, debug=True)
zf = ZipFile(f)
zf.printdir()
zf.extract("python.exe")
Изменить: на самом деле в этом ответе есть практически идентичная, если немного более минимальная, реализация: fooobar.com/info/370302/...