Как получить файл из интернета с помощью Python: примеры кода
Основные подходы к загрузке файлов
Как наиболее эффективно скачать файл с контролем потока и возможностью обработки ошибок?
Современный и гибкий способ - использование библиотеки requests с параметром stream=True. Такой подход позволяет обрабатывать файл по частям (chunks), не загружая его целиком в память, что критично для больших объёмов.
import requests
url = "https://example.com/bigfile.zip"
local_filename = "bigfile.zip"
with requests.get(url, stream=True, timeout=30) as r:
r.raise_for_status() # проверка кода ответа
with open(local_filename, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
Python download file (скачивание файла с помощью python)
Пояснение шагов:
- stream=True - не загружает тело ответа сразу, а позволяет читать его частями.
- raise_for_status() - генерирует исключение при неудачном статусе (4xx, 5xx).
- iter_content(chunk_size) - итератор по бинарным частям указанного размера.
- timeout=30 - ограничение времени ожидания соединения.
Возможные проблемы и их решения:
- Ошибка соединения (ConnectionError) - проверьте URL, интернет и настройки прокси. Используйте try-except.
- Превышение времени ожидания (Timeout) - увеличьте значение timeout или настройте повторные попытки (retry).
- Слишком много редиректов (TooManyRedirects) - проверьте, не уходит ли запрос в бесконечный цикл. Ограничьте количество редиректов через allow_redirects=True и установите maximum.
- Недостаточно места на диске - проверяйте свободное место перед записью, используйте os.fstat или перехватывайте исключение OSError.
Цель применения: универсальный метод для большинства сценариев - от небольших файлов до гигабайтных архивов. Позволяет легко добавить прогресс-бар (см. примеры ниже) и обрабатывать ошибки.
Как скачать файл без сторонних библиотек, используя только стандартную библиотеку?
Функция urllib.request.urlretrieve - самый простой способ, встроенный в Python. Она скачивает файл по URL и сохраняет его локально.
import urllib.request
url = "https://example.com/smallfile.txt"
local_filename, headers = urllib.request.urlretrieve(url, filename="smallfile.txt")
Функция возвращает кортеж из имени сохранённого файла и объекта http.client.HTTPMessage с заголовками ответа.
Недостатки и ошибки:
- Нет встроенного контроля прогресса (но можно передать callback reporthook).
- При ошибках (например, 404) функция генерирует URLError, но не проверяет код статуса напрямую.
- Устаревшая функция - в Python 3.9+ рекомендуется использовать requests или urllib.request.urlopen с ручной записью.
Цель применения: быстрые скрипты без установки дополнительных пакетов, когда не требуется детальная обработка ошибок или прогресс.
Как имитировать работу консольной утилиты wget из Python?
Библиотека wget предоставляет функцию download, которая скачивает файл и выводит прогресс-бар в консоль.
import wget
url = "https://example.com/file.zip"
local_filename = wget.download(url, out="file.zip")
Прогресс отображается автоматически. Функция возвращает имя сохранённого файла.
Ограничения:
- Не поддерживает настройку таймаутов, заголовков, куки.
- При ошибке соединения может зависнуть.
- Не обновлялась несколько лет, совместимость с новыми версиями Python может быть нарушена.
Цель применения: когда требуется минималистичное решение с визуальным прогрессом и нет необходимости в тонкой настройке.
Как загрузить несколько файлов одновременно, чтобы ускорить процесс?
Асинхронный подход с помощью aiohttp и asyncio позволяет запускать множество запросов параллельно. Это особенно полезно для большого количества небольших файлов.
import asyncio
import aiohttp
async def download_file(session, url, filename):
async with session.get(url) as response:
response.raise_for_status()
with open(filename, "wb") as f:
async for chunk in response.content.iter_chunked(8192):
f.write(chunk)
async def main():
urls = ["url1", "url2", "url3"]
async with aiohttp.ClientSession() as session:
tasks = [download_file(session, url, f"file{i}.dat") for i, url in enumerate(urls)]
await asyncio.gather(*tasks)
asyncio.run(main())
Сложности и решения:
- Необходимо установить aiohttp (pip install aiohttp).
- Важно управлять количеством одновременных соединений через asyncio.Semaphore, чтобы не заблокировать сервер или не получить отказ из-за лимита.
- Ошибки в одном файле не должны останавливать остальные - используйте return_exceptions=True в gather.
Цель применения: массовая загрузка десятков и сотен файлов, когда последовательная загрузка занимает слишком много времени.
Как скачать файл с FTP-сервера?
Модуль ftplib предоставляет класс FTP для работы с протоколом FTP. Файл загружается командой retrbinary.
from ftplib import FTP
ftp = FTP("ftp.example.com")
ftp.login(user="anonymous", passwd="guest@")
with open("remote_file.zip", "wb") as f:
ftp.retrbinary("RETR remote_file.zip", f.write)
ftp.quit()
Типичные ошибки:
- Ошибка соединения - проверьте хост, порт, наличие брандмауэра.
- Проблемы с активным/пассивным режимом. Если соединение зависает, попробуйте ftp.set_pasv(False) или True.
- Неверные учётные данные - используйте исключение ftplib.error_perm.
Цель применения: работа с FTP-серверами, часто встречающимися в корпоративной среде или на старых хостингах.
Как отображать прогресс загрузки большого файла в консоли?
Комбинация библиотеки tqdm и метода iter_content из requests даёт удобный индикатор выполнения.
import requests
from tqdm import tqdm
url = "https://example.com/bigfile.iso"
response = requests.get(url, stream=True)
total_size = int(response.headers.get("content-length", 0))
with open("bigfile.iso", "wb") as f:
with tqdm(total=total_size, unit="B", unit_scale=True, desc="Загрузка") as pbar:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
pbar.update(len(chunk))
Индикатор показывает скорость, оставшееся время и процент выполнения.
Нюансы:
- Если сервер не возвращает content-length, индикатор не сможет показать процент (будет только счётчик).
- Для корректного отображения необходимо передать общий размер в tqdm.
Цель применения: любой сценарий, где важно видеть динамику загрузки, особенно для файлов размером от десятков мегабайт.
Продвинутые техники загрузки файлов
Загрузка с возобновлением после обрыва (Range)
Для докачки файла используется HTTP-заголовок Range, который указывает серверу, с какого байта продолжить. Ниже приведён пример, который проверяет, существует ли уже частично скачанный файл, и при необходимости продолжает загрузку.
import requests
import os
url = "https://example.com/largefile.bin"
local_path = "largefile.bin"
resume_header = {}
if os.path.exists(local_path):
resume_header["Range"] = f"bytes={os.path.getsize(local_path)}-"
with requests.get(url, headers=resume_header, stream=True) as r:
if r.status_code == 416: # Range Not Satisfiable - файл уже полностью скачан
print("Файл уже загружен полностью.")
else:
mode = "ab" if os.path.exists(local_path) else "wb"
with open(local_path, mode) as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
print("Загрузка завершена.")
Вывод (пример): Файл уже загружен полностью. - или - Загрузка завершена.
Пояснение: если сервер поддерживает докачку (код 206 Partial Content), он вернёт только недостающие байты. Код 416 означает, что файл уже скачан целиком. Данный метод требует поддержки Range сервером.
Многопоточная загрузка частей файла
Для ускорения можно разбить файл на несколько частей и загружать их параллельно, а затем собрать. Это особенно эффективно на высокоскоростных каналах, где узким местом является задержка соединения.
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
import os
url = "https://example.com/bigfile.bin"
num_threads = 4
chunk_size = 1024 * 1024 * 10 # 10 MB
def download_chunk(start, end, i):
headers = {"Range": f"bytes={start}-{end - 1}"}
with requests.get(url, headers=headers, stream=True) as r:
with open(f"part_{i}", "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
# Получить общий размер
resp_head = requests.head(url)
total_size = int(resp_head.headers["content-length"])
ranges = []
for i in range(num_threads):
start = i * (total_size // num_threads)
end = total_size if i == num_threads - 1 else (i + 1) * (total_size // num_threads)
ranges.append((start, end, i))
with ThreadPoolExecutor(max_workers=num_threads) as executor:
futures = [executor.submit(download_chunk, *r) for r in ranges]
for future in as_completed(futures):
future.result() # проверяем ошибки
# Собрать части
with open("bigfile.bin", "wb") as out:
for i in range(num_threads):
with open(f"part_{i}", "rb") as part:
out.write(part.read())
os.remove(f"part_{i}")
print("Файл собран из частей.")
Вывод: Файл собран из частей.
Пояснение: каждый поток качает свой диапазон байтов. После завершения всех потоков части объединяются в итоговый файл. Этот метод требует поддержки Range на сервере и увеличивает нагрузку на сеть.
Загрузка через прокси-сервер с аутентификацией
Библиотека requests позволяет указать прокси, в том числе с логином и паролем. Это часто требуется в корпоративных сетях.
import requests
proxies = {
"http": "http://user:password@proxy.company.com:8080",
"https": "http://user:password@proxy.company.com:8080"
}
url = "https://example.com/file.pdf"
response = requests.get(url, proxies=proxies, stream=True, timeout=30)
response.raise_for_status()
with open("file.pdf", "wb") as f:
for chunk in response.iter_content(chunk_size=4096):
f.write(chunk)
print("Файл скачан через прокси.")
Вывод: Файл скачан через прокси.
Пояснение: прокси задаются в виде словаря, где ключ - протокол, значение - адрес прокси. Если прокси не требует аутентификации, логин и пароль можно опустить. Для больших файлов также используйте stream=True.