¿Cómo puedo encontrar a dónde se me redirigirá usando cURL?

149

Estoy tratando de hacer que curl siga una redirección, pero no consigo que funcione correctamente. Tengo una cadena que quiero enviar como parámetro GET a un servidor y obtener la URL resultante.

Ejemplo:

Cadena = URL de alimañas Kobold
= www.wowhead.com/search?q=Kobold+Worker

Si va a esa URL, lo redirigirá a "www.wowhead.com/npc=257". Quiero que curl devuelva esta URL a mi código PHP para poder extraer el "npc = 257" y usarlo.

Código actual:

function npcID($name) {
    $urltopost = "http://www.wowhead.com/search?q=" . $name;
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1");
    curl_setopt($ch, CURLOPT_URL, $urltopost);
    curl_setopt($ch, CURLOPT_REFERER, "http://www.wowhead.com");
    curl_setopt($ch, CURLOPT_HTTPHEADER, Array("Content-Type:application/x-www-form-urlencoded"));
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
    return curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
}

Sin embargo, esto devuelve www.wowhead.com/search?q=Kobold+Worker y no www.wowhead.com/npc=257 .

Sospecho que PHP está regresando antes de que ocurra la redirección externa. ¿Cómo puedo arreglar esto?

Thomas Van Nuffel
fuente
8
Esta es una de las principales preguntas para "redireccionamientos de seguimiento de curl". Para seguir automáticamente los redireccionamientos con el curlcomando, pase el indicador -Lo --location. Por ejemplocurl -L http://example.com/
Rob W

Respuestas:

256

Para hacer que cURL siga una redirección, use:

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

Erm ... no creo que realmente estés ejecutando el rizo ... Prueba:

curl_exec($ch);

... después de configurar las opciones y antes de la curl_getinfo()llamada.

EDITAR: si solo desea averiguar hacia dónde se redirige una página, usaría el consejo aquí , y solo usar Curl para tomar los encabezados y extraer el Ubicación: encabezado de ellos:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
if (preg_match('~Location: (.*)~i', $result, $match)) {
   $location = trim($match[1]);
}
Matt Gibson
fuente
2
Esto hace que php siga la redirección. No quiero seguir la redirección, solo quiero saber la URL de la página redirigida.
Thomas Van Nuffel
9
Ah, ¿entonces no quieres ir a la página? ¿Solo averigua la ubicación? En ese caso, sugeriría la táctica utilizada aquí: zzz.rezo.net/HowTo-Expand-Short-URLs.html , básicamente solo tome el encabezado de la página que redirige y tome la ubicación: encabezado de él. De cualquier manera, sin embargo, aún necesita hacer el exec () para que Curl realmente haga algo ...
Matt Gibson
1
Sugiero echar un vistazo a la solución de Luca Camillos a continuación, porque esta solución no tiene en cuenta las redirecciones múltiples.
Christian Engel
Esta solución abre la nueva página web dentro de la misma URL. Quiero cambiar la url también junto con publicar los parámetros en esa url. ¿Cómo puedo lograr eso?
amanpurohit
@MattGibson cuando uso $ httpCode = curl_getinfo ($ handle, CURLINFO_HTTP_CODE); con CURLOPT_FOLLOWLOCATION establecido en verdadero, lo que será el código http. Quiero decir, será para la primera url o para la url de redireccionamiento
Manigandan Arjunan
26

Agregue esta línea para curvar la inicialización

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

y use getinfo antes de curl_close

$redirectURL = curl_getinfo($ch,CURLINFO_EFFECTIVE_URL );

es:

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_USERAGENT,'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT ,0); 
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
$html = curl_exec($ch);
$redirectURL = curl_getinfo($ch,CURLINFO_EFFECTIVE_URL );
curl_close($ch);
Luca Camillo
fuente
2
Creo que esta es la mejor solución, porque también despliega múltiples redirecciones.
Christian Engel
Recuerde: (ok, duh) los datos POST no se volverán a enviar después de una redirección. En mi caso, esto sucedió y luego me sentí estúpido porque: solo usa la URL apropiada y está arreglado.
twicejr
Usar curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);es una vulnerabilidad de seguridad. Básicamente dice "Ignora los errores de SSL si está roto, confía en lo mismo que lo haría con una URL sin cifrar".
Finesse
8

La respuesta anterior no funcionó para mí en uno de mis servidores, algo relacionado con basedir, por lo que lo reescribí un poco. El siguiente código funciona en todos mis servidores.

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$a = curl_exec($ch);
curl_close( $ch ); 
// the returned headers
$headers = explode("\n",$a);
// if there is no redirection this will be the final url
$redir = $url;
// loop through the headers and check for a Location: str
$j = count($headers);
for($i = 0; $i < $j; $i++){
// if we find the Location header strip it and fill the redir var       
if(strpos($headers[$i],"Location:") !== false){
        $redir = trim(str_replace("Location:","",$headers[$i]));
        break;
    }
}
// do whatever you want with the result
echo redir;
GR1NN3R
fuente
El Location: encabezado no siempre es seguir una redirección. También vea una pregunta explícita sobre esto: error de ubicación de seguimiento de curl
hakre
5

La respuesta elegida aquí es decente, pero distingue entre mayúsculas y minúsculas, no protege contra location:encabezados relativos (lo que hacen algunos sitios) o páginas que en realidad podrían tener la fraseLocation: en su contenido ... (que zillow tiene actualmente).

Un poco descuidado, pero un par de ediciones rápidas para hacer esto un poco más inteligente son:

function getOriginalURL($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    $result = curl_exec($ch);
    $httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    // if it's not a redirection (3XX), move along
    if ($httpStatus < 300 || $httpStatus >= 400)
        return $url;

    // look for a location: header to find the target URL
    if(preg_match('/location: (.*)/i', $result, $r)) {
        $location = trim($r[1]);

        // if the location is a relative URL, attempt to make it absolute
        if (preg_match('/^\/(.*)/', $location)) {
            $urlParts = parse_url($url);
            if ($urlParts['scheme'])
                $baseURL = $urlParts['scheme'].'://';

            if ($urlParts['host'])
                $baseURL .= $urlParts['host'];

            if ($urlParts['port'])
                $baseURL .= ':'.$urlParts['port'];

            return $baseURL.$location;
        }

        return $location;
    }
    return $url;
}

Tenga en cuenta que esto todavía solo va 1 redirección profunda. Para profundizar, realmente necesita obtener el contenido y seguir las redirecciones.

broox
fuente
5

A veces necesita obtener encabezados HTTP, pero al mismo tiempo no desea devolver esos encabezados. **

Este esqueleto se encarga de las cookies y redireccionamientos HTTP mediante recursividad. La idea principal aquí es evitar devolver encabezados HTTP al código del cliente.

Puedes construir una clase de rizo muy fuerte sobre ella. Agregar funcionalidad POST, etc.

<?php

class curl {

  static private $cookie_file            = '';
  static private $user_agent             = '';  
  static private $max_redirects          = 10;  
  static private $followlocation_allowed = true;

  function __construct()
  {
    // set a file to store cookies
    self::$cookie_file = 'cookies.txt';

    // set some general User Agent
    self::$user_agent = 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)';

    if ( ! file_exists(self::$cookie_file) || ! is_writable(self::$cookie_file))
    {
      throw new Exception('Cookie file missing or not writable.');
    }

    // check for PHP settings that unfits
    // correct functioning of CURLOPT_FOLLOWLOCATION 
    if (ini_get('open_basedir') != '' || ini_get('safe_mode') == 'On')
    {
      self::$followlocation_allowed = false;
    }    
  }

  /**
   * Main method for GET requests
   * @param  string $url URI to get
   * @return string      request's body
   */
  static public function get($url)
  {
    $process = curl_init($url);    

    self::_set_basic_options($process);

    // this function is in charge of output request's body
    // so DO NOT include HTTP headers
    curl_setopt($process, CURLOPT_HEADER, 0);

    if (self::$followlocation_allowed)
    {
      // if PHP settings allow it use AUTOMATIC REDIRECTION
      curl_setopt($process, CURLOPT_FOLLOWLOCATION, true);
      curl_setopt($process, CURLOPT_MAXREDIRS, self::$max_redirects); 
    }
    else
    {
      curl_setopt($process, CURLOPT_FOLLOWLOCATION, false);
    }

    $return = curl_exec($process);

    if ($return === false)
    {
      throw new Exception('Curl error: ' . curl_error($process));
    }

    // test for redirection HTTP codes
    $code = curl_getinfo($process, CURLINFO_HTTP_CODE);
    if ($code == 301 || $code == 302)
    {
      curl_close($process);

      try
      {
        // go to extract new Location URI
        $location = self::_parse_redirection_header($url);
      }
      catch (Exception $e)
      {
        throw $e;
      }

      // IMPORTANT return 
      return self::get($location);
    }

    curl_close($process);

    return $return;
  }

  static function _set_basic_options($process)
  {

    curl_setopt($process, CURLOPT_USERAGENT, self::$user_agent);
    curl_setopt($process, CURLOPT_COOKIEFILE, self::$cookie_file);
    curl_setopt($process, CURLOPT_COOKIEJAR, self::$cookie_file);
    curl_setopt($process, CURLOPT_RETURNTRANSFER, 1);
    // curl_setopt($process, CURLOPT_VERBOSE, 1);
    // curl_setopt($process, CURLOPT_SSL_VERIFYHOST, false);
    // curl_setopt($process, CURLOPT_SSL_VERIFYPEER, false);
  }

  static function _parse_redirection_header($url)
  {
    $process = curl_init($url);    

    self::_set_basic_options($process);

    // NOW we need to parse HTTP headers
    curl_setopt($process, CURLOPT_HEADER, 1);

    $return = curl_exec($process);

    if ($return === false)
    {
      throw new Exception('Curl error: ' . curl_error($process));
    }

    curl_close($process);

    if ( ! preg_match('#Location: (.*)#', $return, $location))
    {
      throw new Exception('No Location found');
    }

    if (self::$max_redirects-- <= 0)
    {
      throw new Exception('Max redirections reached trying to get: ' . $url);
    }

    return trim($location[1]);
  }

}
Igor Parra
fuente
0

Muchas expresiones regulares aquí, a pesar de que realmente me gustan de esta manera, podrían ser más estables para mí:

$resultCurl=curl_exec($curl); //get curl result
//Optional line if you want to store the http status code
$headerHttpCode=curl_getinfo($curl,CURLINFO_HTTP_CODE);

//let's use dom and xpath
$dom = new \DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($resultCurl, LIBXML_HTML_NODEFDTD);
libxml_use_internal_errors(false);
$xpath = new \DOMXPath($dom);
$head=$xpath->query("/html/body/p/a/@href");

$newUrl=$head[0]->nodeValue;

La parte de ubicación es un enlace en el HTML enviado por apache. Entonces Xpath es perfecto para recuperarlo.

Patrick Valibus
fuente
-1

Puedes usar:

$redirectURL = curl_getinfo($ch,CURLINFO_REDIRECT_URL);
Abhilash Nayak
fuente