Как скачать файл по HTTP с помощью Python?
У меня есть утилита, которая по расписанию скачивает MP3-файл с веб-сайта, а затем создает/обновляет XML-файл подкаста для iTunes. Обработка XML написана на Python, но в настоящее время я использую wget в Windows .bat файле для скачивания MP3-файла. Я хочу заменить wget на код на Python, чтобы вся утилита была на Python.
Как можно скачивать файлы с помощью Python вместо wget?
Вы можете загружать файлы по протоколу HTTP, используя встроенную библиотеку urllib Python или популярную библиотеку requests. Библиотека requests обычно рекомендуется за свою простоту и надежную обработку ошибок, в то время как urllib встроен в Python и не требует дополнительных зависимостей. Оба метода могут легко заменить wget в вашем утилите для подкастов, позволяя выполнить весь процесс на Python.
Содержание
- Базовые методы загрузки
- Использование библиотеки requests
- Расширенные возможности
- Обработка ошибок и лучшие практики
- Пример полной утилиты для подкастов
- Выбор правильного подхода
Базовые методы загрузки
Python предлагает несколько встроенных способов загрузки файлов по протоколу HTTP. Простейший подход использует urllib.request, который является частью стандартной библиотеки Python.
Использование urllib.request.urlretrieve
Функция urlretrieve - это самый простой способ загрузки файла, аналогичный wget:
import urllib.request
url = 'https://example.com/podcast_episode.mp3'
filename = 'podcast_episode.mp3'
urllib.request.urlretrieve(url, filename)
Этот метод загружает весь файл сразу и сохраняет его непосредственно в указанный файл. Как отмечено на Real Python, этот подход подходит для простых задач загрузки файлов, когда вам не нужен обратный связь о прогрессе или расширенные функции.
Использование urllib.request.urlopen
Для большего контроля над процессом загрузки можно использовать urlopen:
import urllib.request
url = 'https://example.com/podcast_episode.mp3'
filename = 'podcast_episode.mp3'
with urllib.request.urlopen(url) as response, open(filename, 'wb') as out_file:
out_file.write(response.read())
Как объясняется на Stack Overflow, этот метод дает больше гибкости, так как он работает с объектами, похожими на файлы, и позволяет обрабатывать ответ перед записью на диск.
Использование библиотеки requests
Библиотека requests - это сторонний пакет, который предоставляет более элегантный и мощный API для HTTP-запросов. Она не включена в стандартную библиотеку Python, поэтому сначала нужно установить ее:
pip install requests
Базовая загрузка с requests.get
import requests
url = 'https://example.com/podcast_episode.mp3'
filename = 'podcast_episode.mp3'
response = requests.get(url)
with open(filename, 'wb') as file:
file.write(response.content)
Как объясняется на Stack Overflow, пакет requests имеет очень простой API для начала работы и предпочитается многими разработчиками для задач, связанных с HTTP.
Потоковая загрузка для больших файлов
Для больших файлов, таких как MP3, лучше использовать потоковую загрузку, а не загружать весь файл в память:
import requests
def download_file(url, filename):
with requests.get(url, stream=True) as r:
r.raise_for_status()
with open(filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
url = 'https://example.com/large_podcast_episode.mp3'
download_file(url, 'large_episode.mp3')
Расширенные возможности
Индикатор прогресса с tqdm
Для лучшего пользовательского опыта, особенно при загрузке больших файлов, можно добавить индикатор прогресса с помощью библиотеки tqdm:
pip install tqdm
import requests
from tqdm import tqdm
def download_with_progress(url, filename):
response = requests.get(url, stream=True)
total_size = int(response.headers.get('content-length', 0))
with open(filename, 'wb') as file, tqdm(
desc=filename,
total=total_size,
unit='B',
unit_scale=True,
unit_divisor=1024,
) as progress_bar:
for chunk in response.iter_content(chunk_size=8192):
size = file.write(chunk)
progress_bar.update(size)
# Использование
download_with_progress('https://example.com/podcast_episode.mp3', 'episode.mp3')
Как объясняется на Alpharithms, эта реализация обеспечивает визуальную обратную связь во время загрузки, что полезно для длительных загрузок.
Аутентификация для защищенных файлов
Если ваши файлы подкастов защищены аутентификацией:
import requests
def download_protected_file(url, filename, username, password):
response = requests.get(url, auth=(username, password), stream=True)
response.raise_for_status()
with open(filename, 'wb') as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
Согласно HeyCoach, аутентификация может быть обработана бесшовно с помощью параметра auth.
Обработка ошибок и лучшие практики
Правильная обработка ошибок
Всегда реализовывайте правильную обработку ошибок для сетевых операций:
import requests
import urllib.request
import urllib.error
def safe_download_with_requests(url, filename):
try:
response = requests.get(url, stream=True, timeout=30)
response.raise_for_status() # Вызывает HTTPError для плохих ответов
with open(filename, 'wb') as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
return True
except requests.exceptions.RequestException as e:
print(f"Загрузка не удалась: {e}")
return False
def safe_download_with_urllib(url, filename):
try:
urllib.request.urlretrieve(url, filename)
return True
except urllib.error.URLError as e:
print(f"Загрузка не удалась: {e}")
return False
Лучшие практики из Medium
- Всегда используйте
raise_for_status()для перехвата HTTP-ошибок - Используйте потоковую загрузку с
stream=Trueиiter_content()для больших файлов - Добавляйте правильную обработку ошибок для сетевых проблем
- Используйте индикаторы прогресса для лучшего пользовательского опыта
- Проверяйте загруженные файлы
Пример полной утилиты для подкастов
Вот полный пример, который заменяет ваш подход с использованием wget:
import requests
import os
import xml.etree.ElementTree as ET
from datetime import datetime
from urllib.parse import urljoin
import logging
# Настройка логирования
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class PodcastUpdater:
def __init__(self, base_url, output_dir='podcasts'):
self.base_url = base_url
self.output_dir = output_dir
os.makedirs(output_dir, exist_ok=True)
def download_episode(self, episode_url, episode_id):
"""Загрузка эпизода подкаста с отслеживанием прогресса и обработкой ошибок"""
filename = os.path.join(self.output_dir, f"{episode_id}.mp3")
try:
# Потоковая загрузка с индикатором прогресса
with requests.get(episode_url, stream=True, timeout=30) as response:
response.raise_for_status()
# Получение общего размера файла
total_size = int(response.headers.get('content-length', 0))
logger.info(f"Загрузка {episode_id} ({total_size/1024/1024:.1f} МБ)")
with open(filename, 'wb') as file:
downloaded = 0
for chunk in response.iter_content(chunk_size=8192):
if chunk: # фильтрация keep-alive чанков
file.write(chunk)
downloaded += len(chunk)
# Логирование прогресса каждые 10%
if total_size > 0 and downloaded % (total_size // 10) == 0:
progress = (downloaded / total_size) * 100
logger.info(f"Прогресс: {progress:.0f}%")
logger.info(f"Успешно загружен {filename}")
return True
except requests.exceptions.RequestException as e:
logger.error(f"Не удалось загрузить {episode_id}: {e}")
return False
def update_podcast_xml(self, episodes_config):
"""Обновление XML-файла подкаста новыми эпизодами"""
xml_file = os.path.join(self.output_dir, 'podcast.xml')
# Создание XML-структуры (упрощенный пример)
root = ET.Element('rss', version='2.0')
channel = ET.SubElement(root, 'channel')
# Добавление базовой информации о подкасте
ET.SubElement(channel, 'title').text = "Мой подкаст"
ET.SubElement(channel, 'description').text = "Отличный подкаст"
ET.SubElement(channel, 'link').text = self.base_url
# Добавление эпизодов
for episode in episodes_config:
if self.download_episode(episode['url'], episode['id']):
item = ET.SubElement(channel, 'item')
ET.SubElement(item, 'title').text = episode['title']
ET.SubElement(item, 'description').text = episode['description']
ET.SubElement(item, 'pubDate').text = datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT')
ET.SubElement(item, 'enclosure', {
'url': urljoin(self.base_url, f"{episode['id']}.mp3"),
'type': 'audio/mpeg',
'length': str(episode.get('size', 0))
})
# Запись XML-файла
tree = ET.ElementTree(root)
tree.write(xml_file, encoding='utf-8', xml_declaration=True)
logger.info(f"Обновлен XML подкаста: {xml_file}")
# Пример использования
if __name__ == "__main__":
podcast = PodcastUpdater('https://my-podcast-website.com')
episodes = [
{
'id': 'episode001',
'title': 'Первый эпизод',
'description': 'Это мой первый эпизод',
'url': 'https://my-podcast-website.com/episodes/episode001.mp3',
'size': 52428800 # 50 МБ
},
{
'id': 'episode002',
'title': 'Второй эпизод',
'description': 'Это мой второй эпизод',
'url': 'https://my-podcast-website.com/episodes/episode002.mp3',
'size': 78643200 # 75 МБ
}
]
podcast.update_podcast_xml(episodes)
Выбор правильного подхода
Когда использовать urllib
Используйте встроенный urllib Python, когда:
- Вы хотите избежать внешних зависимостей
- Вы работаете в ограниченной среде
- Вам нужны простые загрузки без расширенных функций
- Ваша утилита должна быть самодостаточной
Как отмечено на Tutorialspoint, urllib.request подходит для простых задач загрузки файлов.
Когда использовать requests
Используйте библиотеку requests, когда:
- Вам нужна лучшая обработка ошибок
- Вы хотите получить более чистый и читаемый код
- Требуются расширенные функции, такие как потоковая передача, сессии или аутентификация
- Вы работаете со сложными сценариями HTTP
- Вы хотите легко добавить индикаторы прогресса
Согласно AskPython, библиотека requests предоставляет эксклюзивный и эффективный способ обработки HTTP-запросов в Python.
Рекомендации для вашего случая использования
Для вашей утилиты подкаста я рекомендую использовать библиотеку requests с потоковой передачей и правильной обработкой ошибок, потому что:
- Файлы MP3 могут быть большими, поэтому потоковая передача предотвращает проблемы с памятью
- Обработка ошибок критически важна для автоматизированных запланированных загрузок
- Индикаторы прогресса обеспечивают обратную связь во время загрузки
- Код будет более поддерживаемым и читаемым
- Вы можете легко расширить его для аутентификации, если это потребуется в будущем
Заключение
Загрузка файлов по протоколу HTTP в Python проста и может легко заменить wget в вашей утилите для подкастов. Вот ключевые выводы:
- И
urllib, иrequestsмогут обрабатывать загрузку файлов, при этомrequestsпредлагает лучшую обработку ошибок и больше функций - Используйте потоковую загрузку (
stream=Trueсiter_content()) для больших файлов, таких как MP3, чтобы избежать проблем с памятью - Всегда реализовывайте правильную обработку ошибок с помощью
raise_for_status()и блоков try-catch - Добавляйте индикаторы прогресса с помощью
tqdmдля лучшего пользовательского опыта при загрузке больших файлов - Учитывайте аутентификацию, если ваши файлы подкастов защищены доступом
Для вашей конкретной утилиты подкаста библиотека requests с потоковой передачей, обработкой ошибок и отслеживанием прогресса обеспечивает наиболее надежное решение. Полный пример выше показывает, как интегрировать загрузку файлов с обработкой XML в один скрипт Python, устраняя необходимость во внешних инструментах, таких как wget.
Источники
- Real Python - How to Download Files From URLs With Python
- Stack Overflow - How to download a file over HTTP?
- Stack Overflow - Download file from web in Python 3
- Tutorialspoint - Download a file over HTTP in Python
- AskPython - Python HTTP File Download: Using the Requests Library
- Alpharithms - Progress Bars for Python Downloads
- HeyCoach - Handling File Downloads In Python
- Medium - Downloading Files from URLs in Python
- WebScraping.AI - How do I handle file downloads during web scraping with Python?
- Geekflare - 5 Ways to Download Files from a URL Using Python