Як можна перевірити, чи існує віддалений файл за допомогою PHP?


86

Найкраще, що я міг знайти, щось на зразок if fclose fopenшрифта, робить завантаження сторінки дуже повільним.

В основному я намагаюся зробити наступне: у мене є список веб-сайтів, і я хочу відображати їхні значки поруч із ними. Однак, якщо на сайті його немає, я хотів би замінити його іншим, а не відображати зламане зображення.


Я думаю, ви можете використовувати CURL і перевірити його коди повернення. Але якщо проблема полягає в швидкості, просто зробіть це в автономному режимі та кешуйте.
Міхал Татаринович

Так, але я все одно рекомендую використовувати автономний скрипт (запущений з cron), який аналізує список веб-сайтів, перевіряє, чи є у них значки, та кешує ці дані для інтерфейсу. Якщо ви не можете / не можете використовувати cron, принаймні кешуйте результати для кожної нової перевіреної URL-адреси.
Міхал Татаринович

3
Для заміни зламаного зображення на зображення-заповнювач у браузері, будь ласка, розгляньте рішення на стороні клієнта з використанням onerrorзображення, наприклад рішення з використанням jQuery

Відповіді:


135

Ви можете доручити curl використовувати метод HTTP HEAD через CURLOPT_NOBODY.

Більш-менш

$ch = curl_init("http://www.example.com/favicon.ico");

curl_setopt($ch, CURLOPT_NOBODY, true);
curl_exec($ch);
$retcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// $retcode >= 400 -> not found, $retcode = 200, found.
curl_close($ch);

У будь-якому випадку, ви економите лише вартість передачі HTTP, а не встановлення та закриття TCP-з'єднання. Будучи незначними, ви, можливо, не побачите особливого покращення.

Кешування результату локально здається непоганою ідеєю, якщо воно виявляється занадто повільним. HEAD перевіряє час файлу і повертає його в заголовки. Ви можете робити, як браузери, і отримати CURLINFO_FILETIME значка. У кеш-пам’яті ви можете зберегти URL => [favicon, timestamp]. Потім можна порівняти позначку часу та перезавантажити значок.


6
лише примітка: retcodeпомилки на всіх 400 кодах, тому перевірка буде >=не просто>
Джастін Булл

4
Деякі сайти блокують доступ, якщо ви не надаєте рядок агента користувача, тому я пропоную слідувати цьому посібнику, щоб додати CURLOPT_USERAGENT на додаток до CURLOPT_NOBODY: davidwalsh.name/set-user-agent-php-curl-spoof
rlorenzo

6
Реткоди @Lyth 3XX - це не помилка, а переспрямування. Їх слід обробляти вручну або за допомогою CURLOPT_FOLLOWLOCATION.
Ramon Poca

6
Використовуйте curl_setopt ($ ch, CURLOPT_SSL_VERIFYPEER, false); а також переконатися, що той самий код працює для URL-адрес, починаючи з HTTPS!
Krishan Gopal

61

Як пироги кажуть, ви можете використовувати curl. Ви можете отримати curl, щоб дати вам лише заголовки, а не тіло, що може зробити це швидшим. Поганий домен завжди може зайняти деякий час, тому що ви будете чекати на час очікування запиту; можливо, ви могли б змінити тривалість очікування за допомогою cURL.

Ось приклад:

function remoteFileExists($url) {
    $curl = curl_init($url);

    //don't fetch the actual page, you only want to check the connection is ok
    curl_setopt($curl, CURLOPT_NOBODY, true);

    //do request
    $result = curl_exec($curl);

    $ret = false;

    //if request did not fail
    if ($result !== false) {
        //if request was ok, check response code
        $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);  

        if ($statusCode == 200) {
            $ret = true;   
        }
    }

    curl_close($curl);

    return $ret;
}

$exists = remoteFileExists('http://stackoverflow.com/favicon.ico');
if ($exists) {
    echo 'file exists';
} else {
    echo 'file does not exist';   
}

3
remoteFileExists ('stackoverflow.com/ ' ) це також повертає true, але це просто посилання. Ця функція не перевіряє тип вмісту посилання - файл.
Donatas Navidonskis

36

Рішення CoolGoose хороше, але це швидше для великих файлів (оскільки він намагається прочитати лише 1 байт):

if (false === file_get_contents("http://example.com/path/to/image",0,null,0,1)) {
    $image = $default_image;
}

+1. Чи існують які недоліки цього рішення проти CURL?
Адріано Варолі Пьяцца

1
ви можете просто використовувати fopen- якщо код повернення запиту - 404, fopen повертає false.
s3v3n

це дійсно повільно і не спрацювало для мене (мається на увазі, що воно все ще відображало зламане зображення, якщо шлях до файлу був неправильним)
Гельмут,

Цей підхід не працює, якщо сервер робить переспрямування, коли зображення чи файл не існує. Це трапляється, коли сайт використовує mod_rewrite або якісь інші "правила", як слід обробляти запити.
Ерік Черпняк,

28

Це не відповідь на ваше початкове запитання, а кращий спосіб зробити те, що ви намагаєтесь зробити:

Замість того, щоб насправді намагатися отримати значок веб-сайту безпосередньо (що є великим болем, враховуючи, що це може бути /favicon.png, /favicon.ico, /favicon.gif або навіть /path/to/favicon.png), використовуйте google:

<img src="http://www.google.com/s2/favicons?domain=[domain]">

Готово.


4
Синтаксис вносить трохи плутанини. Отже, ось один приклад: <img src = " google.com/s2/favicons?domain=stackoverflow.com ">
Хабіб Первад

19

Повна функція найбільш голосованої відповіді:

function remote_file_exists($url)
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_NOBODY, 1);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); # handles 301/2 redirects
    curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if( $httpCode == 200 ){return true;}
}

Ви можете використовувати його так:

if(remote_file_exists($url))
{
    //file exists, do something
}

О! Я був у відсутності останні пару днів, але початок місяця був майже цілодобово. Дякую, що повідомили!
Педро Лобіто,

Це не працює, якщо сервер не відповідає на будь-який код HTTP (або cUrl його не вловлює). Що трапляється у мене досить часто. Напр. у разі зображень.
Ваці

що робити, якщо URL-адресу переспрямовано на іншу URL-адресу або версію https? У цьому випадку цей код curl не зможе виконати цю роботу. найкращий спосіб - отримати інформацію про заголовок та знайти рядок, що не враховує регістр, "200 добре"
Infoconic

@Infoconic Ви можете додати curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);. Я оновив відповідь для обробки 302переадресацій.
Педро Лобіто

18

Якщо ви маєте справу з зображеннями, використовуйте getimagesize. На відміну від file_exists, ця вбудована функція підтримує віддалені файли. Він поверне масив, що містить інформацію про зображення (ширина, висота, тип ... тощо). Все, що вам потрібно зробити, це перевірити перший елемент у масиві (ширина). використовуйте print_r для виведення вмісту масиву

$imageArray = getimagesize("http://www.example.com/image.jpg");
if($imageArray[0])
{
    echo "it's an image and here is the image's info<br>";
    print_r($imageArray);
}
else
{
    echo "invalid image";
}

Результати 404 попередження, коли віддалений ресурс недоступний. На даний момент я вирішив це, придушивши помилку, використовуючи @перед getimagesize, але відчуваючи провину за цей хак.

У моєму випадку це був найкращий підхід, оскільки я отримую перенаправлення, коли зображення / файл не існує. Я також вважаю, що придушення помилок за допомогою @ не можна робити, але в цьому випадку це було необхідним.
Ерік Черпняк,

Я зрозумів, що ми могли б використовувати exif_imagetype, і це набагато швидше stackoverflow.com/a/38295345/1250044
yckart

7

Це можна зробити, отримавши код стану HTTP (404 = не знайдено), що можливо в file_get_contentsДокументах із використанням параметрів контексту. Наступний код враховує переспрямування та повертає код стану кінцевого пункту призначення ( Демо ):

$url = 'http://example.com/';
$code = FALSE;

$options['http'] = array(
    'method' => "HEAD",
    'ignore_errors' => 1
);

$body = file_get_contents($url, NULL, stream_context_create($options));

foreach($http_response_header as $header)
    sscanf($header, 'HTTP/%*d.%*d %d', $code);

echo "Status code: $code";

Якщо ви не хочете стежити за переспрямуваннями, ви можете зробити це подібним чином ( Демо ):

$url = 'http://example.com/';
$code = FALSE;

$options['http'] = array(
    'method' => "HEAD",
    'ignore_errors' => 1,
    'max_redirects' => 0
);

$body = file_get_contents($url, NULL, stream_context_create($options));

sscanf($http_response_header[0], 'HTTP/%*d.%*d %d', $code);

echo "Status code: $code";

Деякі функції, параметри та змінні, що використовуються, пояснюються більш докладно в дописі в блозі, який я написав: HEAD first with PHP Streams .




Більше про PHP $http_response_headerдив. На php.net/manual/en/reserved.variables.httpresponseheader.php .
Big McLargeHuge

1
Другий варіант працював у мене, і порівняно із викликом file_get_contents за замовчуванням (без власного потокового_тексту) він був на 50% швидшим, тобто від 3,4 до 1,7 с для запиту.
Ерік Черпняк,

@ ErikČerpnjak: Якщо немає "спеціального" потокового_контексту, це за замовчуванням. Ви можете отримати параметри із контексту за замовчуванням і подивитися, як вони відрізняються від вашого користувацького контексту. Це має дати вам певне розуміння, чому час відрізняється. - php.net/stream-context-get-default та php.net/stream-context-get-options
hakre

6
if (false === file_get_contents("http://example.com/path/to/image")) {
    $image = $default_image;
}

Має працювати;)


add @ before function
Тебе

6

Вбудовані функції PHP можуть не працювати для перевірки URL-адреси, якщо параметр allow_url_fopen вимкнено з міркувань безпеки. Curl - кращий варіант, оскільки нам не потрібно буде змінювати наш код на пізніх стадіях. Нижче наведено код, який я використовував для перевірки дійсної URL-адреси:

$url = str_replace(' ', '%20', $url);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);  
curl_close($ch);
if($httpcode>=200 && $httpcode<300){  return true; } else { return false; } 

Будь ласка, зверніть увагу на опцію CURLOPT_SSL_VERIFYPEER, яка також підтверджує, що URL-адреса починається з HTTPS.


6

Щоб перевірити наявність зображень, exif_imagetypeслід віддати перевагу над ними getimagesize, оскільки це набагато швидше.

Щоб придушити E_NOTICE, просто додайте оператор контролю помилок ( @).

if (@exif_imagetype($filename)) {
  // Image exist
}

Як бонус, із поверненим значенням ( IMAGETYPE_XXX) від exif_imagetypeми також можемо отримати mime-тип або розширення файлу за допомогою image_type_to_mime_type/ image_type_to_extension.


4

Радикальним рішенням буде відображення значків як фонових зображень у div над значком за замовчуванням. Таким чином, всі накладні витрати будуть розміщені на клієнті, при цьому все ще не відображатимуть зламані зображення (відсутні фонові зображення ігноруються у всіх браузерах AFAIK).


1
+1, якщо ви не перевіряєте кілька місць для їх значка (favicon.ico, favicon.gif, favicon.png), це, мабуть, найкраще рішення
Гален,

3
function remote_file_exists($url){
   return(bool)preg_match('~HTTP/1\.\d\s+200\s+OK~', @current(get_headers($url)));
}  
$ff = "http://www.emeditor.com/pub/emed32_11.0.5.exe";
    if(remote_file_exists($ff)){
        echo "file exist!";
    }
    else{
        echo "file not exist!!!";
    }

3

Ви можете використовувати наступне:

$file = 'http://mysite.co.za/images/favicon.ico';
$file_exists = (@fopen($file, "r")) ? true : false;

Працював у мене при спробі перевірити, чи є зображення в URL-адресі


2

Ви можете використовувати:

$url=getimagesize(“http://www.flickr.com/photos/27505599@N07/2564389539/”);

if(!is_array($url))
{
   $default_image =”…/directoryFolder/junal.jpg”;
}

2

Це працює для мене, щоб перевірити, чи існує віддалений файл у PHP:

$url = 'https://cdn.sstatic.net/Sites/stackoverflow/img/favicon.ico';
    $header_response = get_headers($url, 1);

    if ( strpos( $header_response[0], "404" ) !== false ) {
        echo 'File does NOT exist';
        } else {
        echo 'File exists';
        }

1

Вам слід надсилати запити HEAD, а не GET, оскільки вміст URI вам зовсім не потрібен. Як писав Пирог вище, вам слід перевірити наявність коду стану (у діапазонах 200-299, і ви можете за бажанням перейти за переспрямуванням 3xx).

Питання відповідей містить багато прикладів коду, які можуть бути корисними: PHP / Curl: HEAD Request займає багато часу на деяких сайтах


1

Існує ще більш складна альтернатива. Ви можете виконати перевірку на всій стороні клієнта, використовуючи фокус JQuery.

$('a[href^="http://"]').filter(function(){
     return this.hostname && this.hostname !== location.hostname;
}).each(function() {
    var link = jQuery(this);
    var faviconURL =
      link.attr('href').replace(/^(http:\/\/[^\/]+).*$/, '$1')+'/favicon.ico';
    var faviconIMG = jQuery('<img src="favicon.png" alt="" />')['appendTo'](link);
    var extImg = new Image();
    extImg.src = faviconURL;
    if (extImg.complete)
      faviconIMG.attr('src', faviconURL);
    else
      extImg.onload = function() { faviconIMG.attr('src', faviconURL); };
});

З http://snipplr.com/view/18782/add-a-favicon-near-external-links-with-jquery/ (оригінальний блог зараз недоступний)


1

всі відповіді тут, які використовують get_headers (), роблять запит GET. Набагато швидше / дешевше просто зробити запит HEAD.

Щоб переконатися, що get_headers () робить запит HEAD замість GET, слід додати це:

stream_context_set_default(
    array(
        'http' => array(
            'method' => 'HEAD'
        )
    )
);

отже, щоб перевірити, чи існує файл, ваш код буде виглядати приблизно так:

stream_context_set_default(
    array(
        'http' => array(
            'method' => 'HEAD'
        )
    )
);
$headers = get_headers('http://website.com/dir/file.jpg', 1);
$file_found = stristr($headers[0], '200');

$ file_found поверне або false, або true, очевидно.


0

Не знаю, чи це швидше, коли файл не існує віддалено, is_file () , але ви можете спробувати .

$favIcon = 'default FavIcon';
if(is_file($remotePath)) {
   $favIcon = file_get_contents($remotePath);
}

З документів: "Починаючи з PHP 5.0.0, цю функцію також можна використовувати з деякими обгортками URL-адрес. Зверніться до Підтримувані протоколи та обгортки, щоб визначити, які обгортки підтримують функціональність сімейства stat ()."
PatrikAkerstrand

Ви маєте на увазі, що це може спрацювати, якщо ви зареєструєте обгортку потоку? Відредагуйте своє запитання, щоб показати діючий приклад, і я зніму свій голос проти (і, якщо зможу), вас. Але на даний момент я перевірив is_file з php cli за допомогою віддаленого файлу і отримав false.
greg0ire

немає робочого прикладу:var_dump(is_file('http://cdn.sstatic.net/stackoverflow/img/sprites.png')); bool(false)
greg0ire

0

Якщо файл не розміщений зовні, ви можете перевести віддалену URL-адресу в абсолютний шлях на своєму веб-сервері. Таким чином, вам не доведеться викликати CURL або file_get_contents тощо.

function remoteFileExists($url) {

    $root = realpath($_SERVER["DOCUMENT_ROOT"]);
    $urlParts = parse_url( $url );

    if ( !isset( $urlParts['path'] ) )
        return false;

    if ( is_file( $root . $urlParts['path'] ) )
        return true;
    else
        return false;

}

remoteFileExists( 'https://www.yourdomain.com/path/to/remote/image.png' );

Примітка: Ваш веб-сервер повинен заповнити DOCUMENT_ROOT, щоб використовувати цю функцію


0

Якщо ви використовуєте фреймворк Symfony, існує також набагато простіший спосіб використання HttpClientInterface:

private function remoteFileExists(string $url, HttpClientInterface $client): bool {
    $response = $client->request(
        'GET',
        $url //e.g. http://example.com/file.txt
    );

    return $response->getStatusCode() == 200;
}

Документи для HttpClient також дуже хороші і, можливо, варто їх розглянути, якщо вам потрібен більш конкретний підхід: https://symfony.com/doc/current/http_client.html


-1

Ви можете використовувати файлову систему: використовуйте Symfony \ Component \ Filesystem \ Filesystem; використовувати Symfony \ Component \ Filesystem \ Exception \ IOExceptionInterface;

і перевірте $ fileSystem = new Filesystem (); якщо ($ fileSystem-> існує ('path_to_file') == true) {...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.