Spider和CrawlSpider的原始碼分析
一、Spider原始碼分析
在對CrawlSpider進行原始碼分析之前,先對Spider原始碼進行一個分析。
1.1、Spider介紹及主要函式講解
Spider類定義瞭如何爬取某個(或某些)網站。包括了爬取的動作(是否跟進連結)以及如何從網頁的內容中提取結構化資料(提取Item)。 Spider就是定義爬取的動作以及分析某個(或某些)網頁的地方。
Spider是最基本的類,所有爬蟲必須繼承這個類。
Spider類主要用到的函式及呼叫順序為:
1)init()
初始化爬蟲名字和start_urls列表。
重點:這裡爬蟲名稱是必須的,而且必須是唯一的。
def __init__(self, name=None, **kwargs): if name is not None: self.name = name elif not getattr(self, 'name', None): raise ValueError("%s must have a name" % type(self).__name__) self.__dict__.update(kwargs) if not hasattr(self, 'start_urls'): self.start_urls = []
2)start_requests()呼叫make_requests_from_url()
生成Requests物件交給Scrapy下載並返回Response。
重點:該方法只會呼叫一次。
def start_requests(self): cls = self.__class__ if method_is_overridden(cls, Spider, 'make_requests_from_url'): warnings.warn( "Spider.make_requests_from_url method is deprecated; it " "won't be called in future Scrapy releases. Please " "override Spider.start_requests method instead (see %s.%s)." % ( cls.__module__, cls.__name__ ), ) for url in self.start_urls: yield self.make_requests_from_url(url) else: for url in self.start_urls: yield Request(url, dont_filter=True) def make_requests_from_url(self, url): """ This method is deprecated. """ return Request(url, dont_filter=True)
3)parse()
解析response,並返回Item或Requests(需指定回撥函式)。Item傳給Item pipline持久化,Requests交由Scrapy下載,並由指定的回撥函式處理,一直進行迴圈,直到處理完所有的資料為止。
重點:這個類需要我們自己去實現。並且是預設的Request物件回撥函式,處理返回的response。
def parse(self, response):
raise NotImplementedError
1.2、Spider原始碼分析
因為Spider原始碼不是很多,我直接在它的原始碼加上註釋的方式進行講解,如下:
""" Base class for Scrapy spiders See documentation in docs/topics/spiders.rst """ import logging import warnings from scrapy import signals from scrapy.http import Request from scrapy.utils.trackref import object_ref from scrapy.utils.url import url_is_from_spider from scrapy.utils.deprecate import create_deprecated_class from scrapy.exceptions import ScrapyDeprecationWarning from scrapy.utils.deprecate import method_is_overridden #所有爬蟲的基類,使用者定義的爬蟲必須從這個類繼承 class Spider(object_ref): """Base class for scrapy spiders. All spiders must inherit from this class. """ #1、定義spider名字的字串。spider的名字定義了Scrapy如何定位(並初始化)spider,所以其必須是唯一的。 #2、name是spider最重要的屬性,而且是必須的。一般做法是以該網站的域名來命名spider。例如我們在爬取豆瓣讀書爬蟲時使用‘name = "douban_book_spider"’ name = None custom_settings = None #初始化爬蟲名字和start_urls列表。上面已經提到。 def __init__(self, name=None, **kwargs): #初始化爬蟲名字 if name is not None: self.name = name elif not getattr(self, 'name', None): raise ValueError("%s must have a name" % type(self).__name__) self.__dict__.update(kwargs) #初始化start_urls列表,當沒有指定的URL時,spider將從該列表中開始進行爬取。 因此,第一個被獲取到的頁面的URL將是該列表之一,後續的URL將會從獲取到的資料中提取。 if not hasattr(self, 'start_urls'): self.start_urls = [] @property def logger(self): logger = logging.getLogger(self.name) return logging.LoggerAdapter(logger, {'spider': self}) def log(self, message, level=logging.DEBUG, **kw): """Log the given message at the given log level This helper wraps a log call to the logger within the spider, but you can use it directly (e.g. Spider.logger.info('msg')) or use any other Python logger too. """ self.logger.log(level, message, **kw) @classmethod def from_crawler(cls, crawler, *args, **kwargs): spider = cls(*args, **kwargs) spider._set_crawler(crawler) return spider def set_crawler(self, crawler): warnings.warn("set_crawler is deprecated, instantiate and bound the " "spider to this crawler with from_crawler method " "instead.", category=ScrapyDeprecationWarning, stacklevel=2) assert not hasattr(self, 'crawler'), "Spider already bounded to a " \ "crawler" self._set_crawler(crawler) def _set_crawler(self, crawler): self.crawler = crawler self.settings = crawler.settings crawler.signals.connect(self.close, signals.spider_closed) #該方法將讀取start_urls列表內的地址,為每一個地址生成一個Request物件,並返回這些物件的迭代器。 #注意:該方法只會呼叫一次。 def start_requests(self): cls = self.__class__ if method_is_overridden(cls, Spider, 'make_requests_from_url'): warnings.warn( "Spider.make_requests_from_url method is deprecated; it " "won't be called in future Scrapy releases. Please " "override Spider.start_requests method instead (see %s.%s)." % ( cls.__module__, cls.__name__ ), ) for url in self.start_urls: yield self.make_requests_from_url(url) else: for url in self.start_urls: yield Request(url, dont_filter=True) #1、start_requests()中呼叫,實際生成Request的函式。 #2、Request物件預設的回撥函式為parse(),提交的方式為get。 def make_requests_from_url(self, url): """ This method is deprecated. """ return Request(url, dont_filter=True) #預設的Request物件回撥函式,處理返回的response。 #生成Item或者Request物件。這個類需要我們自己去實現。 def parse(self, response): raise NotImplementedError @classmethod def update_settings(cls, settings): settings.setdict(cls.custom_settings or {}, priority='spider') @classmethod def handles_request(cls, request): return url_is_from_spider(request.url, cls) @staticmethod def close(spider, reason): closed = getattr(spider, 'closed', None) if callable(closed): return closed(reason) def __str__(self): return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self)) __repr__ = __str__ BaseSpider = create_deprecated_class('BaseSpider', Spider) class ObsoleteClass(object): def __init__(self, message): self.message = message def __getattr__(self, name): raise AttributeError(self.message) spiders = ObsoleteClass( '"from scrapy.spider import spiders" no longer works - use ' '"from scrapy.spiderloader import SpiderLoader" and instantiate ' 'it with your project settings"' ) # Top-level imports from scrapy.spiders.crawl import CrawlSpider, Rule from scrapy.spiders.feed import XMLFeedSpider, CSVFeedSpider from scrapy.spiders.sitemap import SitemapSpider
二、CrawlSpider原始碼分析
講解完Spider原始碼分析之後,我再來對CrawlSpider的原始碼進行一個分析。
2.1、CrawlSpider介紹及主要函式講解
CrawlSpider是爬取一般網站常用的spider。它定義了一些規則(rule)來提供跟進link的方便的機制。也許這個spider並不是完全適合特定網站或專案,但它對很多情況都使用。
因此我們可以在它的基礎上,根據需求修改部分方法。當然我們也可以實現自己的spider。除了從Spider繼承過來的(必須提供的)屬性外,它還提供了一個新的屬性:
1)rules
一個包含一個(或多個)Rule物件的集合(list)。 每個Rule對爬取網站的動作定義了特定表現。如果多個Rule匹配了相同的連結,則根據他們在本屬性中被定義的順序,第一個會被使用。
使用方式案例如下:
rules = (
# 提取匹配 'category.php' (但不匹配 'subsection.php') 的連結並跟進連結(沒有callback意味著follow預設為True)
Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),
# 提取匹配 'item.php' 的連結並使用spider的parse_item方法進行分析
Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item', follow=True),
)
2)parse_start_url(response)
當start_url的請求返回時,該方法被呼叫。 該方法分析最初的返回值並必須返回一個Item物件或者一個Request物件或者一個可迭代的包含二者物件。
該spider方法需要使用者自己重寫。
def parse_start_url(self, response):
return []
3)parse(),一定不要重寫這個方法
通過上面的介紹,我們知道Spider中的parse()方法是需要我們重寫的,如下:
def parse(self, response):
raise NotImplementedError
但是,CrawlSpider中的parse()方法在原始碼中已經實現了一些功能,如下:
def parse(self, response):
return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)
所以我們在使用CrawlSpider時,一定一定不要去重寫parse()函式(重點)。可以使用Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item', follow=True)
中的callback去指定需要跳轉的parse。
例如我們在爬蟲課堂(二十五)|使用CrawlSpider、LinkExtractors、Rule進行全站爬取中講解簡書全站爬取的時候使用方法,如下:
class JianshuCrawl(CrawlSpider):
name = "jianshu_spider_crawl"
# 可選,加上會有一個爬取的範圍
allowed_domains = ["jianshu.com"]
start_urls = ['https://www.jianshu.com/']
# response中提取連結的匹配規則,得出符合條件的連結
pattern = '.*jianshu.com/u/*.'
pagelink = LinkExtractor(allow=pattern)
# 可以寫多個rule規則
rules = [
# 只要符合匹配規則,在rule中都會發送請求,同時呼叫回撥函式處理響應。
# rule就是批量處理請求。
Rule(pagelink, callback='parse_item', follow=True),
]
# 不能寫parse方法,因為原始碼中已經有了,會覆蓋導致程式不能跑
def parse_item(self, response):
for each in response.xpath("//div[@class='main-top']"):
......
callback='parse_item'
(這裡的parse_item是字串)指定了跳轉的函式def parse_item(self, response)
。
2.2、CrawlSpider原始碼分析
同樣的,因為CrawlSpider原始碼不是很多,我直接在它的原始碼加上註釋的方式進行講解,如下:
class CrawlSpider(Spider):
rules = ()
def __init__(self, *a, **kw):
super(CrawlSpider, self).__init__(*a, **kw)
self._compile_rules()
#1、首先呼叫parse()方法來處理start_urls中返回的response物件。
#2、parse()將這些response物件傳遞給了_parse_response()函式處理,並設定回撥函式為parse_start_url()。
#3、設定了跟進標誌位True,即follow=True。
#4、返回response。
def parse(self, response):
return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)
#處理start_url中返回的response,需要重寫。
def parse_start_url(self, response):
return []
def process_results(self, response, results):
return results
def _build_request(self, rule, link):
#構造Request物件,並將Rule規則中定義的回撥函式作為這個Request物件的回撥函式。這個‘_build_request’函式在下面呼叫。
r = Request(url=link.url, callback=self._response_downloaded)
r.meta.update(rule=rule, link_text=link.text)
return r
#從response中抽取符合任一使用者定義'規則'的連結,並構造成Resquest物件返回。
def _requests_to_follow(self, response):
if not isinstance(response, HtmlResponse):
return
seen = set()
#抽取所有連結,只要通過任意一個'規則',即表示合法。
for n, rule in enumerate(self._rules):
links = [lnk for lnk in rule.link_extractor.extract_links(response)
if lnk not in seen]
if links and rule.process_links:
links = rule.process_links(links)
#將連結加入seen集合,為每個連結生成Request物件,並設定回撥函式為_repsonse_downloaded()。
for link in links:
seen.add(link)
#構造Request物件,並將Rule規則中定義的回撥函式作為這個Request物件的回撥函式。這個‘_build_request’函式在上面定義。
r = self._build_request(n, link)
#對每個Request呼叫process_request()函式。該函式預設為indentify,即不做任何處理,直接返回該Request。
yield rule.process_request(r)
#處理通過rule提取出的連線,並返回item以及request。
def _response_downloaded(self, response):
rule = self._rules[response.meta['rule']]
return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow)
#解析response物件,使用callback解析處理他,並返回request或Item物件。
def _parse_response(self, response, callback, cb_kwargs, follow=True):
#1、首先判斷是否設定了回撥函式。(該回調函式可能是rule中的解析函式,也可能是 parse_start_url函式)
#2、如果設定了回撥函式(parse_start_url()),那麼首先用parse_start_url()處理response物件,
#3、然後再交給process_results處理。返回cb_res的一個列表。
if callback:
cb_res = callback(response, **cb_kwargs) or ()
cb_res = self.process_results(response, cb_res)
for requests_or_item in iterate_spider_output(cb_res):
yield requests_or_item
#如果需要跟進,那麼使用定義的Rule規則提取並返回這些Request物件。
if follow and self._follow_links:
#返回每個Request物件。
for request_or_item in self._requests_to_follow(response):
yield request_or_item
def _compile_rules(self):
def get_method(method):
if callable(method):
return method
elif isinstance(method, six.string_types):
return getattr(self, method, None)
self._rules = [copy.copy(r) for r in self.rules]
for rule in self._rules:
rule.callback = get_method(rule.callback)
rule.process_links = get_method(rule.process_links)
rule.process_request = get_method(rule.process_request)
@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
spider = super(CrawlSpider, cls).from_crawler(crawler, *args, **kwargs)
spider._follow_links = crawler.settings.getbool(
'CRAWLSPIDER_FOLLOW_LINKS', True)
return spider
def set_crawler(self, crawler):
super(CrawlSpider, self).set_crawler(crawler)
self._follow_links = crawler.settings.getbool('CRAWLSPIDER_FOLLOW_LINKS', True)
參考資料:scrapy官網(官方對這塊講的不多,但是其他我這邊沒有講的內容還有很多,真的需要好好看看官網),CSDN上的兩篇Scrapy原始碼分析的文章。
作者:小怪聊職場
連結:https://www.jianshu.com/p/d492adf17312
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。
相關推薦
Spider和CrawlSpider的原始碼分析
一、Spider原始碼分析 在對CrawlSpider進行原始碼分析之前,先對Spider原始碼進行一個分析。 1.1、Spider介紹及主要函式講解 Spider類定義瞭如何爬取某個(或某些)網站。包括了爬取的動作(是否跟進連結)以及如何從網頁的內容中提取結構化資料(
【原始碼】防抖和節流原始碼分析
前言 防抖、節流主要用於頻繁事件觸發,例如滑鼠移動、改變視窗大小等。lodash等函式庫具備相對應的api, _.debounce 、_.throttle。 核心技術:閉包。 區別: 防抖, 連續觸發, 第一次和最後一次觸發有效 節流, 一段時間內僅觸發一次(第一次) 本文以防抖函式為
python3 整數型別PyLongObject 和PyObject原始碼分析
python3 整數型別PyLongObject 和PyObject原始碼分析一 測試環境介紹和準備測試環境:作業系統:windows10Python版本:3.7.0 下載地址VS版本:vs2015社群版(免費) 下載地址win10SDK(安裝vs2015是可以選擇,如果沒有安裝則需要獨立安裝)http://
資料結構(三)Stack和Vector原始碼分析
一、基本概念: 1、棧是什麼? 是一個只能在某一端進行插入、刪除操作的線性表。 * 從棧頂插入一個元素稱之為入棧(push) * 從棧頂刪除一個元素稱之為出棧(pop) 2、圖解: 3、棧的實現: 鏈式儲存(連結串列) 順序儲存(陣列) 4
30.以太坊原始碼分析(30)eth-bloombits和filter原始碼分析
以太坊的布隆過濾器 以太坊的區塊頭中包含了一個叫做logsBloom的區域。 這個區域儲存了當前區塊中所有的收據的日誌的布隆過濾器,一共是2048個bit。也就是256個位元組。 而我們的一個交易的收據包含了很多的日誌記錄。 每個日誌記錄包含了 合約的地址, 多個Topic。 而在我
Comparable和Comparator原始碼分析與對比
Comparable使用 Comparable只是一個簡單的介面, public interface Comparable<T> { public int compareTo(T o); } 使用如下: public class Person imp
javaIO(4):Reader,InputStreamReader和FileReader原始碼分析
前言 前面把OutputStream,InputStream和Writer體系講了,同時也講了“裝飾者模式”在IO體系中的應用。Reader體系跟前面的很相似。本文就將最後一個Reader體系給講了。 正文 一,Reader原始碼 package
javaIO(1):OutputStream和FileOutputStream原始碼分析及“裝飾者模式”在IO中的應用
前言 一,IO體系 從現在起,我們將基於JDK1.8詳細介紹java.io包中的關於輸入輸出有關的類。瞭解過這個包的都知道,裡面的類繼承關係錯綜複雜,光是弄清楚這些類的關係就夠喝一壺的了。說實話,我也沒有什麼好的方法來一下子就能弄清這些類,但是如果你瞭解“裝
Java集合框架之Map---HashMap和LinkedHashMap原始碼分析
1、HashMap概述: HashMap是基於雜湊表的Map介面的非同步實現。此實現提供所有可選的對映操作,並允許使用null值和null鍵。此類不保證對映的順序,特別是它不保證該順序恆久不變。 2、HashMap的資料結構 資料結構中有陣列和連結串列來實現對資料的
interface_cast和asBinder原始碼分析
研究Android底層程式碼時,尤其是Binder跨程序通訊時,經常會發現interface_cast和asBinder,很容易被這兩個函式繞暈,下面通過分析原始碼來講解一下: interface_cast 下面根據下述ICameraClient例子進行分析
Window 和windowManager原始碼分析
所有需要顯示在螢幕上的內容都要通過windowManager windowManager 是一個介面 三個方法 add remove update 他的具體實現類是windowManagerImpl類(橋接模式) 具體的實現是交給了windowManag
以太坊原始碼分析(30)eth-bloombits和filter原始碼分析
## 以太坊的布隆過濾器以太坊的區塊頭中包含了一個叫做logsBloom的區域。 這個區域儲存了當前區塊中所有的收據的日誌的布隆過濾器,一共是2048個bit。也就是256個位元組。而我們的一個交易的收據包含了很多的日誌記錄。 每個日誌記錄包含了 合約的地址, 多個Topic。 而在我們的收據中也存在一個布隆
android中window和windowManager原始碼分析(android-api-23)
一、前言 在android中window無處不在,如activity、dialog、toast等。它是view所依附的載體,每個window都對應於一個View和一個ViewRootImpl。ViewRootImpl就是Window和view的連線紐帶。windowMana
Scrapy框架-Spider和CrawlSpider的區別
sta don .com num 鏈接 links pan src () 目錄 1.目標 2.方法1:通過Spider爬取 3. 通過CrawlSpider爬取
Android Settings和SettingsProvider原始碼分析與修改
StringACCESSIBILITY_DISPLAY_INVERSION_ENABLEDSetting that specifies whether display color inversion is enabled. StringACCESSIBILITY_ENABLEDIf accessibili
Mybatis原始碼分析(4)—— Mapper的建立和獲取
Mybatis我們一般都是和Spring一起使用的,它們是怎麼融合到一起的,又各自發揮了什麼作用? 就拿這個Mapper來說,我們定義了一個介面,聲明瞭一個方法,然後對應的xml寫了這個sql語句, 它怎麼就執行成功了?這傢伙是怎麼實現的,帶著這個好奇心,我一步步跟蹤,慢慢揭開了它的
NSQ原始碼分析(四)——inFlightPqueue和PriorityQueue優先順序佇列
在Channel結構體中用到了兩種優先順序佇列pqueue.PriorityQueue和inFlightPqueue。 deferredMessages map[MessageID]*pqueue.Item deferredPQ pqueue.Priorit
Android原始碼分析之為什麼在onCreate() 和 onResume() 獲取不到 View 的寬高
轉載自:https://www.jianshu.com/p/d7ab114ac1f7 先來看一段很熟悉的程式碼,可能在最開始接觸安卓的時候,大部分人都寫過的一段程式碼;即嘗試在 onCreate() 和 onResume() 方法中去獲取某個 View 的寬高資訊: 但是列印輸出後,我們會發
Android IntentService用法和原始碼分析
關於IntentService的介紹,我個人覺得還是先看官方描述比較好: IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) o
zigbee 之ZStack-2.5.1a原始碼分析(三)無線資料傳送和接收
前面說過SampleApp_Init和SampleApp_ProcessEvent是我們重點關注的函式,接下來分析無線傳送和接收相關的程式碼: 在SampleApp_ProcessEvent函式中: if ( events & SYS_EVENT_MSG ) { &nbs