Como verificar se existe um arquivo remoto usando PHP?

86

O melhor que pude encontrar, um if fclose fopentipo de coisa, faz a página carregar bem devagar.

Basicamente, o que estou tentando fazer é o seguinte: tenho uma lista de sites e desejo exibir seus favicons ao lado deles. No entanto, se um site não tiver um, gostaria de substituí-lo por outra imagem em vez de exibir uma imagem corrompida.


fonte
Acho que você pode usar o CURL e verificar seus códigos de retorno. Mas se é a velocidade que é um problema, basta fazê-lo offline e em cache.
Michał Tatarynowicz
Sim, mas eu ainda recomendaria usar um script offline (executado a partir do cron) que analisa a lista de sites, verifica se eles têm favicons e armazena em cache os dados para o frontend. Se você não pode / não pode usar o cron, pelo menos armazene em cache os resultados para cada nova URL que você verificar.
Michał Tatarynowicz de
3
Para substituir uma imagem quebrada por uma imagem de espaço reservado no navegador, considere uma solução do lado do cliente usando uma onerrorimagem, por exemplo, uma solução usando jQuery
Possível duplicata do PHP: Como verificar se o arquivo de imagem existe?
Cees Timmerman

Respostas:

135

Você pode instruir o curl a usar o método HTTP HEAD via CURLOPT_NOBODY.

Mais ou menos

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

De qualquer forma, você economiza apenas no custo da transferência HTTP, não no estabelecimento e fechamento da conexão TCP. E sendo favicons pequenos, você pode não ver muitas melhorias.

Armazenar o resultado localmente em cache parece uma boa ideia se ficar muito lento. HEAD verifica a hora do arquivo e a retorna nos cabeçalhos. Você pode fazer como navegadores e obter o CURLINFO_FILETIME do ícone. Em seu cache, você pode armazenar o URL => [favicon, timestamp]. Você pode então comparar o carimbo de data / hora e recarregar o favicon.

Ramon Poca
fonte
6
apenas uma observação: retcodeerros em todos os 400 códigos para que a validação não fosse >=justa>
Justin Bull
4
Alguns sites bloqueiam o acesso se você não fornecer uma string de agente de usuário, então sugiro seguir este guia para adicionar CURLOPT_USERAGENT além de CURLOPT_NOBODY: davidwalsh.name/set-user-agent-php-curl-spoof
rlorenzo
6
Os códigos retos @Lyth 3XX não são um erro, mas um redirecionamento. Esses devem ser tratados manualmente ou usando CURLOPT_FOLLOWLOCATION.
Ramon Poca,
6
Use curl_setopt ($ ch, CURLOPT_SSL_VERIFYPEER, false); e também para garantir que o mesmo código funcione para URLs começando com HTTPS!
Krishan Gopal
61

Como Pies dizem, você pode usar cURL. Você pode fazer com que o cURL forneça apenas os cabeçalhos, e não o corpo, o que pode torná-lo mais rápido. Um domínio inválido sempre pode demorar um pouco porque você estará aguardando o tempo limite da solicitação expirar; você provavelmente poderia alterar a duração do tempo limite usando cURL.

Aqui está um exemplo:

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
fonte
3
remoteFileExists (' stackoverflow.com/' ) também retornará verdadeiro, mas é apenas um link. Esta função não verifica se o tipo de conteúdo do link é arquivo.
Donatas Navidonskis
36

A solução do CoolGoose é boa, mas é mais rápida para arquivos grandes (pois tenta ler apenas 1 byte):

if (false === file_get_contents("http://example.com/path/to/image",0,null,0,1)) {
    $image = $default_image;
}
luBar
fonte
+1. Quais são as desvantagens desta solução em relação à do CURL?
Adriano Varoli Piazza
1
você pode apenas usar fopen- se o código de retorno da solicitação for 404, fopen retornará falso.
s3v3n
isso é muito lento e não funcionou para mim (o que significa que ainda exibia uma imagem quebrada se o caminho do arquivo não estivesse correto)
Helmut
Essa abordagem não funciona se o servidor fizer um redirecionamento sempre que uma imagem ou arquivo não existir. Isso acontece quando um site usa mod_rewrite ou algum tipo de outra "regra" de como as solicitações devem ser tratadas.
Erik Čerpnjak
28

Esta não é uma resposta à sua pergunta original, mas uma maneira melhor de fazer o que você está tentando fazer:

Em vez de tentar obter o favicon do site diretamente (o que é uma pena, pois pode ser /favicon.png, /favicon.ico, /favicon.gif ou mesmo /path/to/favicon.png), use o google:

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

Feito.

Mala
fonte
4
A sintaxe confunde um pouco. Então, aqui está um exemplo: <img src = " google.com/s2/favicons?domain=stackoverflow.com ">
Habeeb Perwad
19

Uma função completa da resposta mais votada:

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

Você pode usá-lo assim:

if(remote_file_exists($url))
{
    //file exists, do something
}
Pedro Lobito
fonte
Oh! Estive fora nos últimos dias, mas o início do mês era quase 24 horas por dia, 7 dias por semana. Obrigado por me avisar!
Pedro Lobito
Isso não funciona se o servidor não responder a nenhum código HTTP (ou o cUrl não o capturar). O que está acontecendo comigo com frequência. Por exemplo. no caso de imagens.
Vaci de
e se o url for redirecionado para outro URL ou versão https? Nesse caso, este código curl não será capaz de fazer o trabalho. a melhor maneira é obter informações de cabeçalho e pesquisar a string "200 ok", que não diferencia maiúsculas de minúsculas.
Infoconic
@Infoconic Você pode adicionar curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);. Eu atualizei a resposta para lidar com 302redirecionamentos.
Pedro Lobito
18

Se você estiver lidando com imagens, use getimagesize. Ao contrário de file_exists, esta função integrada oferece suporte a arquivos remotos. Ele retornará um array que contém as informações da imagem (largura, altura, tipo ... etc). Tudo que você precisa fazer é verificar o primeiro elemento do array (a largura). use print_r para produzir o conteúdo do array

$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
fonte
Resulta em um aviso 404 quando o recurso remoto não está disponível. Por enquanto, lidei com isso suprimindo o erro de uso @na frente de getimagesize, mas me sentindo culpado por esse hack.
No meu caso, essa foi a melhor abordagem, pois sou redirecionado sempre que uma imagem / arquivo não existe. Em segundo lugar, a supressão de erros com @ é proibida, mas neste caso foi necessária.
Erik Čerpnjak
Eu descobri que também poderíamos usar exif_imagetype, e é muito mais rápido stackoverflow.com/a/38295345/1250044
yckart
7

Isso pode ser feito obtendo o código de status HTTP (404 = não encontrado), que é possível com o file_get_contentsDocs usando opções de contexto. O código a seguir leva os redirecionamentos em consideração e retornará o código de status do destino final ( 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";

Se não quiser seguir redirecionamentos, você pode fazer isso de forma semelhante ( 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";

Algumas das funções, opções e variáveis ​​em uso são explicadas com mais detalhes em uma postagem de blog que escrevi: HEAD first with PHP Streams .

hakre
fonte
Para mais informações sobre PHP, $http_response_headerconsulte php.net/manual/en/reserved.variables.httpresponseheader.php .
Big McLargeHuge
1
A segunda variante funcionou para mim e em comparação com a chamada padrão file_get_contents (sem stream_context customizado) foi 50% mais rápida, ou seja, de 3,4s para 1,7s para uma solicitação.
Erik Čerpnjak
@ ErikČerpnjak: Se não houver "nenhum stream_context personalizado", é o padrão. Você pode obter as opções do contexto padrão e ver como elas variam em relação ao seu contexto personalizado. Isso deve lhe dar algumas dicas sobre por que os tempos são diferentes. - php.net/stream-context-get-default and php.net/stream-context-get-options
hakre
6
if (false === file_get_contents("http://example.com/path/to/image")) {
    $image = $default_image;
}

Deveria trabalhar ;)

CoolGoose
fonte
add @ before function
Tebe
6

As funções embutidas do PHP podem não funcionar para verificar a URL se a configuração allow_url_fopen estiver desligada por razões de segurança. Curl é uma opção melhor, pois não precisaríamos alterar nosso código em um estágio posterior. Abaixo está o código que usei para verificar um URL válido:

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

Observe a opção CURLOPT_SSL_VERIFYPEER, que também verifica se o URL começa com HTTPS.

Krishan Gopal
fonte
6

Para verificar a existência de imagens, exif_imagetypedeve-se preferir getimagesize, pois é muito mais rápido.

Para suprimir o E_NOTICE, basta acrescentar o operador de controle de erro ( @).

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

Como um bônus, com o valor retornado ( IMAGETYPE_XXX) de exif_imagetypetambém podemos obter o tipo MIME ou a extensão de arquivo com image_type_to_mime_type/ image_type_to_extension.

Yckart
fonte
4

Uma solução radical seria exibir os favicons como imagens de fundo em um div acima do ícone padrão. Dessa forma, toda a sobrecarga seria colocada no cliente, embora ainda não exibisse imagens quebradas (imagens de fundo ausentes são ignoradas em todos os navegadores AFAIK).

truppo
fonte
1
+1 se você não estiver verificando vários locais para seu favicon (favicon.ico, favicon.gif, favicon.png) esta parece ser a melhor solução
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
fonte
3

Você pode usar o seguinte:

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

Funcionou para mim ao tentar verificar se existe uma imagem no URL

Rickus Harmse
fonte
2

Você pode usar :

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

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

Isso funciona para mim para verificar se existe um arquivo remoto no 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
fonte
1

Você deve emitir solicitações HEAD, não GET, porque você não precisa do conteúdo URI de forma alguma. Como Pies disse acima, você deve verificar o código de status (em intervalos de 200-299, e você pode opcionalmente seguir redirecionamentos 3xx).

A questão das respostas contém muitos exemplos de código que podem ser úteis: PHP / Curl: Solicitação HEAD demora muito em alguns sites

drdaeman
fonte
1

Existe uma alternativa ainda mais sofisticada. Você pode fazer a verificação de todo o lado do cliente usando um truque 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); };
});

De http://snipplr.com/view/18782/add-a-favicon-near-external-links-with-jquery/ (o blog original está atualmente fora do ar)

S Pangborn
fonte
1

todas as respostas aqui que usam get_headers () estão fazendo uma solicitação GET. É muito mais rápido / mais barato apenas fazer uma solicitação HEAD.

Para garantir que get_headers () faça uma solicitação HEAD em vez de GET, você deve adicionar isto:

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

então, para verificar se existe um arquivo, seu código seria semelhante a este:

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 retornará falso ou verdadeiro, obviamente.

Ludo - não oficialmente
fonte
0

Não sei se este é mais rápido quando o arquivo não existe remotamente, is_file () , mas você pode tentar.

$favIcon = 'default FavIcon';
if(is_file($remotePath)) {
   $favIcon = file_get_contents($remotePath);
}
PatrikAkerstrand
fonte
Dos documentos: "A partir do PHP 5.0.0, esta função também pode ser usada com alguns wrappers de URL. Consulte Protocolos e Wrappers Suportados para determinar quais wrappers suportam a família de funcionalidade stat ()."
PatrikAkerstrand
Você quer dizer que isso poderia funcionar se você registrar um wrapper de fluxo? Edite sua pergunta para mostrar um exemplo de trabalho e eu removerei meu voto negativo (e votarei positivamente em você, se possível). Mas, por enquanto, testei is_file do php cli com um arquivo remoto e obtive falso.
greg0ire
nenhum exemplo de trabalho:var_dump(is_file('http://cdn.sstatic.net/stackoverflow/img/sprites.png')); bool(false)
greg0ire
0

Se o arquivo não estiver hospedado externamente, você pode traduzir o URL remoto em um caminho absoluto em seu servidor da web. Dessa forma, você não precisa chamar CURL ou file_get_contents, etc.

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

Nota: Seu servidor da web deve preencher DOCUMENT_ROOT para usar esta função

Bastian Fießinger
fonte
0

Se você estiver usando o framework Symfony, também há uma maneira muito mais simples de usar 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;
}

Os documentos para o HttpClient também são muito bons e talvez valha a pena dar uma olhada se você precisar de uma abordagem mais específica: https://symfony.com/doc/current/http_client.html

Filnor
fonte
-1

Você pode usar o sistema de arquivos: use Symfony \ Component \ Filesystem \ Filesystem; use Symfony \ Component \ Filesystem \ Exception \ IOExceptionInterface;

e verifique $ fileSystem = new Filesystem (); if ($ fileSystem-> exists ('path_to_file') == true) {...

Lenwë Galathil
fonte