通过twisted来自己写scrapy框架来了解scrapy源码

from twisted.internet import reactor #事件循环 相当于selecet作用 监听是否有连接成功(终止条件,所有的socket对象都被移除。)
from twisted.web.client import getPage #socket对象(如果下载完成,自动从事件循环中移除)
from twisted.internet import defer    #defer.Deferred 特殊的socket对象(不会发请求,目的是不让事件循环结束,可以手动移除。)
from queue import Queue

class Request:#请求类
    def __init__(self, url, callback):
        self.url = url
        self.callback = callback


class HttpResponse:#响应类
    def __init__(self, content, request):
        self.content = content
        self.request = request
        
             
class Command:
    def run(self):#开始运行
        crawl_process = CrawlerProcess()#创建crawl_process对象
        spider_cls_path_list = ["chouti.ChoutiSpider", "baidu.BaiduSpider"]
        for spider_cls_path in spider_cls_path_list:
            crawl_process.crawl(spider_cls_path)#创建爬虫 开启引擎
        crawl_process.start()#开启事件循环


class CrawlerProcess:#用于开启事件循环
    def __init__(self):
        self._active = set()

    def crawl(self, spider_cls_path):
        crawler = Crawler()
        d = crawler.crawl(spider_cls_path)#通过spider_cls_path建立socket对象
        self._active.add(d)

    def start(self):#开启事件循环
        dd = defer.DeferredList(self._active)#添加到集合中方便defer.DeferredList(用来监听的事件循环列表)监听
        dd.addBoth(lambda _: reactor.stop())#如果事件循环中的socket对象都被移除则循环停止
        reactor.run()#开启事件循环


class Crawler:#用于封装引擎和调度器
    @defer.inlineCallbacks#异步处理 此装饰器下的函数必须返回yield 不想返回东西可以返回None
    def crawl(self, spider_cls_path):
        engine = self.create_engine()#创建引擎
        spider = self.create_spider(spider_cls_path)#创建爬虫
        start_requests = iter(spider.start_requests())#通过爬虫的start_requests()方法获取请求,
        # 因为start_requests()返回的是yield,所以要从生成器转成迭代器然后用next获取请求
        yield engine.open_spider(start_requests)#引擎开始
        yield engine.start()#引擎开启

    def create_engine(self):
        return ExecutionEngine()#返回ExecutionEngine对象 -- 引擎

    def create_spider(self, spider_cls_path):#通过路径创建爬虫的对象
        module_path, cls_name = spider_cls_path.rsplit(".", maxsplit=1)
        import importlib
        m = importlib.import_module(module_path)
        cls = getattr(m, cls_name)#获取爬虫的类名
        return cls()#返回爬虫的对象


class ExecutionEngine:#引擎:所有的调度
    def __init__(self):#引擎初始化
        self._close = None#特殊的socket类 不发请求
        self.scheduler = None
        self.max = 5#正在运行的请求的限制数量
        self.crawlling = []#正在运行的请求

    def get_response_callback(self, content, request):#获得响应
        self.crawlling.remove(request)#先把获得响应的请求从正在运行的请求列表中删除
        response = HttpResponse(content, request)#获得响应的对象 里面封装了响应内容和之前发送的请求
        result = request.callback(response)#其实就是调用Request的parse 来判断是否还有其他请求
        import types
        if isinstance(result, types.GeneratorType):#如果result是迭代器 说明又返回了其他请求
            for req in result:
                self.scheduler.enqueue_request(req)#把新的请求加入到调度队列中

    def _next_request(self):
        if self.scheduler.size() == 0 and len(self.crawlling) == 0:#事件循环的终止条件既调度队列为空且正在运行的请求列表也为空
            self._close.callback(None)#从事件循环中移除,关闭事件循环,进而关闭爬虫
            return
        while len(self.crawlling) < self.max:#当正在运行的请求小于5时
            req = self.scheduler.next_request()#获取新的请求
            if not req:#如果请求不为None
                return
            self.crawlling.append(req)#把不为None的请求放入到正在运行的请求列表中
            d = getPage(req.url.encode("utf-8"))
            d.addCallback(self.get_response_callback, req)#用get_response_callback获得响应的内容
            d.addCallback(lambda _:reactor.callLater(0, self._next_request))

    @defer.inlineCallbacks
    def open_spider(self, start_requests):#开启爬虫
        self.scheduler = Scheduler()#创建调度器
        yield self.scheduler.open()#self.scheduler.open()为返回值为None 这里一定要yield 因为@defer.inlineCallbacks规定的
        while True:
            try:
                req = next(start_requests)#通过迭代器获取请求
            except StopIteration:#如果迭代器为空 跳出循环
                break
            self.scheduler.enqueue_request(req)#不断地把获得的请求加入调度队列中,直到迭代器取空
        reactor.callLater(0, self._next_request)#调用回调函数 self._next_request来把调度队列中的请求发送出去

    @defer.inlineCallbacks
    def start(self):
        self._close = defer.Deferred()#创建特殊的socket对象,此对象不发请求,加入到事件循环中,
                                        # 等socket请求都得到响应,再从事件循环中删除
        yield self._close


class Scheduler:#任务调度器
    def __init__(self):
        self.q = Queue()#建立调度的队列

    def enqueue_request(self, req):#从队列中放入请求
        self.q.put(req)

    def next_request(self):#从队列中取请求
        try:
            req = self.q.get(block=False)#非阻塞
        except Exception as e:#如果队列为空则会发生异常 抓住这个异常
            req = None
        return req#返回请求 可能为None

    def size(self):#队列里的数量
        return self.q.qsize()

    def open(self):
        pass


if __name__ == '__main__':
    cmd = Command()
    cmd.run()




ChoutiSpider

from engine import Request

class ChoutiSpider:
    def start_requests(self):
        start_urls = ["https://www.chouti.com","https://www.taobao.com"]
        for url in start_urls:
            yield Request(url=url, callback=self.parse)

    def parse(self, response):
        print(response.request.url)
        # yield Request("https://www.zhihu.com",callback=self.parse)

BaiduSpider

from engine import Request

class BaiduSpider:
    def start_requests(self):
        start_urls = ["https://www.baidu.com","https://www.bilibili.com"]
        for url in start_urls:
            yield Request(url=url, callback=self.parse)

    def parse(self, response):
        print(response.request.url)

结果:

获得网页代码太大,不方便放在csdn上,所以我就打印下获得的响应里封装的url,parse里可以继续发请求。

D:\untitled\spider\venv\Scripts\python.exe D:/spider/engine.py
https://www.baidu.com
https://www.bilibili.com
https://www.taobao.com
https://www.chouti.com

Process finished with exit code 0

猜你喜欢

转载自blog.csdn.net/u014248032/article/details/83152119
今日推荐