使用ruby获取商品信息并且做相应的解析处理

现在比较主流的爬虫应该是用python,python我也写了这样子的一份框架,和ruby相对应。今天在这里说说ruby。我觉得ruby也是ok的,我试试看写了一个爬虫的小程序,并作出相应的解析,下载图片,生成excel报表。我是写了一个框架,专门拿来爬取商品信息的。废话不多说,直接搞事情。

第一步:当然是获取商品的信息啦,输入对应的商品的网址,以及你需要爬虫的层数,判断你点击的这个网页是否是商品页面,进行相应的存储,保存源码。

    工具就是用mechanize啦,很好用的爬虫工具哦,无页面的可以减小不少内存的开销呢。(这种情况说的不加载js的情况哦 如果需要js加载的 要用selenium 原理一样的)接下来 写代码 贴上爬虫的部分代码 讲的主要就是爬虫的一个大概思想

  1 require 'rubygems'
  2 require "mechanize"
  3 require "json"
  4 require "mysql2"
  5 require 'fileutils'
  6 load "./deal_link.rb"
  7 load "./sql_op.rb"  #这边是对当前位置的增删改查  方便从断点开始
  8 load "./sql_table.rb"  #这个是每个平台创建一个数据库表 点过的链接不点 同一个链接 同一层
  9 proxy = get_proxy
 10 puts proxy
 11 agent = Mechanize.new
 12 agent.open_timeout = 15
 13 agent.set_proxy proxy[0],proxy[1]
 14 client = Mysql2::Client.new(:host => "数据库的地址", :username => "用户名",:password=>"密码",:database=>"数据库表")
 15 
 16 deep = 0
 17 puts "请输入要遍历的商品信息的主url:"
 18 root_url = gets.chomp
 19 page = agent.get root_url
 20 puts "请输入链接需要点击的层数:"
 21 num = gets.chomp.to_i    
 22 host = root_url.scan(/www\.(.*?)\./)[0][0]
 23 
 24 ##创建目录
 25 FileUtils.mkdir_p("/samba/xww/me/new/evesaddiction/#{host}")
 26 
 27 stack = []
 28 Node = Struct.new :url,:deep
 29 res_select = sql_select(client,root_url)
 30 if res_select[0] == 1 #查到click_url
 31     stack = res_select[1]
 32     puts "当前数据库已经有这条记录啦 直接导出"
 33 elsif res_select[0] == -1 #查到root_url
 34     puts "该网站已经遍历结束 程序退出"
 35     exit
 36 else #什么都没查到    
 37     deep = deep + 1
 38     page.links.each do |link|
 39         obj = Node.new(link.href,deep)
 40         stack.push obj
 41     end
 42     stack.uniq!
 43     puts "第一层的链接数长度为#{stack.length}"
 44 end
 45 
 46 res_exist_table = host_table_exist(client,host)
 47 if res_exist_table == 0
 48     create_table_host(client,host)
 49 end    
 50 
 51 
 52 puts "======================="
 53 puts "当前需要遍历的url是#{root_url}"
 54 puts "该网站需要遍历的层数是#{num}"
 55 puts "======================="
 56 
 57 update_flag = 0
 58 
 59 while stack.length !=0
 60     
 61     get_url = stack[stack.length-1].url
 62     deep = stack[stack.length-1].deep
 63     stack.pop
 64 
 65     puts "----------------------------------"        
 66     puts "当前stack的长度为#{stack.length}"
 67     puts "当前的url是#{get_url}"
 68     puts "当前的层数是#{deep}"
 69     puts "----------------------------------"
 70 
 71     
 72     sql_update(client,stack,root_url)
 73     
 74     ##判断这个链接在当前层被点过没 点过就不点
 75     res_select_link_exist = select_host_table(client,host,get_url,deep)
 76     if res_select_link_exist == 1
 77         puts "该链接已经存在啦 不点啦"
 78         next
 79     else
 80         puts "该链接不存在 存进#{host}数据库表"    
 81         insert_host_table(client,host,get_url,deep)
 82     end            
 83     puts "----------------------------------"    
 84     
 85     #判断这个链接要不要点 
 86     get_check_res = check(get_url,root_url)
 87     if get_check_res == 1
 88         puts "链接中含有pdf 或者 mp4 不点 跳过 或者 facebook twitter 等"
 89         puts "----------------------------------"        
 90         next
 91     end
 92     puts "----------------------------------"    
 93 
 94     #判断一个链接是否可以点击成功
 95     res_click_link = click_link(agent,get_url,root_url)
 96     if res_click_link[0] == 1
 97         new_page = res_click_link[1]
 98         puts "点击该链接成功"
 99     else
100         puts "该链接点击失败 跳过"
101         puts "----------------------------------"
102         next
103     end    
104     
105     #判断内容是否为0
106     if new_page.body.length <= 0
107         puts "当前页面没有内容 跳过"
108         puts "----------------------------------"
109         next
110     end
111     
112     #判断是否是产品页 没达到指定的层数 不跳
113     res = is_product_page(new_page.body,host)
114     if res == 1
115         puts "该页面有完整的产品信息 存进磁盘"
116     end
117     
118     if deep < num
119         deep = deep+1
120         puts "当前页面new_page的链接数为#{new_page.links.length}"
121         begin
122             new_page.links.each do |link|
123                 res_include_link = include_link(link.href,stack)
124                 if res_include_link == 0
125                     obj = Node.new(link.href,deep)
126                     stack.push obj
127                 end
128             end
129             stack.uniq!
130         rescue
131             puts "当前页面的链接数为0"
132         end
133     else
134         #查询数据库是否有该字段
135         #没有的话 插入 stack
136         res_select = sql_select(client,root_url)
137         if res_select[0] == 0
138             puts "没有该记录"
139             res_insert = sql_insert(client,stack,root_url)
140             if res_insert == 1
141                 puts "插入成功"
142             end    
143         end    
144         if res_select[0] == 1
145             puts "更新数据库"
146             res_update = sql_update(client,stack,root_url)
147             if res_update == 1
148                 puts "更新成功"
149             end    
150             update_flag = 1
151         end
152     end
153     
154 end
View Code

第二步:解析数据库的信息啦

    解析的时候用的是nokogiri库

    用的时候直接 gem install nokogiri

  1 Encoding.default_internal = "UTF-8"
  2 require 'rubygems'
  3 require 'mysql2'
  4 require "mongo"
  5 require "nokogiri"
  6 require "json"
  7 
  8 #这边我存mongo
  9 client = Mongo::Client.new(["数据库地址:端口号"],:database=>"数据库名字")
 10 
 11 puts "请输入要解析的平台的网站"
 12 u = gets.chomp
 13 dir = u.scan(/www\.(.*?)\./)[0][0]
 14 
 15 time = gets.chomp
 16 time.gsub!(":","-")
 17 
 18 full_dir = "./#{dir}"
 19 
 20 
 21 def go_dir(full_dir,agent,client,u,time)
 22     input_url_front = nil
 23     Dir.glob("#{full_dir}/*") do |path|
 24             
 25         res = File.directory? path
 26         if res == true    
 27             puts "#{path}是一个目录 继续遍历"
 28             puts "------------------------------------------------------"
 29             go_dir(path,agent,client,u,time)
 30             puts "目录的文件读取完毕 目录不用读取跳过"
 31             next
 32         end
 33         
 34         s=File.read(path)
 35         doc = Nokogiri::HTML(s)
 36 
 37           #提取我需要的字段 
 38         begin
 39             title = s.scan(/(?i)meta\s*property="og:title"\s*content="([^"]+)"/)[0][0]  #^>  content里面
 40             description = s.scan(/(?i)[meta\s*property="og:|meta\s*name="]description"\s*content=\s*"([^\"]+)"/)[0][0]
 41             url = s.scan(/(?i)meta\s*property="og:url"\s*content="([^"]+)"/)[0][0]
 42             image = s.scan(/(?i)meta\s*property="og:image"\s*content="([^"]+)"/)[0][0] 
 43         rescue
 44             puts "没有匹配到 title  description url image  其中之一的字段 不完整 可能不是商品页"
 45             puts "------------------------------------------------------"
 46             next
 47         end
 48         
 49         if url.scan("http").length == 0
 50             url = u + url
 51         end    
 52         
 53         begin
 54             price = s.scan(/(?i)itemprop=['\"]price['\"]\s*>?['\"]?(\$?\d+(?:\,\d+)?(?:\.\d+)?(?:\s\w+)?)(?=['\"<])/)[0][0]  
 55             price_label = s.scan(/(?i)itemprop=['"]priceCurrency['"]\s*content=['"]([^['"]]+)(?=['"])/)[0][0]
 56         rescue
 57             puts "没有匹配到价格 换一个匹配"
 58             begin
 59                 price = s.scan(/meta\s*property="product:price:amount"\s*content="([^"]+)"/)[0][0]  
 60                 price_label = s.scan(/(?i)meta\s*property="product:price:currency"\s*content="([^"]+)"/)[0][0]
 61             rescue
 62                 puts "还是没有匹配到价格信息 不完整"
 63                 puts "---------------------------------------------------------"
 64                 next
 65             end
 66         end
 67         
 68         begin
 69             classify = doc.css("ul.grid_12 li").text
 70             classify = classify.split("\n")
 71             for i in 0..classify.length-1
 72                 classify[i].strip!
 73             end
 74             for i in 0..classify.length-1
 75                 if classify[i] == ""
 76                     classify.delete(classify[i])
 77                 end    
 78             end
 79             3.times{classify.delete_at(0)}
 80             for i in 0..classify.length-1
 81                 classify_name = classify_name.to_s + classify[i].to_s + ">" 
 82             end
 83             classify_name.chop!
 84         rescue
 85             classify_name = nil
 86         end
 87         
 88         #保留两位小数
 89         price = price.to_f.round(2)
 90         
 91         begin
 92             material = s.scan(/Metal<\/dd><dt>(.*?)</)[0][0]
 93         rescue
 94             puts "没有匹配到material"
 95             material = nil
 96         end
 97         
 98         begin
 99             item_statue = s.scan(/\"WebTrend\":\"(.*?)\"/)[0][0]
100         rescue
101             puts "没有匹配到item_statue"
102             item_statue = nil
103         end
104 
105          ##相应的信息解析好之后存进数据库
106         product_col = client[:product]
107         res = product_col.find({product_detail_url:"#{url}"}).first 
108         
109         if res == nil
110             puts "这条记录数据库没有 可以插入 新品"
111             new_classify_collection = []
112             if classify_name != nil
113                 new_classify_collection.push classify_name
114             end
115 
116             old_price = nil
117             product_update_time = nil
118             price_statue = nil
119             
120             product_col.insert_one({product_title:"#{title}",product_description:"#{description}",product_detail_url:"#{url}",product_image_addr:"#{image}",product_price:price,product_price_label:"#{price_label}",product_come_time:"#{time}",product_update_time:"#{product_update_time}",product_shop:"#{u}",classify_name:"#{classify_name}",new_classify_collection:"#{new_classify_collection}",old_price:"#{old_price}",price_statue:"#{price_statue}",item_statue:"#{item_statue}",material:"#{material}"})            
121             puts "插入成功"
122             puts "==================================================="
123         else
124             puts "该字段已在数据库中有记录 准备更新数据库"
125             puts res 
126             new_classify_collection = [] 
127             new_classify_collection = JSON.parse(res["new_classify_collection"])
128             
129             if (!new_classify_collection.include? classify_name) && (classify_name != nil)
130                 new_classify_collection.push classify_name
131             end
132             
133             puts "-------------------------------------------------"
134             if price > res["product_price"].to_f 
135                 puts "涨价"
136                 old_price = res["product_price"].to_f
137                 price_statue = "Rise Price"
138                 product_col.update_one( { 'product_detail_url' => "#{url}" }, { '$set' => { 'product_price' => price.to_f, 'product_update_time' => "#{time}", 'classify_name' => "#{classify_name}",'new_classify_collection' => "#{new_classify_collection}",'old_price':old_price,'price_statue':"#{price_statue}",'item_statue':"#{item_statue}"} } )
139             end
140             if price < res["product_price"].to_f
141                 puts "降价"
142                 old_price = res["product_price"].to_f
143                 price_statue = "Reduced Price"
144                 product_col.update_one( { 'product_detail_url' => "#{url}" }, { '$set' => { 'product_price' => price.to_f, 'product_update_time' => "#{time}", 'classify_name' => "#{classify_name}",'new_classify_collection' => "#{new_classify_collection}",'old_price':old_price,'price_statue':"#{price_statue}",'item_statue':"#{item_statue}"} } )
145             end
146             puts "更新成功"
147             puts "==================================================="
148         end        
149         
150     end
151 end
152 
153 go_dir(full_dir,agent,client,u,time)
View Code

 

第三步:商品都有图片的,解析完成后,我们下载图片

    下载图片我们用的还是mechanize 

    page = agent.get img_url

    page.body就是图片的内容啦 直接保存就好啦 

    

    有时候我们下载下来的图片比较大 我们会使用图片压缩

    这里我们用这个库 mini_magick 我就 稍微写一下把

 

 1 require "mini_magick"
 2 
 3 u = "https://www.evesaddiction.com"
 4 up =  u.scan(/www\.(.*?)\./)[0][0] + "Img" #这个是下载好的图片了
 5 host = u.scan(/www\.(.*?)\./)[0][0] + "Ok" #保存要压缩的图片
 6 ##创建目录
 7 FileUtils.mkdir_p("./#{host}")
 8 
 9 Dir.glob("./#{up}/*") do |p|
10     s=p
11     s = s.scan(/[^\/]+$/)[0].scan(/(.*?).jpg/)[0][0]
12     
13     image = MiniMagick::Image.open(p)
14     image.resize "100x100"
15     image.write "./#{host}/#{s}.png"
16 end
View Code

 

第四步:有时候查数据库那么大的信息量看起来有点麻烦,我们可以生成excel表格,清晰明了。

    我们可以用这个库 axlsx 不仅可以插入文字 也可以插入图片(缩略图) 在你看这份报表的时候 更直观看出是什么商品

    这里就不写代码啦 相应的文档

      

    https://github.com/randym/axlsx/blob/master/examples/example.rb
    https://gist.github.com/randym/2371912

 

ps:有时候会觉得爬虫的速度特别慢,就是第一步获取商品的源码的时候,可以大家可以用socket的udp通讯多进程来解决这个问题。

服务器负责分发要点击的网址,以及当前网址的层数,客户端负责判断这个链接是否是我需要的商品信息,最后返回结果给服务端,服务端在做相应的数据更近,会增加不少的速度。

ruby的线程是假线程,不推荐使用。

 

    

posted @ 2018-06-19 10:44  WangHello  阅读(293)  评论(0编辑  收藏  举报