【发布时间】:2021-04-11 12:07:44
【问题描述】:
我一直在 tkinter 和 requests 中制作这个下载管理器应用程序,我意识到有时如果用户同时下载多个文件,它无法跟上,所有下载都没有任何结果错误。我还尝试了 urllib3 和标准 urllib,尽管 urrlib 的唯一区别是它只是引发了错误,但仍然失败。如果下载结束,我想以某种方式制作我的程序:
- 首先检查文件大小是否小于应有的大小
- 如果是,则获取该文件的大小并制作一个范围标题,如下所示:{"Range": f"bytes={current_size}-{file_size}"}
- 将文件的其余部分存储在临时文件中。下载后,从两个文件中获取数据并将其写入一个(合并文件)
我使用了 while 循环和临时计数器,但问题是当请求无法跟上并到达 while 循环时,它会生成数百万个临时文件,每个文件的大小为 197 字节,但它不会工作。我还尝试只使用一个 if 循环,希望它能够被修复,不同之处在于它没有创建数百万个文件但仍然无法正常工作。最后,我尝试编写一个单独的模拟程序,该程序直接获取其余文件并将其合并为下载一半的文件,并且它可以工作,但是由于某种原因,当我在我的程序中尝试它时却没有。请记住,我不想为每个临时文件创建一个线程,因为它可以很容易地写在与下载文件的线程相同的线程中。我怎样才能做到这一点?我的代码(请注意,此函数在单独的线程中运行):
currently_downloading = np.array([], dtype='S')
current_temp = 0
def download_files():
global files_downloading, times_clicked, currently_downloading, packed, last_temp, current_temp
try:
abort = False
win = None
available_num = 0
downloaded = 0
url = str(url_entry.get())
try:
headers = requests.head(url, headers={'accept-encoding': ''}).headers
except ValueError:
raise InvalidURL()
try:
file_size = float(headers['Content-Length'])
except TypeError:
raise NotDownloadable()
name = ""
formatname = ""
if num.get() == 1:
name = url.split("/")[-1].split(".")[0]
else:
if name_entry.get().strip() != "":
for char in str(name_entry.get()):
if char in banned_chars:
print("Usage of banned characters")
raise BannedCharsUsage()
else:
name = str(name_entry.get())
else:
raise EmptyName()
if var.get() == 1:
formatname = '.' + headers['Content-Type'].split('/')[1]
else:
if str(format_entry.get())[0] == '.' and len(format_entry.get()) >= 3:
formatname = str(format_entry.get())
else:
raise InvalidFormat()
fullname = str(name) + formatname
path = (str(output_entry.get()) + "/").replace(r" \ ".strip(), "/")
if chum.get() == 1:
conn = sqlite3.connect("DEF_PATH.db")
c = conn.cursor()
c.execute("SELECT * FROM DIRECTORY_LIST WHERE SELECTED_DEF = 1")
crnt_default_path = np.array(c.fetchone())
path = str(crnt_default_path[0] + "/").replace(r" \ ".strip(), "/")
conn.commit()
conn.close()
else:
pass
all_files_dir = np.array([], dtype='S')
for file in os.listdir(path):
all_files_dir = np.append(all_files_dir, path + file)
all_files_dir = np.concatenate((all_files_dir, currently_downloading))
while path + fullname in all_files_dir:
for element in currently_downloading:
if element not in all_files_dir:
all_files_dir = np.append(all_files_dir, element)
available_num += 1
if num.get() == 1:
name = url.split("/")[-1].split(".")[0] + f" ({available_num})"
else:
name = str(name_entry.get()) + f" ({available_num})"
fullname = name + formatname
if path + fullname not in all_files_dir:
currently_downloading = np.append(currently_downloading, path + fullname)
available_num = 0
break
else:
currently_downloading = np.append(currently_downloading, path + fullname)
def cancel_dl():
nonlocal abort
abort = True
start_time = time.time()
try:
r = requests.get(url, allow_redirects=False, stream=True)
start = last_print = time.time()
with open(path + fullname, 'wb') as fp:
for chunk in r.iter_content(chunk_size=4096):
if abort:
raise AbortException()
downloaded += fp.write(chunk)
if downloaded > 1000000:
lbl_crnt_size.config(text=f"Downloaded: {round(downloaded / 1000000, 2)} MB")
else:
lbl_crnt_size.config(text=f"Downloaded: {round(downloaded / 1000, 2)} KB")
pct_done = int(downloaded / file_size * 100)
lbl_percent.config(text=f"{round(pct_done, 2)} %")
download_prg["value"] = pct_done
now = time.time()
if now - last_print >= 1:
speed_sec = round(downloaded / (now - start))
if speed_sec > 1000000:
lbl_speed.config(text=f"{round(speed_sec / 1000000, 3)} MB/s")
else:
lbl_speed.config(text=f"{round(speed_sec / 1000, 3)} KB/s")
last_print = time.time()
while os.stat(path + fullname).st_size < file_size:
current_temp += 1
rng = {"Range": f"bytes={os.stat(path + fullname).st_size}-{file_size}"}
r = requests.get(url, allow_redirects=False, stream=True, headers=rng)
start = last_print = time.time()
with open(f"temp/Temp-{current_temp}{formatname}", 'wb') as fp:
for chunk in r.iter_content(chunk_size=4096):
if abort:
raise AbortException()
downloaded += fp.write(chunk)
if downloaded > 1000000:
lbl_crnt_size.config(text=f"Downloaded: {round(downloaded / 1000000, 2)} MB")
else:
lbl_crnt_size.config(text=f"Downloaded: {round(downloaded / 1000, 2)} KB")
pct_done = int(downloaded / file_size * 100)
lbl_percent.config(text=f"{round(pct_done, 2)} %")
download_prg["value"] = pct_done
now = time.time()
if now - last_print >= 1:
speed_sec = round(downloaded / (now - start))
if speed_sec > 1000000:
lbl_speed.config(text=f"{round(speed_sec / 1000000, 3)} MB/s")
else:
lbl_speed.config(text=f"{round(speed_sec / 1000, 3)} KB/s")
last_print = time.time()
with open(f"temp/Temp-{current_temp}{formatname}", 'rb') as fp:
temp_binary = fp.read()
with open(path + fullname, 'rb') as fp:
main_binary = fp.read()
with open(path + fullname, 'wb') as fp:
fp.write(main_binary + temp_binary)
except AbortException:
if os.path.exists(path + fullname):
os.remove(path + fullname)
【问题讨论】:
-
与其允许如此多的下载导致连接失败,不如设置一个下载队列,一次下载一个(或两个,或三个)不是更容易吗?
-
@joedeandev,嗯,我希望程序像谷歌浏览器一样,这样你就可以同时下载文件,但这不是唯一的问题,因为我意识到如果用户的互联网连接不是很快它仍然失败,如果用户的互联网连接中断怎么办?我需要考虑这些协议。
-
@OmidKetabollahi 为什么不将下载的内容添加到临时缓存文件夹中?所以即使下载失败,下载的数据仍然会保存在那个文件夹中,当用户重新连接时,可以查看文件夹中的缓存文件,然后继续?
-
这能回答你的问题吗? How to resume file download in Python?
标签: python python-3.x file tkinter python-requests