zoukankan      html  css  js  c++  java
  • 爬虫--scrapy框架

    twisted介绍

    Twisted是用Python实现的基于事件驱动的网络引擎框架,scrapy正是依赖于twisted,从而基于事件循环机制实现爬虫的并发。

    scrapy的项目结构目录

    project_name/
       scrapy.cfg
       project_name/
           __init__.py
           items.py
           pipelines.py
           settings.py
           spiders/
               __init__.py
               爬虫1.py
               爬虫2.py
               爬虫3.py

    文件说明:

    • scrapy.cfg  项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
    • items.py    设置数据存储模板,用于结构化数据,如:Django的Model
    • pipelines    数据处理行为,如:一般结构化的数据持久化
    • settings.py 配置文件,如:递归的层数、并发数,延迟下载等
    • spiders      爬虫目录,如:创建文件,编写爬虫规则

    scrapy的pipeline文件和items文件

    # -*- coding: utf-8 -*-
    import scrapy
      
      
    class ChoutiSpider(scrapy.Spider):
        '''
        爬去抽屉网的帖子信息
        '''
        name = 'chouti'
        allowed_domains = ['chouti.com']
        start_urls = ['http://chouti.com/']
      
        def parse(self, response):
            # 获取帖子列表的父级div
            content_div = response.xpath('//div[@id="content-list"]')
      
            # 获取帖子item的列表
            items_list = content_div.xpath('.//div[@class="item"]')
      
            # 打开一个文件句柄,目的是为了将获取的东西写入文件
            with open('articles.log','a+',encoding='utf-8') as f:
                # 循环item_list
                for item in items_list:
                    # 获取每个item的第一个a标签的文本和url链接
                    text = item.xpath('.//a/text()').extract_first()
                    href = item.xpath('.//a/@href').extract_first()
                    # print(href, text.strip())
                    # print('-'*100)
                    f.write(href+'
    ')
                    f.write(text.strip()+'
    ')
                    f.write('-'*100+'
    ')
      
            # 获取分页的页码,然后让程序循环爬去每个链接
            # 页码标签对象列表
            page_list = response.xpath('//div[@id="dig_lcpage"]')
            # 循环列表
            for page in page_list:
                # 获取每个标签下的a标签的url,即每页的链接
                page_a_url = page.xpath('.//a/@href').extract()
                # 将域名和url拼接起来
                page_url = 'https://dig.chouti.com' + page_a_url
      
                # 重要的一步!!!!
                # 导入Request模块,然后实例化一个Request对象,然后yield它
                # 就会自动执行Request对象的callback方法,爬去的是url参数中的链接
                from scrapy.http import Request
                yield Request(url=page_url,callback=self.parse)
    爬抽屉

      在这个示例中,我们通过chouti.py一个文件的parse方法实现了爬抽屉网的新闻并将之保存在文件中的功能,但是会发现有两个问题:

    1、在循环爬去每一页的时候,每次都需要重新打开然后再关闭文件

    2、我们将解析和数据持久化都放在了同一个文件的同一个方法中,没有做到分工明确

    解决这两个问题就需要用到pipeline文件和items文件

    用法:

    如果我们要使用这两个文件从而解决问题,则需要有四部操作:

    a.编写pipeline文件中的类,格式如下:

    class XXXPipeline(object):
        def process_item(self, item, spider):
            return item

    b.编写items文件中的类,格式如下:

    class XXXItem(scrapy.Item):
        href = scrapy.Field()
        title = scrapy.Field()

    c.配置settings文件

    ITEM_PIPELINES = {
       'xxx.pipelines.XXXPipeline': 300,
       # 'xxx.pipelines.XXXPipeline2': 600,  # 后面的数字为优先级,数字越大,优先级月底
    }

    d.在parse方法中yield一个Item对象

    from xxx.items import XXXItem
     
    def parse(self, response):
        ...
        yield XXXItem(text=text,href=href)

    执行流程为:

    当我们在执行爬虫的parse方法的时候,scrapy一旦解析到有yield XXXitem的语句,就会到配置文件中找ITEM_PIPELINES的配置项,进而找到XXXPipeline类,然后执行其中的方法,我们就可以在方法中做很多操作,当然pipeline中不止process_item一个方法。

    Pipeline中的方法详解

    class FilePipeline(object):
     
        def __init__(self,path):
            self.f = None
            self.path = path
     
        @classmethod
        def from_crawler(cls, crawler):
            """
            初始化时候,用于创建pipeline对象
            :param crawler:
            :return:
            """
                    # 从配置文件中获取配置好的文件存放目录
            path = crawler.settings.get('HREF_FILE_PATH')
            return cls(path)
     
        def open_spider(self,spider):
            """
            爬虫开始执行时,调用
            :param spider:
            :return:
            """
            self.f = open(self.path,'a+')
     
        def process_item(self, item, spider):
            # 在这里做持久化
            self.f.write(item['href']+'
    ')
            return item     # 交给下一个pipeline的process_item方法
            # raise DropItem()# 如果写上这一句,后续的 pipeline的process_item方法不再执行
     
        def close_spider(self,spider):
            """
            爬虫关闭时,被调用
            :param spider:
            :return:
            """
            self.f.close()

    去重(两种方法)

    方法一:scrapy内部实现的去重方法

    其实scrapy内部在循环爬页码的时候,已经帮我们做了去重功能,

    因为我们在首页可以看到1,2,3,4,5,6,7,8,9,10页的页码以及连接,当爬虫爬到第二页的时候,

    还是可以看到这10个页面及连接,然后它并没有再重新把第一页爬一遍。

    它内部实现去重的原理是,将已爬去的网址存入一个set集合里,每次爬取新页面的时候就先看一下是否在集合里面

    如果在,就不再爬去,如果不在就爬取,然后再添加入到set里。当然,这个集合存放的不是原网址,

    而是将链接通过request_fingerprint()方法将它变成一个类似于md5的值,这样可以节省存储空间

    自定义去重

    1.编写DupeFilter类

    from scrapy.dupefilter import BaseDupeFilter
    from scrapy.utils.request import request_fingerprint
     
    class XXXDupeFilter(BaseDupeFilter):
     
        def __init__(self):
            '''初始化一个集合,用来存放爬去过的网址'''
            self.visited_fd = set()
     
        @classmethod
        def from_settings(cls, settings):
            '''
            如果我们自定义了DupeFilter类并且重写了父类的该方法,
            scrapy会首先执行该方法,获取DupeFilter对象,
            如果没有定义,则会执行init方法来获取对象
            '''
            return cls()
     
        def request_seen(self, request):
            '''在此方法中做操作,判断以及添加网址到set里'''
            # 将request里的url转换下,然后判断是否在set里
            fd = request_fingerprint(request=request)
            # 循环set集合,如果已经在集合里,则返回True,爬虫将不会继续爬取该网址
            if fd in self.visited_fd:
                return True
            self.visited_fd.add(fd)
     
        def open(self):  # can return deferred
            '''开始前执行此方法'''
            print('开始')
     
        def close(self, reason):  # can return a deferred
            '''结束后执行此方法'''
            print('结束')
     
        def log(self, request, spider):  # log that a request has been filtered
            '''在此方法中可以做日志操作'''
            print('日志')

    2.配置settings文件

    # 修改默认的去重规则
    # DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
    DUPEFILTER_CLASS = 'xxx.dupefilters.XXXDupeFilter'

    深度

    深度就是爬虫所要爬取的层级,限制深度需要配置一下settings

    # 限制深度
    DEPTH_LIMIT = 3

    获取cookie

    获取上一次请求之后获得的cookie

    from scrapy.http.cookies import CookieJar
     
    class ChoutiSpider(scrapy.Spider):
        name = 'chouti'
        allowed_domains = ['chouti.com']
        start_urls = ['https://dig.chouti.com/']
        cookie_dict = {}
        def parse(self, response):
     
            # 去响应头中获取cookie,cookie保存在cookie_jar对象
            cookie_jar = CookieJar()
            cookie_jar.extract_cookies(response, response.request)
     
            # 去对象中将cookie解析到字典
            for k, v in cookie_jar._cookies.items():
                for i, j in v.items():
                    for m, n in j.items():
                        self.cookie_dict[m] = n.value

    再次请求的时候携带cookie

    yield Request(
               url='https://dig.chouti.com/login',
               method='POST',
               body="phone=861300000000&password=12345678&oneMonth=1",#
               cookies=self.cookie_dict,
               headers={
                   'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
               },
               callback=self.check_login
           )

    中间件

    class SpiderMiddleware(object):
    
        def process_spider_input(self,response, spider):
            """
            下载完成,执行,然后交给parse处理
            :param response: 
            :param spider: 
            :return: 
            """
            pass
    
        def process_spider_output(self,response, result, spider):
            """
            spider处理完成,返回时调用
            :param response:
            :param result:
            :param spider:
            :return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)
            """
            return result
    
        def process_spider_exception(self,response, exception, spider):
            """
            异常调用
            :param response:
            :param exception:
            :param spider:
            :return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline
            """
            return None
    
    
        def process_start_requests(self,start_requests, spider):
            """
            爬虫启动时调用
            :param start_requests:
            :param spider:
            :return: 包含 Request 对象的可迭代对象
            """
            return start_requests
    爬虫中间件
    class DownMiddleware1(object):
        def process_request(self, request, spider):
            """
            请求需要被下载时,经过所有下载器中间件的process_request调用
            :param request: 
            :param spider: 
            :return:  
                None,继续后续中间件去下载;
                Response对象,停止process_request的执行,开始执行process_response
                Request对象,停止中间件的执行,将Request重新调度器
                raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
            """
            pass
    
    
    
        def process_response(self, request, response, spider):
            """
            spider处理完成,返回时调用
            :param response:
            :param result:
            :param spider:
            :return: 
                Response 对象:转交给其他中间件process_response
                Request 对象:停止中间件,request会被重新调度下载
                raise IgnoreRequest 异常:调用Request.errback
            """
            print('response1')
            return response
    
        def process_exception(self, request, exception, spider):
            """
            当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
            :param response:
            :param exception:
            :param spider:
            :return: 
                None:继续交给后续中间件处理异常;
                Response对象:停止后续process_exception方法
                Request对象:停止中间件,request将会被重新调用下载
            """
            return None
    下载中间件

    自定义启动项目文件

    多爬虫项目:

    在spiders同级创建任意目录, 例如:目录名commands

    from scrapy.commands import ScrapyCommand
    
    
    class Command(ScrapyCommand):
    
        requires_project = True
    
        def syntax(self):
            return '[options]'
    
        def short_desc(self):
            return 'Runs all of the spiders'
    
        def run(self, args, opts):
            spider_list = self.crawler_process.spiders.list()
            for name in spider_list:
                self.crawler_process.crawl(name, **opts.__dict__)
            self.crawler_process.start()
    crawlall.py

    在其中创建crawlall.py文件(文件名就是自定义的命令名)

    在settings.py中添加配置COMMANDS_MODULE = '项目名称.目录名称'

    在项目目录(和.cfg同一级)创建start文件

    import sys
    from scrapy.cmdline import execute
    
    if __name__ == '__main__':
        execute(["scrapy","crawlall","--nolog"])
    start.py

    启动时,可直接执行start.py文件即可

    单爬虫项目:

    import sys
    from scrapy.cmdline import execute
    
    if __name__ == '__main__':
        execute(["scrapy","github","--nolog"])
    单爬虫

    信号:类

    from scrapy import signals
    
    
    class MyExtend(object):
        def __init__(self):
            pass
    
        @classmethod
        def from_crawler(cls, crawler):
            self = cls()
            crawler.signals.connect(self.x1, signal=signals.spider_opened)
            crawler.signals.connect(self.x2, signal=signals.spider_closed)
    
            return self
    
        def x1(self, spider):
            print('open')
    
        def x2(self, spider):
            print('close')
    ext.py

    在settings里配置

    EXTENSIONS = {'xdb.ext.MyExtend': 666}
  • 相关阅读:
    php 链接不上 mysql数据库,不是扩展的问题,也不是数据库的问题
    php中magic_quotes_gpc的作用
    QuickSkin简单学习控制结构
    Xdebug调试PHP程序 (NetBeans)
    记录bind方法。。。和ajax二级联动
    自己写的php分页代码,喜欢的就看看,很实用的
    想去掉启动时的win7选择怎么办
    QuickSkin简单学习属性和方法
    【CDQ分治】P3810 【模板】三维偏序(陌上花开)
    LibraryBar、LibraryContainer的宽高设置
  • 原文地址:https://www.cnblogs.com/tsboy/p/9252784.html
Copyright © 2011-2022 走看看