Ruby 脚本实现数据爬取

本贴最后更新于 3100 天前,其中的信息可能已经时移世异

工作上使用Redis,为了测试其性能,需要大量的测试数据,所以利用周末的时间用ruby写了个脚本,跑了一天,从搜搜问问百度知道爬了大量的数据下来,分成问题和答案,总共有50万条数据,大小也就50M左右;周一去上班,写了一个ruby脚本连接Redis数据库,再循环分别导入问题和答案数据,这样测试数据就有了,由于测试机器内存的限制,导入的测试数据总共100万条,占内存1G。

下面想说说ruby脚本的结构,很简单,还有需要改进的地方,但是脚本就是一次性的工具,如没有通用性可能,有没有必要再改进另当其说。

建立http连接功能使用了内部包含的gem包open-uri,解析获取到的页面对象,并抓取特定的document元素,使用的gem是nokogiri,脚本分几个功能部分,各负其责,分别介绍如下:

  • 递归抓取页面所有超级链接(spider_url.rb)
#!/usr/bin/ruby -w

require 'rubygems'
require 'nokogiri'
require 'open-uri'

load 'spider_document.rb'

class URL
attr_accessor :available_url, :pre_url, :visited_url, :error_url

def initialize
    @available_url = {}	
    @visited_url = {}
    @error_url = {}
    @pre_url = "http://wenwen.soso.com"	
end

# 抓取页面上的所有超链接,形如 'href...'
def crawl_url (target_url)
	puts '获取超级链接页面地址 -> ' + target_url
	temp_available_url = {}

	begin
	   open(target_url) do |uri|	  
	      doc_content = uri.read
	      doc_content.scan(/href=["|'](.+?)["|']/) do |href_item|
	         url = href_item[0]
                     # TODO pattern is not exactely
		 url.match(/.z./) do |m|
					
		 # build hash {url=>real url} 
		 if !@visited_url.has_key?(url) 
		   temp_available_url["#{url}"] = url 
		   @visited_url["#{url}"] = url
		 end
					
		 # puts '新增访问url:' + url
		 end
		 # url=@target_url.match(/(http:\/\/([^\/]+))\//)[1] << url if url =~ /^\//
	      end				
	   end			
	rescue
	   puts $!
	   @error_url["#{target_url}"] = target_url
	   puts 'error' 
	end
	temp_available_url
end


def crawl_content (target_url)
    doc = Document.new
    doc.crawl_content(target_url)				
end			

end

url_spider = URL.new
puts 'url spider begining ...'

url_spider.available_url = url_spider.crawl_url("http://wenwen.soso.com")
while(!url_spider.available_url.empty?)
url_spider.available_url.each do |key,value|
url_spider.crawl_content(url_spider.pre_url + value)
url_spider.available_url = url_spider.available_url.merge url_spider.crawl_url(url_spider.pre_url + value)
url_spider.available_url.delete(key)
puts 'current available_url size : ' + url_spider.available_url.size.to_s
end
end

puts 'Total available_url size : ' + url_spider.available_url.size.to_s
puts 'Total visited_url size : ' + url_spider.visited_url.size.to_s
puts 'Total error_url size : ' + url_spider.error_url.size.to_s

  • 解析页面文档(spider_document.rb)
#!/usr/bin/ruby -w

require 'rubygems'
require 'nokogiri'
require 'open-uri'

class Document
@@quesion_count = 0
@@answer_count = 0

def get_question (page)
   questionArray = page.css('div.qa_title')
   questionArray.each do |question|
   #puts question.text
   File.open("question.txt",'a') { |f|
     f.puts question.text.strip.delete "快速回答".strip
   }
   end

 @@quesion_count = @@quesion_count + questionArray.size
 puts "current question count is : [" + @@quesion_count.to_s + "]"
end

def get_answer (page)
   answerArray = page.css('div.answer_con')
   answerArray.each do |answer|
     #puts answer.text
     File.open("answer.txt",'a') { |f|
       f.puts answer.text.strip.delete "快速回答".strip
     }
   end

   @@answer_count = @@answer_count + answerArray.size
   puts "current answer count is : [" + @@answer_count.to_s + "]"
end

def crawl_content (target_url)
    puts '抓取页面内容地址 -> ' + target_url
    begin
      page = Nokogiri::HTML(open(target_url))
      get_question (page)
      get_answer (page)	
    rescue Exception => e	
          puts $!
    end	
end

end

  • 批量导入Redis(spider_persistence.rb)
#!/usr/bin/ruby -w

require 'rubygems'
require 'nest'
require 'redis'

class Persistence

attr_accessor :redis, :question_count, :answer_count

def initialize
  @redis = Redis.new
  # @redis = Redis.new(:host=>"192.168.1.67",:port=>6379)
  @question_count = 0
  @answer_count = 0
end

# 批量生产账号
def batch_account
  account_namespace = Nest.new("account",@redis)

  File.open("account_email_local.txt") do |f| 
     f.each_line do |line|
             pre_str = line.chomp.split('@')[0]
	 account_namespace[line.chomp].hset("nickName",pre_str)
	 account_namespace[line.chomp].hset("email",pre_str)
         account_namespace[line.chomp].hset("passWordHash","49") # 密码为1
	 account_namespace[line.chomp].hset("answerCount","0")
	 account_namespace[line.chomp].hset("selfDescription","非理性人类一枚")
	 account_namespace[line.chomp].hset("followCount","0")
	 account_namespace[line.chomp].hset("followerCount","0")
	 account_namespace[line.chomp].hset("questionCount","0")

	 puts line.chomp + " is builded."
     end 
  end
end

# 批量生成问题集合
def batch_question
  account_namespace = Nest.new("account",@redis)
      question_namespace = Nest.new("question",@redis)
  pre_email = "rayootech" # 默认的账号 rayootech@163.com

  begin
        File.open("question.txt","r") do |file|
           file.each_line do |line|
	 # 生成随机的20位问题id
	 id = random_id(20)
							
	 if (!line.strip.empty? && line.strip.length>3)
	   puts "#{file.lineno} : #{line}"					
	   question_namespace[id].hset("id",id)
	   question_namespace[id].hset("content",line)
	   question_namespace[id].hset("author",pre_email+"@163.com")
	   question_namespace[id].hset("createTime","2014-01-14")
	   question_namespace[id].hset("followerCount","0")
	   question_namespace[id].hset("browseCount","1")

           # 用户和提出的问题关系集合 account:[id]:question
	   account_namespace["#{pre_email}@163.com"]["questions"].zadd(1401141645,id)
	   @question_count = @question_count + 1
	   File.open("question_id_local.txt", "a") { |f| f.puts id }
          end

       # 生成随机email地址前缀,并保存,后期生成account账号导入redis,一个email账户提500个问题
       if (@question_count%500==0)
	   pre_email = random_id(10)
	   File.open("account_email_local.txt","a"){|file|file.puts "#{pre_email}@163.com"}
       end
      end
    end	
    rescue Exception => e
      puts $!	
    end
    end

# 批量生成回答集合
def batch_answer
	account_namespace = Nest.new("account",@redis)
	qa_relation_ns = Nest.new("question",@redis)
	answer_namespace = Nest.new("answer",@redis)
	question_id = "lzj4ggcgfpmj5uxnhtgx" # 【提问时间】 默认问题id

	begin
          File.open("answer.txt","r") do |file|
                 file.each_line do |line|
	     # 生成随机的20位回答id
	     id = random_id(20)
	     author = random_account_email
                 if (!line.strip.empty?)
                     puts "#{file.lineno} : #{line}"
		 answer_namespace[id].hset("id",id)
		 answer_namespace[id].hset("content",line)
		 answer_namespace[id].hset("author",author)
		 answer_namespace[id].hset("createTime","2014-01-15")
		 answer_namespace[id].hset("approveCount","0")
		 answer_namespace[id].hset("qId",question_id)

		 # 问题和回答关系数据
		 qa_relation_ns[question_id]["answers"].zadd(1401152040,id)
		 # 问题的所有回答者关系数据
		 qa_relation_ns[question_id]["respondents"].sadd(author)
		 # 用户所有的回答数据
		 account_namespace[author]["answers"].zadd(1401159088,id)

		 @answer_count = @answer_count + 1
		 File.open("answer_id_local.txt", "a") { |f| f.puts id }
                   end

	       # 每个问题下有平均100个回答
	       if (@answer_count%100==0)
		 question_id = random_question_id	
	       end

                 end
	end	
	rescue Exception => e
	  puts $!	
	end
end

# 批量生成问题浏览者集合
def batch_question_browser
end

# 随机返回一个问题id
def random_question_id
    question_id_arr = []
    index = 0
    File.open("question_id.txt") do |f| 
      f.each_line do |line| 
	 question_id_arr[index]=line
	 index = index + 1
      end 
    end
    question_id_arr[rand(question_id_arr.size-1)].chomp
end

# 随机返回一个回答id
def random_answer_id
end

# 随机返回一个email
def random_account_email
    account_email_arr = []
    index = 0
    File.open("account_email.txt") do |f| 
      f.each_line do |line| 
	 account_email_arr[index]=line
	 index = index + 1
      end 
    end
    account_email_arr[rand(account_email_arr.size-1)].chomp
end

# 生成随机数
def random_id(len)
	   chars = ("a".."z").to_a + ("a".."z").to_a + ("0".."9").to_a
	   random_id = ""
	   1.upto(len) { |i| random_id << chars[rand(chars.size-1)] }
	   return random_id
end

end

persistence = Persistence.new

1.times

puts "persistence question count : " + persistence.question_count.to_s

persistence.batch_account

1.times {|i| persistence.batch_answer }
puts "persistence answer count : " + persistence.answer_count.to_s

  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 229 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    548 引用 • 674 回帖
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    223 引用 • 474 回帖
  • 智能合约

    智能合约(Smart contract)是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。智能合约概念于 1994 年由 Nick Szabo 首次提出。

    1 引用 • 11 回帖
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖 • 1 关注
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 601 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    78 引用 • 391 回帖
  • Shell

    Shell 脚本与 Windows/Dos 下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比 Windows 下的批处理更强大,比用其他编程程序编辑的程序效率更高,因为它使用了 Linux/Unix 下的命令。

    124 引用 • 74 回帖 • 2 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3192 引用 • 8214 回帖
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 9 关注
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    210 引用 • 358 回帖 • 1 关注
  • Vim

    Vim 是类 UNIX 系统文本编辑器 Vi 的加强版本,加入了更多特性来帮助编辑源代码。Vim 的部分增强功能包括文件比较(vimdiff)、语法高亮、全面的帮助系统、本地脚本(Vimscript)和便于选择的可视化模式。

    29 引用 • 66 回帖 • 2 关注
  • 阿里云

    阿里云是阿里巴巴集团旗下公司,是全球领先的云计算及人工智能科技公司。提供云服务器、云数据库、云安全等云计算服务,以及大数据、人工智能服务、精准定制基于场景的行业解决方案。

    89 引用 • 345 回帖 • 2 关注
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 165 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 361 关注
  • 叶归
    4 引用 • 11 回帖 • 4 关注
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 375 关注
  • 开源

    Open Source, Open Mind, Open Sight, Open Future!

    409 引用 • 3578 回帖
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    6 引用 • 63 回帖 • 1 关注
  • 资讯

    资讯是用户因为及时地获得它并利用它而能够在相对短的时间内给自己带来价值的信息,资讯有时效性和地域性。

    55 引用 • 85 回帖
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖
  • PWA

    PWA(Progressive Web App)是 Google 在 2015 年提出、2016 年 6 月开始推广的项目。它结合了一系列现代 Web 技术,在网页应用中实现和原生应用相近的用户体验。

    14 引用 • 69 回帖 • 170 关注
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 653 关注
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 81 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 541 关注
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    179 引用 • 407 回帖 • 476 关注
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    132 引用 • 796 回帖 • 1 关注
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    132 引用 • 1114 回帖 • 122 关注