Cómo iniciar sesión en una web externa utilizando PHP

Me llegó esta pregunta a través de un grupo de Facebook en el que participo:

Quiero iniciar sesión en una web mediante curl con php. ¿Cómo puedo modificar este php?

El tema me recuerda un poco lo que escribí sobre obtener información de un sitio que no ofrece una API pero, si bien el problema es similar no es exactamente el mismo.

La idea aquí es simple, se trata de obtener programáticamente la información que podemos obtener usando un navegador para entrar a algún sitio.

Recientemente un amigo tiene en una farmacia me pedía ayuda para lograr algo bastante parecido: lo que él buscaba era ingresar al sitio de la droguería (su proveedor) y consultar el stock de un determinado medicamento (Algo que puede hacer muy sencillamente ingresando manualmente al sitio… imaginate que hacer una consulta por una buena cantidad de productos no es para nada práctico).

Pues bien, veamos cómo resolver este problema:

Para empezar, debemos entender que el sitio target está diseñado pensando en que será accedido mediante un navegador web, con lo cual, está esperando recibir peticiones HTTP (y sus respuestas serán HTML).

Si conocés un poco cómo funciona HTTP y sus sesiones sabrás que en realidad no existe tal cosa… todo se simula utilizando cookies.

cURL es una librería que permite realizar peticiones HTTP a bastante bajo nivel. Si bien no es la única opción (y probablemente no sea la mejor tampoco), voy a usarla para este ejemplo para dar respuesta a la pregunta original:

Lo primero que necesitaremos hacer es realizar una petición al sitio objetivo.

Seguramente en alguna parte de este sitio exista un formulario de login… algo que se vea más o menos así:

En realidad, lo que nosotros queremos no es la URL de este formulario. Lo que buscamos es la dirección del receptor del formulario, ya que lo que haremos será simular un envío.

Para ver eso simplemente necesitamos ver el código fuente de la página (CTRL+U) donde encontraremos algo como:

<form action="http://dominio.com/login/logear" method="post">
   <input name="user" type="text"/>
   <input name="pass" type="password"/>
</form>

Con lo cual, ya sabemos que nuestros datos serán enviados a http://dominio.com/login/logear.

Ahora sí, veamos algo de PHP 🙂

<?php

$processLoginURL = 'http://dominio.com/login/logear';

$userName = 'xxxxx';
$password = 'yyyyy';

$ch = curl_init($processLoginURL);
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS,
    "user=$userName&pass=$password");

$res = curl_exec( $ch );
echo curl_getinfo( $ch, CURLINFO_HTTP_CODE);
if ( $error = curl_error( $ch ) ) {
    die ($error);
}
echo $res;
curl_close( $ch );

Este código dará como resultado la página que se obtiene luego de hacer login (asumiendo que los datos son correctos).

Pero lo realmente importante aquí es tener la cookie de sesión que nos permita hacer nuevas peticiones como un usuario autenticado.

Veamos el código mejorado:

<?php

$processLoginURL = 'http://dominio.com/login/logear';

$userName = 'xxxxx';
$password = 'yyyyy';

$ch = curl_init($processLoginURL);
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS,
    "user=$userName&pass=$password");
curl_setopt( $ch, CURLOPT_COOKIEJAR, __DIR__.'/cookies.txt' );

$res = curl_exec( $ch );
echo curl_getinfo( $ch, CURLINFO_HTTP_CODE);
if ( $error = curl_error( $ch ) ) {
    die ($error);
}
echo $res;
curl_close( $ch );

Al agregar la línea curl_setopt( $ch, CURLOPT_COOKIEJAR, __DIR__.'/cookies.txt' ); estamos indicando a cURL que almacene las cookies en el archivo cookies.txt.

Una vez ejecutado este código, el archivo se verá similar a:

# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

dominio.com   FALSE   /       FALSE   0       PHPSESSID       a9e5bdbdf0e28755c3f558222858f39a

A partir de aquí, el desafío es usar esta cookie en pedidos subsiguientes.

Como decía, nos interesaba entrar a ver información de stock, si vamos a la URI /stock nos encontramos con algo como:

Así que nuestro próximo pedido será a http://dominio.com/stock (Información obtenida inspeccionando el HTML del form):

curl_setopt( $ch, CURLOPT_URL, 'http://dominio.com/stock' );
curl_setopt( $ch, CURLOPT_POSTFIELDS,
    "buscar[empieza_con]=A" );
curl_setopt( $ch, CURLOPT_COOKIEFILE, __DIR__.'/cookies.txt' );
$res = curl_exec( $ch );
if ( $error = curl_error( $ch ) ) {
    die ($error);
}

Aquí la línea importante es curl_setopt( $ch, CURLOPT_COOKIEFILE, __DIR__.'/cookies.txt' ); con ella estamos indicando a cURL que tome el valor de las cookies de este archivo y lo envíe como parte del request.

A la vuelta pues recibimos un HTML, por ejemplo:

<table class=" zebra stockConsulta min3 tablesorter sortcompletecallback-callback-calczebra" id="fd-table-1" width="80%" cellspacing="0" cellpadding="2">
 <thead>
 <tr>
 <th><b>Cant</b></th><th> </th>
 <th class="sortable-text fd-column-2" style="width: 350px; -moz-user-select: none;"><a href="#" class="fdTableSortTrigger" title="Ordenar por “      Nombre”">      Nombre</a><span></span></th>
 <th> </th>
 <th class="d"><b>% Dto <br>S/Púb.</b></th>
 <th class="m sortable-text fd-column-5" style="-moz-user-select: none;"><a href="#" class="fdTableSortTrigger" title="Ordenar por “Oferta”">Oferta</a><span></span></th>
 <th class="d sortable-text fd-column-6" style="-moz-user-select: none;"><a href="#" class="fdTableSortTrigger" title="Ordenar por “Stock”">Stock</a><span></span></th>
 <th class="c"></th>
 
 <th class="d sortable-numeric fd-column-8" style="-moz-user-select: none;"><a href="#" class="fdTableSortTrigger" title="Ordenar por “Su Precio”">Su Precio</a><span></span></th>
 <th class="d sortable-numeric fd-column-9" style="-moz-user-select: none;"><a href="#" class="fdTableSortTrigger" title="Ordenar por “Su Preciocon Dto”">Su Precio<br>con Dto</a><span></span></th>
 <th class="d sortable-numeric fd-column-10" style="-moz-user-select: none;"><a href="#" class="fdTableSortTrigger" title="Ordenar por “Preciocon Bon”">Precio<br>con Bon</a><span></span></th>
 <th class="d " width="85"><b>$ Púb<br>Sugerido</b></th>
 </tr>
 </thead>
 <tbody>
 <tr class="even"> 
 <td width="25px"> <input maxlength="3" tabindex="1" autocomplete="off" class="cant" value="" name="cant[2168891]" type="text"></td>
 <td width="16px"><a href="#2168891" title="Buscar productos con las mismas monodrogas" class="siafCons"><img src="http://dominio.com/img/ico_siaf.png" alt="A ACIDO 005% CREMA X 10 GRS"></a></td>
 <td class="t1 filtro-b" onmouseover="ddrivetip('<center class=\'red\'></center><center><b>A ACIDO 005% CREMA X 10 GRS</b></center><b>Cod.Barras:</b> 7795327060006<br/><b>IVA:</b> No<br/><b>Proveedor:</b> DOMINGUEZ<br/><b>Rubro:</b> ESPECIALIDADES<br/><b>Troquel:</b> 2168891<br/>', 300,'#FFF','2168891',false); " onmouseout="hideddrivetip()"><a class="fbox" href="http://dominio.com/stock/verArticuloDetalle&alfabeta=2586&codigo=2168891">A ACIDO 005% CREMA X 10 GRS</a></td>
 <td class="t4"></td><td class="d">40,90</td>
 <td class="m"><b>PSL-14.29% Min.:7 Final</b></td>
 <td class="d">Si</td>
 <td class="c"></td>
 <td class="d ri">136,28</td>
 <td class="d ri">100,69</td>
 <td class="d"></td>
 <td class="d t2 ri" onmouseover="ddrivetip('<center><b>A ACIDO 005% CREMA X 10 GRS</b></center><b>Precio vigente desde: </b>08-02-2018 16:35:28<br/><b>Pre. Pub. anterior:</b> 163.33<br/><b>Variación:</b> 4.30%<br/>', 300,'#FFF');" onmouseout="hideddrivetip()"> 170,35</td>
 </tr>
 <tr class="odd">
 <td width="25px"> <input maxlength="3" tabindex="2" autocomplete="off" class="cant" value="" name="cant[2168971]" type="text"></td>
 <td width="16px"><a href="#2168971" title="Buscar productos con las mismas monodrogas" class="siafCons"><img src="http://dominio.com/img/ico_siaf.png" alt="A ACIDO 01% CREMA X 10 GRS"></a></td>
 <td class="t1 filtro-b" onmouseover="ddrivetip('<center class=\'red\'></center><center><b>A ACIDO 01% CREMA X 10 GRS</b></center><b>Cod.Barras:</b> 7795327060020<br/><b>IVA:</b> No<br/><b>Proveedor:</b> DOMINGUEZ<br/><b>Rubro:</b> ESPECIALIDADES<br/><b>Troquel:</b> 2168971<br/>', 300,'#FFF','2168971',false); " onmouseout="hideddrivetip()"><a class="fbox" href="http://dominio.com/stock/verArticuloDetalle&alfabeta=2587&codigo=2168971">A ACIDO 01% CREMA X 10 GRS</a></td>
 <td class="t4"></td><td class="d">40,90</td>
 <td class="m"><b>PSL-14.29% Min.:7 Final</b></td>
 <td class="d">Pocos</td>
 <td class="c"></td>
 <td class="d ri">161,30</td>
 <td class="d ri">119,18</td>
 <td class="d"></td>
 <td class="d t2 ri" onmouseover="ddrivetip('<center><b>A ACIDO 01% CREMA X 10 GRS</b></center><b>Precio vigente desde: </b>08-02-2018 16:35:28<br/><b>Pre. Pub. anterior:</b> 193.32<br/><b>Variación:</b> 4.30%<br/>', 300,'#FFF');" onmouseout="hideddrivetip()"> 201,63</td>
 </tr>
 </tbody>
</table>

Y ahora, ¿qué hacemos con esto? Bueno, una posibilidad es usar un parser de DOM (¿Qué tal SimpleDOM?) para buscar el elemento que quiera, almacenar el resultado en un archivo, enviarlo en un email… en fin, las posibilidades son variadas.

Otra posibilidad para hacer lo mismo es usar alguna librería de más alto nivel que cURL, por ejemplo Guzzle o mejor el genial DomCrawler de Symfony.

Es importante entender que, si bien esto va a funcionar, no es para nada recomendable ya que es un mecanismo muy endeble.

Basta con que se modifique ligeramente el HTML para que la respuesta obtenida no sea la buscada (Ni hablar si llegan a cambiar las URLs)… siempre debemos preferir usar una API para este tipo de tareas (Asumiendo que esté disponible por supuesto :)).

¿Te quedó alguna duda? ¡Dejala en un comentario!

mchojrin

Por mchojrin

Ayudo a desarrolladores PHP a acceder mercados y clientes más sofisticados y exigentes

¿Te quedó alguna duda? Publica aca tu pregunta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.