Alters

Aventuras de DoHITB: parte II

Buenas!

Seguimos con mis pequeñas aventuras...

Hoy toca explicar un poco de qué iba el encargo que me mandaron, y como me las arreglé para hacerlo.

Al explicar cómo me las apañé, veremos código y algoritmos que nos podrán servir para otros proyectos.

Empecemos:
  • Descripción del encargo
Acceder a una web (la llamaré paginaweb.es), especializada en, digamos, intercambios (no penséis mal ahora! No son exactamente intercambios, pero si digo exactamente lo que es, se podría averiguar muy rápido qué web es...), y contestar a una selección de publicaciones (es decir, aplicando un filtro).

Es, por así decirlo, una especie de contestador automático (como el AT-5000 de los simpson jajajaja), pero para una web en exclusivo y para una pequeña porción de esta.

Yo, desde luego, solo me encargué de prepararlo todo; el contenido del mensaje lo dejé "configurable" para que quien me pidió el encargo pusiera lo que creyera conveniente.

Bien, se puede advertir que no es del todo "limpio" este tema, como ya comenté en la entrada anterior (de todas maneras, como "excusa", puedo decir que esa web tiene ya mucho spam, y por un poco más...)

Entonces, veamos un pequeño algoritmo (muy general) de lo que tenía que hace mi programa:

Configurar correo
Buscar publicaciones de las secciones indicadas
Enviar los correos (se envían desde la misma web)

La parte de configurar el correo es simple:

  • En PHP: se hace un form con los campos necesarios (en este caso: email de quien lo envía, asunto, texto). Al hacer submit del form, generamos un archivo de texto.
  • En vb.net: hacemos un button en el form (formulario, ventana) principal; al pulsarlo saldrá una nueva ventana con (de nuevo) los campos necesarios. Al validar el formulario, generamos un archivo de texto.
Modelo de archivo de texto: _GLOBAL_DATA.dat

[mail] = direccion_de_correo@direccion.com
[asunto] = asunto
[texto] = texto a enviar.| Sustituimos (para mejor gestión) los saltos de linea por pipelines ("|").|A la hora de recoger el texto, cambiamos las pipelines por saltos de línea.||Fin.

Ahora viene una parte que debemos solventar: ¿Cómo se qué publicaciones son las indicadas?

Pues con este interrogante me quedé... así que decidí investigar.

Lo primero es inspeccionar la página visualmente. Así que entro a www.paginaweb.es, y veo que hay un buscador por secciones

Busco, usando la primera sección, y me lleva a una página similar a la principal, con las publicaciones de esa sección. Para mi sorpresa veo que hay subsecciones (secciones dentro de secciones).

Entro a la primera subsección, y veo que no hay un tercer nivel. Bien... el problema es que hay varias páginas

Para más inri, veo que la web usa URL amigables, es decir, en vez de mostrar los parámetros de la URL los enmascara. Un ejemplo:

  • Sin URL amigables: http://paginaweb.es/buscar.php?seccion=X&subseccion=Y&pagina=1
  • Con URL amigables: http://paginaweb.es/buscar/X/Y/1
Se llaman "amigables" porque son más fáciles de recordar, y más bonitas visualmente (se ve más limpio).

Las URL amigables se consiguen moficando el archivo ".htaccess" del servidor (en futuras entradas tengo previsto una entrada para este tema)


Por curiosidad decido probar la página, y escribo directamente una URL que sé que no existirá, como:

http://www.paginaweb.es/buscar/qwertyui/holahola/999

No creo que exista la sección "qwertyui", subsección "holahola", página "999"... así que debería dar error. Y como era de esperar, la web me dice que la página no existe.

Hemos llegado a un "checkpoint" en nuestra pequeña investigación. De momento sabemos:

  • Tiene URL amigables
  • Se compone de secciones, subsecciones y páginas
  • Tiene protección contra errores

El siguiente paso es... ¿De donde saco las categorías? Podría copiarlas a mano mirando de la lista, pero... ¿Y si la actualizan? Entonces tendré que volver a copiar la lista... e ir revisando que la lista esté actualizada.

Por suerte (o por desgracia, según se mire), hay dos archivos con útil información que si están (y es recomendable que estén) han de ser públicos, es decir, que cualquiera los puede ver. Éstos son:
  • El mapa web
  • El archivo de robots 
El primer archivo es un archivo, generalmente con extensión "xml", que contiene una lista estructurada de los archivos que componen una web. Su estructura se basa en la siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc></loc>
    <lastmod></lastmod>
    <changefreq></changefreq>
    <priority></priority>
  </url>
</urlset>

Las dos primeras líneas son siempre las mismas: la primera indica la versión de XML y el charset (conjunto de caracteres) a usar (en este caso, UTF-8)

Después, hay una serie (aunque aquí solo haya uno) de nodos "url". Cada uno de estos nodos tiene información de una página web, a saber:

  • loc: la dirección web (http://www.paginaweb.com/buscar, por ejemplo)
  • lastmod: fecha de la última modificación de la página en cuestión
  • changefreq: aquí indicamos cada cuanto se actualiza la página: "daily" (cada día), "monthly" (cada mes)...
  • priority: le damos una importancia entre 0 y 1 (con un decimal) a la página, con respecto a las otras. Las páginas de prio. 1 tendrán mas prioridad que las que tengan 0.9



Y, ¿para qué se usa? Este archivo es usado por los buscadores (google, bing, yahoo) para insertarlos en sus bases de datos, y que cuando busquemos, nos salgan los resultados.

El otro archivo es el archivo de robots.

Éste siempre se tiene que llamar igual: robots.txt.

Sirve para que los robots (los "agentes" que rastrean las webs y los mapas webs) sepan qué páginas tienen que mirar y cuales no.

La estructura básica de este archivo es la siguiente:

User-agent: (nombre)
Disallow/Allow: página web

User-agent es el agente de búsqueda, por Internet están los nombres, aunque se suele aludir a todos a la vez con "*".

Bajo el User-agent indicado, permitiremos (Allow) o no (Disallow) que se mire la web, por ejemplo:

User-agent: *
Disallow: /pagina-secreta
Allow: /buscar

Se pueden poner varios User-agent si se quiere, para hacer más específico el archivo.

Ahora bien (al margen de todo), si tenemos deshabilitada la indexación de una página en concreto e intentamos entrar, ¿qué? Pues pasarán dos cosas:
  • Si la web ha estado bien programada: obtendremos un error, o una redirección
  • Si la web tiene un error: quien sabe lo que puede pasar... página en blanco, una página con errores...
Pero esto aún no lo trataremos (quizás lo vemos más adelante)

Volviendo a la investigación: fui a buscar el mapa web, para ver qué encontraba. Para encontrarlo hay dos vías:
  • Accedemos a robots.txt y buscamos ".xml", a ver si hay suerte
  • Usando un poco de sentido común vamos probando suerte
A mi me tocó usar la segunda... así que fui probando:
  • paginaweb.es/mapa.xml
  • paginaweb.es/mapaweb.xml
  • paginaweb.es/webmap.xml
  • paginaweb.es/sitemap.xml
Bingo! era sitemap.xml

Para mi sorpresa, veo que el mapa web tiene referencias a otros mapas web, que además están comprimidos con gzip (.gz)

Bueno, vamos avanzando... por suerte, PHP tiene soporte para comprimir y descomprimir archivos con gzip.

Pero antes de ponerse a ello, hay que ver si de verdad servirán los archivos comprimidos.

Copio la ruta de un <loc>, y se me descarga un archivo; lo descomprimo con winRAR, y veo que, efectivamente, cada archivo tiene una buena cantidad de <loc>, y que, a parte de tener las categorías, tiene las URL de TODAS las publicaciones de la web (cerca de 1.000.000.000)... guardar todas sería demasiado para PHP (en cuanto a memoria y tiempo), así que tendré que hacer algo para guardar solo las subcategorías (así las puedo poner en una lista)

Bien, de momento la investigación está completada: tengo la manera de acceder a las categorías... ahora falta averiguar la manera de sacarlas...

Para dar el máximo de código útil, haremos lo siguiente:
  • Obtener el mapa web general [PHP]
  • Obtener los mapas web referidos [PHP]
  • Descomprimirlos y guardarlos en nuestro servidor [PHP]
  • Descargar los archivos vía FTP [vb.NET]
  • Desmenuzar los archivos descargados [vb.NET]
  • Guardarlos en un archivo [vb.NET]
Entonces, todo empezará con un archivo .exe generado con vb.NET, al cual, al pulsar un button, hará todo eso.

Vayamos por partes:

Para obtener de una manera rápida el contenido de un documento (sea cual sea - fichero o web), en PHP tenemos la función file_get_contents()

Podemos definir, pues la siguiente función:

function getContent($url){
  return file_get_contents($url);
}

Esta función recibe un parámetro ($url) del que extraeremos el contenido. Puede parecer absurda esta función, pero después veremos que la podemos mejorar un poco...

El siguiente paso sería obtener los sitiemap referidos. Para ello tendremos que desmenuzar el archivo que acabamos de leer.

Lo más sencillo para desmenuzar es ir por pasos. Primero pasaremos el contenido del fichero a un array, con la función explode() de PHP.

function getMarkList($fgc){
    $array = explode("\n", $fgc);
    return $array;
}

Explode funciona de la siguiente manera: recibe dos parámetros. El primero se usa como delimitador, y el segundo es la cadena a partir. De esta manera, por ejemplo, si yo hago:

$array = explode("a", "Hola me llamo David");

El resultado sería:

$array[0] = "Hol", $array[1] = " me ll", $array[2] = "mo D", $array[3] = "vid".

Como veis, el delimitador ("a", en este caso) se pierde, por eso luego iteramos sobre el array y le añadimos una "a" al principio.

Además, nosotros buscamos las etiquetas "loc", por lo que podemos mejorar la función "getMarkList()"

function getMarkList($fgc, $token){
    $array = explode("\n", $fgc);
    $ret = array();

    for($i=0;$i<count($array);$i++){
        $aux = explode("<".$token.">", $array[$i]);

        if(count($aux) == 2){
            $aux = explode("</".$token.">", $aux[1];
            $ret[] = $aux[0];
        } 
    }

    return $ret;
}

Veamos: en $array guardamos lo mismo que antes. Luego iteramos sobre $array, y creamos un arreglo temporal ($aux). Éste será el resultado de hacer explode de "<$token>" sobre el elemento que estamos tratando.

Si este array ($aux) tiene 2 elementos, partimos el segundo elemento (índice 1) por el elemento "</$token>", y de este resultado agregamos el primer elemento (índice 0) al array que retornaremos.

Ahora, un ejemplo visual de porqué esta estructura:

Supongamos el siguiente $fgc

"<uno>
  <dos>
    <tres></tres>
  </dos>
  <tres></tres>
</uno>"

Al hacer explode, tendríamos:

$array = ("<uno>", "<dos>", "<tres>c1</tres>", "</dos>", "<tres>c2</tres>", "</uno>")

Y, supongamos que queremos el contenido de los elementos "tres", pues haríamos:

getMarkList($fgc, 'tres');

Al ir por los elementos de $array, los índices 0, 1, 3, y 5 no pasarían del primer "if", ya que al partirlo por "<tres>" no obtendríamos un array de dos posiciones.

Por otra parte, los índices 2 y 4 sí pasarían, quedando así:

$aux = array("<tres>" "c1</tres>")

Ahora, al el índice 1 por "</tres>", obtenemos:

$aux = array("c1", "</tres>")

Siendo $aux[0] lo que buscamos.

Ahora que tenemos la manera de obtener los "loc" del mapa web global, los guardaremos en un archivo. Para ello, usaremos las funciones fopen y fwrite de PHP.

function createBigIndex($list){
    $f = fopen("BIG_INDEX.dat", "w");

    for($i=0;$i<count($list);$i++)
        fwrite($f, "[".$i."] = ".$list[$i]."\n");

    fclose($f);
}

y ya tendremos nuestro archivo. Esta función itera sobre una lista, y la escribe con el siguiente patrón:

[0] = www....
[1] = www...

Bien, elaboremos ahora una llamada que cree el archivo grande:

createBigIndex(getMarkList(getContent("www.paginaweb.com/sitemap.xml"), 'loc'))

Aquí está todo en una sola llamada, para ahorrar espacio, pero vamos a desmenuzarlo:

Primero llamamos a getContent, y el resultado (una lista) pasa a getMarkList, con 'loc'. Este resultado pasará a ser el contenido del fichero.

Como esta entrada está quedando muy grande, seguiré en la próxima.

Veremos cómo apañárselas para obtener archivos y descomprimirlos; y usaremos algunas funciones que ya tenemos para obtener el listado de categorías.

A partir de ahí será cosa de vb.NET, el acceder vía FTP al archivo y generar una lista.

Las dos otras partes (conseguir las publicaciones y enviar los correos) los iremos viendo.

¡Hasta la próxima!