zoukankan      html  css  js  c++  java
  • Scrapy爬虫框架学习

    一、Scrapy框架简介

    1. 下载页面
    2. 解析
    3. 并发
    4. 深度

    二、安装

    linux下安装    
        pip3 install scrapy
        
    windows下安装
        a.pip3 install wheel
        b.下载twisted和pywin32 http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
        c.进入下载目录
             执行pip3 install Twisted-18.7.0-cp36-cp36m-win_amd64.whl     #cp36为适合python3.6
             执行pip3 install  pywin32-224-cp36-cp36m-win_amd64.whl
      d.pip3 install scrapy
        e.下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/  #找到适合本机python版本的64位

    三、Scrapy整体架构图

      3.1 Scrapy使用Twisted异步网络库来处理网络通讯

      3.2 各个主要文件说明

      spiders(蜘蛛)文件夹:如ip138.com

    name    #不能省略,最好不要修改
    starts_urls  #起始url
    allowed_domains    #爬取允许域名列表,起始url不受影响。网页中的外链受此限制

    四、使用

      4.1 基本使用

    1. 指定初始url
    2. 解析器响应内容
        - 给调度器
        - 给item:pipeline;用于做格式化;持久化
    
    基本步骤:
    a:scrapy startproject 项目名   #创建项目
    b:进入项目目录
    c:scrapy genspider baidu www.baidu.com  #创建start_url
    d:打开项目名spidersaidu.py进行编辑
    e:scrapy crawl baidu    #执行,加--nolog可以不显示日志,如果没有内容显示,可能此IP已经有防爬机制,可换个不知名ip试试
    #此处的baidu不是项目名,而是spiders文件夹下的baidu.py

    *f:scrapy shell http://www.baidu.com #下载url里的内容,进入调试模式,对于捕捉过滤字段非常方便

       

      4.2 scrapy.loader.ItemLoader的使用

        a.Itemloader介绍

    优点:避免重复代码,提高代码可读性
        (解决在爬虫文件中代码结构杂乱,无序,可读性差的缺点)

        b.Itemloader简单使用

    #ke.py爬虫文件
    from scrapy.loader import ItemLoader
    from .. import items     #导入items.py文件里的类
    
    class KeSpider(scrapy.Spider):
        name = 'ke'
        allowed_domains = ['ke.com']
        start_urls = ['https://sz.ke.com/xiaoqu/']
    
        def parse(self, response):
            # 通过变量item_loader加载item
            item_loader = ItemLoader(item=items.BeiKeItem(), response=response)
            item_loader.add_css()  #使用css选择器
            item_loader.add_xpath('name', '//div[@class="title"]/h1/text()')  # 将response用xpath选择器指定的过滤字段,得到的结果赋值给items.py文件里BeiKeItem类里的name变量
            item_loader.add_value('accessUrl', response.url)  # 将response.url的值赋值给items.py文件里BeiKeItem类里的accessUrl变量
    
            item_obj = item_loader.load_item()      #将item_loader里的值装入Item
            yield item_obj

      4.3 scrapy.loader.processors.MapCompose使用

        a.MapCompose()介绍

    MapCompose()
    #传入函数的列表的每一个元素都会经过第一个函数,
    #得到值在经过第二个函数,如果有返回值为None的,则抛弃,
    #最后返回一个列表

       

        b.简单使用

    #items.py文件
    class BeiKeItem(scrapy.Item):
        name = scrapy.Field(input_processor=MapCompose(addName)) #input_processor输入时进行预处理,MapCompose为加载函数处理,每一个元素后面都加上"-伍木"
        name = scrapy.Field(input_processor=MapCompose(lambda x:x+"-伍木")) #input_processor输入时进行预处理,MapCompose为加载lambda处理,每一个元素后面都加上"-伍木"
        name = scrapy.Field(input_processor=MapCompose(lambda x:x+"-伍木1",addName)) #MapCompose第一个参数传入lambda,第二个参数传入函数,每一个元素先经过第一个lambda,结果在一一经过第二个函数

      4.4 scrapy.loader.processors.TakeFirst使用

                  a.TakeFirst()介绍

    只取列表里对象元素的第一个值

        b.简单使用

    #items.py文件
    class BeiKeItem(scrapy.Item):
        name = scrapy.Field(out_processor = TakeFirst())    #tekeFirst()函数只取列表第一个元素,即便空列表也不报错

       c.让每一个Item对象输出结果都默认只取第一个元素

    笨办法(每个字段输出时都用一次TakeFirst()):
    #items.py
    from scrapy.loader.processors import TakeFirst
    class BeiKeItem(scrapy.Item):
        name = scrapy.Field(out_processor = TakeFirst())    #tekeFirst()函数只取列表第一个元素,即便空列表也不报错
        avgPrice = scrapy.Field(out_processor = TakeFirst())
        accessUrl = scrapy.Field(out_processor = TakeFirst())
        address = scrapy.Field(out_processor = TakeFirst())
    
    ###############################################################################################################
    
    聪明办法(改写ItemLoader),一次改写,无需重复
    #items.py
    from scrapy.loader.processors import TakeFirst
    from scrapy.loader import ItemLoader
    class BeiKeItemLoader(ItemLoader):
        #自定义itemloader
        default_output_processor = TakeFirst()
    
    #爬虫文件ke.py 
    from scrapy.loader import ItemLoader
    
    class KeSpider(scrapy.Spider):
        name = 'ke'
        allowed_domains = ['ke.com']
        start_urls = ['https://sz.ke.com/xiaoqu/']
    
        def parse(self, response):
             item_loader = BeiKeItemLoader(item=items.BeiKeItem(), response=response) #将ItemLoader换成重写的BeiKeItemLoader
    item_loader.add_xpath('name', '//div[@class="title"]/h1/text()')  
            item_loader.add_value('accessUrl', response.url)
            item_obj = item_loader.load_item()   
            yield item_obj

      

      4.5 scrapy.loader.processors.Join

        a. Join()介绍

    将元素那指定分隔符拼接,()括号内为指定分隔符,如","

        

        b.简单使用

    #items.py文件
    from scrapy.loader.processors import Join
    class BeiKeItem(scrapy.Item):
        tag = scrapy.Field(out_processor = Join(','))   #将输出的元素以“,”分割,返回字符串类型    

    五、筛选器

      5.1  Selector介绍

      在scrapy中,可以使用Selector筛选器,代替BeautifulSoup

      在scrapy项目里的spiders文件里的baidu.py爬虫文件编辑,导入Selecotr模块

    from scrapy.selector import Selector

      5.2 Selector(response=response).xpath()基本用法  或者使用response.xpath()

    //        #表示子子孙孙,即所有
    .//        #当前对象的子孙中
    /            #儿子
    /div        #儿子中的div标签
    //div[@id]  #所有标签含有id的div标签
    /div[@id='i1']    #儿子中的div且id ='i1'的标签
    obj.extract()        #列表中每一个对象转换成字符串 ==>返回的是列表
    obj.extract_first()  #列表中每一各对象转换成字符串==>返回的是列表中第一个元素
    //div/text()        #获取所有div标签里的文本==》返回的是列表,元素是对象
    //a/@href           #获取所有a标签里的url==》返回的是列表,元素是对象
    //a[starts-with(@href,"link")]    #获取所有a标签,并且href属性是以link开头的
    //a[re:test(@href,"/sitehome/p/d+")]/@href   #正则,获取所有a标签属性href符合/sitehome/p/数字的
    //div[@id='i1'][@href=''xx]   #[][]且的意思,即所有含有id='i1'且href='xxx'的div标签
    //a[contains(@href, "link")]    #所有href字段包含link字符串的a标签

      5.3 简单示例

      需求:在www.ip138.com网站里,打印如下a标签的文本内容和url地址

      在爬虫文件(baidu.py)里的parse类方法里编辑 

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.selector import Selector
    
    class BaiduSpider(scrapy.Spider):
        name = 'baidu'
        allowed_domains = ['ip138.com']
        start_urls = ['http://www.ip138.com/']
    
        def parse(self, response):
            #请求该页面下所有a标签==》列表,每个元素都是对象
            #找到div标签里有class=mod-guide属性下的所有子孙a标签里文本内容
            text_obj_list = Selector(response=response).xpath('//div[@class="module mod-guide"]//a/text()')
    
            # 找到div标签里有class=mod-guide属性下的所有子孙a标签里url
            url_obj_list = Selector(response=response).xpath('//div[@class="module mod-guide"]//a/@href')
    
            #将列表中对象转化成列表中字符串
            text_str_list = text_obj_list.extract()
            url_str_list = url_obj_list.extract()
    
            for a_text,a_url in zip(text_str_list,url_str_list):
                print(a_text,a_url)
    代码

      如何运行代码?

    在cmd窗口,进入项目目录,运行scrapy  crawl  baidu

      5.4 获取当前网页中的所有页码实例

      需求:获取博客园里的首页页码url

      

      在爬虫文件(ip138.py)里的parse类方法里编辑

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.selector import Selector
    
    class Ip138Spider(scrapy.Spider):
        name = 'ip138'
        allowed_domains = ['www.cnblogs.com']
        start_urls = ['https://www.cnblogs.com/']
    
        urls_set = set()
    
        def parse(self, response):
            # text_obj_list = Selector(response=response).xpath('//div[@class="module mod-guide"]//a/text()')
            # url_obj_list = Selector(response=response).xpath('//div[@class="module mod-guide"]//a/@href')
            #
            # text_str_list = text_obj_list.extract()
            # url_str_list = url_obj_list.extract()
            #
            # for text,url in zip(text_str_list,url_str_list):
            #     print(text,url)
    
            #获取当前页里的所有页码的url对象列表
            url_obj_list = Selector(response=response).xpath('//div[@class="pager"]/a/@href')
            ##或者2: starts-with(@属性,'值开头')
            #url_obj_list = Selector(response=response).xpath('//a[starts-with(@href,"/sitehome/p/")]/@href')
            ##或者3:正则表达式固定用法re:test(@属性值,"正则表达式")
            #url_obj_list = Selector(response=response).xpath('//a[re:test(@href,"/sitehome/p/d+")]/@href')
    
            #将对象列表转换成字符串列表
            url_str_list = url_obj_list.extract()
            for url in url_str_list:
                print(url)
    
                #1.通过集合去除重复url
                #2.使用加密的MD5存储url,好处:加密和等长
                url_md5 = self.my_md5(url)
                if url_md5 not in self.urls_set:
                    self.urls_set.add(url_md5)
                    print(url_md5)
                else:
                    print('%s已经存在'%url_md5)
    
        def my_md5(self,url):
            import hashlib
            obj = hashlib.md5()
            obj.update(bytes(url,encoding='utf-8'))
            return obj.hexdigest()
    代码

      5.5 获得当前网页里的页码自动请求爬取实例

      需求:自动爬取博客园的所有页码(基于5.4案例的基础)

      

      在爬虫文件(ip138.py)里的parse类方法里编辑

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.selector import Selector
    from scrapy.http import Request
    
    class Ip138Spider(scrapy.Spider):
        name = 'ip138'
        allowed_domains = ['www.cnblogs.com']
        start_urls = ['https://www.cnblogs.com/']
    
        urls_set = set()
    
        def parse(self, response):
    
            #获取当前页里的所有页码的url对象列表
            url_obj_list = Selector(response=response).xpath('//div[@class="pager"]/a/@href')
            ##或者2: starts-with(@属性,'值开头')
            #url_obj_list = Selector(response=response).xpath('//a[starts-with(@href,"/sitehome/p/")]/@href')
            ##或者3:正则表达式固定用法re:test(@属性值,"正则表达式")
            #url_obj_list = Selector(response=response).xpath('//a[re:test(@href,"/sitehome/p/d+")]/@href')
    
            #将对象列表转换成字符串列表
            url_str_list = url_obj_list.extract()
            for url in url_str_list:
    
                #1.通过集合去除重复url
                if url not in self.urls_set:
                    #print(url)
                    self.urls_set.add(url)
                    #拼接完整的url地址
                    full_url = self.start_urls[0] + url
                    #将url页码传给调度器去请求,并将下载的结果交给parse方法,yield的作用是将请求放入调度器
                    yield Request(url=full_url,callback=self.parse)
    
                for url in self.urls_set:
                    print(url)
    代码

      在setting.py中结尾新增一行DEPTH_LIMIT=1来指定递归的层次,默认是0,所有层次

       实例中关键点概括

    @href    #取属性值
    starts-with(@href,"xx")   #属性href的值以xx开始
    re:test(@href,"/sitehome/p/d+")   #正则re:test固定搭配
    yield Request(url=full_url,callback=self.parse)   #交给调度器   或者使用 yield response.follow(url=full_url,callback=self.parse),效果一样

    六、item,pipeline使用

      6.1 需求:将博客园的文章标题和url保存到一个文件a.txt文件里。

         各文件代码如下:

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.selector import Selector
    from scrapy.http import Request
    from .. import items
    
    class Ip138Spider(scrapy.Spider):
        name = 'ip138'
        allowed_domains = ['www.cnblogs.com']
        start_urls = ['https://www.cnblogs.com/']
    
        urls_set = set()
    
        def parse(self, response):
    
            #获取当前页面里的标题和url==>返回字符串
            title_str_list = Selector(response=response).xpath('//a[@class="titlelnk"]/text()').extract()
            href_str_list = Selector(response=response).xpath('//a[@class="titlelnk"]/@href').extract()
    
            for title_str,href_str in zip(title_str_list,href_str_list):
                #print(title_str,'   ',href_str)
                
                #此处的参数title,和href来自于items.py文件里类Cnblogs里的属性
                item_obj = items.Cnblogs(title=title_str,href=href_str)
    
                #将item对象传递给pipelines
                yield item_obj
    
    
    
            #获取当前页里的所有页码的url对象列表
            page_obj_list = Selector(response=response).xpath('//div[@class="pager"]/a/@href')
            ##或者2: starts-with(@属性,'值开头')
            #page_obj_list = Selector(response=response).xpath('//a[starts-with(@href,"/sitehome/p/")]/@href')
            ##或者3:正则表达式固定用法re:test(@属性值,"正则表达式")
            #page_obj_list = Selector(response=response).xpath('//a[re:test(@href,"/sitehome/p/d+")]/@href')
    
            #将对象列表转换成字符串列表
            page_str_list = page_obj_list.extract()
            for url in page_str_list:
    
                #1.通过集合去除重复url
                if url not in self.urls_set:
                    self.urls_set.add(url)
                    print(url)
    
                    #拼接完整的url地址
                    full_url = self.start_urls[0] + url
                    #将url页码传给调度器去请求,并将下载的结果交给parse方法,yield的作用是将请求放入调度器
                    yield Request(url=full_url,callback=self.parse)
    ip138.py文件
    # -*- coding: utf-8 -*-
    
    # Define here the models for your scraped items
    #
    # See documentation in:
    # https://doc.scrapy.org/en/latest/topics/items.html
    
    import scrapy
    
    
    class Cnblogs(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        title = scrapy.Field()
        href = scrapy.Field()
    items.py文件
    # -*- coding: utf-8 -*-
    
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
    
    
    class Day0310Pipeline(object):
        def process_item(self, item, spider):
            content = "%s    %s
    "%(item['title'],item['href'])
            f = open('a.json','a')
            f.write(content)
            f.close()
            # return item
    pipelines.py文件
    ITEM_PIPELINES = {
       'day0310.pipelines.Day0310Pipeline': 300,
    }
    setting.py文件

      6.2 实例中关键点概

    #items.py文件中
    class Cnblogs(scrapy.Item):     
        title = scrapy.Field()
        href = scrapy.Field()
    
    #ip138.py文件中
    from .. import items
    item_obj = items.Cnblogs(title=title_str,href=href_str)   #此处的参数title,和href来自于items.py文件里类Cnblogs里的属性
    
    yield item_obj        #将item对象传递给pipelines
    
    #pipelines.py文件中,将数据保存在文本a.txt里
    class Day0310Pipeline(object):
        def process_item(self, item, spider):   #item为ip138.py文件里的对象化的数据,spider为来自哪只蜘蛛对象
            content = "%s    %s
    "%(item['title'],item['href'])
            f = open('a.txt','a')
            f.write(content)
            f.close()
    
    #【可选】pipelines.py文件中,将数据保存在csv文件里
    import csv
    class Day0310Pipeline(object):
        def process_item(self, item, spider):   #item为ip138.py文件里的对象化的数据,spider为来自哪只蜘蛛对象
            with open("a_gbk.csv", "a", encoding="gbk") as f:    #此处encoding指定为gbk,是因此csv在windows中文系统下打开是GBK编码
                                                                #可参考文章https://www.cnblogs.com/lisenlin/p/14241416.html里的“四、常见问题”4.2的解决方法一
                csv_writer = csv.writer(f)
                csv_writer.writerow(item.values())    #将字典里的每个值存放到csv的行里每个单元格
    
    
    #setting.py文件中注册下pipelines的类
    
    ITEM_PIPELINES = {
       'day0310.pipelines.Day0310Pipeline': 300,
    }
    数字越小越先进入管道
  • 相关阅读:
    Android应用性能优化
    打造高质量Android应用:Android开发必知的50个诀窍
    毕向东day23--java基础-网络总结
    《编写高质量代码:改善Java程序的151个建议》
    最新java数组的详解
    主线程中一定不能放耗时操作,必须要开子线程,比如下载文件,不然会不让你拿到输入流--报错显示android.os.NetworkOnMainThreadException
    《Head First设计模式(中文版)》
    码表由来:ascll码-Gbk2312-GBK-Unicode-UTF-8
    《Java程序性能优化:让你的Java程序更快、更稳定》
    LeetCode 147. 对链表进行插入排序
  • 原文地址:https://www.cnblogs.com/lisenlin/p/9321145.html
Copyright © 2011-2022 走看看