Urllib2 Python - 重新连接和拆分响应
Urllib2 Python - Reconnecting and Splitting Response
我正从其他语言转到 Python,我不确定如何正确解决这个问题。使用 urllib2 库可以很容易地设置代理并从站点获取数据:
import urllib2
req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req)
the_page = response.read()
我遇到的问题是检索到的文本文件非常大(数百 MB)并且连接经常出现问题。该代码还需要捕获连接、服务器和传输错误(它将成为小型广泛使用的管道的一部分)。
谁能建议如何修改上面的代码以确保代码自动重新连接 n 次(例如 100 次)并可能将响应分成块以便更快、更可靠地下载数据?
我已经尽可能多地拆分请求,所以现在必须确保检索代码尽可能好。基于核心 python 库的解决方案是理想的。
也许图书馆已经在进行上述操作,在这种情况下,有什么方法可以改进大文件的下载吗?我正在使用 UNIX,需要处理代理。
感谢您的帮助。
你可以尝试这样的事情。它逐行读取文件并将其附加到文件中。它还会检查以确保您没有越过同一条线。我将编写另一个脚本来分块执行。
import urllib2
file_checker = None
print("Please Wait...")
while True:
try:
req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req, timeout=20)
print("Connected")
with open("outfile.html", 'w+') as out_data:
for data in response.readlines():
file_checker = open("outfile.html")
if data not in file_checker.readlines():
out_data.write(str(data))
break
except urllib2.URLError:
print("Connection Error!")
print("Connecting again...please wait")
file_checker.close()
print("done")
以下是如何以块而不是按行读取数据
import urllib2
CHUNK = 16 * 1024
file_checker = None
print("Please Wait...")
while True:
try:
req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req, timeout=1)
print("Connected")
with open("outdata", 'wb+') as out_data:
while True:
chunk = response.read(CHUNK)
file_checker = open("outdata")
if chunk and chunk not in file_checker.readlines():
out_data.write(chunk)
else:
break
break
except urllib2.URLError:
print("Connection Error!")
print("Connecting again...please wait")
file_checker.close()
print("done")
我举了一个示例,说明您可能希望如何使用 python-requests 库执行此操作。下面的脚本检查目标文件是否已经存在。如果部分目标文件存在,则假定它是部分下载的文件,并尝试恢复下载。如果服务器声称支持 HTTP 部分请求(即对 HEAD 请求的响应包含 Accept-Range header),则脚本会根据部分下载文件的大小恢复;否则它只是定期下载并丢弃已经下载的部分。我认为如果您不想使用 python-requests,将它转换为仅使用 urllib2 应该是相当直接的,它可能会更加冗长。
请注意,如果服务器上的文件在初始下载和恢复之间被修改,恢复下载可能会损坏文件。如果服务器支持强 HTTP ETag header,则可以检测到这一点,因此下载器可以检查它是否正在恢复相同的文件。
我没有声称它是 bug-free。
您可能应该围绕此脚本添加校验和逻辑以检测下载错误,并在校验和不匹配时从头开始重试。
import logging
import os
import re
import requests
CHUNK_SIZE = 5*1024 # 5KB
logging.basicConfig(level=logging.INFO)
def stream_download(input_iterator, output_stream):
for chunk in input_iterator:
output_stream.write(chunk)
def skip(input_iterator, output_stream, bytes_to_skip):
total_read = 0
while total_read <= bytes_to_skip:
chunk = next(input_iterator)
total_read += len(chunk)
output_stream.write(chunk[bytes_to_skip - total_read:])
assert total_read == output_stream.tell()
return input_iterator
def resume_with_range(url, output_stream):
dest_size = output_stream.tell()
headers = {'Range': 'bytes=%s-' % dest_size}
resp = requests.get(url, stream=True, headers=headers)
input_iterator = resp.iter_content(CHUNK_SIZE)
if resp.status_code != requests.codes.partial_content:
logging.warn('server does not agree to do partial request, skipping instead')
input_iterator = skip(input_iterator, output_stream, output_stream.tell())
return input_iterator
rng_unit, rng_start, rng_end, rng_size = re.match('(\w+) (\d+)-(\d+)/(\d+|\*)', resp.headers['Content-Range']).groups()
rng_start, rng_end, rng_size = map(int, [rng_start, rng_end, rng_size])
assert rng_start <= dest_size
if rng_start != dest_size:
logging.warn('server returned different Range than requested')
output_stream.seek(rng_start)
return input_iterator
def download(url, dest):
''' Download `url` to `dest`, resuming if `dest` already exists
If `dest` already exists it is assumed to be a partially
downloaded file for the url.
'''
output_stream = open(dest, 'ab+')
output_stream.seek(0, os.SEEK_END)
dest_size = output_stream.tell()
if dest_size == 0:
logging.info('STARTING download from %s to %s', url, dest)
resp = requests.get(url, stream=True)
input_iterator = resp.iter_content(CHUNK_SIZE)
stream_download(input_iterator, output_stream)
logging.info('FINISHED download from %s to %s', url, dest)
return
remote_headers = requests.head(url).headers
remote_size = int(remote_headers['Content-Length'])
if dest_size < remote_size:
logging.info('RESUMING download from %s to %s', url, dest)
support_range = 'bytes' in [s.strip() for s in remote_headers['Accept-Ranges'].split(',')]
if support_range:
logging.debug('server supports Range request')
logging.debug('downloading "Range: bytes=%s-"', dest_size)
input_iterator = resume_with_range(url, output_stream)
else:
logging.debug('skipping %s bytes', dest_size)
resp = requests.get(url, stream=True)
input_iterator = resp.iter_content(CHUNK_SIZE)
input_iterator = skip(input_iterator, output_stream, bytes_to_skip=dest_size)
stream_download(input_iterator, output_stream)
logging.info('FINISHED download from %s to %s', url, dest)
return
logging.debug('NOTHING TO DO')
return
def main():
TEST_URL = 'http://mirror.internode.on.net/pub/test/1meg.test'
DEST = TEST_URL.split('/')[-1]
download(TEST_URL, DEST)
main()
我正从其他语言转到 Python,我不确定如何正确解决这个问题。使用 urllib2 库可以很容易地设置代理并从站点获取数据:
import urllib2
req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req)
the_page = response.read()
我遇到的问题是检索到的文本文件非常大(数百 MB)并且连接经常出现问题。该代码还需要捕获连接、服务器和传输错误(它将成为小型广泛使用的管道的一部分)。
谁能建议如何修改上面的代码以确保代码自动重新连接 n 次(例如 100 次)并可能将响应分成块以便更快、更可靠地下载数据?
我已经尽可能多地拆分请求,所以现在必须确保检索代码尽可能好。基于核心 python 库的解决方案是理想的。
也许图书馆已经在进行上述操作,在这种情况下,有什么方法可以改进大文件的下载吗?我正在使用 UNIX,需要处理代理。
感谢您的帮助。
你可以尝试这样的事情。它逐行读取文件并将其附加到文件中。它还会检查以确保您没有越过同一条线。我将编写另一个脚本来分块执行。
import urllib2
file_checker = None
print("Please Wait...")
while True:
try:
req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req, timeout=20)
print("Connected")
with open("outfile.html", 'w+') as out_data:
for data in response.readlines():
file_checker = open("outfile.html")
if data not in file_checker.readlines():
out_data.write(str(data))
break
except urllib2.URLError:
print("Connection Error!")
print("Connecting again...please wait")
file_checker.close()
print("done")
以下是如何以块而不是按行读取数据
import urllib2
CHUNK = 16 * 1024
file_checker = None
print("Please Wait...")
while True:
try:
req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req, timeout=1)
print("Connected")
with open("outdata", 'wb+') as out_data:
while True:
chunk = response.read(CHUNK)
file_checker = open("outdata")
if chunk and chunk not in file_checker.readlines():
out_data.write(chunk)
else:
break
break
except urllib2.URLError:
print("Connection Error!")
print("Connecting again...please wait")
file_checker.close()
print("done")
我举了一个示例,说明您可能希望如何使用 python-requests 库执行此操作。下面的脚本检查目标文件是否已经存在。如果部分目标文件存在,则假定它是部分下载的文件,并尝试恢复下载。如果服务器声称支持 HTTP 部分请求(即对 HEAD 请求的响应包含 Accept-Range header),则脚本会根据部分下载文件的大小恢复;否则它只是定期下载并丢弃已经下载的部分。我认为如果您不想使用 python-requests,将它转换为仅使用 urllib2 应该是相当直接的,它可能会更加冗长。
请注意,如果服务器上的文件在初始下载和恢复之间被修改,恢复下载可能会损坏文件。如果服务器支持强 HTTP ETag header,则可以检测到这一点,因此下载器可以检查它是否正在恢复相同的文件。
我没有声称它是 bug-free。 您可能应该围绕此脚本添加校验和逻辑以检测下载错误,并在校验和不匹配时从头开始重试。
import logging
import os
import re
import requests
CHUNK_SIZE = 5*1024 # 5KB
logging.basicConfig(level=logging.INFO)
def stream_download(input_iterator, output_stream):
for chunk in input_iterator:
output_stream.write(chunk)
def skip(input_iterator, output_stream, bytes_to_skip):
total_read = 0
while total_read <= bytes_to_skip:
chunk = next(input_iterator)
total_read += len(chunk)
output_stream.write(chunk[bytes_to_skip - total_read:])
assert total_read == output_stream.tell()
return input_iterator
def resume_with_range(url, output_stream):
dest_size = output_stream.tell()
headers = {'Range': 'bytes=%s-' % dest_size}
resp = requests.get(url, stream=True, headers=headers)
input_iterator = resp.iter_content(CHUNK_SIZE)
if resp.status_code != requests.codes.partial_content:
logging.warn('server does not agree to do partial request, skipping instead')
input_iterator = skip(input_iterator, output_stream, output_stream.tell())
return input_iterator
rng_unit, rng_start, rng_end, rng_size = re.match('(\w+) (\d+)-(\d+)/(\d+|\*)', resp.headers['Content-Range']).groups()
rng_start, rng_end, rng_size = map(int, [rng_start, rng_end, rng_size])
assert rng_start <= dest_size
if rng_start != dest_size:
logging.warn('server returned different Range than requested')
output_stream.seek(rng_start)
return input_iterator
def download(url, dest):
''' Download `url` to `dest`, resuming if `dest` already exists
If `dest` already exists it is assumed to be a partially
downloaded file for the url.
'''
output_stream = open(dest, 'ab+')
output_stream.seek(0, os.SEEK_END)
dest_size = output_stream.tell()
if dest_size == 0:
logging.info('STARTING download from %s to %s', url, dest)
resp = requests.get(url, stream=True)
input_iterator = resp.iter_content(CHUNK_SIZE)
stream_download(input_iterator, output_stream)
logging.info('FINISHED download from %s to %s', url, dest)
return
remote_headers = requests.head(url).headers
remote_size = int(remote_headers['Content-Length'])
if dest_size < remote_size:
logging.info('RESUMING download from %s to %s', url, dest)
support_range = 'bytes' in [s.strip() for s in remote_headers['Accept-Ranges'].split(',')]
if support_range:
logging.debug('server supports Range request')
logging.debug('downloading "Range: bytes=%s-"', dest_size)
input_iterator = resume_with_range(url, output_stream)
else:
logging.debug('skipping %s bytes', dest_size)
resp = requests.get(url, stream=True)
input_iterator = resp.iter_content(CHUNK_SIZE)
input_iterator = skip(input_iterator, output_stream, bytes_to_skip=dest_size)
stream_download(input_iterator, output_stream)
logging.info('FINISHED download from %s to %s', url, dest)
return
logging.debug('NOTHING TO DO')
return
def main():
TEST_URL = 'http://mirror.internode.on.net/pub/test/1meg.test'
DEST = TEST_URL.split('/')[-1]
download(TEST_URL, DEST)
main()