Jak można sprawdzić, czy zdalny plik istnieje przy użyciu PHP?

86

Najlepsze, co mogłem znaleźć, to if fclose fopentyp, sprawia, że ​​strona ładuje się bardzo wolno.

Zasadniczo próbuję wykonać następujące czynności: Mam listę witryn internetowych i chcę wyświetlić obok nich ich ikony ulubionych. Jeśli jednak witryna go nie ma, wolę zastąpić ją innym obrazem, zamiast wyświetlać uszkodzony obraz.


źródło
Myślę, że możesz użyć CURL i sprawdzić jego kody zwrotne. Ale jeśli problemem jest szybkość, po prostu zrób to w trybie offline i buforuj.
Michał Tatarynowicz
Tak, ale nadal zalecałbym używanie skryptu offline (uruchamianego z crona), który analizuje listę stron internetowych, sprawdza, czy mają favicony i buforuje te dane dla frontendu. Jeśli nie możesz / nie możesz użyć crona, przynajmniej buforuj wyniki dla każdego nowego sprawdzanego adresu URL.
Michał Tatarynowicz
3
Na wymianie uszkodzonego obrazu z obrazem zastępczym w przeglądarce, prosimy rozważyć rozwiązanie po stronie klienta za pomocą onerrorobrazu, np rozwiązanie przy użyciu jQuery
Możliwy duplikat PHP: Jak sprawdzić, czy plik obrazu istnieje?
Cees Timmerman

Odpowiedzi:

135

Możesz poinstruować curl, aby używał metody HTTP HEAD za pośrednictwem CURLOPT_NOBODY.

Mniej więcej

$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);

W każdym razie oszczędzasz tylko koszt transferu HTTP, a nie ustanowienie i zamknięcie połączenia TCP. Ponieważ ikony ulubionych są małe, możesz nie zauważyć dużej poprawy.

Lokalne buforowanie wyniku wydaje się dobrym pomysłem, jeśli okaże się, że jest zbyt wolne. HEAD sprawdza czas pliku i zwraca go w nagłówkach. Możesz lubić przeglądarki i uzyskać CURLINFO_FILETIME ikony. W swojej pamięci podręcznej możesz przechowywać URL => [favicon, timestamp]. Następnie możesz porównać sygnaturę czasową i ponownie załadować ikonę ulubionych.

Ramon Poca
źródło
6
tylko uwaga: retcodebłędy na wszystkich 400 kodach, więc walidacja byłaby >=nie tylko>
Justin Bull
4
Niektóre witryny blokują dostęp, jeśli nie podasz ciągu agenta użytkownika, więc sugeruję skorzystanie z tego przewodnika, aby dodać CURLOPT_USERAGENT oprócz CURLOPT_NOBODY: davidwalsh.name/set-user-agent-php-curl-spoof
rlorenzo
6
Retkodowanie @Lyth 3XX nie jest błędem, ale przekierowaniem. Powinny być obsługiwane ręcznie lub za pomocą CURLOPT_FOLLOWLOCATION.
Ramon Poca,
6
Użyj curl_setopt ($ ch, CURLOPT_SSL_VERIFYPEER, false); aby upewnić się, że ten sam kod działa dla adresów URL zaczynających się od HTTPS!
Krishan Gopal
61

Jak mówi Pies, możesz użyć cURL. Możesz uzyskać cURL, aby podawał tylko nagłówki, a nie treść, co może przyspieszyć. Zła domena zawsze może zająć trochę czasu, ponieważ będziesz czekać na przekroczenie limitu czasu żądania; prawdopodobnie możesz zmienić limit czasu za pomocą cURL.

Oto przykład:

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';   
}
Tom Haigh
źródło
3
remoteFileExists (' stackoverflow.com/' ) to również zwróci wartość true, ale to tylko link. Ta funkcja nie sprawdza, czy typ zawartości łącza to plik.
Donatas Navidonskis
36

Rozwiązanie CoolGoose jest dobre, ale jest szybsze w przypadku dużych plików (ponieważ próbuje odczytać tylko 1 bajt):

if (false === file_get_contents("http://example.com/path/to/image",0,null,0,1)) {
    $image = $default_image;
}
luBar
źródło
+1. Czy są jakieś wady tego rozwiązania w porównaniu z CURL?
Adriano Varoli Piazza
1
możesz po prostu użyć fopen- jeśli kod powrotu żądania to 404, fopen zwraca false.
s3v3n
to jest naprawdę powolne i nie działało dla mnie (co oznacza, że ​​nadal wyświetlał uszkodzony obraz, jeśli ścieżka pliku była nieprawidłowa)
Helmut
To podejście nie działa, jeśli serwer dokonuje przekierowania za każdym razem, gdy obraz lub plik nie istnieje. Dzieje się tak, gdy witryna używa mod_rewrite lub innego rodzaju „reguł”, jak powinny być obsługiwane żądania.
Erik Čerpnjak
28

To nie jest odpowiedź na Twoje pierwotne pytanie, ale lepszy sposób na zrobienie tego, co próbujesz zrobić:

Zamiast bezpośrednio próbować uzyskać favicon witryny (co jest królewskim problemem, biorąc pod uwagę, że może to być /favicon.png, /favicon.ico, /favicon.gif lub nawet /path/to/favicon.png), użyj google:

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

Gotowe.

Mala
źródło
4
Składnia wprowadza pewne zamieszanie. Oto przykład: <img src = " google.com/s2/favicons?domain=stackoverflow.com ">
Habeeb Perwad
19

Pełna funkcja najczęściej głosowanej odpowiedzi:

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;}
}

Możesz go używać w ten sposób:

if(remote_file_exists($url))
{
    //file exists, do something
}
Pedro Lobito
źródło
O! Nie było mnie przez ostatnie kilka dni, ale początek miesiąca był prawie 24/7. Dziękuję za informację!
Pedro Lobito
To nie działa, jeśli serwer nie odpowiada na żaden kod HTTP (lub cUrl go nie przechwytuje). Co zdarza mi się dość często. Na przykład. w przypadku obrazów.
Vaci
co się stanie, jeśli adres URL zostanie przekierowany na inny adres URL lub wersję https? W takim przypadku ten kod curl nie będzie w stanie wykonać zadania. najlepszym sposobem jest uzyskanie informacji z nagłówka i wyszukanie ciągu znaków „200 ok” bez uwzględniania wielkości liter.
Infoconic
@Infoconic Możesz dodać curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);. Zaktualizowałem odpowiedź, aby obsługiwać 302przekierowania.
Pedro Lobito
18

Jeśli masz do czynienia z obrazami, użyj getimagesize. W przeciwieństwie do file_exists, ta wbudowana funkcja obsługuje zdalne pliki. Zwróci tablicę zawierającą informacje o obrazie (szerokość, wysokość, typ… itd.). Wszystko co musisz zrobić to sprawdzić pierwszy element tablicy (szerokość). użyj print_r do wyprowadzenia zawartości tablicy

$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";
}
Eyad Fallatah
źródło
Powoduje ostrzeżenie 404, gdy zasób zdalny jest niedostępny. Na razie poradziłem sobie z tym, tłumiąc błąd przy użyciu @przed getimagesize, ale czując się winny za ten hack.
W moim przypadku było to najlepsze podejście, ponieważ jestem przekierowywany, gdy obraz / plik nie istnieje. Po drugie, tłumienie błędów za pomocą @ nie jest możliwe, ale w tym przypadku było to konieczne.
Erik Čerpnjak
Doszedłem do wniosku, że możemy również użyć exif_imagetypei jest to znacznie szybsze stackoverflow.com/a/38295345/1250044
yckart
7

Można to zrobić, uzyskując kod stanu HTTP (404 = nie znaleziono), co jest możliwe w przypadku file_get_contentsDokumentów korzystających z opcji kontekstu. Poniższy kod bierze pod uwagę przekierowania i zwraca kod statusu miejsca docelowego ( Demo ):

$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";

Jeśli nie chcesz podążać za przekierowaniami, możesz to zrobić podobnie ( Demo ):

$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";

Niektóre z używanych funkcji, opcji i zmiennych są bardziej szczegółowo wyjaśnione w poście na blogu, który napisałem: HEAD najpierw ze strumieniami PHP .

hakre
źródło
Aby uzyskać więcej informacji na temat PHP, $http_response_headerzobacz php.net/manual/en/reserved.variables.httpresponseheader.php .
Big McLargeHuge
1
Drugi wariant zadziałał dla mnie i w porównaniu z domyślnym wywołaniem file_get_contents (bez niestandardowego stream_context) był o 50% szybszy, tj. Od 3,4s do 1,7s dla żądania.
Erik Čerpnjak
@ ErikČerpnjak: Jeśli nie ma „niestandardowego” stream_context, jest to domyślny. Możesz pobrać opcje z domyślnego kontekstu i zobaczyć, jak różnią się od kontekstu niestandardowego. To powinno dać ci wgląd w różnice w czasie. - php.net/stream-context-get-default i php.net/stream-context-get-options
hakre
6
if (false === file_get_contents("http://example.com/path/to/image")) {
    $image = $default_image;
}

Powinno działać ;)

CoolGoose
źródło
dodaj @ przed funkcją
Tebe
6

Wbudowane funkcje PHP mogą nie działać do sprawdzania adresu URL, jeśli ustawienie allow_url_fopen jest wyłączone ze względów bezpieczeństwa. Curl to lepsza opcja, ponieważ nie musielibyśmy zmieniać naszego kodu na późniejszym etapie. Poniżej znajduje się kod, którego użyłem do zweryfikowania prawidłowego adresu 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; } 

Zwróć uwagę na opcję CURLOPT_SSL_VERIFYPEER , która również weryfikuje adresy URL zaczynające się od HTTPS.

Krishan Gopal
źródło
6

Aby sprawdzić istnienie obrazów, exif_imagetypepowinno być preferowane getimagesize, ponieważ jest znacznie szybsze.

Aby pominąć E_NOTICE, po prostu dodaj przed operatorem kontroli błędów ( @).

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

Jako bonus, zwracając wartość ( IMAGETYPE_XXX) z, exif_imagetypemożemy również uzyskać typ MIME lub rozszerzenie pliku za pomocą image_type_to_mime_type/ image_type_to_extension.

yckart
źródło
4

Radykalnym rozwiązaniem byłoby wyświetlenie ikon ulubionych jako obrazów tła w div nad domyślną ikoną. W ten sposób cały narzut zostanie umieszczony na kliencie, a nadal nie będą wyświetlać uszkodzonych obrazów (brakujące obrazy tła są ignorowane we wszystkich przeglądarkach AFAIK).

truppo
źródło
1
+1, jeśli nie sprawdzasz wielu lokalizacji dla ich favicon (favicon.ico, favicon.gif, favicon.png), wydaje się, że jest to najlepsze rozwiązanie
Galen
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!!!";
    }
dr.linux
źródło
3

Możesz użyć następujących:

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

U mnie zadziałało, gdy próbowałem sprawdzić, czy pod adresem URL istnieje obraz

Rickus Harmse
źródło
2

Możesz użyć :

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

if(!is_array($url))
{
   $default_image =”…/directoryFolder/junal.jpg”;
}
CP Soni
źródło
2

To działa dla mnie, aby sprawdzić, czy zdalny plik istnieje w 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';
        }
user7018984
źródło
1

Powinieneś wysyłać żądania HEAD, a nie GET, ponieważ w ogóle nie potrzebujesz zawartości URI. Jak powiedział Pies powyżej, powinieneś sprawdzić kod statusu (w zakresach 200-299 i opcjonalnie możesz śledzić przekierowania 3xx).

Odpowiedzi zawierają wiele przykładów kodu, które mogą być pomocne: PHP / Curl: HEAD Żądanie zajmuje dużo czasu w niektórych witrynach

drdaeman
źródło
1

Jest jeszcze bardziej wyrafinowana alternatywa. Możesz sprawdzić wszystko po stronie klienta, używając sztuczki 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); };
});

Z http://snipplr.com/view/18782/add-a-favicon-near-external-links-with-jquery/ (oryginalny blog jest obecnie niedostępny)

S Pangborn
źródło
1

wszystkie odpowiedzi tutaj, które używają get_headers (), wykonują żądanie GET. O wiele szybciej / taniej jest po prostu wykonać żądanie HEAD.

Aby upewnić się, że get_headers () wykonuje żądanie HEAD zamiast GET, należy dodać to:

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

aby sprawdzić, czy plik istnieje, Twój kod będzie wyglądał mniej więcej tak:

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 oczywiście zwróci wartość false lub true.

Ludo - Nieoficjalnie
źródło
0

Nie wiem, czy ten jest szybszy, gdy plik nie istnieje zdalnie, is_file () , ale możesz spróbować.

$favIcon = 'default FavIcon';
if(is_file($remotePath)) {
   $favIcon = file_get_contents($remotePath);
}
PatrikAkerstrand
źródło
Z dokumentacji: „Począwszy od PHP 5.0.0, ta funkcja może być również używana z niektórymi opakowaniami adresów URL. Zobacz Obsługiwane protokoły i opakowania, aby określić, które opakowania obsługują rodzinę funkcji stat ().”
PatrikAkerstrand
Czy masz na myśli to, że to zadziała, jeśli zarejestrujesz opakowanie strumienia? Edytuj swoje pytanie, aby pokazać działający przykład, a ja usunę mój głos przeciw (i zagłosuję za tobą, jeśli mogę). Ale na razie przetestowałem is_file z CLI php ze zdalnym plikiem i otrzymałem fałsz.
greg0ire
brak działającego przykładu:var_dump(is_file('http://cdn.sstatic.net/stackoverflow/img/sprites.png')); bool(false)
greg0ire
0

Jeśli plik nie jest hostowany na zewnątrz, możesz przetłumaczyć zdalny adres URL na bezwzględną ścieżkę na serwerze WWW. W ten sposób nie musisz wywoływać CURL ani file_get_contents itp.

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' );

Uwaga: Twój serwer WWW musi wypełnić DOCUMENT_ROOT, aby móc korzystać z tej funkcji

Bastian Fießinger
źródło
0

Jeśli używasz frameworka Symfony, istnieje również znacznie prostszy sposób użycia 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;
}

Dokumentacja dla HttpClient jest również bardzo dobra i być może warto się jej przyjrzeć, jeśli potrzebujesz bardziej szczegółowego podejścia: https://symfony.com/doc/current/http_client.html

Filnor
źródło
-1

Możesz użyć systemu plików: użyj Symfony \ Component \ Filesystem \ Filesystem; użyj Symfony \ Component \ Filesystem \ Exception \ IOExceptionInterface;

i sprawdź $ fileSystem = new Filesystem (); if ($ fileSystem-> exist ('path_to_file') == true) {...

Lenwë Galathil
źródło