Clase Spider para obtener metas y contenido sin html tags de una web

28 28UTC Agosto 28UTCJueves 2008 at 11:38 pm (PHP)

Hola, estaba trabajando en un pequeño buscador de sitios webs, y me tope con el problema de que no habia ninguna script por ahi que limpie correctamente una web de los html tags para obtener solo el codigo, los meta y el titulo. Por ello me puse a escribir una clase que haga estas cosas.

Quedaron algunas pequeñas funcionalidades por crear, pueden verse en los comentarios de la script, como arreglar acentos y usar fsockopen si no hay libreria curl, tambien algunas funcionalidades un poco mas importantes como obtener todos los alts de las imagenes y obtener todas las links. No obstante creo que funcionara de maravillas para lo que necesito.

Ejemplo de uso;

$webFetch=Spider::getWebFull('http://technorati.com/');
print_r($webFetch);

//@author Eugenio Fage
abstract class Spider {
	public function getWebFull($url){
		$htmlCode=self::getWebCode($url);
		if($htmlCode=='') return array();
		$return['title']=self::getTitle($htmlCode);
		$return['metas']=self::getMetas($htmlCode);

		$return['text']=self::justText($htmlCode);

		return $return;
	}

	public function getWebCode($url){
		//@todo si no existen las curl functions usar fsockopen
		$ch = curl_init();

		curl_setopt ($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
		$data = curl_exec ($ch);
		curl_close ($ch);

		return $data;
	}

	public function getTitle($html,$charset=null){
		//@todo corregir acentos sin usar multi byte functions
		$arr=array();
		preg_match_all('@(<title>(.*)</title>)@i',$html,$arr);
		$arr=$arr[2];
		//el titulo no va a ser mas largoque 100 caracteres
		return(substr(strip_tags($arr[0]),0,110));
	}

	public function getMetas($html,$charset=null){
		//@todo corregir acentos sin usar multi byte functions
		$arr=array();
		preg_match_all('@(meta\sname=\"(.*)\"\scontent=\"(.*)\"[ /]*>)@i',$html,$arr);
		$meta=$arr[2];
		$content=$arr[3];
		unset($arr);

		while(($unMeta=array_pop($meta))){
			$metas[strtolower($unMeta)]=array_pop($content);
		}

		while(($unMeta=array_pop($meta))){
			$metas[strtolower($unMeta)]=array_pop($content);
		}

		preg_match_all('@(meta\scontent=\"(.*)\"\sname=\"(.*)\"[ /]*>)@i',$html,$arr);
		$meta=$arr[3];
		$content=$arr[2];
		unset($arr);

		while(($unMeta=array_pop($meta))){
			$metas[strtolower($unMeta)]=array_pop($content);
		}

		return $metas;
	}

	public function justText($html,$charset=null){
		//@todo corregir acentos sin usar multi byte functions
		$html=str_replace('>','> ', $html);

		$buscar=array('@<!--.*?-->@si','@<script[^>]*?>.*?</script>@si','@<style[^>]*?>.*?</style>@si');
		$html = preg_replace($buscar, ' ', $html);

		$html = preg_replace('@<.*?>@si', ' ', $html);

		$html=str_replace('&lt;',' ',$html);
		$html=str_replace('&gt;',' ',$html);

		$html=html_entity_decode(strip_tags($html));

		$html=str_replace(array('<','>','&gt;','&lt;',"\t",chr(13),chr(10),chr(160)),' ',$html);

		while(strpos($html,'  ')!==false){
			$html=str_replace('  ',' ',$html);
		}

		return substr($html,0,1500);
	}

}

Permalink 1 comentario

Benchmarking de funciones

1 01UTC Agosto 01UTCViernes 2008 at 4:48 am (PHP)

Hola, en muchos blogs en los que se habla de optimizacion en el codigo php puede leerse que la funcion include es mas rapida que require y que include_once.
Asi mismo tambien se sabe que incluir un archivo con su ruta completa es mas rapido que incluir un archivo sin su ruta correspondiente. A continuacion voy a comentar como se llega a estas conclusiones por medio de una herramienta de benchmark, aunque no es necesario hacer un benchmark para saber todo esto, basta con leer el manual de php y saber algo de sistemas operativos ;)

1) Primero les copiare el objeto Benckmark, que es el que voy a usar para los testeo.

<?php

/**
* Benchmarking for PHP applications
*
* Benchmark class provides an easy way to meassure the performance
* of different parts of your PHP applications. You can get any
* counters as you want in the same page execution, and get the output
* via html output or file.
*
*  LICENCE
*  ========
*	copyright (c) 2000 Patxi Echarte [patxi@eslomas.com]
*
*	This program is free software; you can redistribute it and/or
*	modify it under the terms of the GNU Lesser General Public License
*	version 2.1 as published by the Free Software Foundation.
*
*	This library is distributed in the hope that it will be useful,
*	but WITHOUT ANY WARRANTY; without even the implied warranty of
*	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*	GNU Lesser General Public License for more details at
*	http://www.gnu.org/copyleft/lgpl.html
*
*	You should have received a copy of the GNU General Public License
*	along with this program; if not, write to the Free Software
*	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*
* @package Benchmark
* @category Benchmarking
* @version $Id: Benchmark.class.php,v 1.2 2005/06/07 $
* @author Patxi Echarte <patxi@eslomas.com>
*/

class Benchmark{
	var $__start_times;  //contendrá los tiempos de inicio para cada contador
	var $__stop_times;
	var $__delta_points;

	/**
	* Constructor
    *
    * @access public
    */
   function Benchmark(){
      $this->_start_times = array();
      $this->_stop_times = array();
      $this->_delta_points = array();
   }

    /**
    * Inicializa un contador interno de benchmarking. Se le puede asignar un nombre
	* específico para llevar diferentes contadores en la misma petición
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @access public
    */
	function timingStart ($name = 'default') {
		$this->_start_times[$name] = explode(' ', microtime());
	}

    /**
    * Detiene un contador de benchmarking.
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @access public
    */
	function timingStop ($name = 'default') {
		$this->_stop_times[$name] = explode(' ', microtime());
	}

    /**
    * Devuelve el tiempo transcurrido para el contador que se indique, hasta
	* el momento actual si no se ha detenido ya el contador, o hasta el
	* momento en el que se detuvo en contador
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @return int número de milisegundos transcurridos
    * @access public
    */
	function timingCurrent ($name = 'default') {
		if (!isset($this->_start_times[$name])) {
			return 0;
		}
		if (!isset($this->_stop_times[$name])) {
			$stop_time = explode(' ', microtime());
		}
		else {
			$stop_time = $this->_stop_times[$name];
		}
		// do the big numbers first so the small ones aren't lost
		$current = $stop_time[1] - $this->_start_times[$name][1];
		$current += $stop_time[0] - $this->_start_times[$name][0];
		return $current;
	}

    /**
    * Añade un punto intermedio en el contador, de forma que el contador
	* continúa activo y cuando lo visualicemos obtengamos su información
	* y la de todos sus puntos intermedios
    *
    * @param string $txt identificador de texto para el punto, por defecto ''
    * @param string $name nombre del contador, por defecto 'default'
    * @access public
    */
	function addDeltaPoint($txt='',$name='default'){
		if(!is_array($this->_delta_points[$name]))
			$this->_delta_points[$name] = array();
		$this->_delta_points[$name][] = array('texto' => $txt, 'time' => explode(' ',microtime()));
	}

    /**
    * Devuelve un array con el contenido de todos los puntos intermedios de
	* ejecución del contador que se indique. Su formato es:
	*		array(texto=>nombre del punto, time=> milisegundos).
	* De esta forma obtenemos para un contador lo que le ha costado llegar a cada
	* punto intermedio desde el anterior, teniendo una imagen clara de que tareas
	* han costado más y cuales menos.
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @return array con los puntos y los milisegundos que ha costado llegar a ellos
    * @access public
    */
	function getDeltaPoints($name='default'){

		if(!is_array($this->_delta_points[$name])) return;

		$ini = array($this->_start_times[$name][0],$this->_start_times[$name][1]);

		foreach($this->_delta_points[$name] as $id => $point){

			$delta = $point['time'][1] - $ini[1];
			$delta += $point['time'][0] - $ini[0];

			$result[] = array(texto => $point['texto'], 'time' => $delta);

			$ini = $point['time'];

		}
		return $result;
	}

    /**
    * Devuelve una tabla HTML con el contenido de todos los puntos intermedios
	* para el contador indicado.
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @return string con el contenido de la tabla html
    * @access public
    */
	function getDeltaPointsHtmlTable($name='default'){

		if(!is_array($this->_delta_points[$name])) return;
		$ini = array($this->_start_times[$name][0],$this->_start_times[$name][1]);

		$res = '';
		$res = '<table border="1">';

		foreach($this->_delta_points[$name] as $id => $point){

			$delta = $point['time'][1] - $ini[1];
			$delta += $point['time'][0] - $ini[0];

			$res .= "<tr><td>$point[texto]</td><td>$delta</td></tr>";

			$ini = $point['time'];

		}
		$res .= "<tr><td><b>Total</b></td><td>" . $this->timingCurrent($name) . "</td></tr></table>";

		return $res;
	}

    /**
    * Escribe en el archivo que se indique una nueva línea al final del archivo,
	* con el contenido de cada punto intermedio, en forma de incrementos sobre
	* el punto anterior. Cada valor se escribe separado por comas para poder ser
	* leido fácilmente con CSV
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @param string $file nombre del archivo a escribir, por defecto '/tmp/phpbenchmarck.log'
    * @access public
    */
	function saveDeltaPointsToFile($name='default',$file='/tmp/phpbenchmarck.log'){

		if(!is_array($this->_delta_points[$name])) return;
		$ini = array($this->_start_times[$name][0],$this->_start_times[$name][1]);

		foreach($this->_delta_points[$name] as $id => $point){
			 $delta = $point['time'][1] - $ini[1];
			 $delta += $point['time'][0] - $ini[0];

			 $result[] =  number_format($delta,8,",",".");

			 $ini = $point['time'];
		}
		$line = implode(';',$result);

		//abro el fichero y escribo la línea con LOCK!!!
		$fp = @fopen($file, 'a');
		@flock($fp, LOCK_EX);
		@fputs($fp,$line."\n");
		@flock($fp, LOCK_UN);
	}
}
?>

2) Bien, ahora voy a crear una carpeta llamada includes, donde voy a meter archivos del tipo unNumero.php, y cada archivo contendra 3 asignaciones y una operacion de suma.
Yo voy a meter uno 100 archivos, con la siguiente scritp, ustedes en sus casa pueden usar el bloc de notas, jajaja

for ($i=0;$i<99;$i++){
	$caracteres=array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o');
	shuffle($caracteres);
	$var1='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var2='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var3='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);

	$out='<? '.$var1.'='.rand().'; '.$var2.'='.rand().'; '.$var3.'='.$var1.'+'.$var2.'; ?>';

	$file=fopen('includes/'.$i.'.php','w');
	fwrite($file,$out);
	fclose($file);
}

3) Testeando el tiempo de respuesta, ahora necesitamos crear 3 archivos;
require.php: este se encargara de usar la funcion require

<?php
include('Benchmark.class.php');
$bench=new Benchmark();

$bench->timingStart('test');
escribir();
$mInicio=memory_get_usage();
for ($i=0;$i<99;$i++){
	require('includes/'.$i.'.php');
}
$mFin=memory_get_usage();
$bench->addDeltaPoint('fin de test require', 'test');
echo 'requiere uso: '.($mFin-$mInicio).' bytes<br>';
$bench->saveDeltaPointsToFile('test','require.log');
$bench->timingStop();

function escribir(){
   for ($i=0;$i<99;$i++){
	$caracteres=array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o');
	shuffle($caracteres);
	$var1='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var2='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var3='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);

	$out='<? '.$var1.'='.rand().'; '.$var2.'='.rand().'; '.$var3.'='.$var1.'+'.$var2.'; ?>';

	$file=fopen('includes/'.$i.'.php','w');
	fwrite($file,$out);
	fclose($file);
   }
}
?>

Luego el archivo inlcude_once.php que testeara la funcion include_once;

<?php
include('Benchmark.class.php');
$bench=new Benchmark();

$bench->timingStart('test');
escribir();
$mInicio=memory_get_usage();
for ($i=0;$i<99;$i++){
	include_once('includes/'.$i.'.php');
}
$mFin=memory_get_usage();
$bench->addDeltaPoint('fin de test include_once', 'test');
echo 'include_once uso: '.($mFin-$mInicio).' bytes<br>';
$bench->saveDeltaPointsToFile('test','include_once.log');
$bench->timingStop();

function escribir(){
   for ($i=0;$i<99;$i++){
	$caracteres=array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o');
	shuffle($caracteres);
	$var1='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var2='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var3='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);

	$out='<? '.$var1.'='.rand().'; '.$var2.'='.rand().'; '.$var3.'='.$var1.'+'.$var2.'; ?>';

	$file=fopen('includes/'.$i.'.php','w');
	fwrite($file,$out);
	fclose($file);
   }
}
?>

Y por ultimo include.php que testeara la funcion include;

<?php
include('Benchmark.class.php');
$bench=new Benchmark();

$bench->timingStart('test');
escribir();
$mInicio=memory_get_usage();
for ($i=0;$i<99;$i++){
	include('includes/'.$i.'.php');
}
$mFin=memory_get_usage();
$bench->addDeltaPoint('fin de test include', 'test');
echo 'include uso: '.($mFin-$mInicio).' bytes<br>';
$bench->saveDeltaPointsToFile('test','include.log');
$bench->timingStop();

function escribir(){
   for ($i=0;$i<99;$i++){
	$caracteres=array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o');
	shuffle($caracteres);
	$var1='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var2='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var3='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);

	$out='<? '.$var1.'='.rand().'; '.$var2.'='.rand().'; '.$var3.'='.$var1.'+'.$var2.'; ?>';

	$file=fopen('includes/'.$i.'.php','w');
	fwrite($file,$out);
	fclose($file);
   }
}
?>

Bien, espero que hagan las pruebas y veran que el include es el mas rapido y menos costoso, ademas si agregamos la ruta completa, cosa que yo no hice en los ejemplos, veran que el tiempo de carga es todavia menor.

Saludos, Eugenio

Permalink Dejar un comentario