我正在从其他语言转向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,需要处理一个代理。
谢谢你的帮助。
我举了一个例子,说明您可能希望如何使用python请求库来实现这一点。下面的脚本检查目标文件是否已经存在。如果部分目标文件存在,则假定它是部分下载的文件,并尝试恢复下载。如果服务器声称支持HTTP Partial Request(即,对HEAD请求的响应包含Accept Range标头),则脚本将根据部分下载的文件的大小恢复;否则,它只是进行定期下载并丢弃已经下载的部分。我认为应该非常直接地将其转换为仅使用urlib2。如果您不想使用python请求,它可能会更加冗长。
请注意,如果在初始下载和恢复之间修改了服务器上的文件,则恢复下载可能会损坏文件。如果服务器支持强HTTP ETag标头,则可以检测到这一点,以便下载者可以检查是否正在恢复相同的文件。
我并不声称它没有bug。您可能应该围绕这个脚本添加一个校验和逻辑来检测下载错误,如果校验和不匹配,则从头开始重试。
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()
您可以尝试这样的方法。它逐行读取文件并将其附加到文件中。它还进行检查以确保您不会越过同一条线。我会写另一个脚本,也可以分块完成。
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")