Clase wget

      No hay comentarios en Clase wget

Esta es una clase que escribí hace ya unos meses para parsear páginas web, bajar archivos de sitios web y otras muchas cosas para las que utilizaba Wget y scripts cmd.

Cuando la cosa se complica, empieza a resultar muy difícil hacer las cosas mediante wget, greps y truquitos… es entonces cuando conviene cambiar de lenguaje.

En PHP utilizaba file_get_contents para los html normales, y abría los ficheros en lectura en un bucle de lectura fopen-fread-fclose para los binarios. Este método, si bien me ha funcionado muy bien durante años, implica que para gestionar las cookies tienes que abrir sockets y es un lío, aunque es posible y así lo hicimos durante algunos años.

Más tarde descubrí la extensión pecl_http, con esta extensión podía gestionar de maravilla las cookies y otras muchas cosas… pero bajar binarios de más de 128Mb era un reto. El problema es obvio: pecl_http realiza todo el trabajo en memoria, lo que significa que la página/binario más grande que puede manejar viene determinado por la memoria asignada al PHP (memory_limit). Así que otra vez tenía que recurrir a un bucle de lectura fopen-fread-fclose.

Buscando otra manera de hacer las cosas me intereso por cURL. Acostumbrado a pecl_http había algunas cosas que me resultaban extrañas, como que cURL por defecto envía el resultado de la petición a la salida estándar, por lo que para obtener el resultado en una variable se suele recurrir al truco de utilizar las funciones del control de buffer de salida mediante ob_start-ob_get_contents-ob_end_clean. Esto me parecía burdo, demasiado burdo.

Evidentemente sabía que cURL, como otras tantas cosas buenas que tiene el PHP, venía de librerías con sobrada solvencia. Lo que significa que tenía que haber una manera mejor de hacer las cosas… y la hay.

cURL es una librería maravillosa, y toda su potencia está disponible en PHP. Sin embargo, como suele ocurrir con las herramientas potentes, manejar un monstruo no es tarea de un día. Así que cree una clase para facilitar el trabajo con cURL al estilo de GNU Wget, basado, que no igual.

Wget ha sido mi inspiración, también mi anterior experiencia con pecl_http, he creado una clase para encapsular todo de manera que sea más sencillo utilizar todas las posibilidades, además eso previene la colisión de nombres de funciones y variables (es una manera muy eficaz de evitar el uso de variables globales, por ejemplo). Además si trabajas con un framework es más fácil de utilizar una clase externa que un conjunto de funciones sueltos por ahí, por ejemplo yo la he utilizado con éxito junto con CakePHP con simplemente declararla:

App::uses('wget', 'Vendor');

La clase no ha sido un trabajo de un sólo día… llevo meses utilizándola para diversas tonterías sin importancia pero con necesidades distintas que me han hecho ir modificando y añadiendo más funciones.

Las últimas modificaciones tiene que ver con la versión de PHP y el entorno productivo… yo la he estado utilizando con PHP-CLI y PHP-5.5/5.6, sin embargo con versiones inferiores hay una función de cURL que no existe. Además en según que entorno con la configuración del módulo de Apache la constante STDOUT no está definida.

La solución práctica a dichas incompatibilidades es comprobar si la función/constante existe y de no existir se crea/define. En el caso de la función lo que he hecho es crear una función “dummy”, es decir, no hace nada, pero no importa porque es una función de información. Para la constante STDOUT lo que hago es declararla como un handle a php://stdout, y así todo funciona bien en los tres entornos distintos en los que he probado la clase.

if (!defined('STDOUT')) {
  define('STDOUT', fopen('php://stdout', 'w'));
}

if (!function_exists('curl_strerror')) {
  function curl_strerror ($curl_error) {
    return $curl_error;
  }
}

// Based on mipa code:
// http://php.net/manual/es/curlfile.construct.php#114539
if (!function_exists('curl_file_create')) {
  function curl_file_create ($filename, $mimetype = null, $postname = null) {
    $file = null;
    if (is_file($filename)) {
      if (empty($mimetype) and function_exists('mime_content_type')) $mimetype = mime_content_type($filename);
      if (empty($postname)) $postname = basename($filename);
      $file = '@';
      $file .= realpath($filename);
      $file .= ';filename=' . $postname;
      if (isset($mimetype)) $file .= ';type=' . $mimetype;
    }
    return $file;
  }
}

Como he dicho, una de las motivaciones de crear esta clase era interpretar y extraer información de páginas web. Hasta ahora utilizaba expresiones regulares, lo que era una lata porque llevaba mucho tiempo deducir las expresiones de búsqueda y cualquier pequeña variación en los datos podía dar al traste con el invento… y hablo de años de experiencia en estos menesteres.

Recientemente, junto con el descubrimiento y aprendizaje de cURL, he cambiado de técnica de análisis de páginas web: XML. Efectivamente, se me ocurrió cargar las páginas html dentro de un objeto xml y procesarlas mediante las funciones de PHP para XML.

Para la extracción de información mediante XML he decidido utilizar SimpleXML porque, como su propio nombre indica, es una clase muy simple y rápida de aprender. Sin embargo carece de la función de importación de un HTML como XML. Dicha función si está disponible en el DOM, pero no en SimpleXML, así que he añadido una función suelta a la clase: simplexml_import_html. Esta función se crea si no existe (en previsión de un futuro en el que la incluyan en el SimpleXML) y trabaja de la misma manera que simplexml_load_string pero con HTML en lugar de XML.

if (!function_exists('simplexml_import_html')) {
  function simplexml_import_html ($html) {
    $doc = new DOMDocument();
    @$doc->loadHTML($html);
    $xml = simplexml_import_dom($doc);
    return $xml;
  }
}

La clase wget completa en GitHub

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *