1. 程式人生 > >使用ruby獲取商品信息並且做相應的解析處理

使用ruby獲取商品信息並且做相應的解析處理

服務器 size 寫代碼 技術 can 增刪 報表 http spa

現在比較主流的爬蟲應該是用python,但是我覺得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的線程是假線程,不推薦使用。

    

使用ruby獲取商品信息並且做相應的解析處理