Avatar ·

Как сохранить изображение с URL в PHP? Функция не работает с некоторыми картинками

Всем привет. Я новичок в PHP и столкнулся с проблемой при сохранении изображений с внешних URL. Пишу простой парсер для загрузки аватарок пользователей. У меня есть такой код: ```php $url = 'https://example.com/avatar.jpg'; $image = file_get_contents($url); file_put_contents('avatars/user1.jpg', $image); ``` С некоторыми сайтами это работает, но с другими нет – возвращает пустой файл или ошибку. Я пробовал разные подходы: 1. Использовал `copy($url, $local_path)` – тоже не всегда срабатывает. 2. Пытался через CURL, но там еще больше путаницы. 3. Некоторые картинки сохраняются битыми (не открываются). Подозреваю, что проблема в том, что сервер может блокировать прямые запросы или требует заголовки User-Agent. Также замечал, что если в URL есть кириллические символы или пробелы, то ничего не работает. Вот пример URL, который не работает: ``` https://site.com/фото/аватарка пользователя.jpg ``` Подскажите, как правильно и надежно сохранять изображения в PHP? Нужно учесть разные случаи: HTTPS, редиректы, кириллицу в именах файлов, проверку на то, что это действительно изображение, а не что-то другое. Заранее спасибо! <<>>

Правильное и надежное сохранение изображений с URL в PHP

Проблема, с которой вы столкнулись, очень распространена. file_get_contents() и copy() — простые, но ненадежные функции для загрузки файлов из внешних источников. Они не позволяют контролировать HTTP-заголовки, таймауты, не обрабатывают редиректы и часто блокируются серверами.

Лучший подход — использовать cURL с правильной настройкой. Вот полное решение с обработкой всех типичных проблем:

<?php
/**
 * Надежное сохранение изображения с URL
 * 
 * @param string $url       URL изображения
 * @param string $save_path Путь для сохранения
 * @return bool             true при успехе, false при ошибке
 */
function saveImageFromUrl($url, $save_path) {
    // 1. Обрабатываем кириллицу и спецсимволы в URL
    $url = encodeUrl($url);
    
    // 2. Инициализируем cURL
    $ch = curl_init($url);
    
    // 3. Настройки для работы как реальный браузер
    curl_setopt_array($ch, [
        // Возвращаем результат в переменную
        CURLOPT_RETURNTRANSFER => true,
        // Следовать редиректам (до 5)
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 5,
        // Таймауты
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_CONNECTTIMEOUT => 10,
        // User-Agent как у реального браузера
        CURLOPT_USERAGENT      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        // Принимаем любые SSL-сертификаты (для HTTPS)
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        // Разрешаем любые типы контента
        CURLOPT_HTTPHEADER     => [
            'Accept: image/webp,image/*,*/*;q=0.8',
            'Accept-Language: ru-RU,ru;q=0.9,en;q=0.8',
        ],
    ]);
    
    // 4. Выполняем запрос
    $image_data = curl_exec($ch);
    $http_code  = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    $error      = curl_error($ch);
    curl_close($ch);
    
    // 5. Проверяем ошибки
    if ($error) {
        error_log("cURL error: " . $error);
        return false;
    }
    
    // 6. Проверяем HTTP-код ответа
    if ($http_code !== 200) {
        error_log("HTTP error: " . $http_code);
        return false;
    }
    
    // 7. Проверяем, что это действительно изображение
    if (!isValidImageType($content_type)) {
        error_log("Invalid content type: " . $content_type);
        return false;
    }
    
    // 8. Дополнительная проверка через getimagesizefromstring
    $image_info = @getimagesizefromstring($image_data);
    if (!$image_info) {
        error_log("Not a valid image file");
        return false;
    }
    
    // 9. Создаем директорию, если её нет
    $dir = dirname($save_path);
    if (!is_dir($dir)) {
        mkdir($dir, 0755, true);
    }
    
    // 10. Сохраняем файл
    $result = file_put_contents($save_path, $image_data);
    if ($result === false) {
        error_log("Failed to write file: " . $save_path);
        return false;
    }
    
    return true;
}

/**
 * Обработка кириллицы в URL
 */
function encodeUrl($url) {
    // Разбиваем URL на части
    $parts = parse_url($url);
    if (!isset($parts['path'])) {
        return $url;
    }
    
    // Кодируем только кириллические символы в пути
    $path_parts = explode('/', $parts['path']);
    foreach ($path_parts as &$part) {
        if (preg_match('/[а-яА-ЯёЁ]/u', $part)) {
            $part = rawurlencode($part);
        }
    }
    $parts['path'] = implode('/', $path_parts);
    
    // Собираем URL обратно
    return (isset($parts['scheme']) ? $parts['scheme'] . '://' : '') .
           (isset($parts['host']) ? $parts['host'] : '') .
           (isset($parts['path']) ? $parts['path'] : '') .
           (isset($parts['query']) ? '?' . $parts['query'] : '');
}

/**
 * Проверка, что контент — изображение
 */
function isValidImageType($content_type) {
    $valid_types = [
        'image/jpeg',
        'image/png',
        'image/gif',
        'image/webp',
        'image/bmp',
        'image/svg+xml',
    ];
    
    foreach ($valid_types as $type) {
        if (strpos($content_type, $type) !== false) {
            return true;
        }
    }
    return false;
}

// Пример использования
$url = 'https://site.com/фото/аватарка пользователя.jpg';
$save_path = __DIR__ . '/avatars/user1.jpg';

if (saveImageFromUrl($url, $save_path)) {
    echo "Изображение успешно сохранено: " . $save_path;
} else {
    echo "Ошибка при сохранении изображения";
}
?>

Почему это работает?

  • cURL — полный контроль над HTTP-запросами
  • User-Agent — сервер считает, что запрос от браузера
  • Follow redirects — обрабатываем редиректы
  • URL encoding — корректная работа с кириллицей
  • Проверка типа контента — защита от загрузки не-изображений
  • getimagesizefromstring() — финальная проверка, что файл — валидное изображение

Также рекомендую добавить обработку реферальных заголовков (Referer) для сайтов, которые защищают контент, и использовать прокси, если сервер банит по IP.

<<>>

Альтернативный подход: использование GD библиотеки для полного контроля

Если вам нужно не просто сохранить изображение, но и изменить его размер, формат или наложить водяной знак, используйте GD библиотеку в паре с cURL:

<?php
function saveAndOptimizeImage($url, $save_path, $max_width = 1920) {
    // Получаем изображение через cURL (как в первом ответе)
    $image_data = curlGetImage($url);
    if (!$image_data) return false;
    
    // Создаем ресурс из строки
    $source_image = @imagecreatefromstring($image_data);
    if (!$source_image) return false;
    
    // Получаем размеры
    $width = imagesx($source_image);
    $height = imagesy($source_image);
    
    // Если ширина больше максимальной — уменьшаем
    if ($width > $max_width) {
        $ratio = $max_width / $width;
        $new_width = $max_width;
        $new_height = (int)($height * $ratio);
        
        $resized_image = imagecreatetruecolor($new_width, $new_height);
        imagecopyresampled($resized_image, $source_image, 0, 0, 0, 0, 
                          $new_width, $new_height, $width, $height);
        
        // Сохраняем уменьшенную копию
        imagejpeg($resized_image, $save_path, 85); // 85% качество
        imagedestroy($resized_image);
    } else {
        // Сохраняем как есть
        imagejpeg($source_image, $save_path, 90);
    }
    
    imagedestroy($source_image);
    return true;
}
?>

Плюсы этого подхода: