zoukankan      html  css  js  c++  java
  • Scrapy源码研究前戏

    一、Twisted的使用

    在 Twisted 中,有一种特殊的对象用于实现事件循环。这个对象叫做 reactor。可以把反应器
    (reactor)想象为 Twisted 程序的中枢神经。除了分发事件循环之外,反应器还做很多重要的
    工作:定时任务、线程、建立网络连接、监听连接。为了让反应器可以正常工作,需要启动
    事件循环。
    1、一个简单的使用
    #########################
    # 1.利用getPage创建socket
    # 2.将socket添加到事件循环中
    # 3.开始事件循环(自动结束)
    #########################
    def response(content):
        print(content)
    
    # 该装饰器装饰的内容,只要yield是一个阻塞的对象都会转交给reactor接手
    @defer.inlineCallbacks
    def task():
        url = "http://www.baidu.com"
        d = getPage(url.encode('utf-8'))
        d.addCallback(response)
        yield d
        url = "http://www.baidu.com"
        d = getPage(url.encode('utf-8'))
        d.addCallback(response)
        yield d
    
    def done(*args,**kwargs):
        reactor.stop()
    
    li = []
    for i in range(10):
        d = task()
        li.append(d)
    # DeferredList也属于defer的对象,也会转交给reactor接手
    dd = defer.DeferredList(li)
    # 给它增加了一个回调函数
    dd.addBoth(done)
    
    reactor.run()

    二、自定义爬虫包

    from twisted.internet import reactor  # 事件循环(终止条件,所有的socket都已经移除)
    from twisted.web.client import getPage  # socket对象(如果下载完成,自动从时间循环中移除...)
    from twisted.internet import defer  # defer.Deferred 特殊的socket对象 (不会发请求,手动移除)
    
    
    # 自定义一个Request 类
    class Request(object):
        def __init__(self, url, callback):
            """
            初始化接受url和callback回调函数
            :param url: 请求的url
            :param callback: 获取内容后的callback
            """
            self.url = url
            self.callback = callback
    
    
    # 响应对象
    class HttpResponse(object):
        def __init__(self, content, request):
            """
            初始化相应内容
            :param content: 下载 下来的响应的content
            :param request: response对应的request
            """
            # 响应的内容
            self.content = content
            # 响应的请求
            self.request = request
            # response对应的request
            self.url = request.url
            # 将内容转换为文本
            self.text = str(content, encoding='utf-8')
    
    
    class ChoutiSpider(object):
        """
        初始化顶一个小蜘蛛
        """
        name = 'chouti'
    
        # 蜘蛛一开始的执行方法
        def start_requests(self):
            start_url = ['http://www.baidu.com', 'http://www.bing.com', ]
            for url in start_url:
                yield Request(url, self.parse)
    
        # 收到response后的解析函数
        def parse(self, response):
            print(response)  # response是下载的页面
            yield Request('http://www.cnblogs.com', callback=self.parse)
    
    
    
    import queue
    # 这里是调度器
    Q = queue.Queue()
    
    
    # 定义了一个引擎类
    class Engine(object):
        def __init__(self):
            # 引擎关闭
            self._close = None
            # 最大的请求数
            self.max = 5
            # 正在爬的请求
            self.crawlling = []
    
        # 拿着相应的回调函数
        def get_response_callback(self, content, request):
            """
    
            :param content: 响应的content
            :param request: 响应对应的request
            :return:
            """
            # 一旦执行回调函数,就可以从调度中拿走这个请求
            self.crawlling.remove(request)
            # 将内容封装成 一个 HttpResponse对象
            rep = HttpResponse(content, request)
            # 调用请求时的回调函数,将封装的HttpResponse传递进去
            result = request.callback(rep)
            import types
            # 查看回调函数是否继续返回迭代器对象
            if isinstance(result, types.GeneratorType):
                # 将回调函数 新的请求放到调度器
                for req in result:
                    Q.put(req)
    
        # 从调度器取请求,执行,下载,并控制最大并发数
        def _next_request(self):
            """
            去取request对象,并发送请求
            最大并发数限制
            :return:
            """
            print(self.crawlling, Q.qsize())
            # 如果调度器的长度为0,而且处于正在爬取的数目也为 0 ,那么就说明该关闭了
            if Q.qsize() == 0 and len(self.crawlling) == 0:
                # 直接调用 defer.Deferred().callback(None)就会关闭defer
                self._close.callback(None)
                return
    
            # 如果正在爬取的数目超过了最大的并发限制,直接返回
            if len(self.crawlling) >= self.max:
                return
            # 如果没有达到并发限制,就执行以下内容
            while len(self.crawlling) < self.max:
                try:
                    # 从 调度器 取一个请求 任务
                    req = Q.get(block=False)
                    # 把拿到的请求放到 正在爬取的列表中
                    self.crawlling.append(req)
                    # 获取相应的页面
                    d = getPage(req.url.encode('utf-8'))
                    # 页面下载完成,get_response_callback,调用用户spider中定义的parse方法,并且将新请求添加到调度器
                    d.addCallback(self.get_response_callback, req)
                    # 未达到最大并发数,可以再去调度器中获取Request
                    # 继续给d添加回调函数,这个回调函数可以是匿名函数
                    d.addCallback(lambda _: reactor.callLater(0, self._next_request))
                except Exception as e:
                    print(e)
                    return
    
        @defer.inlineCallbacks
        def crawl(self, spider):
            # 将start_requests包含的生成器,初始Request对象添加到调度器
            start_requests = iter(spider.start_requests())
            while True:
                try:
                    # 拿到每个request,放到调度器中
                    request = next(start_requests)
                    Q.put( request)
                except StopIteration as e:
                    break
    
            # 去调度器中取request,并发送请求
            # self._next_request()
    
            reactor.callLater(0, self._next_request)
            # 初始化self._close
            self._close = defer.Deferred()
            yield self._close
    
    # 初始化一个抽屉爬虫
    spider = ChoutiSpider()
    
    _active = set()
    # 实例化一个引擎对象
    engine = Engine()
    
    # 引擎对象 调用 crawl方法,运行指定的spider
    d = engine.crawl(spider)
    
    # 将crawl方法放到set中
    _active.add(d)
    # 实例化一个DeferredList,将_active 内容放进去,返回一个defer.Deferred()对象,若defer.Deferred()被关闭,dd就为空
    dd = defer.DeferredList(_active)
    
    # 一旦dd里面为空,就调用reactor.stop()方法
    dd.addBoth(lambda a: reactor.stop())
    
    # 让它run起来
    reactor.run()
  • 相关阅读:
    wamp配置虚拟主机
    php单例模式
    YII缓存操作
    YII的延迟加载
    之字形打印二叉树
    对称的二叉树
    二叉树的下一节点
    删除链表的重复节点
    链表中环的入口
    字符流中第一个不重复的字符
  • 原文地址:https://www.cnblogs.com/nulige/p/6897598.html
Copyright © 2011-2022 走看看