构建自己的DSL之一 Simple Crawler

转载请标明出处:http://fuliang.iteye.com/blog/1122008 

经常需要从网上抓取一些需要的内容做成语料,供分类使用。所以需要一个灵活的抓取、抽取程序-自己的DSL来做这件事,这样每次只需要写几行代码就能得到需要的内容。比如我比较希望以下几行代码就能把我的博客的内容给抓下来: 
Ruby代码  收藏代码
  1. crawler = Crawler.new  
  2. 1.upto(10) do |pn|  
  3.     urls = []  
  4.     crawler.fetch "http://fuliang.iteye.com/?page=#{pn}" do |page|  
  5.         page.css("div.blog_title > h3 > a").each do |node|  
  6.             urls << "http://fuliang.iteye.com#{node.attributes['href']}"  
  7.         end  
  8.     end  
  9.   
  10.     urls.each do |url|  
  11.         crawler.fetch url do |page|  
  12.             page.xpath(:title => '//*[@id="main"]/div/div[2]/h3/a',:content => '//*[@id="blog_content"]').each do |entry|  
  13.                 printf("%s\t%s\n",entry[:title].text.gsub(/\s+/,""),entry[:content].text.gsub(/\s+/,""))  
  14.             end  
  15.         end  
  16.     end  
  17.     break  
  18. end  

我们先创建一个Crawler对象,然后按照我博客的列表页分页特征,得到第pn页的url是 
http://fuliang.iteye.com/?page=#{pn},当然有可能有复杂的规则,构建列表页的url列表,然后遍历。crawler只有一个fetch方法,就可以把页面fetch下来,然后得到这个页面在块中处理。这个页面可以直接根据xpath、css来得到需要抽取的内容,还可以一次抽取一个记录,只需要向xpath,css方法中传递一个字段到xpath/css的hash,然后得到对应的记录的hash。 
按照上面的描述,我们先编写一个简单的Crawler,为了防止被封我们使用了几个代理: 
Ruby代码  收藏代码
  1. class Crawler  
  2.     def initialize  
  3.         @proxies = 1.upto(6).collect{|index| "http://l-crwl#{index}:1080"}  
  4.     end  
  5.   
  6.     def fetch(url)  
  7.         yield Page.new( Nokogiri::HTML(open(url,fetch_options)) )  
  8.     end  
  9.   
  10. private  
  11.     def rand_proxy  
  12.         @proxies[(rand * 6).to_i]  
  13.     end  
  14.   
  15.     def fetch_options  
  16.         user_agent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.2) Gecko/20061201 Firefox/2.0.0.2 (Ubuntu-feisty)"  
  17.   
  18.         fetch_options = {  
  19.             "User-Agent" => user_agent,  
  20.             "proxy" => rand_proxy  
  21.         }  
  22.     end  
  23. end  

然后我们定义Page类,动态定义了css和xpath的方法,我们直接代理给Nokogiri 
的css、xpath,让它来做事情,我们收集一下抽取的结果,一些就ok了: 
Ruby代码  收藏代码
  1. class Page  
  2.     def initialize(html)  
  3.         @html = html  
  4.     end  
  5.   
  6.     class_eval do  
  7.         [:css,:xpath].each do |extract_by|  
  8.             define_method extract_by do |arg,&block|  
  9.                 if arg.is_a? String then  
  10.                     if block.nilthen  
  11.                        @html.send(extract_by,arg)  
  12.                     else  
  13.                         block.call(@html.send(extract_by,arg))  
  14.                     end  
  15.                 elsif arg.is_a? Hash then  
  16.                     extract_raw = arg.collect{|key,value| [key, @html.send(extract_by,value)]}  
  17.                     data = extract_raw.collect do |key, vals|  
  18.                         ([key] * vals.size).zip(vals)  
  19.                     end  
  20.                     result =  data[0].zip(*data[1..-1]).collect{|e| Hash[ * e.flatten ]}  
  21.                     if block.nilthen  
  22.                         result  
  23.                     else  
  24.                         block.call(result)  
  25.                     end  
  26.                 else  
  27.                     raise ArgumentError.new('Argument type must String or Hash type')  
  28.                 end  
  29.             end  
  30.         end  
  31.     end  
  32. end  


整个的代码: 
Ruby代码  收藏代码
  1. #!/usr/bin/env ruby  
  2.   
  3. require 'rubygems'  
  4. require 'nokogiri'  
  5. require 'open-uri'  
  6.   
  7. class Crawler  
  8.     def initialize  
  9.         @proxies = 1.upto(6).collect{|index| "http://l-crwl#{index}:1080"}  
  10.     end  
  11.       
  12.     def fetch(url)  
  13.         yield Page.new( Nokogiri::HTML(open(url,fetch_options)) )  
  14.     end  
  15.   
  16. private  
  17.     def rand_proxy  
  18.         @proxies[(rand * 6).to_i]    
  19.     end  
  20.   
  21.     def fetch_options    
  22.         user_agent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.2) Gecko/20061201 Firefox/2.0.0.2 (Ubuntu-feisty)"  
  23.   
  24.         fetch_options = {    
  25.             "User-Agent" => user_agent,    
  26.             "proxy" => rand_proxy    
  27.         }    
  28.     end    
  29. end  
  30.   
  31. class Page  
  32.     def initialize(html)  
  33.         @html = html  
  34.     end  
  35.   
  36.     class_eval do  
  37.         [:css,:xpath].each do |extract_by|  
  38.             define_method extract_by do |arg,&block|  
  39.                 if arg.is_a? String then  
  40.                     if block.nilthen   
  41.                        @html.send(extract_by,arg)  
  42.                     else  
  43.                         block.call(@html.send(extract_by,arg))  
  44.                     end  
  45.                 elsif arg.is_a? Hash then  
  46.                     extract_raw = arg.collect{|key,value| [key, @html.send(extract_by,value)]}  
  47.                     data = extract_raw.collect do |key, vals|  
  48.                         ([key] * vals.size).zip(vals)  
  49.                     end  
  50.                     result =  data[0].zip(*data[1..-1]).collect{|e| Hash[ * e.flatten ]}  
  51.                     if block.nilthen  
  52.                         result  
  53.                     else  
  54.                         block.call(result)  
  55.                     end  
  56.                 else  
  57.                     raise ArgumentError.new('Argument type must String or Hash type')  
  58.                 end  
  59.             end  
  60.         end  
  61.     end  
  62. end  
  63.   
  64. crawler = Crawler.new  
  65. 1.upto(10) do |pn|  
  66.     urls = []  
  67.     crawler.fetch "http://fuliang.iteye.com/?page=#{pn}" do |page|  
  68.         page.css("div.blog_title > h3 > a").each do |node|  
  69.             urls << "http://fuliang.iteye.com#{node.attributes['href']}"  
  70.         end  
  71.     end  
  72.   
  73.     urls.each do |url|  
  74.         crawler.fetch url do |page|  
  75.             page.xpath(:title => '//*[@id="main"]/div/div[2]/h3/a',:content => '//*[@id="blog_content"]').each do |entry|  
  76.                 printf("%s\t%s\n",entry[:title].text.gsub(/\s+/,""),entry[:content].text.gsub(/\s+/,""))  
  77.             end  
  78.         end  
  79.     end  
  80.     break  
  81. end  
原文地址:https://www.cnblogs.com/lexus/p/2265965.html