使用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
第二步:解析数据库的信息啦
解析的时候用的是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)
第三步:商品都有图片的,解析完成后,我们下载图片
下载图片我们用的还是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
第四步:有时候查数据库那么大的信息量看起来有点麻烦,我们可以生成excel表格,清晰明了。
我们可以用这个库 axlsx 不仅可以插入文字 也可以插入图片(缩略图) 在你看这份报表的时候 更直观看出是什么商品
这里就不写代码啦 相应的文档
https://github.com/randym/axlsx/blob/master/examples/example.rb
https://gist.github.com/randym/2371912
ps:有时候会觉得爬虫的速度特别慢,就是第一步获取商品的源码的时候,可以大家可以用socket的udp通讯多进程来解决这个问题。
服务器负责分发要点击的网址,以及当前网址的层数,客户端负责判断这个链接是否是我需要的商品信息,最后返回结果给服务端,服务端在做相应的数据更近,会增加不少的速度。
ruby的线程是假线程,不推荐使用。