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

  发布日期:   2017-09-13
  最新修改:   2020-04-08
  阅读次数:   24 次

一、前言:

  • 我们在之前学习了爬虫的页面下载以及演示了如何用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抽取出安居客的租房信息列表以及下一页的链接。 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浏览器中看不到空格,因为浏览器自动帮我们处理了。

  • 我们看下抽取结果:

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();
  • 运行结果如下: 99e93e259505474695804c307f6aa33d-1.png

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

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

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

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();
  • 运行结果:

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了。

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

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


   转载规则

《Python爬虫系列(六)数据处理篇字》GajAngels 采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。