En este artículo, veremos cómo hacer un rastreador web simple y relativamente avanzado (o spider-bot) en PHP. El más simple simplemente generará todos los enlaces que encuentre en una página web, mientras que el avanzado agregará los títulos, palabras clave y descripciones a una base de datos conceptual (conceptual significa que no se utiliza una base de datos SQL en este artículo). En comparación con Google, incluso nuestro rastreador web avanzado es en realidad un simple rastreador web, ya que nuestro rastreador no utiliza ningún agente de IA. Pasaremos por un total de 3 iteraciones antes de concluir el artículo, cada una de ellas con una explicación.
Nota: A lo largo de este artículo, usaré las palabras spider-bot y web-crawler indistintamente. Algunas personas pueden usarlas en un contexto diferente, pero en este artículo ambas palabras significan esencialmente lo mismo.
Hay muchas cosas que puede hacer para mejorar este robot araña y hacerlo más avanzado: agregue funcionalidades como mantener un índice de popularidad y también implementar algunas funciones antispam como penalizar sitios web sin contenido o sitios web que usan «click-bait» ¡estrategias como agregar palabras clave que no tienen nada que ver con el contenido de la página! Además, puede intentar generar las palabras clave y la descripción de la página, algo que GoogleBot hace todo el tiempo. A continuación hay una lista de artículos relevantes que puede consultar si desea mejorar este robot araña.
Un SpiderBot simple: la versión simple no será recursiva y simplemente imprimirá todos los enlaces que encuentre en una página web. ¡ Tenga en cuenta que toda nuestra lógica principal sucederá en followLinks
función!
- Programa:
<?php
function
followLink(
$url
) {
// We need this options when creating context
$options
=
array
(
'http'
=>
array
(
'method'
=>
"GET"
,
'user-agent'
=>
"gfgBot/0.1\n"
)
);
// Create context for communication
$context
=stream_context_create(
$options
);
// Create a new HTML DomDocument for web-scraping
$doc
=
new
DomDocument();
@
$doc
-> loadHTML(
file_get_contents
(
$url
, false,
$context
) );
// Get all the anchor nodes in the DOM
$links
=
$doc
-> getElementsByTagName(
'a'
);
// Iterate through all the anchor nodes
// found in the document
foreach
(
$links
as
$i
)
echo
$i
->getAttribute(
'href'
) .
'<br/>'
;
}
?>
- Salida: Ahora, esto no fue bueno, solo obtenemos un enlace, eso se debe a que solo tenemos un enlace en el sitio
example.com
y, dado que no somos recursivos, no seguimos el enlace que obtuvimos. Puedes correrfollowLink("http://apple.com")
si quieres verlo en acción completa. Sin embargo, si usa geeksforgeeks.com, es posible que obtenga algún error, ya que GeeksforGeeks bloqueará nuestra solicitud (por razones de seguridad, por supuesto).https://www.iana.org/domains/example
- Línea 3: Estamos creando una
$options
array. No tiene que entender mucho al respecto, aparte de que esto será necesario en la creación de contexto. Tenga en cuenta que eluser-agent
nombre es gfgBot; puede cambiarlo por el que desee. Incluso puede usar GoogleBot para engañar a un sitio web para que piense que su rastreador es el bot araña de Google, siempre que use este método para descubrir el bot. - Línea 10: Estamos creando contexto para la comunicación. Para cualquier cosa necesitas contexto: para contar una historia necesitas un contexto. Para crear una ventana en OpenGL, necesita un contexto: ¡lo mismo para HTML5 Canvas y lo mismo para PHP Network Communication! Lo siento si me salí de «contexto» pero tenía que hacer eso.
- Línea 13: cree un DomDocument que es básicamente una estructura de datos para el manejo de DOM que se usa generalmente para archivos HTML y XML.
- Línea 14: ¡Cargamos HTML proporcionando el contenido del documento! Este proceso puede crear algunas advertencias (ya que está en desuso), por lo que suprimimos todas las advertencias.
- Línea 17: Creamos básicamente una array de todos los Nodes de anclaje que encontramos en el DOM.
- Línea 21: Imprimimos todos los enlaces a los que hacen referencia esos Nodes de anclaje.
- Línea 24: ¡Obtenemos todos los enlaces en el sitio web ejemplo.com ! Solo tiene un enlace que se emite.
- Programa:
<?php
// List of all the links we have crawled!
$crawledLinks
=
array
();
function
followLink(
$url
,
$depth
= 0){
global
$crawledLinks
;
$crawling
=
array
();
// Give up to prevent any seemingly infinite loop
if
(
$depth
>5){
echo
"<div style='color:red;'>The Crawler is giving up!</div>"
;
return
;
}
$options
=
array
(
'http'
=>
array
(
'method'
=>
"GET"
,
'user-agent'
=>
"gfgBot/0.1\n"
)
);
$context
= stream_context_create(
$options
);
$doc
=
new
DomDocument();
@
$doc
-> loadHTML(
file_get_contents
(
$url
, false,
$context
));
$links
=
$doc
->getElementsByTagName(
'a'
);
foreach
(
$links
as
$i
){
$link
=
$i
->getAttribute(
'href'
);
if
(ignoreLink(
$link
))
continue
;
$link
= convertLink(
$url
,
$link
);
if
(!in_array(
$link
,
$crawledLinks
)){
$crawledLinks
[] =
$link
;
$crawling
[] =
$link
;
insertIntoDatabase(
$link
,
$depth
);
}
}
foreach
(
$crawling
as
$crawlURL
){
echo
(
"<span style='color:grey;margin-left:"
.(10*
$depth
).
";'>"
.
"[+] Crawling <u>$crawlURL</u></span><br/>"
);
followLink(
$crawlURL
,
$depth
+1);
}
if
(
count
(
$crawling
)==0)
echo
(
"<span style='color:red;margin-left:"
.(10*
$depth
).
";'>"
.
"[!] Didn't Find any Links in <u>$url!</u></span><br/>"
);
}
// Converts Relative URL to Absolute URL
// No conversion is done if it is already in Absolute URL
function
convertLink(
$site
,
$path
){
if
(
substr_compare
(
$path
,
"//"
, 0, 2) == 0)
return
parse_url
(
$site
)[
'scheme'
].
$path
;
substr_compare
(
$path
,
"www."
, 0, 4) == 0)
return
$path
;
// Absolutely an Absolute URL!!
else
return
$site
.
'/'
.
$path
;
}
// Whether or not we want to ignore the link
function
ignoreLink(
$url
){
return
$url
[0]==
"#"
or
substr
(
$url
, 0, 11) ==
"javascript:"
;
}
// Print a message and insert into the array/database!
function
insertIntoDatabase(
$link
,
$depth
){
echo
(
"<span style='margin-left:"
.(10*
$depth
).
"'>"
.
"Inserting new Link:- <span style='color:green'>$link"
.
"</span></span><br/>"
);
$crawledLinks
[]=
$link
;
}
?>
- Producción:
Inserting new Link:- Inserting new Link:- Inserting new Link:- Inserting new Link:- Inserting new Link:- Inserting new Link:- Inserting new Link:-
- Línea 3: Cree una array global,
$crawledLinks
que contenga todos los enlaces que hemos capturado en la sesión. ¡Lo usaremos para buscar y ver si un enlace ya está en la base de datos! Buscar en una array es menos eficiente que buscar en una tabla hash. Podríamos usar una tabla hash, pero no será muy eficiente que una array, ya que las claves son strings muy largas (una URL), así que creo que usar una array sería más rápido. - Línea 8: ¡Le decimos al intérprete que estamos usando la array global
$crawledLinks
que acabamos de crear! Y en la siguiente línea, creamos una nueva array$crawling
que simplemente contendría todos los enlaces que estamos rastreando actualmente. - Línea 31: ¡Ignoramos todos los enlaces que no vinculen a una página externa! ¡Un enlace puede ser un enlace interno, un enlace profundo o un enlace del sistema! Esta función no verifica todos los casos (eso lo haría muy largo), sino los dos casos más comunes: cuando un enlace es un enlace interno y cuando un enlace se refiere a un código javascript.
- Línea 33: Convertimos un enlace de enlace relativo a enlace absoluto y también hacemos otras conversiones (como //wikipedia.org a http://wikipedia.org o https://wikipedia.org dependiendo del esquema de la URL original ).
- Línea 35: Simplemente comprobamos si lo
$link
que estamos iterando no está ya en la base de datos. Si es así, lo ignoramos; si no, lo agregamos a la base de datos, así como en la$crawling
array para que podamos seguir los enlaces en esa URL también. - Línea 43: Aquí el rastreador recurre. Sigue todos los enlaces que tiene que seguir (enlaces que se agregaron en la
$crawling
array). - Línea 83: Usamos
followLink("http://guimp.com/"),
la URL http://guimp.com/ como punto de partida solo porque resulta ser (o afirma ser) el sitio web más pequeño del mundo. - Programa:
<?php
$crawledLinks
=
array
();
const
MAX_DEPTH=5;
function
followLink(
$url
,
$depth
=0){
global
$crawledLinks
;
$crawling
=
array
();
if
(
$depth
>MAX_DEPTH){
echo
"<div style='color:red;'>The Crawler is giving up!</div>"
;
return
;
}
$options
=
array
(
'http'
=>
array
(
'method'
=>
"GET"
,
'user-agent'
=>
"gfgBot/0.1\n"
)
);
$context
=stream_context_create(
$options
);
$doc
=
new
DomDocument();
@
$doc
->loadHTML(
file_get_contents
(
$url
, false,
$context
));
$links
=
$doc
->getElementsByTagName(
'a'
);
$pageTitle
=getDocTitle(
$doc
,
$url
);
$metaData
=getDocMetaData(
$doc
);
foreach
(
$links
as
$i
){
$link
=
$i
->getAttribute(
'href'
);
if
(ignoreLink(
$link
))
continue
;
$link
=convertLink(
$url
,
$link
);
if
(!in_array(
$link
,
$crawledLinks
)){
$crawledLinks
[]=
$link
;
$crawling
[]=
$link
;
insertIntoDatabase(
$link
,
$pageTitle
,
$metaData
,
$depth
);
}
}
foreach
(
$crawling
as
$crawlURL
)
followLink(
$crawlURL
,
$depth
+1);
}
function
convertLink(
$site
,
$path
){
if
(
substr_compare
(
$path
,
"//"
, 0, 2)==0)
return
parse_url
(
$site
)[
'scheme'
].
$path
;
substr_compare
(
$path
,
"www."
, 0, 4)==0)
return
$path
;
else
return
$site
.
'/'
.
$path
;
}
function
ignoreLink(
$url
){
return
$url
[0]==
"#"
or
substr
(
$url
, 0, 11) ==
"javascript:"
;
}
function
insertIntoDatabase(
$link
,
$title
, &
$metaData
,
$depth
){
echo
(
"Inserting new record {URL= $link"
.
", Title = '$title'"
.
", Description = '"
.
$metaData
['description'].
"', Keywords = ' "
.
$metaData
[
'keywords'
].
"'}<br/><br/><br/>"
);
$crawledLinks
[]=
$link
;
}
function
getDocTitle(&
$doc
,
$url
){
$titleNodes
=
$doc
->getElementsByTagName(
'title'
);
if
(
count
(
$titleNodes
)==0
or
!isset(
$titleNodes
[0]->nodeValue))
return
$url
;
$title
=
str_replace
(
''
,
'\n'
,
$titleNodes
[0]->nodeValue);
return
(
strlen
(
$title
)<1)?
$url
:
$title
;
}
function
getDocMetaData(&
$doc
){
$metaData
=
array
();
$metaNodes
=
$doc
->getElementsByTagName(
'meta'
);
foreach
(
$metaNodes
as
$node
)
$metaData
[
$node
->getAttribute(
"name"
)]
=
$node
->getAttribute(
"content"
);
if
(!isset(
$metaData
[
'description'
]))
$metaData
[
'description'
]=
'No Description Available'
;
if
(!isset(
$metaData
[
'keywords'
]))
$metaData
[
'keywords'
]=
''
;
return
array
(
'keywords'
=>
str_replace
(
''
,
'\n'
,
$metaData
[
'keywords'
]),
'description'
=>
str_replace
(
''
,
'\n'
,
$metaData
[
'description'
])
);
}
?>
- Producción:
Inserting new record {URL= https://www.iana.org/domains/example, Title = 'Example Domain', Description = 'No Description Available', Keywords = ' '}
- Línea 3: Estamos creando una nueva constante global
MAX_DEPTH
. Anteriormente, simplemente usamos 5 como la profundidad máxima, pero esta vez usamosMAX_DEPTH
constante en lugar de eso. - Línea 22 y Línea 23: Básicamente, obtenemos el título de la página
$pageTitle
y la descripción y las palabras clave que se almacenarían en la$metaData
variable (una array asociativa). Puede consultar la línea 64 y la línea 72 para conocer la información que se extrajo. - Línea 31: Pasamos algunos parámetros adicionales a la
insertIntoDatabase
función.
Explicación:
Un Spider-Bot un poco más complicado: en el código anterior teníamos un spider-bot básico y era bueno, pero era más un raspador que un rastreador (para ver la diferencia entre raspador y rastreador, consulte este artículo ). No estábamos recurriendo, no estábamos «siguiendo» los enlaces que obtuvimos. Entonces, en esta iteración, haremos exactamente eso y también asumiremos que tenemos una base de datos en la que insertaríamos los enlaces (para la indexación). ¡ Cualquier enlace se insertará en la base de datos a través de la insertIntoDatabase
función!
Explicación:
Spider-bot más avanzado: en la iteración anterior, seguimos recursivamente todos los enlaces que obtuvimos en una página y los agregamos en una base de datos (que era solo una array). Pero agregamos solo la URL a la base de datos, sin embargo, los motores de búsqueda tienen muchos campos para cada página: la miniatura, la información del autor, la fecha y la hora y, lo que es más importante, el título de la página y las palabras clave. Algunos incluso tienen una copia en caché de la página para una búsqueda más rápida. Sin embargo, en aras de la simplicidad, solo eliminaremos el título, la descripción y las palabras clave de la página.
Nota: Depende de usted qué base de datos use: PostgreSQL , MariaDB, etc.
La descripción y las palabras clave están presentes en las etiquetas meta . La búsqueda de algunos motores de búsqueda se basa (casi) por completo en la información de los metadatos, mientras que algunos motores de búsqueda no les dan mucha relevancia. Google ni siquiera los tiene en cuenta, su búsqueda se basa completamente en la popularidad y la relevancia de una página (utilizando el algoritmo PageRank ) y las palabras clave y la descripción se generan en lugar de extraerlas .ellos de las etiquetas meta. Google no penaliza un sitio web sin ninguna descripción o palabras clave. Pero sí penaliza los sitios web sin títulos. Nuestro motor de búsqueda conceptual (que se construirá utilizando este robot araña «avanzado») hará lo contrario, penalizará los sitios web sin descripción y palabras clave (aunque los agregaría a la base de datos, les otorgaría una clasificación más baja). ) y no penalizará los sitios web sin títulos. Establecerá la URL del sitio web como título.
Explicación: Todavía no hay ningún cambio innovador. Me gustaría explicar algunas cosas:
Problemas con nuestro rastreador web: hemos creado este rastreador web solo para aprender. Implementarlo en el código de producción (como hacer un motor de búsqueda con él) puede crear algunos problemas serios. Los siguientes son algunos problemas con nuestro rastreador web:
- No es escalable Nuestro rastreador web no puede rastrear miles de millones de páginas web como GoogleBot.
- No obedece del todo al estándar de comunicación del rastreador con los sitios web. No sigue el
robots.txt
de un sitio y rastreará un sitio incluso si el administrador del sitio solicita que no lo haga. - No es automático . Seguro que obtendrá «automáticamente» todas las URL de la página actual y rastreará cada una de ellas, pero no es exactamente automático . No tiene ningún concepto de frecuencia de rastreo.
- No se distribuye . Si se están ejecutando dos spider-bots, actualmente no hay forma de que puedan comunicarse entre sí (para ver si el otro spider-bot no está rastreando la misma página)
- El análisis es demasiado simple . Nuestro spider-bot no manejará el marcado codificado (o incluso las URL codificadas para el caso)