分类: LINUX
2008-05-02 20:34:09
Web spider 是用来进行 Internet 信息的搜索、过滤和聚合的软件代理。使用通用脚本语言和一些 Web 模块组合,就可以轻松地开发自己的 Web spider。本文介绍了如何为 Linux® 构建 spider 和 scraper,从而实现在 Web 站点上爬行并搜集信息,具体到本例就是搜集股票数据。
spider 是一个为实现特定目的以特定方法在 Internet 上爬行的程序。其目的可能是为了搜集信息也可能是为了理解 Web 站点的结构和有效性。spider 是现代搜索引擎(例如 Google 和 AltaVista)的基础。这些 spider 会自动从 Web 上搜索数据并将数据传递给其他应用程序,这些应用程序会对 Web 站点的内容进行索引,以便获得最好的搜索条件集。
|
Web scraper 是一种与 spider 类似的技术,不过它具有更多合法性问题。scraper 是一种 spider,其目标是为了从 Web 上获取特定的内容,例如产品的成本或服务。scraper 的一种用途是为了获得有竞争力的价格,从而确定给定产品的价格,以便能够制定出自己产品的合理价格或相应地进行宣传。scraper 还可以从很多 Web 站点上搜集大量数据并将这些信息提供给用户。
生物学动机
当考察自然界中真正的蜘蛛时,您会从它与环境的交互性(而绝非孤立地)去考察它。蜘蛛会看到并感受到自己身旁的路,并会有目的地从一个地方移动到另外一个地方。Web spider 也会以类似的方式进行操作。Web spider 是一种使用高级语言编写的程序。它通过使用网络协议与环境进行交互,例如对 Web 使用超文本传输协议(HTTP)。如果 Web spider 需要与您进行通信,那么它可以使用简单邮件传输协议(SMTP)来发送 e-mail 消息。
不过 Web spider 并不限定于使用 HTTP 或 SMTP。有些 spider 使用 Web 服务,例如 SOAP 或可扩展标记语言远程过程调用(XML-RPC)协议。其他 spider 会使用网络新闻传输协议(NNTP)对新闻组进行遍历,或者寻找 RSS(Really Simple Syndication)提要中有趣的新条目。尽管自然界中的大部分蜘蛛都只能看到明暗强度和动作的变化,但是 Web spider 却可以使用很多种协议来看和感知。
spider 和 scraper 的应用程序
|
Web spider 和 scraper 是非常有用的应用程序,因此可以找到它们的很多种应用,其中有好有坏。下面让我们来看一下使用这些技术的几个应用程序。
搜索引擎 Web 爬虫
Web spider 可以使 Internet 的搜索变得非常简单而有效。搜索引擎使用了很多 Web spider 在 Internet 上搜索 Web 页面,返回它们的内容,并对其进行索引。在这个过程完成之后,搜索引擎就可以快速搜索本地索引来确定哪些结果最适用于该次搜索。Google 还会使用 PageRank 算法,一个 Web 页面在搜索结果中的排名是基于链接到此 Web 页面的其他页面的多少。这就像是一个投票系统,获得最多票数的页面在整个结果中的排名也最高。
对于 Internet 进行这样的搜索,其代价非常昂贵,Web 内容与索引程序进行通信所需要的带宽以及对结果进行索引所需要的计算成本都很高。为此需要很多存储空间,但是当我们考虑到 Google 可以为 Gmail 用户提供 1000 MB 的存储空间时,显然可以看出存储空间已经不是什么主要问题了。
Web spider 可以使用一组策略来最小化对 Internet 的消耗。Google 要对 80 亿 Web 页面进行索引,这一挑战的难易程度可见一斑。这种行为策略定义了爬虫会将哪些页面带入索引程序、以什么样的频率回到 Web 站点上再次对它进行检查,以及一种礼貌原则。Web 服务器可以使用一个名为 robot.txt 的文件来执行爬虫,它会告诉爬虫不能搜索的内容。
企业 Web 爬虫
与标准的搜索引擎 spider 一样,这种 Web spider 对大众不能使用的内容进行索引。例如,公司通常有一些内部 Web 站点只能由公司的员工使用。这种 spider 被限定于本地环境。由于它的搜索是受限的,因此通常会有更多计算能力可用,进行专门的和更加完整的索引操作也是可能的。Google 则更进一步,为了对个人计算机的内容进行索引,它专门提供了一个桌面搜索引擎。
专用爬虫
爬虫也有一些非传统的用途,例如对内容进行归档或生成统计数据。归档爬虫会简单地对 Web 站点进行遍历,将其网站的本地内容存储到一个长期的存储介质上。这可以用来进行备份,或者在更重要的情况中,用来获取 Internet 内容的快照。统计数据对理解 Internet 的内容或其中的缺陷方面很有用处。爬虫可以用来确定有多少 Web 服务器在运行、有多少给定类型的 Web 服务器在运行、可用 Web 页面的数量,甚至失效链接的个数(这会返回 HTTP 404 错误,表明页面没有找到)。
其他有用的专用爬虫包括 Web 站点检查器。这些爬虫会查找缺少的内容、验证所有的链接,并会确保超文本标记语言(HTML)是有效的。
E-mail 收集爬虫
现在要介绍黑暗面了。不幸的是,一小撮坏家伙却会破坏我们大家使用的整个 Internet。这种爬虫会在 Web 站点上搜索 e-mail 地址,然后生成我们每天不得不处理的大量垃圾邮件。据 Postini 报告,到 2005 年 8 月,Postini 用户的 e-mail 消息中有 70% 都是不想要的垃圾邮件。
E-mail 收集可能是最容易的一种爬行行为,在本文中最后一个爬虫例子中我们会看到这一点。
现在我们已经了解了一些 Web spider 和 scraper 的基本知识,接下来的 4 个例子显示了如何使用流行的脚本语言(例如 Ruby 和 Python)来为 Linux 构建 spider 和 scraper。
例子 1:简单的 scraper
这个例子向您展示了该如何确定给定的 Web 站点正在运行哪种 Web 服务器。这可能非常有趣,而且如果能在一个足够大的示例上实现,还可以提供关于 Web 服务器在政府、学术界和工业界中的普及率的有趣统计数据。
清单 1 给出了用来搜索 Web 站点以确定 HTTP 服务器的 Ruby 脚本。Net::HTTP
类实现了一个 HTTP 客户机和 GET
、HEAD
和 POST
方法。只要向 HTTP 服务器发起一个请求,HTTP 响应消息的一部分就会指出这些内容是由哪个服务器提供的。这里使用 HEAD
方法来获取有关根页面('/')的信息,而没有从站点上下载一个页面。只要 HTTP 服务器成功响应(由响应代码 "200" 指示),就会循环迭代响应消息的每行内容,来寻找 server
关键字,如果找到,就打印这个值。这个关键字的值是一个代表 HTTP 服务器的字符串。
清单 1. 用来简单搜索元数据的 Ruby 脚本(srvinfo.rb)
#!/usr/local/bin/ruby require 'net/http' # Get the first argument from the command-line (the URL) url = ARGV[0] begin # Create a new HTTP connection httpCon = Net::HTTP.new( url, 80 ) # Perform a HEAD request resp, data = httpCon.head( "/", nil ) # If it succeeded (200 is success) if resp.code == "200" then # Iterate through the response hash resp.each {|key,val| # If the key is the server, print the value if key == "server" then print " The server at "+url+" is "+val+" " end } end end |
除了显示如何使用 srvinfo 脚本之外,清单 2 还给出了 scraper 在很多政府、学术和商业 Web 站点上的应用。这些服务器有很大差异,从 Apache(占 68% )到 Sun 和 Microsoft® 的 IIS(Internet Information Services)。您还可以看到其中有一个应用没有给出所使用的服务器。有趣的是就当密克罗尼西亚联邦政府还在运行一个旧版本的 Apache(应该更新了)的时候,Apache.org 却在技术上不断大胆尝试、推陈出新。
清单 2. 服务器 scraper 的示例应用
[mtj@camus]$ ./srvrinfo.rb The server at is Apache [mtj@camus]$ ./srvrinfo.rb The server at is Apache/2.0 (Unix) [mtj@camus]$ ./srvrinfo.rb The server at is Apache/1.3.29 (Unix) [mtj@camus]$ ./srvrinfo.rb [mtj@camus]$ ./srvrinfo.rb The server at is Apache [mtj@camus]$ ./srvrinfo.rb The server at is Apache/2.0.46 (Red Hat Linux) [mtj@camus]$ ./srvrinfo.rb The server at is Apache/1.3.27 (Unix) PHP/3.0.18 PHP/4.2.3 [mtj@camus]$ ./srvrinfo.rb The server at is Apache/1.0 (Unix) [mtj@camus]$ ./srvrinfo.rb The server at is MIT Web Server Apache/1.3.26 Mark/1.5 (Unix) mod_ssl/2.8.9 OpenSSL/0.9.7c [mtj@camus]$ ./srvrinfo.rb The server at is Apache/2.0.54 (Debian GNU/Linux) mod_fastcgi/2.4.2 mod_ssl/2.0.54 OpenSSL/0.9.7e WebAuth/3.2.8 [mtj@camus]$ ./srvrinfo.rb The server at is Apache/1.3.27 (Unix) PHP/4.3.1 [mtj@camus]$ ./srvrinfo.rb The server at is Sun-ONE-Web-Server/6.1 [mtj@camus]$ ./srvrinfo.rb The server at is Sun Java System Web Server 6.1 [mtj@camus]$ ./srvrinfo.rb The server at is Microsoft-IIS/6.0 [mtj@camus]$ ./srvrinfo.rb The server at is Apache/2.2.3 (Unix) mod_ssl/2.2.3 OpenSSL/0.9.7g |
例子 2:搜集股票价格的 scraper
在这个例子中,构建了一个简单的 Web scraper(也称为 屏幕 scraper)来搜集股票价格信息。本例通过使用响应的 Web 页面的模式来强制实现这种功能,代码如下所示:
清单 3. 用来搜集股票价格的简单 Web scraper
#!/usr/local/bin/ruby require 'net/http' host = "" link = "/eqsnaps/index.cfm?story=snapshot&symbol="+ARGV[0] begin # Create a new HTTP connection httpCon = Net::HTTP.new( host, 80 ) # Perform a HEAD request resp = httpCon.get( link, nil ) stroffset = resp.body =~ /class="price">/ subset = resp.body.slice(stroffset+14, 10) limit = subset.index('<') print ARGV[0] + " current stock price " + subset[0..limit-1] + " (from stockmoney.com) " end |
在这个 Ruby 脚本中,先是打开一个 HTTP 客户机连接到一台服务器上(在本例中是 ),并构建一个链接,它会请求获得用户传递进来的某个股票(通过 &symbol=
)的价格。然后使用 HTTP GET
方法(检索完整的响应页面)来请求这个链接,从中搜索 class="price">
,它后面紧接着就是这个股票的当前价格。最后从 Web 页面中截取出这一信息,为用户呈现出来。
要使用股票价格 scraper,只需使用感兴趣的那个股票的符号来调用这个脚本即可,如清单 4 所示。
清单 4. 股票价格 scraper 的示例应用
[mtj@camus]$ ./stockprice.rb ibm ibm current stock price 79.28 (from stockmoney.com) [mtj@camus]$ ./stockprice.rb intl intl current stock price 21.69 (from stockmoney.com) [mtj@camus]$ ./stockprice.rb nt nt current stock price 2.07 (from stockmoney.com) [mtj@camus]$ |
例子 3:与股票价格 scraper 通信
例子 2 中用来搜集股票价格的 Web scraper 非常吸引人,不过如果能让这个 scraper 经常性地监视股票价格并在您感兴趣的股票的价格上涨到某个特定值或下跌到某个特定值时就给您发送邮件通知,将更加有用。您不必再等待了。在清单 5 中,将会让这个简单的 Web scraper 能够监视股票并在股票超过预先定义的价格范围时就发送 e-mail 消息。
#!/usr/local/bin/ruby require 'net/http' require 'net/smtp' # # Given a web-site and link, return the stock price # def getStockQuote(host, link) # Create a new HTTP connection httpCon = Net::HTTP.new( host, 80 ) # Perform a HEAD request resp = httpCon.get( link, nil ) stroffset = resp.body =~ /class="price">/ subset = resp.body.slice(stroffset+14, 10) limit = subset.index('<') return subset[0..limit-1].to_f end # # Send a message (msg) to a user. # Note: assumes the SMTP server is on the same host. # def sendStockAlert( user, msg ) lmsg = [ "Subject: Stock Alert ", " ", msg ] Net::SMTP.start('localhost') do |smtp| smtp.sendmail( lmsg, "rubystockmonitor@localhost.localdomain", [user] ) end end # # Our main program, checks the stock within the price band every two # minutes, emails and exits if the stock price strays from the band. # # Usage: ./monitor_sp.rb |
这个 Ruby 脚本有点长,不过它是在 清单 3 中现有的股票价格搜集脚本基础之上构建的。一个新的函数 getStockQuote
对股票价格搜集功能进行了封装。另外一个函数 sendStockAlert
会向某个 e-mail 地址发送消息(e-mail 地址和发送的消息都可以由用户定义)。主程序只是一个循环,用来获得股票的当前价格,检查价格是否在所限定的范围内,如果不在就发送 e-mail 警告来提醒用户。这里还在检查股票价格时进行了一下延时,原因是不想造成服务器的过载。
清单 6 是一个对一只非常流行的科技股调用这个股票监视程序的例子。每两分钟,这个股票的价格就会被检查并打印出来。当股票超过高位时,就会发送一条 e-mail 消息并会退出脚本。
[mtj@camus]$ ./monitor_sp.rb ibm 83.00 75.00 mtj@mtjones.com
current price 82.06
current price 82.32
current price 82.75
current price 83.36
|
所生成的 e-mail 如图 1 所示,后面有个到所搜集的数据源的链接。
至此对 scraper 的介绍就告一段落,接下来将深入地了解一下 Web spider 的构建。
例子 4: Web 站点爬虫
在最后这个例子中,将探索一下在 Web 站点上爬行的 Web spider。为了安全起见,我将避免在该站点之外浪费时间,而只会深入研究一个 Web 页面。
要在 Web 站点上爬行并访问这个站点上所提供的链接,必须要对 HTML 页面进行解析。如果可以成功解析 Web 页面,就可以确定到其他资源的链接,这些链接有些指定的是本地资源(文件),而有些则会代表非本地的资源(例如到其他 Web 页面的链接)。
要在 Web 上爬行,需要从一个给定的 Web 页面开始,确定这个页面中的所有链接,将它们放入一个等待访问的队列中进行排序,然后使用等待访问队列中的第一项来重复这个处理过程。这会产生广度优先遍历(与优先处理首先找到的第一个链接不同,后者是一种深度优先遍历)。
如果能够避免非本地的链接而只访问本地 Web 页面,就可以为这个单一 Web 站点提供 Web 爬虫了,如清单 7 所示。在本例中,使用 Python 语言来代替 Ruby 语言,这样做是为了利用 Python 非常有用的 HTMLParser
类。
清单 7. 简单的 Python Web 站点爬虫(minispider.py)
#!/usr/local/bin/python import httplib import sys import re from HTMLParser import HTMLParser class miniHTMLParser( HTMLParser ): viewedQueue = [] instQueue = [] def get_next_link( self ): if self.instQueue == []: return '' else: return self.instQueue.pop(0) def gethtmlfile( self, site, page ): try: httpconn = httplib.HTTPConnection(site) httpconn.request("GET", page) resp = httpconn.getresponse() resppage = resp.read() except: resppage = "" return resppage def handle_starttag( self, tag, attrs ): if tag == 'a': newstr = str(attrs[0][1]) if re.search('http', newstr) == None: if re.search('mailto', newstr) == None: if re.search('htm', newstr) != None: if (newstr in self.viewedQueue) == False: print " adding", newstr self.instQueue.append( newstr ) self.viewedQueue.append( newstr ) else: print " ignoring", newstr else: print " ignoring", newstr else: print " ignoring", newstr def main(): if sys.argv[1] == '': print "usage is ./minispider.py site link" sys.exit(2) mySpider = miniHTMLParser() link = sys.argv[2] while link != '': print " Checking link ", link # Get the file from the site and link retfile = mySpider.gethtmlfile( sys.argv[1], link ) # Feed the file into the HTML parser mySpider.feed(retfile) # Search the retfile here # Get the next link in level traversal order link = mySpider.get_next_link() mySpider.close() print " done " if __name__ == "__main__": main() |
这个爬虫的基本设计是加载第一个链接并将其放入一个队列。此队列就是下一个要询问 (next-to-interrogate) 队列。当一个链接被选中时,所发现的任何新链接都被加入相同的队列中。这提供了一种广度优先的搜索。另外还维护了一个已查看过的队列以防止再次访问过去已经查看过的链接。基本上就这些,很多实际工作都可以由 HTML 解析器来完成。
先是从 Python 的 HTMLParser
类获取一个新类 miniHTMLParser
。这个类可以实现几个功能。首先,它可以用作 HTML 解析器,只要碰到开始的 HTML 标记都会提供一个回调方法 (handle_starttag
)。其次,这个类还可以用来访问在爬行中所碰到的链接 (get_next_link
) 并检索这个链接所代表的文件(在本例中是一个 HTML 文件)。
这个类中还包含了两个实例变量:viewedQueue
,其中包含了到目前为止已经检查过的链接;instQueue
,表示将要被审查的链接。
正如您所见,类方法非常简单。get_next_link
方法检查 instQueue
是否为空,并返回 ''。否则,就通过 pop
方法返回下一项。gethtmlfile
方法使用 HTTPConnectionK
连接到站点上并返回指定页面的内容。最后,对 Web 页面中的每个开始标记都调用 handle_starttag
(它是通过 feed
方法传递给 HTML 解析器的)。在这个函数中,检查该链接是否是非本地链接(如果链接中包含 http),是否是 e-mail 地址(如果包含 mailto),以及链接中是否包含 'htm',如果包含则说明它(有很大的可能)是一个 Web 页面。另外还会检查以确保之前没有访问过这个链接;否则,就将这个链接加载到已经审查过的队列中。
main
方法非常简单。创建一个新 miniHTMLParser
实例并着手处理用户定义的站点(argv[1]
)和链接(argv[2]
)。然后获取这个链接的内容,将其传递给 HTML 解析器,并获取下一个要访问的链接(如果存在)。当还存在需要访问的链接时,循环继续。
要调用这个 Web spider,需要提供一个 Web 站点地址和一个链接:
./minispider.py /
在本例中,会请求 Free Software Foundation 的根文件。这个命令的结果如清单 8 所示。可以看到新链接已经被加入检查队列和那些被忽略的链接中,例如非本地链接。在这个清单底部,可以看到在根文件所找到的审查链接。
清单 8. minispider 脚本的输出结果
[mtj@camus]$ ./minispider.py /
Checking link /
ignoring hiddenStructure
ignoring http://
ignoring http://
ignoring http:///news
ignoring http:///events
ignoring http:///campaigns
ignoring http:///resources
ignoring http:///donate
ignoring http:///associate
ignoring http:///licensing
ignoring http:///blogs
ignoring http:///about
ignoring https:///login_form
ignoring http:///join_form
ignoring http:///news/fs-award-2005.html
ignoring http:///news/fsfsysadmin.html
ignoring http:///news/digital-communities.html
ignoring http:///news/patents-defeated.html
ignoring /news/RSS
ignoring http:///news
ignoring http:///blogs/rms/entry-20050802.html
ignoring http:///blogs/rms/entry-20050712.html
ignoring http:///blogs/rms/entry-20050601.html
ignoring http:///blogs/rms/entry-20050526.html
ignoring http:///blogs/rms/entry-20050513.html
ignoring http:///index_html/SimpleBlogFullSearch
ignoring documentContent
ignoring http:///index_html/sendto_form
ignoring javascript:this.print();
adding licensing/essays/free-sw.html
ignoring /licensing/essays
ignoring
ignoring
ignoring donate
ignoring join_form
adding associate/index_html
ignoring
adding donate/patron/index_html
adding campaigns/priority.html
ignoring
ignoring http://developer.classpath.org/mediation/OpenOffice2GCJ4
ignoring
ignoring
ignoring
ignoring campaigns
adding campaigns/broadcast-flag.html
ignoring
ignoring /fsf/licensing
ignoring
ignoring
ignoring mailto:webmaster@fsf.org
ignoring http:///Members/root
ignoring
ignoring
ignoring
ignoring
ignoring
ignoring
ignoring
ignoring
ignoring /browsersupport
Checking link licensing/essays/free-sw.html
ignoring mailto:webmaster
Checking link associate/index_html
ignoring mailto:webmaster
Checking link donate/patron/index_html
ignoring mailto:webmaster
Checking link campaigns/priority.html
ignoring mailto:webmaster
Checking link campaigns/broadcast-flag.html
ignoring mailto:webmaster
done
[mtj@camus]$
|
这个例子展示了 Web spider 爬行的阶段。当客户机读取一个文件之后,就对这个页面的内容进行扫描,这与索引程序的情况相同。
Linux spider 工具
现在您已经学会如何实现 scraper 和 spider 了。有一些 Linux 工具也可以提供类似功能。
wget
命令(代表 Web get 之义)是一个获取 Web 内容的有用工具,它可以递归遍历 Web 站点并从中提取感兴趣的内容。其中 Web 站点、所感兴趣的内容以及其他一些管理选项都可以自定义。这个命令随后就可以将这些文件下载到本地主机上。例如,下面这个命令可以连接到所指定的 URL 上并对其进行递归遍历,不过深度不会超过 3 层,然后会从中提取扩展名为 mp3、mpg、mpeg 或 avi 的内容。
wget -A mp3,mpg,mpeg,avi -r -l 3 URL>
curl
命令也可以类似地进行操作,其优点是现在它仍然在积极的开发完善之中。可以使用的其他类似命令还有 snarf
、fget
和 fetch
。
合法性问题
在 Internet 上使用 Web spider 进行数据挖掘已经导致了一些法律纠纷,这些纠纷解决得不太顺利。Farechase 公司最近就被 American Airlines 以屏幕 scrape(实时进行的)为由而起诉。American Airlines 先是控告搜集数据行为违反了 American Airlines 的用户协议(可以在 Terms and Conditions 中找到)。当这种控告不成立之后,American Airlines 又指责这是一种侵入行为,并由此胜诉。其他的一些法律纠纷的缘由则是 spider 和 scraper 所占用的带宽影响了合法用户的使用。这些都是有效的权利声明,因此使得礼貌原则变得更加重要。更多信息请参阅 参考资料 部分。
展望
在 Web 上爬行和搜索可能会非常有趣,有时也会非常有益。不过正如前面介绍的那样,这里也有一些合法性问题。在进行这种操作时,一定要遵循服务器上提供的 robots.txt 文件的指示,并将其结合到您的礼貌原则当中。一些新的协议,例如 SOAP,会让爬行对于普通的 Web 操作来说更为容易,并且所受的干扰更小。将来的一些努力,例如语义 Web,将会使得爬行更加简单,因此爬行的解决方案和方法还会不断发展。
原文链接:http://www-128.ibm.com/developerworks/cn/linux/l-spider/