scrapy框架剖析

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

整体架构大致如下

使用twisted异步网络库,我们创建一个简易版的scrapy,去掉item与pipeline这个环节,使其实现scrapy基本功能

代码如下:

#!/usr/bin/env python
#-*- conding:utf-8 -*-
from twisted.internet import reactor   #事件循环(终止条件,所有的socket都已经移除)
from twisted.web.client import getPage  #创建socket对象(如果下载完成,自动从事件循环中移除)
from twisted.internet import defer   #defer.Deferred 特殊的socket对象,不发送socket请求,只能手动从事件循环中移除
import queue


class Request(object):
    '''
    Request请求对象,
    '''
    def __init__(self,url,callback):
        self.url = url
        self.callback = callback

class HttpResponse(object):

    def __init__(self,content,request):
        self.content = content
        self.request = 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)

    def parse(self,response):
        print(35)
        print(response.content)
        yield Request('http://www.baidu.com',callback=self.parse)

Q = queue.Queue()  #生成一个队列,类似于调度器

class Engine(object):

    def __init__(self):
        self._close = None
        self.max = 5  #最大并发数
        self.crawling = []   #正在爬取的URL列表
    def get_req_response(self,content,request):
        self.crawling.remove(request)
        req = HttpResponse(content,request)
        result = request.callback(req)  #调用spidler的parse方法解析
        import types
        if isinstance(result,types.GeneratorType):  #如果parse有返回值,且是可迭代对象
            for i in result:
                Q.put(i)


    def _next_request(self):
        '''
        继续发起reqeust请求
        :return:
        '''
        if Q.qsize() == 0 and len(self.crawling) == 0:   #如果调度器为空并且正在爬取的URL对象列表为空
            self._close.callback(None)   #手动停止特殊的socket
            return
        if len(self.crawling) >= self.max:   #如果正在爬取的并发数已经饱和
            return
        while len(self.crawling) < self.max:   #未饱和状态
            try:
                req = Q.get(block=False)  #取值,没有不等待
                self.crawling.append(req)   #将URL加入正在爬取的URL列表中
                d = getPage(req.url.encode('utf-8'))
                d.addCallback(self.get_req_response,req)  #URL页面下载完成之后执行,此时的正在爬取列表为未饱和状态
                d.addCallback(lambda _:reactor.callLater(0, self._next_request))  #调用twisted内置方法继续调用
            except Exception as  e:
                print(e)
                return




    @defer.inlineCallbacks
    def crawl(self,spider):
        '''
        解析初始URL
        :param spider:
        :return:
        '''
        start_requests = iter(spider.start_requests())
        while True:
            try:
                request = next(start_requests)
                Q.put(request)   #将初始URL加入到调度器中
            except StopIteration as e:
                break
        reactor.callLater(0,self._next_request)   #去调度器取URL执行请求
        self._close = defer.Deferred()   #生成特殊的socket独享,hold住事件循环
        yield self._close

spider = ChoutiSpider()

_active = set()
engine = Engine()
d = engine.crawl(spider)
_active.add(d)

dd = defer.DeferredList(_active)
dd.addBoth(lambda a:reactor.stop())

reactor.run()

  运行代码我们发现,scrapy的requests请求与response解析我们都实现了,且做到了可以深度爬取的效果,当然,我们这只是一个简易版的框架,scrapy框架源码里面还封装了具体的中间件调用等等,但不难发现,根据scrapy的框架结构与我们精简的简易版框架对比,scrapy框架原理我们也一目了然,对比源码,我们在此基础上做封装,代码见链接:

点击下载

 

posted @ 2017-05-22 22:30  amchen  阅读(139)  评论(0)    收藏  举报