Je suis tombé sur une question d’entrevue: "Si vous conceviez un robot Web, comment éviteriez-vous de vous perdre dans des boucles infinies?" Et j’essaie d’y répondre.
Comment tout commence-t-il depuis le début? __. Dites que Google a commencé avec certaines pages de hub, dites-en des centaines (la façon dont ces pages de hub ont été trouvées est une sous-question différente) . Comme Google suit les liens depuis continue de faire une table de hachage pour s’assurer qu’elle ne suit pas les pages précédemment visitées.
Que se passe-t-il si la même page a 2 noms (URL) dire en ces jours où nous avons des raccourcisseurs d'URL, etc.
J'ai pris Google comme exemple. Bien que Google ne divulgue pas le fonctionnement de ses algorithmes de balayage Web et de son classement de page, etc., mais si vous devinez?
Si vous souhaitez obtenir une réponse détaillée, jetez un œil à section 3.8 du présent document , qui décrit le test de l'aperçu d'une URL d'un scraper moderne:
Lors de l'extraction de liens, tout robot d'indexation Web rencontrera plusieurs liens vers le même document. Pour éviter de télécharger et de traiter un document plusieurs fois, vous devez effectuer un test d'URL sur chaque lien extrait avant de l'ajouter à la frontière de l'URL. (Une autre conception consisterait à effectuer le test vu par l'URL lorsque l'URL est supprimée de la frontière, mais cette approche entraînerait une frontière beaucoup plus grande.)
Pour effectuer le test des URL vues, nous stockons toutes les URL vues par Mercator sous forme canonique dans un grand tableau appelé ensemble d'URL. Encore une fois, il y a trop d'entrées pour qu'elles tiennent toutes en mémoire. Par conséquent, à l'instar du jeu d'empreintes de document, le jeu d'URL est principalement stocké sur disque.
Pour économiser de l'espace, nous ne stockons pas la représentation textuelle de chaque URL dans l'ensemble d'URL, mais plutôt une somme de contrôle de taille fixe. Contrairement aux empreintes digitales présentées dans l'ensemble d'empreintes digitales de document du test vu du contenu vu, le flux d'URL testées par rapport à l'ensemble d'URL a une quantité de localité non négligeable. Pour réduire le nombre d'opérations sur le fichier de disque de sauvegarde, nous conservons donc un cache en mémoire d'URL courantes. L'intuition de ce cache est que les liens vers certaines URL sont assez courants, aussi la mise en cache des plus populaires en mémoire entraînera-t-elle un taux de réussite élevé en mémoire.
En fait, en utilisant un cache en mémoire de 2 ^ 18 entrées et une stratégie de remplacement d’horloge de type LRU, nous obtenons un taux de réussite global sur le cache en mémoire de 66,2% et un taux de réussite de 9,5% sur le tableau de URL récemment ajoutées, pour un taux de réussite de 75,7%. En outre, sur les 24,3% de demandes manquant à la fois dans le cache des URL populaires et dans le tableau des URL ajoutées récemment, environ 1 = 3 produisent des occurrences dans la mémoire tampon dans notre implémentation de fichier à accès aléatoire, qui réside également dans l'espace utilisateur. Le résultat net de toute cette mise en mémoire tampon est que chaque test d’appartenance que nous effectuons sur l’ensemble d’URL aboutit à une moyenne de 0,16 appels du noyau de recherche et de 0,17 lecture du noyau (dont une fraction est traitée par les mémoires tampons du système de fichiers du noyau). Ainsi, chaque test d’appartenance à un ensemble d’URL induit un sixième autant d’appels du noyau qu’un test d’appartenance sur l’ensemble d’empreintes de document. Ces économies sont purement dues au nombre de localités d’URL (c'est-à-dire la répétition d'URL populaires) inhérent au flux d'URL rencontré lors d'une analyse.
Fondamentalement, ils hachent toutes les URL avec une fonction de hachage qui garantit des hachages uniques pour chaque URL et, en raison de la localisation des URL, il devient très facile de trouver des URL. Google a même ouvert la source de leur fonction de hachage: CityHash
ATTENTION!
Ils pourraient aussi parler de pièges à bot !!! Un piège de bot est une section d'une page qui génère sans cesse de nouveaux liens avec des URL uniques et vous serez essentiellement piégé dans une "boucle infinie" en suivant les liens servis par cette page. Ce n'est pas exactement une boucle, car une boucle serait le résultat de la visite de la même URL, mais une chaîne infinie d'URL que vous devriez éviter d'analyser.
Commentaire de Fr0zenFyr: si l'on utilise l'algorithme AOPIC pour sélectionner les pages, il est assez facile d'éviter les pièges au bot du type boucle infinie. Voici un résumé du fonctionnement de l’AOPIC:
Puisque la page Lambda collecte continuellement les taxes, ce sera finalement la page avec le plus grand montant de crédit et nous devrons "l'explorer". Je dis "crawl" entre guillemets, car nous ne faisons pas de requête HTTP pour la page Lambda, nous prenons simplement ses crédits et les distribuons équitablement à all des pages de notre base de données.
Puisque les pièges à bot ne donnent que des crédits de liens internes et qu’ils obtiennent rarement des crédits de l’extérieur, ils perdront continuellement des crédits (provenant des taxes) vers la page Lambda. La page Lambda distribuera ces crédits uniformément sur toutes les pages de la base de données et, à chaque cycle, la page des pièges de robots perdra de plus en plus de crédits, jusqu'à ce qu'elle ait si peu de crédits qu'elle ne sera presque plus explorée. Cela ne se produira pas avec les bonnes pages, car elles obtiennent souvent des crédits des liens de retour trouvés sur d'autres pages. Cela se traduit également par un classement dynamique des pages. Ce que vous remarquerez, c'est que chaque fois que vous prenez un instantané de votre base de données, commandez les pages en fonction du nombre de crédits dont elles disposent, elles seront probablement classées approximativement en fonction de leur vrai rang de page .
Ceci évite seulement les pièges de bots du type boucle infinie, mais il y a beaucoup d'autres pièges de bots que vous devez surveiller et il existe des moyens de les contourner aussi.
Bien que tout le monde ici ait déjà suggéré comment créer votre robot d'indexation, voici comment Google classe les pages.
Google attribue à chaque page un classement en fonction du nombre de liens de rappel (nombre de liens sur d'autres sites Web pointant vers un site Web ou une page spécifique). Ceci est appelé score de pertinence. Ceci est basé sur le fait que si une page a beaucoup de liens vers d'autres pages, c'est probablement une page importante.
Chaque site/page est considéré comme un nœud dans un graphique. Les liens vers d'autres pages sont des bords dirigés. Un degré de sommet est défini comme le nombre d'arêtes entrantes. Les nœuds avec un nombre plus élevé de fronts entrants sont mieux classés.
Voici comment le PageRank est déterminé. Supposons que cette page Pj comporte des liens Lj. Si l'un de ces liens est à la page Pi, alors Pj transmettra 1/Lj de son importance à Pi. Le classement d'importance de Pi est alors la somme de toutes les contributions faites par les pages qui y mènent. Donc, si nous dénotons l'ensemble des pages renvoyant vers Pi par Bi, alors nous avons cette formule:
Importance(Pi)= sum( Importance(Pj)/Lj ) for all links from Pi to Bi
Les rangs sont placés dans une matrice appelée matrice d'hyperlien: H [i, j]
Une ligne dans cette matrice est 0 ou 1/Lj s'il existe un lien entre Pi et Bi. Une autre propriété de cette matrice est que si nous additionnons toutes les lignes d'une colonne, nous obtenons 1.
Maintenant, il faut multiplier cette matrice par un vecteur Eigen, nommé I (avec la valeur propre 1) tel que:
I = H*I
Nous commençons maintenant par itérer: IH, je IH, je II H .... I ^ k * H jusqu'à ce que la solution converge. c'est-à-dire que nous obtenons à peu près les mêmes nombres dans la matrice à l'étape k et k + 1.
Maintenant, tout ce qui reste dans le vecteur I est l’importance de chaque page.
Pour un exemple de devoirs de classe simple, voir http://www.math.cornell.edu/~mec/Winter2009/RalucaRemus/Lecture3/lecture3.html
Pour résoudre le problème en double dans votre question d’entrevue, faites une somme de contrôle sur la page entière et utilisez-la ou une fraction de la somme de contrôle comme clé de la carte pour garder une trace des pages visitées.
Cela dépend de la profondeur de leur question. S'ils essayaient simplement d'éviter de suivre les mêmes liens, le hachage des URL serait suffisant.
Qu'en est-il du contenu qui contient littéralement des milliers d'URL menant au même contenu? Comme un paramètre QueryString qui n'affecte rien, mais peut avoir un nombre infini d'itérations. Je suppose que vous pouvez également hacher le contenu de la page et comparer les URL pour voir si elles ressemblent au contenu capturé identifié par plusieurs URL. Voir par exemple Bot Traps mentionné dans le post de @ Lirik.
Le problème ici n'est pas d'explorer les URL dupliquées, qui sont résolues par un index utilisant un hachage obtenu à partir des urls. Le problème est d’explorer le contenu dupliqué. Chaque URL d'un "Crawler Trap" est différente (année, jour, sessionID ...).
Il n'y a pas de solution "parfaite" ... mais vous pouvez utiliser certaines de ces stratégies:
• Conservez un champ indiquant le niveau de l'URL dans le site Web. Pour chaque cycle d'obtention d'URL d'une page, augmentez le niveau. Ce sera comme un arbre. Vous pouvez vous arrêter à ramper à un certain niveau, comme 10 (je pense que Google l'utilise).
• Vous pouvez essayer de créer une sorte de hachage qui peut être comparée pour trouver des documents similaires, car vous ne pouvez pas comparer avec chaque document de votre base de données. Il y a SimHash de Google, mais je n'ai trouvé aucune implémentation à utiliser. Ensuite, j'ai créé le mien. Mon hachage compte les caractères de basse et haute fréquence dans le code html et génère un hachage de 20 octets, ce qui est comparé à un petit cache des dernières pages analysées dans un AVLTree avec une recherche NearNeighbors avec une certaine tolérance (environ 2). Vous ne pouvez utiliser aucune référence aux emplacements de caractères dans ce hachage. Après "reconnaître" le piège, vous pouvez enregistrer le modèle d'URL du contenu dupliqué et commencer à ignorer les pages avec ce dernier également.
• Comme Google, vous pouvez créer un classement pour chaque site Web et "faire confiance" davantage à un site qu'à un autre.
Il vous faudrait une sorte de table de hachage pour stocker les résultats; il vous suffirait de vérifier avant chaque chargement de page.
J'ai également eu besoin d'utiliser un robot d'exploration et je ne peux pas en trouver un qui convient à mon besoin. Après cela, j'ai développé une bibliothèque de base du robot d'exploration afin de mettre en œuvre des exigences simples. Mais permettant de respecter presque tous les principes de l'exploration . Vous pouvez vérifier DotnetCrawler github repo qui implémente les modules Downloader-Processor-Pipeline avec l'implémentation par défaut en utilisant Entity Framework Core afin de sauvegarder les données sur Sql Server.
Le robot d'exploration conserve un pool d'URL contenant toutes les URL à analyser. Pour éviter la «boucle infinie», l’idée de base est de vérifier l’existence de chaque URL avant d’ajouter au pool.
Cependant, cela n’est pas facile à mettre en œuvre lorsque le système a évolué à un certain niveau. L'approche naïve consiste à conserver toutes les URL dans un hachage et à vérifier l'existence de chaque nouvelle URL. Cela ne fonctionnera pas s'il y a trop d'URL pour tenir dans la mémoire.
Il y a deux solutions ici. Par exemple, au lieu de stocker toutes les URL en mémoire, nous devrions les conserver sur le disque. Pour économiser de l'espace, le hachage de l'URL doit être utilisé à la place de l'URL brute. Il convient également de noter que nous devrions conserver la forme canonique de l'URL plutôt que celle d'origine. Donc, si l'URL est raccourcie par des services tels que bit.ly, il est préférable d'obtenir l'URL finale. Pour accélérer le processus de vérification, une couche de cache peut être construite. Ou vous pouvez le voir comme un système de cache distribué, qui est un sujet séparé.
La publication Construire un Web Crawler présente une analyse détaillée de ce problème.
Le robot d'indexation Web est un programme informatique qui collectait/analysait les valeurs de clé suivantes (liens HREF, liens d'image, métadonnées, etc.) à partir d'une URL de site Web donnée. Il est conçu comme intelligent pour suivre différents liens HREF déjà extraits de l'URL précédente. Crawler peut ainsi passer d'un site Web à un autre. Habituellement, cela s'appelle une araignée Web ou un bot Web. Ce mécanisme agit toujours comme l’épine dorsale du moteur de recherche Web.
Veuillez trouver le code source de mon blog technique - http://www.algonuts.info/how-to-built-a-simple-web-crawler-in-php.html
<?php
class webCrawler
{
public $siteURL;
public $error;
function __construct()
{
$this->siteURL = "";
$this->error = "";
}
function parser()
{
global $hrefTag,$hrefTagCountStart,$hrefTagCountFinal,$hrefTagLengthStart,$hrefTagLengthFinal,$hrefTagPointer;
global $imgTag,$imgTagCountStart,$imgTagCountFinal,$imgTagLengthStart,$imgTagLengthFinal,$imgTagPointer;
global $Url_Extensions,$Document_Extensions,$Image_Extensions,$crawlOptions;
$dotCount = 0;
$slashCount = 0;
$singleSlashCount = 0;
$doubleSlashCount = 0;
$parentDirectoryCount = 0;
$linkBuffer = array();
if(($url = trim($this->siteURL)) != "")
{
$crawlURL = rtrim($url,"/");
if(($directoryURL = dirname($crawlURL)) == "http:")
{ $directoryURL = $crawlURL; }
$urlParser = preg_split("/\//",$crawlURL);
//-- Curl Start --
$curlObject = curl_init($crawlURL);
curl_setopt_array($curlObject,$crawlOptions);
$webPageContent = curl_exec($curlObject);
$errorNumber = curl_errno($curlObject);
curl_close($curlObject);
//-- Curl End --
if($errorNumber == 0)
{
$webPageCounter = 0;
$webPageLength = strlen($webPageContent);
while($webPageCounter < $webPageLength)
{
$character = $webPageContent[$webPageCounter];
if($character == "")
{
$webPageCounter++;
continue;
}
$character = strtolower($character);
//-- Href Filter Start --
if($hrefTagPointer[$hrefTagLengthStart] == $character)
{
$hrefTagLengthStart++;
if($hrefTagLengthStart == $hrefTagLengthFinal)
{
$hrefTagCountStart++;
if($hrefTagCountStart == $hrefTagCountFinal)
{
if($hrefURL != "")
{
if($parentDirectoryCount >= 1 || $singleSlashCount >= 1 || $doubleSlashCount >= 1)
{
if($doubleSlashCount >= 1)
{ $hrefURL = "http://".$hrefURL; }
else if($parentDirectoryCount >= 1)
{
$tempData = 0;
$tempString = "";
$tempTotal = count($urlParser) - $parentDirectoryCount;
while($tempData < $tempTotal)
{
$tempString .= $urlParser[$tempData]."/";
$tempData++;
}
$hrefURL = $tempString."".$hrefURL;
}
else if($singleSlashCount >= 1)
{ $hrefURL = $urlParser[0]."/".$urlParser[1]."/".$urlParser[2]."/".$hrefURL; }
}
$Host = "";
$hrefURL = urldecode($hrefURL);
$hrefURL = rtrim($hrefURL,"/");
if(filter_var($hrefURL,FILTER_VALIDATE_URL) == true)
{
$dump = parse_url($hrefURL);
if(isset($dump["Host"]))
{ $Host = trim(strtolower($dump["Host"])); }
}
else
{
$hrefURL = $directoryURL."/".$hrefURL;
if(filter_var($hrefURL,FILTER_VALIDATE_URL) == true)
{
$dump = parse_url($hrefURL);
if(isset($dump["Host"]))
{ $Host = trim(strtolower($dump["Host"])); }
}
}
if($Host != "")
{
$extension = pathinfo($hrefURL,PATHINFO_EXTENSION);
if($extension != "")
{
$tempBuffer ="";
$extensionlength = strlen($extension);
for($tempData = 0; $tempData < $extensionlength; $tempData++)
{
if($extension[$tempData] != "?")
{
$tempBuffer = $tempBuffer.$extension[$tempData];
continue;
}
else
{
$extension = trim($tempBuffer);
break;
}
}
if(in_array($extension,$Url_Extensions))
{ $type = "domain"; }
else if(in_array($extension,$Image_Extensions))
{ $type = "image"; }
else if(in_array($extension,$Document_Extensions))
{ $type = "document"; }
else
{ $type = "unknown"; }
}
else
{ $type = "domain"; }
if($hrefURL != "")
{
if($type == "domain" && !in_array($hrefURL,$this->linkBuffer["domain"]))
{ $this->linkBuffer["domain"][] = $hrefURL; }
if($type == "image" && !in_array($hrefURL,$this->linkBuffer["image"]))
{ $this->linkBuffer["image"][] = $hrefURL; }
if($type == "document" && !in_array($hrefURL,$this->linkBuffer["document"]))
{ $this->linkBuffer["document"][] = $hrefURL; }
if($type == "unknown" && !in_array($hrefURL,$this->linkBuffer["unknown"]))
{ $this->linkBuffer["unknown"][] = $hrefURL; }
}
}
}
$hrefTagCountStart = 0;
}
if($hrefTagCountStart == 3)
{
$hrefURL = "";
$dotCount = 0;
$slashCount = 0;
$singleSlashCount = 0;
$doubleSlashCount = 0;
$parentDirectoryCount = 0;
$webPageCounter++;
while($webPageCounter < $webPageLength)
{
$character = $webPageContent[$webPageCounter];
if($character == "")
{
$webPageCounter++;
continue;
}
if($character == "\"" || $character == "'")
{
$webPageCounter++;
while($webPageCounter < $webPageLength)
{
$character = $webPageContent[$webPageCounter];
if($character == "")
{
$webPageCounter++;
continue;
}
if($character == "\"" || $character == "'" || $character == "#")
{
$webPageCounter--;
break;
}
else if($hrefURL != "")
{ $hrefURL .= $character; }
else if($character == "." || $character == "/")
{
if($character == ".")
{
$dotCount++;
$slashCount = 0;
}
else if($character == "/")
{
$slashCount++;
if($dotCount == 2 && $slashCount == 1)
$parentDirectoryCount++;
else if($dotCount == 0 && $slashCount == 1)
$singleSlashCount++;
else if($dotCount == 0 && $slashCount == 2)
$doubleSlashCount++;
$dotCount = 0;
}
}
else
{ $hrefURL .= $character; }
$webPageCounter++;
}
break;
}
$webPageCounter++;
}
}
$hrefTagLengthStart = 0;
$hrefTagLengthFinal = strlen($hrefTag[$hrefTagCountStart]);
$hrefTagPointer =& $hrefTag[$hrefTagCountStart];
}
}
else
{ $hrefTagLengthStart = 0; }
//-- Href Filter End --
//-- Image Filter Start --
if($imgTagPointer[$imgTagLengthStart] == $character)
{
$imgTagLengthStart++;
if($imgTagLengthStart == $imgTagLengthFinal)
{
$imgTagCountStart++;
if($imgTagCountStart == $imgTagCountFinal)
{
if($imgURL != "")
{
if($parentDirectoryCount >= 1 || $singleSlashCount >= 1 || $doubleSlashCount >= 1)
{
if($doubleSlashCount >= 1)
{ $imgURL = "http://".$imgURL; }
else if($parentDirectoryCount >= 1)
{
$tempData = 0;
$tempString = "";
$tempTotal = count($urlParser) - $parentDirectoryCount;
while($tempData < $tempTotal)
{
$tempString .= $urlParser[$tempData]."/";
$tempData++;
}
$imgURL = $tempString."".$imgURL;
}
else if($singleSlashCount >= 1)
{ $imgURL = $urlParser[0]."/".$urlParser[1]."/".$urlParser[2]."/".$imgURL; }
}
$Host = "";
$imgURL = urldecode($imgURL);
$imgURL = rtrim($imgURL,"/");
if(filter_var($imgURL,FILTER_VALIDATE_URL) == true)
{
$dump = parse_url($imgURL);
$Host = trim(strtolower($dump["Host"]));
}
else
{
$imgURL = $directoryURL."/".$imgURL;
if(filter_var($imgURL,FILTER_VALIDATE_URL) == true)
{
$dump = parse_url($imgURL);
$Host = trim(strtolower($dump["Host"]));
}
}
if($Host != "")
{
$extension = pathinfo($imgURL,PATHINFO_EXTENSION);
if($extension != "")
{
$tempBuffer ="";
$extensionlength = strlen($extension);
for($tempData = 0; $tempData < $extensionlength; $tempData++)
{
if($extension[$tempData] != "?")
{
$tempBuffer = $tempBuffer.$extension[$tempData];
continue;
}
else
{
$extension = trim($tempBuffer);
break;
}
}
if(in_array($extension,$Url_Extensions))
{ $type = "domain"; }
else if(in_array($extension,$Image_Extensions))
{ $type = "image"; }
else if(in_array($extension,$Document_Extensions))
{ $type = "document"; }
else
{ $type = "unknown"; }
}
else
{ $type = "domain"; }
if($imgURL != "")
{
if($type == "domain" && !in_array($imgURL,$this->linkBuffer["domain"]))
{ $this->linkBuffer["domain"][] = $imgURL; }
if($type == "image" && !in_array($imgURL,$this->linkBuffer["image"]))
{ $this->linkBuffer["image"][] = $imgURL; }
if($type == "document" && !in_array($imgURL,$this->linkBuffer["document"]))
{ $this->linkBuffer["document"][] = $imgURL; }
if($type == "unknown" && !in_array($imgURL,$this->linkBuffer["unknown"]))
{ $this->linkBuffer["unknown"][] = $imgURL; }
}
}
}
$imgTagCountStart = 0;
}
if($imgTagCountStart == 3)
{
$imgURL = "";
$dotCount = 0;
$slashCount = 0;
$singleSlashCount = 0;
$doubleSlashCount = 0;
$parentDirectoryCount = 0;
$webPageCounter++;
while($webPageCounter < $webPageLength)
{
$character = $webPageContent[$webPageCounter];
if($character == "")
{
$webPageCounter++;
continue;
}
if($character == "\"" || $character == "'")
{
$webPageCounter++;
while($webPageCounter < $webPageLength)
{
$character = $webPageContent[$webPageCounter];
if($character == "")
{
$webPageCounter++;
continue;
}
if($character == "\"" || $character == "'" || $character == "#")
{
$webPageCounter--;
break;
}
else if($imgURL != "")
{ $imgURL .= $character; }
else if($character == "." || $character == "/")
{
if($character == ".")
{
$dotCount++;
$slashCount = 0;
}
else if($character == "/")
{
$slashCount++;
if($dotCount == 2 && $slashCount == 1)
$parentDirectoryCount++;
else if($dotCount == 0 && $slashCount == 1)
$singleSlashCount++;
else if($dotCount == 0 && $slashCount == 2)
$doubleSlashCount++;
$dotCount = 0;
}
}
else
{ $imgURL .= $character; }
$webPageCounter++;
}
break;
}
$webPageCounter++;
}
}
$imgTagLengthStart = 0;
$imgTagLengthFinal = strlen($imgTag[$imgTagCountStart]);
$imgTagPointer =& $imgTag[$imgTagCountStart];
}
}
else
{ $imgTagLengthStart = 0; }
//-- Image Filter End --
$webPageCounter++;
}
}
else
{ $this->error = "Unable to proceed, permission denied"; }
}
else
{ $this->error = "Please enter url"; }
if($this->error != "")
{ $this->linkBuffer["error"] = $this->error; }
return $this->linkBuffer;
}
}
?>