前面兩章介紹了scrapy的安裝和項目的新建,那么這一章就講講spider吧。
scrapy有個命令是runspider, 這個命令的作用就是將一個spider當做一個python文件去執行,而不用創建一個完整的項目。可以說是最簡單的一個爬蟲項目了,只有一個文件,這也體現出了spider對於scrapy的重要性,item和pipline可有可無,settings等也可以使用默認的,可是spider必須自己構造。而我們寫爬蟲的時候大部分時間和精力也是耗費在這里,所以spider的重要性就不言而喻了。
這次教程將結合官方文檔和源碼一起來講解,希望大家能夠喜歡。
首先看看官方文檔對於spider的介紹:

最上面一段就不解釋了,就是介紹spider的作用和功能。對於spider來說主要經歷如下幾件事:
1.根據url生成Request並指定回調方法處理Response。第一個Request是通過start_requests()產生的,該方法下面會講到。
2. 在回調方法中,解析頁面的Response,返回Item實例或者Request實例,或者這兩種實例的可迭代對象。
3.在回調方法中,通常使用Selectors(也可以使用BeautifulSoup,lxml等)來提取數據。
4.最后spider會return item給Pipline完成數據的清洗,持久化等操作。
scrapy為我們提供了幾款基礎的spider,我們需要繼承這些來實現自己的spider。我們接下來就根據資料及源碼來了解它們。
class scrapy.spiders.Spider 下面是它的源碼:
class Spider(object_ref): """Base class for scrapy spiders. All spiders must inherit from this class. """ name = None custom_settings = None 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 = [] @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) def start_requests(self): for url in self.start_urls: yield self.make_requests_from_url(url) def make_requests_from_url(self, url): return Request(url, dont_filter=True) 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__
該類的基類是object_ref,其定義如下圖所示:

從源碼中可以看到這個類的子類的實例都會記錄它本身的存活狀況,這個作用會在以后講解,目前用不到。
類scrapy.spiders.Spider 是最簡單的spider,所有的spider包括自己定義的和scrapy提供的都會繼承它。它只是提供最基本的特性,也是我最常用的spider類。
name:
這個屬性是字符串變量,是這個類的名稱,代碼會通過它來定位spider,所以它必須唯一,它是spider最重要的屬性。回頭看看源碼中__init__的定義,可以發現這個屬性是可以修改的,如果不喜歡或者有需要重命名spider的name,可以在啟動的時候傳參修改name屬性。
allowed_domains:
這個屬性是一個列表,里面記載了允許采集的網站的域名,該值如果沒定義或者為空時表示所有的域名都不進行過濾操作。如果url的域名不在這個變量中,那么這個url將不會被處理。不想使用域名過濾功能時可以在settings中注釋掉OffsiteMiddleware, 個人不建議這么做。
start_urls:
這個屬性是一個列表或者元組,其作用是存放起始urls,相當於這次任務的種子。使用默認模板創建spider時,該值是個元組,創建元組並且只有一個元素時需要在元素后面添加“,”來消除歧義,不然會報錯:“ValueError: Missing scheme in request url: h”。這邊經常有人出錯,為了避免這個錯誤可以根據上一章內容,將模板中start_urls的值設置為列表。
custom_settings:
這個屬性值是一個字典,存放settings鍵值對,用於覆蓋項目中的settings.py的值,可以做到在一個項目中的不同spider可以有不同的配置。不過這個值要慎用,有些settings的值覆蓋也沒有起作用,eg:“LOG_FILE”。如果想每個spider都有自己的log文件的話就不能這么做。因為日志操作在這個方法執行之前,那么無論怎么改都改不了之前的行為。不過這個問題scrapy研發團隊已經注意到了,相信不久的將來會進行處理的。
crawler:
這個值從源碼可以看出來自於方法from_crawler()。該值是一個Crawler 實例, 其作用后面的教程會講解,這邊就不細說了。
settings:
這個值也是來自於方法from_crawler()。是一個Settings 實例,這個后面也會細說,稍安勿躁哈。
logger:
顧名思義,記錄日志用的,也是后面講,耐心等候哈。
from_crawler:
這是一個類方法,scrapy創建spider的時候會調用。調用位置在crawler.py 的類Crawler中,源碼可以自己去看看,就不帶大家看了。這個方法的源碼在上面,我們可以看到,在實例化這個spider以后,這個實例才有的settings和crawler屬性,所以在__init__方法中是沒法訪問這倆屬性的。如果非要在__init__方法中使用相關屬性,那么只能重寫該方法,大家可以嘗試寫寫。
start_requests():
這個方法必須返回一個可迭代對象,切記!!!!上面就有源碼很簡單,就不細說了。如果想對屬性start_urls做一些操作(增刪改),並希望結果作為種子url去采集網站的時候,可以重寫這個方法來實現。有了這個方法,甚至都不用在代碼中定義start_urls。比如我們想要讀取持久化的url執行采集操作,那么就沒必要轉存進start_urls里面,可以直接請求這些urls。當種子urls需要post請求的話,也需要重寫該方法。
make_requests_from_url(url):
這個方法顧名思義,要是還不懂就看看上面的源碼。這里只說一點,因為這里的Request初始化沒有回調方法,就是默認采用parse方法作為回調。另外這里的dont_filter值為True,這個值的作用是該url不會被過濾,至於具體細節請聽下回分解。
parse(self, response):
這個方法對於有經驗的同學來說再熟悉不過了,我就簡單的說說。這個方法作為默認回調方法,Request沒有指定回調方法的時候會調用它,這個回調方法和別的回調方法一樣返回值只能是Request, 字典和item對象,或者它們的可迭代對象。
log(message[, level, component]):
這個方法是對logger的包裝,看看源碼就好,沒什么什么可說的。
closed(reason):
當這個spider結束時這個方法會被調用,參數是一個字符串,是結束的原因。這種用法以后會介紹,這里只需記住,想在spider結束時做一些操作時可以寫在這里。
scrapy基礎spider算是介紹完了,有興趣的小伙伴可以對照一個好的實例來看這個文章,或許幫助更大。如果有什么疑問,大家可以在評論區討論,共同進步。
