普通爬蟲有啥意思,我寫了個通用Scrapy爬蟲
大家好,我是Kuls。今天是來自讀者劍南的投稿。
除了錢,大家還比較喜歡什么?當然是全能、萬能和通用的人或事物啦,例如:全能、什么都會的員工、萬能鑰匙、通用爬蟲等等。今天我們學習Scrapy通用爬蟲,利用Scrapy通用爬蟲來獲取美食杰網(wǎng)站[1]。
Scrapy通用爬蟲
創(chuàng)建Scrapy項目
Scrapy爬蟲和Scrapy通用爬蟲都是通過以下執(zhí)行命令來創(chuàng)建Scrapy項目,沒什么不同,命令如下所示:
Scrapy startproject Scrapy項目名Spider爬蟲模板
在創(chuàng)建spider爬蟲前,我們先看看有什么可用的爬蟲模板,執(zhí)行命令如下所示:
scrapy genspider -l運行結(jié)果如下圖所示:

其中:
?basic是我們之前創(chuàng)建Spider的時候,默認使用的爬蟲模板,也就是普通的爬蟲模板;?crawl模板是最常用于抓取常規(guī)網(wǎng)站的爬蟲模板,通過指定一些爬取規(guī)則來實現(xiàn)頁面的提取,很多情況下這個模板的爬取就足夠通用;?csvfeed模板是Scrapy最簡單的爬蟲模板,主要用于解析 CSV 文件,它是以行為單位來進行迭代,每迭代一行調(diào)用一次 parse_row() 方法;?xmlfeed模板主要用于處理RSS訂閱信息,RSS是一種信息聚合技術(shù),可以讓信息的發(fā)布和共享更為高效和便捷。
接下來我們主要是講解最常用的爬蟲模板——crawl模板,其他模板我們會在往后的文章里講解,敬請期待?。?!
CrawlSpider
在使用crawl模板前,我們先要了解一下CrawlSpider。
CrawlSpider是Scrapy提供的一個通用Spider,繼承自Spider類,除了擁有Spider類的所有方法和屬性,它還提供了rules屬性和parse_start_url()方法。
其中:
?rules是包含一個或多個Rule對象的列表,我們可以指定一些爬取規(guī)則來實現(xiàn)頁面的提取;?parse_start_url()是一個可重寫的方法,當start_urls里對應(yīng)的Request得到的Response時,該方法被調(diào)用。
創(chuàng)建crawl模板爬蟲
crawl模板的通用爬蟲通過執(zhí)行以下命令來創(chuàng)建,以http://quotes.toscrape.com網(wǎng)站為例子,該網(wǎng)站是一個著名作家名言的網(wǎng)站,命令如下所示:
scrapy genspider -t 模板類型 <爬蟲名字> <允許爬取的域名>scrapy genspider -t crawl quotes quotes.toscrape.com
當然,我們可以把命令中的crawl改為xmlfeed或者csvfeed,這樣就會生成其他類型的爬蟲,成功創(chuàng)建后,在spiders文件夾中多了一個quotes.py文件,該文件正是我們創(chuàng)建的spider爬蟲,其內(nèi)容如下所示:
import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Ruleclass QuotesSpider(CrawlSpider):name = 'quotes'allowed_domains = ['quotes.toscrape.com']start_urls = ['http://quotes.toscrape.com/']rules = (Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),)def parse_item(self, response):item = {}#item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()#item['name'] = response.xpath('//div[@id="name"]').get()#item['description'] = response.xpath('//div[@id="description"]').get()return item
其中:
?class QuotesSpider()是自定義spider類,繼承自CrawlSpider?name是定義此爬蟲名稱的字符串,每個項目唯一的名字,用來區(qū)分不同的Spider,啟動爬蟲時使用scrapy crawl +該爬蟲名字;?allowed_domains是允許爬取的域名,防止爬蟲爬到其他網(wǎng)站;?start_urls是最開始爬取的url;?rules是爬取規(guī)則屬性,是一個包含多個Rule對象的列表,該Rule主要用來確定當前頁面中的哪些鏈接需要繼續(xù)爬取、哪些頁面的爬取結(jié)果需要哪個方法來解析等。?parse_item()方法是負責解析返回響應(yīng)、提取數(shù)據(jù)或進一步生成要處理的請求。
注意:不能修改這個方法的名字,且不能定義parse()方法?。。?/p>
在創(chuàng)建Crawl模板的Spider爬蟲時,Rule中只展示了最常用的參數(shù),其完整參數(shù)如下所示:
Rule(LinkExtractor(allow=r'Items/', deny=(), allowed_domains=(), deny_domains=(), restrict_xpaths=()), callback='parse_item', follow=True, cb_kwargs=None, process_links=None, process_request=None)Rule常見的參數(shù)如下:
?LinkExtractor是一個鏈接提取對象,它定義了如何從每個已爬取的頁面中提取鏈接并用于生成一個requests對象;?callback是一個可調(diào)用對象或字符,和之前定義requests的callback作用一樣,?指定鏈接提取器提取的每個鏈接交給哪個解析函數(shù)去處理;?follow是一個布爾值,它指定是否從使用此規(guī)則提取的每個響應(yīng)中跟蹤鏈接,當callback為None時,follow默認為True,否則為False;?cb_kwargs是字典,其包含了傳遞給回調(diào)用函數(shù)的參數(shù);?process_links指定處理函數(shù),從LinkExtractor中獲取到鏈接列表時,該函數(shù)將會被調(diào)用,主要用于過濾url;?process_request指定哪個函數(shù)將會被調(diào)用,該規(guī)則提取到每個request時都會調(diào)用該函數(shù),主要用于過濾request。
LinkExtractor常用的參數(shù)如下:
?allow:滿足括號中正則表達式的URL會被提取,如果為空,則全部匹配;?deny:滿足括號中正則表達式的URL不會被提取,優(yōu)先級高于allow;?allow_domains:會被提取的鏈接的domains;?deny_domains:不會被提取的鏈接的domains;?restrict_xpaths:使用xpath表達式來規(guī)則URL地址的范圍。
定義rules規(guī)則
定義rules規(guī)則,也就是確定被提取的URL鏈接及其范圍。
首先我們定義翻頁的rules規(guī)則,進入名人名言網(wǎng)站[2]并打開開發(fā)者工具,如下圖所示:

由圖可知,翻頁的URL存放在
Rule(LinkExtractor(allow=r'/page/\d+', restrict_xpaths='//li[@class="next"]'),follow=True),由于我們在翻頁的頁面中,沒有需要提取的數(shù)據(jù),所以這里沒有callback參數(shù),所以需要加上follow=True。

由圖可以知,
rules = (Rule(LinkExtractor(allow=r'/author/\w+',restrict_xpaths='/html/body/div[1]/div[2]/div[1]'), callback='parse_item'),)
由于在鏈接提取對象有我們需要提前的數(shù)據(jù),所以這里需要寫callback參數(shù),不需要寫follow參數(shù)。
定義字段
在提取數(shù)據(jù)之前,我們先在items.py文件中定義字段,具體代碼如下所示:
import scrapyclass Test2Item(scrapy.Item):# define the fields for your item here like:name = scrapy.Field()
作為演示,我們只定義一個字段提取作者名,感興趣的小伙伴可以定義多個字段提取不同的數(shù)據(jù)。
提取數(shù)據(jù)
定義了rules規(guī)則后,我們接下來嘗試在parse_item()方法中提取響應(yīng)的數(shù)據(jù),具體代碼如下所示:
from test2.items import Test2Itemdef parse_item(self, response): item = Test2Item() item['name']=response.xpath('//h3[@class="author-title"]/text()').extract_first() return item首先我們導(dǎo)入Test2Item,實例化Test2Item,作為演示,我們只提取作者名,感興趣的可以提取其他數(shù)據(jù)。
Item Loader模塊
提取響應(yīng)數(shù)據(jù),我們還可以使用Item Loader模塊,其模塊提供了一種便捷的機制來幫助我們方便的提取Item數(shù)據(jù),讓我們的數(shù)據(jù)提取變得更加規(guī)則化,其語法規(guī)則為:
變量名=ItemLoader(item={}, response=())變量名.add_選擇器('數(shù)據(jù)字段名', '選擇器規(guī)則')return 變量名.load_item()其中:
?item是對象;?response是網(wǎng)頁的響應(yīng)數(shù)據(jù);?add_選擇器:其可以為add_xpath、add_css、add_value()
上面的提取數(shù)據(jù)代碼可以修改為如下代碼,具體代碼如下所示:
from test2.items import Test2Itemfrom scrapy.loader import ItemLoaderdef parse_item(self, response): loader=ItemLoader(item=Test2Item(),response=response) loader.add_xpath('name','//h3[@class="author-title"]/text()') return loader.load_item()首先我們導(dǎo)入Test2Item和ItemLoader模塊,并實例化ItemLoader和Test2Item,最后通過return loader.load_item()將數(shù)據(jù)返回給引擎。
這種提取方法比較規(guī)則化,我們可以把一些參數(shù)和規(guī)則單獨提取出來做成配置文件或者存儲到數(shù)據(jù)庫,及可實現(xiàn)可配置化。
在settings.py文件中啟動引擎,并在pipelines.py文件中打印輸出,運行結(jié)果如下:

通用配置抽取
有人可能說,就這?就一個Rule規(guī)則就實現(xiàn)了通用?等等,別急!??!
在我們爬蟲代碼中,很多代碼都是重復(fù)的,例如變量、方法名幾乎都是一致的,那么我們可以把完全不同的地方抽離出來,做成可配置文件。
我們新建一個crawl通用爬蟲,執(zhí)行代碼如下所示:
scrapy genspider -t crawl currency quotes.toscrape.com在剛才創(chuàng)建的crawl通用爬蟲中,我們來思考一下哪些數(shù)據(jù)可以抽離出來做成可配置文件?沒錯,里面所有東西都可以做成配置文件。
配置文件quotes.json
首先我們創(chuàng)建一個名為configs的文件夾來存放我們的配置文件,然后創(chuàng)建名為quotes.json的文件來把剛才創(chuàng)建的crawl通用爬蟲里面的內(nèi)容都寫入在文件中,具體代碼如下所示:
{ "settings": { "USER_AGENT":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36" }, "spider":"currency", "allowed_domains": ["quotes.toscrape.com"], "start_urls": ["http://quotes.toscrape.com/"], "rules": "quotes_rule", "item": { "class": "Test2Item", "loader": "ItemLoader", "attrs": { "name": [ { "method": "xpath", "args": [ "/html/body/div[1]/div[2]/h3/text()" ] } ] } }}首先我們把settings.py文件中的User-Agent配置先寫入到文件中,再把爬蟲名、爬蟲爬取的網(wǎng)站域名、最先爬取的URL鏈接以及rules規(guī)則寫入到文件中,最后把提取數(shù)據(jù)的方法寫入到文件中,其中:
?item:保存抓取數(shù)據(jù)的容器;?class:是我們items.py文件中的類,用來定義數(shù)據(jù)字段;?loader:是填充容器的機制,也就是上面所講的規(guī)范提取數(shù)據(jù)的ItemLoader模塊;?attrs:表示提取數(shù)據(jù)內(nèi)容;?name:是items.py文件中,定義的字段,也就是我們要提取的作者名字;?method:數(shù)據(jù)提取的方法,我們這里選用了xpath提??;?args:表示提取數(shù)據(jù)的規(guī)則、表達式;
rules.py規(guī)則文件
有人可能問,rules規(guī)則這么簡單?當然,rules不會那么簡單,這里我們新建一個rules.py文件來存放Rule規(guī)則,具體代碼如下所示:
from scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import Rulerules = { 'quotes_rule':(Rule(LinkExtractor(allow=r'/author/\w+',restrict_xpaths='/html/body/div[1]/div[2]/div[1]'), callback='parse_item'), Rule(LinkExtractor(allow=r'/page/\d+', restrict_xpaths='//li[@class="next"]'),follow=True),)}這里我們把rules規(guī)則已字典的形式來保存,以便我們獲取rules里面的值。
我們創(chuàng)建了配置文件,當然要把配置的文件讀取出來了,所以我們新建了一個名為Read_configs.py的文件來讀取數(shù)據(jù),具體代碼如下所示:
from os.path import realpath,dirnameimport jsondef get_config(name): path = dirname(realpath(__file__)) + '/configs/' + name + '.json' with open(path, 'r', encoding='utf-8')as f: return json.loads(f.read())啟動爬蟲run.py
創(chuàng)建讀取文件后,接下來要創(chuàng)建一個啟動Spider爬蟲的文件,我們把它命名為run.py,具體代碼如下所示:
import sysfrom scrapy.utils.project import get_project_settingsfrom test2.Read_configs import get_configfrom scrapy.crawler import CrawlerProcessdef run(): name=sys.argv[1] custom_settings=get_config(name) spider=custom_settings.get('spider','currency') project_settings=get_project_settings() settings=dict(project_settings.copy()) settings.update(custom_settings.get('settings')) process=CrawlerProcess(settings) process.crawl(spider,**{'name':name}) process.start()if __name__=='__main__': run()首先我們導(dǎo)入一些模塊和庫,再獲取命令行的參數(shù)并賦值為name,通過剛才在Read_configs.py所創(chuàng)建的get_config()將配置文件quotes.json讀取保存下來,再通過get()方法把Spider爬蟲名獲取下來并存放在spider變量中,通過get_project_settings()方法來獲取Scrapy項目中的settings.py配置并調(diào)用dict()方法把配置變?yōu)樽值涞母袷奖4嬖趕ettings變量中,再調(diào)用update()方法更新custom_settings變量的數(shù)據(jù)內(nèi)容,最后實例化CrawlerProcess,并調(diào)用crawl()和start()方法啟動爬蟲。
spider爬蟲初始化及獲取配置
在啟動爬蟲前,首先我們要初始化爬蟲數(shù)據(jù)并通過parse_item()方法獲取屬性配置,具體代碼如下所示:
import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Rulefrom test2.Read_configs import get_configfrom test2.rules import rulesfrom test2 import nextfrom test2.items import Test2Itemfrom scrapy.loader import ItemLoaderclass CurrencySpider(CrawlSpider): name = 'currency' def __init__(self,name,*args,**kwargs): config=get_config(name) self.config=config self.allowed_domains=config.get('allowed_domains') self.start_urls=config.get('start_urls') self.rules=rules.get(config.get('rules')) super(CurrencySpider, self).__init__(*args,**kwargs) def parse_item(self, response): item=self.config.get('item') cls=eval(item.get('class'))() loader=eval(item.get('loader'))(cls,response=response) for key,value in item.get('attrs').items(): for extractor in value: if extractor.get('method')=='xpath': loader.add_xpath(key,*extractor.get('args')) return loader.load_item()首先我們重新定義__init__()方法,把allowed_domains、start_urls和rules等屬性賦予值,再通過編寫parse_item方法來動態(tài)獲取屬性配置從而提取數(shù)據(jù),首先使用get()方法來獲取item配置信息,在使用eval()方法來獲取返回get()中的值。最后通過for循環(huán)來獲取數(shù)據(jù)并返回給引擎。
這里我們的pipeline.py文件只是簡單地打印數(shù)據(jù),其內(nèi)容如下:
class Test2Pipeline: def process_item(self, item, spider): print(item)最后執(zhí)行以下命令來運行爬蟲:
run.py quotes運行結(jié)果如下所示:

控制翻頁數(shù)
那么問題來了,假如翻頁數(shù)有幾千頁呢,我們不可能每次都要從第一頁爬到最后一頁的吧,怎樣要提取指定頁面的數(shù)據(jù)呢
這時,我們的start_urls可以在quotes.json文件中改為:
"start_urls": { "type": "dynamic", "method": "next", "args": [ 1,2 ] },其中,type是start_urls類型,method是調(diào)用的方法,args是開始頁和結(jié)束頁的頁碼,大家可以根據(jù)需求來獲取想要的頁面。
注意把rules.py文件中以下代碼刪除,要不然不能實現(xiàn)爬取指定頁數(shù):
Rule(LinkExtractor(allow=r'/page/\d+', restrict_xpaths='//li[@class="next"]'),follow=True),)除了修改start_urls,我們還需要創(chuàng)建實現(xiàn)method調(diào)用的方法,這里我們上面我們定義的方法是next,所以我們新建一個next.py文件,其具體代碼為:
def next(start,end): for page in range(start,end+1): yield 'https://www.meishij.net/fenlei/xiafancai/p'+str(page)+'/'再在currency.py文件中加以下代碼來獲取start_urls的值:
from test2 import nextstart_urls=config.get('start_urls')self.start_urls=list(eval('next.'+start_urls.get('method'))(*start_urls.get('args',[])))這樣我們就實現(xiàn)了指定頁面的爬取。
這樣,一個scrapy通用爬蟲就做好了,對了,為了防止大家弄亂了文件位置,導(dǎo)致程序報錯,貼心的我們把項目目錄截圖了下來,如下圖所示:

那么貼心,趕緊轉(zhuǎn)發(fā)、點贊加收藏走一波。
當我們想用剛才創(chuàng)建的通用爬蟲時,只要修改quotes.json、next.py、rules.py中的部分代碼即可。
有人可能覺得,我靠,弄一個Scrapy通用爬蟲要寫那么多.py文件,我還是老老實實寫Scrapy普通的爬蟲算了。
接下來我們通過實戰(zhàn)演練,展示寫了一個Scrapy通用爬蟲對以后的網(wǎng)站爬取有多么地方便。
實戰(zhàn)演練
現(xiàn)在我們來實戰(zhàn)測試一下Scrapy通用爬蟲的方便性,測試的網(wǎng)站為美食杰的下飯菜[3]。
修改rules.py規(guī)則
我們先修改rules規(guī)則:
我們先進入美食杰網(wǎng)站并打開開發(fā)者模式,如下圖所示:

由圖可知,
from scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import Rulerules = { 'quotes_rule':(Rule(LinkExtractor(allow=r'https://www.meishij.net/zuofa/\w+\.html',restrict_xpaths='//div[@class="list_s2"]'), callback='parse_item'),)}修改quotes.json配置
點擊具體做法的URL鏈接并打開開發(fā)者模式,如下圖所示:

菜品名存放在
//h1[@class="recipe_title"]/text()
//h1[@class="recipe_title"]/text()那么我們quotes.json文件中的args改為如下代碼:
"attrs": { "name": [ { "method": "xpath", "args": [ "http://h1[@class=\"recipe_title\"]/text()" ] } ] }因為不同的網(wǎng)站,其域名也不一樣,所以我們要將域名修改為美食杰的域名,其代碼修改為如下代碼:
"allowed_domains": ["www.meishij.net"],修改next.py翻頁
首先經(jīng)過簡單的查找,美食杰的下飯菜前幾頁的URL鏈接為:
https://www.meishij.net/fenlei/xiafancai/p1/https://www.meishij.net/fenlei/xiafancai/p2/https://www.meishij.net/fenlei/xiafancai/p3/很明顯鏈接最后面的數(shù)字是翻頁的重要參數(shù),所以我們可以把next.py文件修改為:
def next(start,end): for page in range(start,end+1): yield 'https://www.meishij.net/fenlei/xiafancai/p'+str(page)+'/'好了,全部代碼已經(jīng)修改完畢了。
結(jié)果展示

從結(jié)果上看,我們只是簡單地修改了Scrapy項目中的一些代碼,就實現(xiàn)了對其他網(wǎng)站的數(shù)據(jù)爬蟲,你們懂的,趕緊把文章點贊收藏做一個Scrapy通用爬蟲來方便自己以后爬取一些簡單網(wǎng)站的數(shù)據(jù)。
好了,Scrapy通用爬蟲就講解到這里了,感謝觀看?。?!
引用鏈接
[1]?美食杰網(wǎng)站:?https://www.meishij.net/[2]?名人名言網(wǎng)站:?https://quotes.toscrape.com/[3]?美食杰的下飯菜:?https://www.meishij.net/fenlei/xiafancai/
E?N?D
各位伙伴們好,詹帥本帥假期搭建了一個個人博客和小程序,匯集各種干貨和資源,也方便大家閱讀,感興趣的小伙伴請移步小程序體驗一下哦?。g迎提建議)
推薦閱讀
牛逼!Python常用數(shù)據(jù)類型的基本操作(長文系列第①篇)
牛逼!Python的判斷、循環(huán)和各種表達式(長文系列第②篇)


