Clase Spider para obtener metas y contenido sin html tags de una web
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('<',' ',$html);
$html=str_replace('>',' ',$html);
$html=html_entity_decode(strip_tags($html));
$html=str_replace(array('<','>','>','<',"\t",chr(13),chr(10),chr(160)),' ',$html);
while(strpos($html,' ')!==false){
$html=str_replace(' ',' ',$html);
}
return substr($html,0,1500);
}
}
Benchmarking de funciones
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