使用ruby獲取商品信息並且做相應的解析處理
現在比較主流的爬蟲應該是用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.newView Code12 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
第二步:解析數據庫的信息啦
解析的時候用的是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 endView Code
第四步:有時候查數據庫那麽大的信息量看起來有點麻煩,我們可以生成excel表格,清晰明了。
我們可以用這個庫 axlsx 不僅可以插入文字 也可以插入圖片(縮略圖) 在你看這份報表的時候 更直觀看出是什麽商品
這裏就不寫代碼啦 相應的文檔
https://github.com/randym/axlsx/blob/master/examples/example.rb
https://gist.github.com/randym/2371912
ps:有時候會覺得爬蟲的速度特別慢,就是第一步獲取商品的源碼的時候,可以大家可以用socket的udp通訊多進程來解決這個問題。
服務器負責分發要點擊的網址,以及當前網址的層數,客戶端負責判斷這個鏈接是否是我需要的商品信息,最後返回結果給服務端,服務端在做相應的數據更近,會增加不少的速度。
ruby的線程是假線程,不推薦使用。
使用ruby獲取商品信息並且做相應的解析處理