НейроАгент

Как скачивать файлы в Python: Полное руководство

Узнайте, как скачивать файлы по HTTP в Python с помощью библиотек urllib и requests. Полное руководство с обработкой ошибок, стримингом и примерами утилит для подкастов.

Как скачать файл по 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.


Содержание


Базовые методы загрузки

Python предлагает несколько встроенных способов загрузки файлов по протоколу HTTP. Простейший подход использует urllib.request, который является частью стандартной библиотеки Python.

Использование urllib.request.urlretrieve

Функция urlretrieve - это самый простой способ загрузки файла, аналогичный wget:

python
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:

python
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, поэтому сначала нужно установить ее:

bash
pip install requests

Базовая загрузка с requests.get

python
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, лучше использовать потоковую загрузку, а не загружать весь файл в память:

python
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:

bash
pip install tqdm
python
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, эта реализация обеспечивает визуальную обратную связь во время загрузки, что полезно для длительных загрузок.

Аутентификация для защищенных файлов

Если ваши файлы подкастов защищены аутентификацией:

python
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.

Обработка ошибок и лучшие практики

Правильная обработка ошибок

Всегда реализовывайте правильную обработку ошибок для сетевых операций:

python
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

  1. Всегда используйте raise_for_status() для перехвата HTTP-ошибок
  2. Используйте потоковую загрузку с stream=True и iter_content() для больших файлов
  3. Добавляйте правильную обработку ошибок для сетевых проблем
  4. Используйте индикаторы прогресса для лучшего пользовательского опыта
  5. Проверяйте загруженные файлы

Пример полной утилиты для подкастов

Вот полный пример, который заменяет ваш подход с использованием wget:

python
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 с потоковой передачей и правильной обработкой ошибок, потому что:

  1. Файлы MP3 могут быть большими, поэтому потоковая передача предотвращает проблемы с памятью
  2. Обработка ошибок критически важна для автоматизированных запланированных загрузок
  3. Индикаторы прогресса обеспечивают обратную связь во время загрузки
  4. Код будет более поддерживаемым и читаемым
  5. Вы можете легко расширить его для аутентификации, если это потребуется в будущем

Заключение

Загрузка файлов по протоколу HTTP в Python проста и может легко заменить wget в вашей утилите для подкастов. Вот ключевые выводы:

  1. И urllib, и requests могут обрабатывать загрузку файлов, при этом requests предлагает лучшую обработку ошибок и больше функций
  2. Используйте потоковую загрузку (stream=True с iter_content()) для больших файлов, таких как MP3, чтобы избежать проблем с памятью
  3. Всегда реализовывайте правильную обработку ошибок с помощью raise_for_status() и блоков try-catch
  4. Добавляйте индикаторы прогресса с помощью tqdm для лучшего пользовательского опыта при загрузке больших файлов
  5. Учитывайте аутентификацию, если ваши файлы подкастов защищены доступом

Для вашей конкретной утилиты подкаста библиотека requests с потоковой передачей, обработкой ошибок и отслеживанием прогресса обеспечивает наиболее надежное решение. Полный пример выше показывает, как интегрировать загрузку файлов с обработкой XML в один скрипт Python, устраняя необходимость во внешних инструментах, таких как wget.


Источники

  1. Real Python - How to Download Files From URLs With Python
  2. Stack Overflow - How to download a file over HTTP?
  3. Stack Overflow - Download file from web in Python 3
  4. Tutorialspoint - Download a file over HTTP in Python
  5. AskPython - Python HTTP File Download: Using the Requests Library
  6. Alpharithms - Progress Bars for Python Downloads
  7. HeyCoach - Handling File Downloads In Python
  8. Medium - Downloading Files from URLs in Python
  9. WebScraping.AI - How do I handle file downloads during web scraping with Python?
  10. Geekflare - 5 Ways to Download Files from a URL Using Python