通過twisted來自己寫scrapy框架來了解scrapy原始碼
阿新 • • 發佈:2018-11-05
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