Как сохранить изображение с URL в PHP? Функция не работает с некоторыми картинками
Правильное и надежное сохранение изображений с 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;
}
?>
Плюсы этого подхода: