目录

Python爬虫系列(六)数据处理篇

一、前言:

  • 我们在之前学习了爬虫的页面下载以及演示了如何用lxml和xpath来抽取数据。
  • 本篇我们主要学习目标是:
  • 1、将数据抽取部的代码分抽取出来进一步封装
  • 2、实现安居客租房列表数据的抽取及下一页链接的抽取

实战部分:

  • 原本我们启动爬虫的代码以及接收下载数据,数据解析等代码都集中在了Spider类中。
  • 本次我们将增加一个Scheduler来负责爬虫的调度工作,sprider专注于数据的处理

SpiderException异常类代码(创建一个异常类来统一来管理异常):

  • 代码如下:

      class SpiderException(Exception):
      	def __init__(self):
      		super(SpiderException,self).__init__();
    

Scheduler类代码

  • 代码如下:

      from com.anjie.download import Download;
      from com.anjie.spider_exception import SpiderException;
    
      class Scheduler:
      	# 待抓取队列
        crawl_queue = [];
      	download = None;
      	spider = None;
    
      	def __init__(self, download=None, spider=None):
      		self.download = download;
      		self.spider = spider;
      		pass;
    
      	# 调用该函数开始让爬虫开始工作
        def start_craw(self):
      		if not self.spider:
      			raise SpiderException("spider obeject is None")
    
      		if not hasattr(self.spider, 'start_url'):
      			raise SpiderException("spider must have an start_url attribute")
    
      		if not hasattr(self.spider, 'pager_back'):
      			raise SpiderException("spider must have an pager_back method")
    
      		self.crawl_queue.extend(self.spider.start_url);
      		next_link = [];
    
      		while self.crawl_queue:
      			url = self.crawl_queue.pop();
      			html = self.download.download(url=url);
      			temp_next_link = self.spider.pager_back(url, html);
      			print('抽取的链接:')
      			print(temp_next_link);
      			if temp_next_link:
      				next_link.extend(temp_next_link);
    
  • 代码解析:

  • start_url字段:spider类增加一个start_url来存储起始链接。

  • pager_back回调:增加一个pager_back回调函数来接收下载好的页面,pager_back回调会返回一个需要继续请求的url列表集合。

Spider类代码

  • 代码如下:

      from com.anjie.download import Download;
    
      from lxml.html import etree;
      from com.anjie.scheduler import Scheduler;
    
      class Spider:
      	# 待抓取队列
        start_url = ['https://gz.zu.anjuke.com/?from=navigation'];
    
      	def __init__(self):
      		pass;
    
      	def pager_back(self,current_url, html):
      		next_link = [];
      		root = etree.HTML(html);
      		result = root.xpath('//div[@class="topbar "]//ul/li/*')
      		for s in result:
      			print(s.text);
    
      		return next_link
    
      if __name__ == '__main__':
      	sp = Spider();
      	sc = Scheduler(download=Download(),spider=sp);
      	sc.start_craw();
    
  • 代码解析:

  • 在pager_back函数里面进行数据的解析,如果有需要继续请求的url,就放入next_link中返回。

  • 这样我们就完成了代码的改造升级。

  • 这样做的好处是将爬虫进行模块化,对于维护性与扩展性都是极好的。

抓取真实数据

  • 接下来我们将完成本次的目标,通过使用lxml与xpath抽取出安居客的租房信息列表以及下一页的链接。 //ow3d01r1a.bkt.clouddn.com//file/2017/9/5ef1df6da792444c9d1621a8211a26c2-1.png

  • 以上为我们通过使用chrome浏览器查看的数据列表对应的html代码。

  • 我们可以通过class属性进行定位。

  • OK,我们看下编写了xpath抽取代码的版本:

      from com.anjie.download import Download;
    
      from lxml.html import etree;
      from com.anjie.scheduler import Scheduler;
    
      class Spider:
      	# 待抓取队列
        start_url = ['https://gz.zu.anjuke.com/?from=navigation'];
    
      	def __init__(self):
      		pass;
    
      	def pager_back(self,current_url, html):
      		next_link = [];
      		root = etree.HTML(html);
      		list_result = root.xpath('//div[@class="maincontent"]//div[@class="zu-itemmod  "]')
      		for s in list_result:
      			print(etree.tostring(s,encoding="utf-8",pretty_print=True,method="html").decode());
    
      		return next_link
    
      if __name__ == '__main__':
      	sp = Spider();
      	sc = Scheduler(download=Download(),spider=sp);
      	sc.start_craw();
    
  • 代码解析:当然你也可以直接//div[@class=“zu-itemmod “]直接定位到列表,注意后面的空格,虽然我们在chrome浏览器中看不到空格,因为浏览器自动帮我们处理了。

  • 我们看下抽取结果:

//ow3d01r1a.bkt.clouddn.com//file/2017/9/1b426d010d574571838199370254f15f-1.png

  • 可以看到,我们成功获得了列表数据,接下来我们开始进行数据的精抽取,首先先封装一个house类来存储数据

      class House:
      	#标题
        title="";
      	#房子请求链接
        url="";
      	#房子类型
        house_type=""
        #销售类型、出租类型
        sale_type=""
        #精装修等
        level = ""
        #楼层
        floor_number=""
        # 房子所在地
        area_name=""
        #具体地址
        addr=""
        #联系人
        user= ""
        #补充
        supplement= ""
    
  • OK,类定义好之后我们就可以开始解析数据了。

  • 代码修改如下:

      from com.anjie.download import Download;
    
      from lxml.html import etree;
      from com.anjie.scheduler import Scheduler;
    
      class Spider:
      	# 待抓取队列
        start_url = ['https://gz.zu.anjuke.com/?from=navigation'];
    
      	def __init__(self):
      		pass;
    
      	def pager_back(self, current_url, html):
      		next_link = [];
      		root = etree.HTML(html);
      		list_result = root.xpath('//div[@class="maincontent"]//div[@class="zu-itemmod  "]')
      		house_list = [];
      		house = None;
      		print(len(list_result))
      		for node in list_result:
      			print(list_result.index(node))
      			# print(etree.tostring(node,encoding="utf-8",pretty_print=True,method="html").decode())
       # 抽取  title = node.xpath('.//div[@class="zu-info"]/h3/a/text()');
      			print("标题:%s" % title)
      			# 抽取链接
        url = node.xpath('.//div[@class="zu-info"]/h3/a/@href')
      			print("url:%s" % url)
      			temp = node.xpath('.//div[@class="zu-info"]/p[1]/text()')
    
      			(house_type, sale_type, level, floor_number) = temp;
      			# 抽取房屋类型
        print("房屋类型:%s" % house_type)
      			# 销售类型
        print("销售类型:%s" % sale_type)
      			# 房屋等级
        print("房屋等级:%s" % level)
      			# 房屋楼层
        print("楼层:%s" % floor_number)
    
      			# 抽取地址
        area_name = node.xpath('.//div[@class="zu-info"]/address/a/text()')
      			print("所在区域:%s" % area_name)
      			area = node.xpath('.//div[@class="zu-info"]/address/text()')
      			for ele in area:
      				if len(ele.strip()) > 0:
      					area = ele.strip();
      					break;
      			print("详细地址:%s" % area);
      			# 抽取联系人
        user = node.xpath('.//div[@class="zu-info"]/p[2]/span/text()')
      			print("联系人:%s" % user)
      			supplement = node.xpath('.//div[@class="zu-info"]/p[2]/em/text()')
    
      			print("补充:%s" % supplement)
    
      		return next_link
    
      if __name__ == '__main__':
      	sp = Spider();
      	sc = Scheduler(download=Download(), spider=sp);
      	sc.start_craw();
    
  • 运行结果如下: //ow3d01r1a.bkt.clouddn.com//file/2017/9/99e93e259505474695804c307f6aa33d-1.png

  • 第一页一共有60条数据,xpath的写法需要多写多练,找个demo实战一下,相信就能基本掌握了。

  • OK,数据数据拿到了接下来我们看下如何获取下一请求的链接。

  • 我们先打开chrome看下下一页链接的抽取模式

//ow3d01r1a.bkt.clouddn.com//file/2017/9/b3d5922b70b44eff83f2ebe401bfcd28-1.png

  • xpath规则代码为:links = root.xpath(’//*[@class=“multi-page”]/a/@href’);

  • 代码修改为:

      from com.anjie.download import Download;
    
      from lxml.html import etree;
      from com.anjie.scheduler import Scheduler;
    
      class Spider:
      	# 待抓取队列
        start_url = ['https://gz.zu.anjuke.com/?from=navigation'];
    
      	def __init__(self):
      		pass;
    
      	def pager_back(self, current_url, html):
      		next_link = [];
      		root = etree.HTML(html);
      		list_result = root.xpath('//div[@class="maincontent"]//div[@class="zu-itemmod  "]')
      		house_list = [];
      		house = None;
      		print(len(list_result))
      		for node in list_result:
      			print(list_result.index(node))
      			# print(etree.tostring(node,encoding="utf-8",pretty_print=True,method="html").decode())
       # 抽取  title = node.xpath('.//div[@class="zu-info"]/h3/a/text()');
      			print("标题:%s" % title)
      			# 抽取链接
        url = node.xpath('.//div[@class="zu-info"]/h3/a/@href')
      			print("url:%s" % url)
      			temp = node.xpath('.//div[@class="zu-info"]/p[1]/text()')
    
      			(house_type, sale_type, level, floor_number) = temp;
      			# 抽取房屋类型
        print("房屋类型:%s" % house_type)
      			# 销售类型
        print("销售类型:%s" % sale_type)
      			# 房屋等级
        print("房屋等级:%s" % level)
      			# 房屋楼层
        print("楼层:%s" % floor_number)
    
      			# 抽取地址
        area_name = node.xpath('.//div[@class="zu-info"]/address/a/text()')
      			print("所在区域:%s" % area_name)
      			area = node.xpath('.//div[@class="zu-info"]/address/text()')
      			for ele in area:
      				if len(ele.strip()) > 0:
      					area = ele.strip();
      					break;
      			print("详细地址:%s" % area);
      			# 抽取联系人
        user = node.xpath('.//div[@class="zu-info"]/p[2]/span/text()')
      			print("联系人:%s" % user)
      			supplement = node.xpath('.//div[@class="zu-info"]/p[2]/em/text()')
    
      			print("补充:%s" % supplement)
    
      		links = root.xpath('//*[@class="multi-page"]/a/@href');
      		#利用集合过滤重复的链接
        s = set(links)
      		next_link = list(s);
      		print('抽取的链接:%s'%next_link)
      		return next_link
    
      if __name__ == '__main__':
      	sp = Spider();
      	sc = Scheduler(download=Download(), spider=sp);
      	sc.start_craw();
    
  • 运行结果:

//ow3d01r1a.bkt.clouddn.com//file/2017/9/0f77794a909242d4b8830c24d904aa3a-1.png

  • 可以看到,我们抽取到了多条请求链接,接下来我们再调整一下Scheduler的代码,让其将抽取的链接加入请求队列中

      from com.anjie.download import Download;
      from com.anjie.spider_exception import SpiderException;
    
      class Scheduler:
      	# 待抓取队列
        crawl_queue = [];
      	#已爬取url
        crawl_over_queue = [];
      	download = None;
      	spider = None;
    
      	def __init__(self, download=None, spider=None):
      		self.download = download;
      		self.spider = spider;
      		pass;
    
      	# 调用该函数开始让爬虫开始工作
        def start_craw(self):
      		if not self.spider:
      			raise SpiderException("spider obeject is None")
    
      		if not hasattr(self.spider, 'start_url'):
      			raise SpiderException("spider must have an start_url attribute")
    
      		if not hasattr(self.spider, 'pager_back'):
      			raise SpiderException("spider must have an pager_back method")
    
      		self.crawl_queue.extend(self.spider.start_url);
    
      		while self.crawl_queue:
      			url = self.crawl_queue.pop();
      			html = self.download.download(url=url);
      			if html:
      				self.crawl_over_queue.append(url);
      			temp_next_link = self.spider.pager_back(url, html);
      			for u in temp_next_link:
      				if not u in self.crawl_over_queue and u not  in self.crawl_queue:
      					self.crawl_queue.append(u);
    
  • ok,我们还增加了crawl_over_queue来存储已经抓取的url,在获得next_url后我们需要先过滤,看url是否已在已请求队列中或者是否已在待请求队列中,如果都不在我们才将其加入待请求队列。如此便可自动增加下一页请求的url了。

  • 本次我们学习了数据的解析抽取部分,但是数据取出来了,总要个地方存放。

  • 我们将在下篇继续学习数据的存储,欢迎关注。