抓产品,采用多进程--perfect
前段时间发现单进程爬虫实在是太慢太慢,有时候一天也不一定爬的完,后面就考虑到用多进程,写好程序后,试试看,速度666的。废话不多说,直接干活。
我是用的语言是ruby(当然python也是可以的,我这边就不贴python的,这边我们说ruby的),socket这边我才用udp通讯,因为tcp速度上会比较慢些,虽然保证不丢包啥的,但是速度成本比较高,如果比较大型的网站。
这边直说下载源码,解析过程比较简单,上次的文章有说过了,这次就不说了。
服务器一直开着,直到程序结束,客户端,写一个脚本,跑完了,让他继续重启。自己认为这个代码思想很不错的,具体提供一些思路,和部分代码。
比如一个 网站 我们点击三层。
具体思路呢,就是我们使用udp,需要客户端先发送一个信号(比如hi),服务器收到了,给他分发一个url,cilent点击之后,如果是产品页面,就告诉sever,是产品页面。下面情况我分两个if说明一下。
if 不是产品页面 && 未到达制定层数
就返回点击后产品的所有url
server 负责去重后存进数据库
end
if 不是产品页面&&到达指定层数
就直接返回0
end
下面贴sever的部分代码,具体过程都很详细
加载ruby的库 加载一些写好的函数文件 #获取代理 这边我是用me se也是一样的 我也写了一套 proxy = get_proxy puts proxy agent = Mechanize.new agent.open_timeout = 15 agent.set_proxy proxy[0],proxy[1] #连接数据库 (因为ruby内存不会回收,我们要把还没点的连接存进数据库 程序挂了 重启保证从断点开始) client = Mysql2::Client.new(:host => "数据库地址", :username => "用户名",:password=>"密码",:database=>"数据库名称") deep = 0 puts "请输入要遍历的商品信息的主url:" root_url = gets.chomp page = agent.get root_url puts "请输入链接需要点击的层数:" num = gets.chomp.to_i host = root_url.scan(/www\.(.*?)\./)[0][0] host.gsub!("-","_") ##创建目录 (下载源码的存放位置) FileUtils.mkdir_p("/samba/xww/me/made-in-china/#{host}") #这边就是实现从数据库读取 还未点击的url的集合 根据主url来查找 #要是有集合存在 就直接导出 #没有的话 就插入 stack = [] Node = Struct.new :url,:deep,:line res_select = sql_select(client,root_url) if res_select[0] == 1 #查到click_url stack = res_select[1] puts "当前数据库已经有这条记录啦 直接导出" elsif res_select[0] == -1 #查到root_url puts "该网站已经遍历结束 程序退出" exit else #什么都没查到 deep = deep + 1 page.links.each do |link| line = Digest::MD5.hexdigest("#{link.href}:#{deep}") obj = Node.new(link.href,deep,line) stack.push obj end stack.uniq! puts "第一层的链接数长度为#{stack.length}" res_select = sql_select(client,root_url) if res_select[0] == 0 puts "没有该记录" res_insert = sql_insert(client,stack,root_url) if res_insert == 1 puts "插入成功" end end end #这边还要创建一个表 保证点过的不在点击 res_exist_table = host_table_exist(client,host) if res_exist_table == 0 create_table_host(client,host) end puts "=======================" puts "当前需要遍历的url是#{root_url}" puts "该网站需要遍历的层数是#{num}" puts "=======================" update_flag = 0 ##绑定server的地址 这个是我服务器的地址 server = UDPSocket.new server.bind("192.168.16.141", 4913) while stack.length !=0 puts "----------------------------------" puts "等待 client 中..." mesg, addr = server.recvfrom(65536) #收到hi 进行分发url 否则更新client 传回来的值 if mesg == "hi" puts "server 收到的信息是 : #{mesg}" puts "有 client 连接进来了..." url_and_deep = stack.pop puts "stack的长度 #{stack.length}" puts "----------------------------------" ##更新host_url 表的root_url字段 以及 当前的host表 eveaddition sql_update(client,stack,root_url) line = Digest::MD5.hexdigest("#{url_and_deep.url}:#{url_and_deep.deep}") insert_host_table(client,host,url_and_deep.url,url_and_deep.deep,line) puts "发之前的 url_and_deep : #{url_and_deep}" server.send url_and_deep.to_s, 0, addr[3], addr[1] #不是收到hi的情况 else new_stack = mesg puts "client端出来的数据回来啦" next if new_stack=="[]" if new_stack == "this is a next" puts "收到 this is a next" res_update = sql_update(client,stack,root_url) next end ok = new_stack.scan(/url=\"(.*?)\".*?deep=(.*?)\,.*?line=\"(.*?)\">/) new_stack = [] for i in 0..ok.length-1 obj = Node.new ok[i][0],ok[i][1].to_i,ok[i][2] new_stack.push obj end puts "查重前的stack的长度: #{stack.length}" #(1)对数据库 进行查重 (2)对当前stack查重 new_stack.each do |obj| res_select_link_exist = select_host_table(client,host,obj.line) res_include_link = include_link(obj.url,stack) #puts "数据库中的查询结果: #{res_select_link_exist} stack中的查询结果:#{res_include_link}" if (res_select_link_exist == 1 || res_include_link == 1) #puts "该链接已经存在啦 或者stack中已有 不存stack" next else puts "存进stack" stack.push obj end puts "----------------------------------" end puts "查重后的stack的长度: #{stack.length}" puts "更新数据库" res_update = sql_update(client,stack,root_url) if res_update == 1 puts "更新成功" end end end puts "server work done" puts "stack 的长度 : #{stack.length}" if stack.length == 0 puts "----------------------------------" puts "等待 client 中..." mesg, addr = server.recvfrom(10000000) if mesg == "hi" puts "server 收到的信息是 : #{mesg}" puts "有 client 连接进来了..." server.send "work ok", 0, addr[3], addr[1] #不是收到hi的情况 else puts "程序退出,服务器的任务已经完成" exit end end
client端就做很简单的事情,点击url,返回最后的结果给server
#加载一些库 #加载文件 proxy = get_proxy puts proxy agent = Mechanize.new agent.open_timeout = 15 agent.set_proxy proxy[0],proxy[1] sql_client = Mysql2::Client.new(:host => "数据库地址", :username => "用户名",:password=>"密码",:database=>"数据库名称") root_url = "https://www.made-in-china.com" page = agent.get root_url host = root_url.scan(/www\.(.*?)\./)[0][0] host.gsub!("-","_") #puts "请输入链接需要点击的层数:" #num = gets.chomp.to_i num = 3 Node = Struct.new :url,:deep,:line server_ip = "192.168.16.141" a=100 return_err = "this is a next" while a > 0 sql_num = sql_select(sql_client,root_url) if sql_num == -1 puts "server分发结束 程序退出" exit end puts a a = a-1 client = UDPSocket.new ok = client.send "hi", 0, server_ip, 4913 puts "client 发送hi成功" url_and_deep="" begin status = Timeout::timeout(10) { url_and_deep = client.recv(10000) } rescue puts "============================================" puts "10秒了还没收到服务器发来的数据,可能是丢包了 重新发送hi信号" puts "============================================" next end ## recv 就是接受 recvfrom 多一个地址 url_and_deep = client.recv(10000) puts "client 收到的不是 work ok" url_and_deep = url_and_deep.scan(/url=\"(.*?)\".*?deep=(.*?)\,.*?line=\"(.*?)\">/) get_url = url_and_deep[0][0] deep = url_and_deep[0][1].to_i line = url_and_deep[0][2] begin puts "client 收到的 url : #{get_url} deep:#{deep}" rescue puts "可能该url 乱码 不打印 继续执行" end #判断一个链接要不要点 get_check_res = check(get_url,root_url) if get_check_res == 1 puts "链接中含有pdf 或者 mp4 不点 跳过 或者 facebook twitter 等" puts "----------------------------------" client.send return_err, 0,server_ip, 4913 next end #判断一个链接是否可以点击成功 res_click_link = click_link(agent,get_url,root_url) if res_click_link[0] == 1 new_page = res_click_link[1] #puts "当前的链接数为#{new_page.links.length}" puts "点击该链接成功" else puts "该链接点击失败 跳过" puts "----------------------------------" client.send return_err, 0,server_ip, 4913 next end #判断内容是否为0 if new_page.body.length <= 0 puts "当前页面没有内容 跳过" puts "----------------------------------" client.send return_err, 0,server_ip, 4913 next end #判断是否是产品页 是产品页 就next res = is_product_page(new_page.body,host) if res == 1 puts "该页面有完整的产品信息 存进磁盘" client.send return_err, 0,server_ip, 4913 next end stack = [] if deep < num deep = deep+1 begin puts "当前页面new_page的链接数为#{new_page.links.length}" new_page.links.each do |link| line = Digest::MD5.hexdigest("#{link.href}:#{deep}") obj = Node.new(link.href,deep,line) stack.push obj end stack.uniq! rescue Exception=>e puts e.message puts "当前页面的链接数为0" client.send return_err, 0,server_ip, 4913 next end end begin puts "发送前的stack的长度 : #{stack.length}" client.send stack.to_s, 0,server_ip, 4913 puts "----------------------------------------" rescue puts "发送内容过长 分开发送" begin client.send stack[0...stack.length/2].to_s, 0,server_ip, 4913 puts "第一次发送成功" client.send stack[stack.length/2..stack.length-1].to_s, 0,server_ip, 4913 puts "第二次发送成功" puts "----------------------------------------" rescue puts "分开两次还是发送失败 内容太长 不要了" client.send return_err, 0,server_ip, 4913 end end end
现在运行这个程序,都觉得爬虫速度快了不少!继续加油