我遇到了一个采访问题“如果您正在设计Web搜寻器,如何避免陷入无限循环?”,而我正试图回答这个问题。
这一切是如何从头开始的。假设Google从一些中心页面开始说,其中有数百个(首先如何找到这些中心页面是一个不同的子问题)。当Google跟踪页面中的链接等时,它是否会继续创建哈希表以确保它不遵循先前访问的页面。
如果当我们拥有URL缩短器等功能时,如果同一页面上有2个名称(URL)怎么办?
我以Google为例。尽管Google不会泄漏其网络爬虫算法和页面排名等的工作原理,但是有什么猜测吗?
Answers:
如果您想获得详细的答案,请参阅本文的第3.8节,其中描述了现代刮板的URL测试:
在提取链接的过程中,任何Web爬网程序都会遇到指向同一文档的多个链接。为避免多次下载和处理文档,必须在每个提取的链接上执行URL可见测试,然后才能将其添加到URL边界。(另一种设计是,当从边界删除URL时,执行URL可见测试,但是这种方法会导致更大的边界。)
为了执行可查看URL的测试,我们将Mercator看到的所有URL以规范形式存储在一个称为URL集的大表中。再次,太多条目无法容纳它们,因此像文档指纹集一样,URL集主要存储在磁盘上。
为了节省空间,我们不在URL集中存储每个URL的文本表示形式,而是存储固定大小的校验和。与呈现给内容可见测试的文档指纹集的指纹不同,针对该URL集测试的URL流具有非常重要的局部性。为了减少对备份磁盘文件的操作次数,因此,我们保留了流行URL的内存中缓存。这种缓存的直觉是指向某些URL的链接非常普遍,因此将流行的URL缓存在内存中将导致较高的内存命中率。
实际上,使用2 ^ 18个条目的内存高速缓存和类似LRU的时钟替换策略,我们在内存高速缓存中的总命中率达到66.2%,而在内存表上的命中率达到9.5%。最近添加的网址,净命中率为75.7%。此外,在流行URL的缓存和最近添加的URL的表中丢失的请求中,有24.3%的请求中,大约1 = 3在我们的随机访问文件实现中的缓冲区上产生了命中,该文件也位于用户空间中。所有这些缓冲的最终结果是,我们对URL集执行的每个成员资格测试平均产生0.16个搜索和0.17个读内核调用(其中一部分是从内核的文件系统缓冲区中提供的)。因此,每个URL集成员资格测试所引发的内核调用的数量是文档指纹集上成员资格测试的六分之一。
基本上,它们使用散列函数对所有URL进行散列,该函数可确保每个URL都有唯一的散列,并且由于URL的局部性,查找URL变得非常容易。Google甚至将其哈希功能开源:CityHash
警告!
他们可能还在谈论机器人陷阱!!!漫游器陷阱是页面的一部分,该页面不断生成具有唯一URL的新链接,通过跟踪该页面提供的链接,您实际上将陷入“无限循环”。这并非完全是一个循环,因为循环将是访问相同URL的结果,但这是无限的URL链,您应避免爬网。
根据Fr0zenFyr的评论:如果使用AOPIC算法选择页面,则避免无限循环类型的陷阱很容易。这是AOPIC工作原理的摘要:
由于Lambda页面不断收取税款,因此最终它将是信用额度最高的页面,我们将不得不对其进行“爬网”。我用引号说“爬网”,因为我们实际上并不向Lambda页面发出HTTP请求,我们只是获得其信用并将其平均分配给数据库中的所有页面。
由于漫游器陷阱只会给内部链接提供积分,而很少从外部获得积分,因此它们会不断(从税收中)泄漏积分到Lambda页面。Lambda页面会将这些信用额平均分配到数据库中的所有页面,并且在每个周期中,bot陷阱页面将失去越来越多的信用额,直到其信用额非常少以至于几乎再也不会被爬网。好的页面不会发生这种情况,因为它们通常会从其他页面上的反向链接中获得收益。这也会导致页面排名动态变化,您会注意到,每当您对数据库进行快照时,都按照页面拥有的信用额度对页面进行排序,那么很可能会根据其真实页面排名对页面进行大致排序。
这只能避免无限循环类型的自动程序陷阱,但是您还应注意许多其他自动程序陷阱,并且也有一些方法可以解决它们。
虽然这里的每个人都已经建议了如何创建您的网络搜寻器,但是这里是Google如何对页面进行排名。
Google根据回调链接的数量(其他网站上指向特定网站/页面的链接数量)对每个页面进行排名。这称为相关性得分。这是基于以下事实:如果页面上有许多其他页面链接到该页面,则该页面可能是重要页面。
每个站点/页面都被视为图中的一个节点。指向其他页面的链接是有向边的。顶点的度数定义为进入边缘的数量。具有较高输入边缘数的节点排名较高。
确定页面等级的方法如下。假设页面Pj具有Lj链接。如果这些链接之一是指向页面Pi,则Pj将其重要性的1 / Lj传递给Pi。然后,Pi的重要性排名是链接到它的页面所做的所有贡献的总和。因此,如果我们表示通过Bi链接到Pi的页面集,则我们具有以下公式:
Importance(Pi)= sum( Importance(Pj)/Lj ) for all links from Pi to Bi
等级放置在称为超链接矩阵的矩阵中:H [i,j]
如果存在从Pi到Bi的链接,则此矩阵中的一行为0或1 / Lj。该矩阵的另一个特性是,如果我们将一列中的所有行相加,则得到1。
现在,我们需要将此矩阵乘以一个特征向量I(特征值1),以使其:
I = H*I
现在我们开始迭代:I H,I I H,I I I^ h .... I ^ K *小时,直到解收敛。也就是说,在步骤k和k + 1中,矩阵中的数字几乎相同。
现在,I向量中剩下的就是每一页的重要性。
有关简单的课堂作业示例,请参见http://www.math.cornell.edu/~mec/Winter2009/RalucaRemus/Lecture3/lecture3.html
至于解决面试问题中的重复问题,请在整个页面上做一个校验和,并使用该校验或bash或bash作为您在地图中的关键字来跟踪访问的页面。
hyperlink matrix具有尺寸every-webpage-on-the-internetX every-webpage-on-the-internet。简单?!?如何做到这一点(鉴于其非常稀疏的矩阵)?
这里的问题不是要爬网重复的URL,这是通过使用从url获得的哈希值的索引来解决的。问题是要检索“重复的内容”。“ Crawler Trap”的每个网址都不同(年,日,SessionID ...)。
没有“完美”的解决方案...但是您可以使用以下一些策略:
•在网站内部保留网址的级别。对于从页面获取网址的每个线索,请提高级别。它将像一棵树。您可以在特定级别停止抓取,例如10(我认为Google使用此级别)。
•您可以尝试创建一种HASH,可以将其比较以查找相似的文档,因为您无法与数据库中的每个文档进行比较。有来自谷歌的SimHash,但我找不到任何实现。然后,我创建了自己的。我的哈希计算html代码中的低频和高频字符,并生成20bytes的哈希,并将其与AVLTree中最近爬行的页面的少量缓存进行比较,并使用具有一定容差(约2)的NearNeighbors搜索。您不能在此哈希中使用任何对字符位置的引用。在“识别”陷阱之后,您可以记录重复内容的url模式,并开始忽略具有该内容的页面。
•与google一样,您可以为每个网站创建排名,并在一个网站中“信任”更多。
搜寻器保留一个URL池,其中包含要搜寻的所有URL。为了避免“无限循环”,基本思想是在添加到池之前检查每个URL的存在。
但是,当系统扩展到一定级别时,这并不容易实现。天真的方法是将所有URL都保留在哈希集中,并检查每个新URL的存在。如果有太多的URL不能容纳到内存中,则无法使用此功能。
这里有几种解决方案。例如,我们应该将所有URL保留在磁盘中,而不是将所有URL存储到内存中。为了节省空间,应使用URL哈希而不是原始URL。还需要注意的是,我们应该保留URL的规范形式,而不是原始形式。因此,如果通过bit.ly之类的服务将URL缩短,则最好获得最终URL。为了加快检查过程,可以构建一个缓存层。或者,您可以将其视为分布式缓存系统,这是一个单独的主题。
“构建网络爬网程序”一文对此问题进行了详细分析。
我还需要使用搜寻器,并且找不到适合我的需求的爬虫,因此在此之后,我开发了基本的搜寻器库以实现简单的需求。但是能够满足履带的几乎所有原理。您可以检查DotnetCrawler github repo,该仓库使用Entity Framework Core的默认实现自行实现Downloader-Processor-Pipeline模块,以便将数据保存到Sql Server中。
网络搜寻器是一种计算机程序,用于从给定的网站URL收集/搜寻以下关键值(HREF链接,图像链接,元数据等)。它的设计非常聪明,可以跟踪从上一个URL已获取的不同HREF链接,因此,Crawler可以从一个网站跳转到其他网站。通常,它称为Web蜘蛛或Web Bot。这种机制始终充当Web搜索引擎的骨干。
请从我的技术博客中找到源代码-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;
}
}
?>
这是一个网络爬虫示例。可用于收集用于MAC欺骗的mac地址。
#!/usr/bin/env python
import sys
import os
import urlparse
import urllib
from bs4 import BeautifulSoup
def mac_addr_str(f_data):
global fptr
global mac_list
word_array = f_data.split(" ")
for word in word_array:
if len(word) == 17 and ':' in word[2] and ':' in word[5] and ':' in word[8] and ':' in word[11] and ':' in word[14]:
if word not in mac_list:
mac_list.append(word)
fptr.writelines(word +"\n")
print word
url = "http://stackoverflow.com/questions/tagged/mac-address"
url_list = [url]
visited = [url]
pwd = os.getcwd();
pwd = pwd + "/internet_mac.txt";
fptr = open(pwd, "a")
mac_list = []
while len(url_list) > 0:
try:
htmltext = urllib.urlopen(url_list[0]).read()
except:
url_list[0]
mac_addr_str(htmltext)
soup = BeautifulSoup(htmltext)
url_list.pop(0)
for tag in soup.findAll('a',href=True):
tag['href'] = urlparse.urljoin(url,tag['href'])
if url in tag['href'] and tag['href'] not in visited:
url_list.append(tag['href'])
visited.append(tag['href'])
更改网址以抓取更多网站……祝您好运