之前分享了很多 requests
、selenium
的 Python 爬蟲文章,本文將從原理到實(shí)戰(zhàn) 帶領(lǐng)大家入門另一個(gè)強(qiáng)大的框架 Scrapy
。如果對Scrapy
感興趣的話,不妨跟隨本文動手做一遍!
一、Scrapy框架簡介 Scrapy
是:由Python
語言開發(fā)的一個(gè)快速、高層次的屏幕抓取和web抓取框架,用于抓取web站點(diǎn)并從頁面中提取結(jié)構(gòu)化的數(shù)據(jù),只需要實(shí)現(xiàn)少量的代碼,就能夠快速的抓取。
二、運(yùn)行原理 Scrapy框架的運(yùn)行原理看下面一張圖就夠了(事實(shí)上原理是比較復(fù)雜的,也不是三言兩語能夠說清楚的,因此感興趣的讀者可以進(jìn)一步閱讀更多的相關(guān)文章來了解,本文不做過多講解 )
Scrapy主要包括了以下組件:
下載器中間件(Downloader Middlewares) 爬蟲中間件(Spider Middlewares) 調(diào)度中間件(Scheduler Middewares) 三. 入門 3.1安裝 第一種:在命令行模式下使用pip命令即可安裝:
$ pip install scrapy
第二種:首先下載,然后再安裝:
$ pip download scrapy -d ./# 通過指定國內(nèi)鏡像源下載 $pip download -i https://pypi.tuna./simple scrapy -d ./
進(jìn)入下載目錄后執(zhí)行下面命令安裝:
$ pip install Scrapy-1.5.0-py2.py3-none-any.whl
3.2使用 使用大概分為下面四步
1 創(chuàng)建一個(gè)scrapy項(xiàng)目
scrapy startproject mySpider
2 生成一個(gè)爬蟲
scrapy genspider demo 'demo.cn'
3 提取數(shù)據(jù)
完善spider 使用xpath等
4 保存數(shù)據(jù)
pipeline中保存數(shù)據(jù)
3.3 程序運(yùn)行 在命令中運(yùn)行爬蟲
scrapy crawl qb # qb爬蟲的名字
在pycharm中運(yùn)行爬蟲
from scrapy import cmdline cmdline.execute('scrapy crawl qb' .split())
四、基本步驟 Scrapy
爬蟲框架的具體使用步驟如下:
“ 定義要抓取的數(shù)據(jù)(通過Scrapy Items來完成的) 執(zhí)行spider,獲取數(shù)據(jù) ” 五. 目錄文件說明 當(dāng)我們創(chuàng)建了一個(gè)scrapy項(xiàng)目后,繼續(xù)創(chuàng)建了一個(gè)spider,目錄結(jié)構(gòu)是這樣的:
下面來簡單介紹一下各個(gè)主要文件的作用:
“ scrapy.cfg :項(xiàng)目的配置文件
mySpider/ :項(xiàng)目的Python模塊,將會從這里引用代碼
mySpider/items.py :項(xiàng)目的目標(biāo)文件
mySpider/pipelines.py :項(xiàng)目的管道文件
mySpider/settings.py :項(xiàng)目的設(shè)置文件
mySpider/spiders/ :存儲爬蟲代碼目錄
” 5.1 scrapy.cfg文件 項(xiàng)目配置文件。這個(gè)是文件的內(nèi)容:
# Automatically created by: scrapy startproject # # For more information about the [deploy] section see: # https://scrapyd./en/latest/deploy.html [settings] default = mySpider.settings [deploy]#url = http://localhost:6800/ project = mySpider
5.2 mySpider**/** 項(xiàng)目的Python模塊,將會從這里引用代碼
5.3 mySpider/items.py 項(xiàng)目的目標(biāo)文件
# Define here the models for your scraped items # # See documentation in: # https://docs./en/latest/topics/items.html import scrapyclass MyspiderItem (scrapy.Item) : # define the fields for your item here like: # name = scrapy.Field() pass
定義scrapy items的模塊,示例: name = scrapy.Field()
5.4 mySpider/pipelines.py 項(xiàng)目的管道文件
# Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: https://docs./en/latest/topics/item-pipeline.html # useful for handling different item types with a single interface from itemadapter import ItemAdapterclass MyspiderPipeline : def process_item (self, item, spider) : return item
這個(gè)文件也就是我們說的管道,當(dāng)Item在Spider中被收集之后,它將會被傳遞到Item Pipeline(管道),這些Item Pipeline組件按定義的順序處理Item。每個(gè)Item Pipeline都是實(shí)現(xiàn)了簡單方法的Python類,比如決定此Item是丟棄而存儲。以下是item pipeline的一些典型應(yīng)用:
驗(yàn)證爬取的數(shù)據(jù)(檢查item包含某些字段,比如說name字段) 將爬取結(jié)果保存到文件或者數(shù)據(jù)庫中 5.5 mySpider/settings.py 項(xiàng)目的設(shè)置文件
# Scrapy settings for mySpider project ... BOT_NAME = 'mySpider' # scrapy項(xiàng)目名 SPIDER_MODULES = ['mySpider.spiders' ] NEWSPIDER_MODULE = 'mySpider.spiders' .......# Obey robots.txt rules ROBOTSTXT_OBEY = False # 是否遵守協(xié)議,一般給位false,但是創(chuàng)建完項(xiàng)目是是True,我們把它改為False # Configure maximum concurrent requests performed by Scrapy (default: 16) #CONCURRENT_REQUESTS = 32 # 最大并發(fā)量 默認(rèn)16 ......#DOWNLOAD_DELAY = 3 # 下載延遲 3秒 # Override the default request headers: # 請求報(bào)頭,我們打開 DEFAULT_REQUEST_HEADERS = { 'Accept' : 'text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8' , 'Accept-Language' : 'en' , }# 爬蟲中間件 #SPIDER_MIDDLEWARES = { # 'mySpider.middlewares.MyspiderSpiderMiddleware': 543, #} # 下載中間件 #DOWNLOADER_MIDDLEWARES = { # 'mySpider.middlewares.MyspiderDownloaderMiddleware': 543, #} ......# Configure item pipelines # See https://docs./en/latest/topics/item-pipeline.html #ITEM_PIPELINES = { # 'mySpider.pipelines.MyspiderPipeline': 300, # 管道 #} .......
省略號省略代碼,一般重要點(diǎn),給了注釋
6.mySpider/spiders/ :存儲爬蟲代碼目錄 import scrapyclass DbSpider (scrapy.Spider) : name = 'db' allowed_domains = ['douban.com' ] # 可以修改 start_urls = ['http://douban.com/' ] # 開始的url也可以修改 def parse (self, response) : # pass
六. Scrapy shell Scrapy終端是一個(gè)交互終端,我們可以在未啟動spider的情況下嘗試及調(diào)試代碼,也可以用來測試XPath或CSS表達(dá)式,查看他們的工作方式,方便我們爬取的網(wǎng)頁中提取的數(shù)據(jù),但是一般使用的不多。感興趣的查看官方文檔:
官方文檔
http://scrapy-chs./zh_CN/latest/topics/shell.html
Scrapy Shell根據(jù)下載的頁面會自動創(chuàng)建一些方便使用的對象,例如 Response 對象,以及 Selector 對象 (對HTML及XML內(nèi)容)
。
當(dāng)shell載入后,將得到一個(gè)包含response數(shù)據(jù)的本地 response 變量,輸入 response.body
將輸出response的包體,輸出 response.headers
可以看到response的包頭。 輸入 response.selector
時(shí), 將獲取到一個(gè)response 初始化的類 Selector 的對象,此時(shí)可以通過使用 response.selector.xpath()
或response.selector.css()
來對 response 進(jìn)行查詢。 Scrapy也提供了一些快捷方式, 例如 response.xpath()
或response.css()
同樣可以生效(如之前的案例)。 Selectors選擇器 “ Scrapy Selectors 內(nèi)置 XPath 和 CSS Selector 表達(dá)式機(jī)制
” Selector有四個(gè)基本的方法,最常用的還是xpath:
xpath(): 傳入xpath表達(dá)式,返回該表達(dá)式所對應(yīng)的所有節(jié)點(diǎn)的selector list列表 extract(): 序列化該節(jié)點(diǎn)為字符串并返回list css(): 傳入CSS表達(dá)式,返回該表達(dá)式所對應(yīng)的所有節(jié)點(diǎn)的selector list列表,語法同 BeautifulSoup4 re(): 根據(jù)傳入的正則表達(dá)式對數(shù)據(jù)進(jìn)行提取,返回字符串list列表 七、案例實(shí)戰(zhàn) 本節(jié),我將使用Scrapy爬取站酷數(shù)據(jù)作為示例
7.1 案例說明 既然已經(jīng)初步了解了scrapy的工作流程以及原理,我們來做一個(gè)入門的小案例,爬取站酷首頁推薦的item信息。如下圖所示,一個(gè)小方框就是一個(gè)item信息。我們要提取每一個(gè)item的六個(gè)組成部分:
likes(推薦人數(shù)) 然后只是一個(gè)頁面的item,我們還要通過翻頁實(shí)現(xiàn)批量數(shù)據(jù)采集。
7.2 文件配置 目錄結(jié)構(gòu) 在上一篇中我們說明了新建scrapy項(xiàng)目(zcool)和 spider項(xiàng)目(zc) ,這里不再贅述,然后得到我們的目錄結(jié)構(gòu)如下圖所示:
start.py文件 然后為了方便運(yùn)行,在zcool目錄下新建start文件。并進(jìn)行初始化設(shè)置。
from scrapy import cmdline cmdline.execute('scrapy crawl zc' .split())
settings.py文件 在這個(gè)文件里我們需要做幾樣設(shè)置??
避免在程序運(yùn)行的時(shí)候打印log日志信息
LOG_LEVEL = 'WARNING'
ROBOTSTXT_OBEY = False
添加請求頭:
打開管道:
item.py文件 import scrapyclass ZcoolItem (scrapy.Item) : # define the fields for your item here like: imgLink = scrapy.Field() # 封面圖片鏈接 title = scrapy.Field() # 標(biāo)題 types = scrapy.Field() # 類型 vistor = scrapy.Field() # 人氣 comment = scrapy.Field() # 評論數(shù) likes = scrapy.Field() # 推薦人數(shù)
7.3 頁面數(shù)據(jù)提取 首先我們在站酷頁面使用xpath-helper測試一下:
然后zc.py文件里面初步測試一下:
def parse (self, response) : divList = response.xpath('//div[@class='work-list-box']/div' ) print(len(divList))
運(yùn)行結(jié)果如下圖所示:
沒有問題,然后我們對各種信息分別解析提取,
def parse (self, response) : divList = response.xpath('//div[@class='work-list-box']/div' ) for div in divList: imgLink = div.xpath('./div[1]/a/img/@src' ).extract()[0 ] # 1.封面圖片鏈接 ... 2. title(標(biāo)題);3 types(類型);4 vistor(人氣);5 comment(評論數(shù)) .... likes = div.xpath('./div[2]/p[3]/span[3]/@title' ).extract_first() # 6likes(推薦人數(shù)) item = ZcoolItem(imgLink=imgLink,title=title,types=types,vistor=vistor,comment=comment,likes=likes) yield item
解釋: xpath提取數(shù)據(jù)方法:
S.N. 方法 & 描述 extract() 返回的是符合要求的所有的數(shù)據(jù),存在一個(gè)列表里。 extract_first() 返回的hrefs 列表里的第一個(gè)數(shù)據(jù)。 get() 和extract_first()方法返回的是一樣的,都是列表里的第一個(gè)數(shù)據(jù)。 getall() 和extract()方法一樣,返回的都是符合要求的所有的數(shù)據(jù),存在一個(gè)列表里。
注意:
“ get() 、getall() 方法是新的方法,extract() 、extract_first()方法是舊的方法。extract() 、extract_first()方法取不到就返回None。get() 、getall() 方法取不到就raise一個(gè)錯(cuò)誤。
” item實(shí)例創(chuàng)建(yield上面一行代碼)
這里我們之前在目錄文件配置的item文件中已經(jīng)進(jìn)行了設(shè)置,對于數(shù)據(jù)存儲,我們在爬蟲文件中開頭要導(dǎo)入這個(gè)類:
from zcool.items import ZcoolItem
然后使用yield返回?cái)?shù)據(jù)。
為什么使用yield而不是return
不能使用return這個(gè)無容置疑,因?yàn)橐摚褂胷eturn直接退出函數(shù);而對于yield:在調(diào)用for的時(shí)候,函數(shù)內(nèi)部不會立即執(zhí)行,只是返回了一個(gè)生成器對象。在迭代的時(shí)候函數(shù)會開始執(zhí)行,當(dāng)在yield的時(shí)候,會返回當(dāng)前值(i)。之后的這個(gè)函數(shù)會在循環(huán)中進(jìn)行,直到?jīng)]有下一個(gè)值。
7.4 翻頁實(shí)現(xiàn)批量數(shù)據(jù)采集 通過上面的代碼已經(jīng)可以初步實(shí)現(xiàn)數(shù)據(jù)采集,只不過只有第一頁的,如下圖所示:
但是我們的目標(biāo)是100個(gè)頁面的批量數(shù)據(jù)采集,所以代碼還需要修改。針對翻頁這里介紹兩種方式:
方式一 :我們首先在頁面中定位到下一頁的按鈕,如下圖所示:
然后編寫如下代碼,在for循環(huán)完畢后。
next_href = response.xpath('//a[@class='laypage_next']/@href' ).extract_first()if next_href: next_url = response.urljoin(next_href) print('*' * 60 ) print(next_url) print('*' * 60 ) request = scrapy.Request(next_url) yield request
scrapy.Request(): 把下一頁的url傳遞給Request函數(shù),進(jìn)行翻頁循環(huán)數(shù)據(jù)采集。
https://www.cnblogs.com/heymonkey/p/11818495. html # scrapy.Request()參考鏈接
注意方式一只有下一頁按鈕它的href 對應(yīng)屬性值和下一頁的url一致才行。
方式二 :定義一個(gè)全局變量count = 0,每爬取一頁數(shù)據(jù),令其加一,構(gòu)建新的url,再使用scrapy.Request() 發(fā)起請求。
如下圖所示:
count = 1 class ZcSpider (scrapy.Spider) : name = 'zc' allowed_domains = ['zcool.com.cn' ] start_urls = ['https://www.zcool.com.cn/home?p=1#tab_anchor' ] # 第一頁的url def parse (self, response) : global count count = 1 for div in divList: # ...xxx... yield item next_url = 'https://www.kuaikanmanhua.com/tag/0?state=1&sort=1&page={}' .format(count) yield scrapy.Request(next_url)
這兩種方式在實(shí)際案例中擇機(jī)采用。
7.5 數(shù)據(jù)存儲 數(shù)據(jù)存儲是在pipline.py中進(jìn)行的,代碼如下:
from itemadapter import ItemAdapterimport csvclass ZcoolPipeline : def __init__ (self) : self.f = open('Zcool.csv' ,'w' ,encoding='utf-8' ,newline='' ) # line1 self.file_name = ['imgLink' , 'title' ,'types' ,'vistor' ,'comment' ,'likes' ] # line2 self.writer = csv.DictWriter(self.f, fieldnames=self.file_name) # line3 self.writer.writeheader() # line4 def process_item (self, item, spider) : self.writer.writerow(dict(item)) # line5 print(item) return item # line6 def close_spider (self,spider) : self.f.close()
解釋:
line1: 打開文件,指定方式為寫,利用第3個(gè)參數(shù)把csv寫數(shù)據(jù)時(shí)產(chǎn)生的空行消除
line2: 設(shè)置文件第一行的字段名,注意要跟spider傳過來的字典key名稱相同
line3: 指定文件的寫入方式為csv字典寫入,參數(shù)1為指定具體文件,參數(shù)2為指定字段名
line4: 寫入第一行字段名,因?yàn)橹灰獙懭胍淮?,所以文件放在__init__里面
line5: 寫入spider傳過來的具體數(shù)值,注意在spider文件中yield的item,是一個(gè)由類創(chuàng)建的實(shí)例對象,我們寫入數(shù)據(jù)時(shí),寫入的是 字典,所以這里還要轉(zhuǎn)化一下。
7.6 程序運(yùn)行 因?yàn)橹皠?chuàng)建了start.py文件,并且對它就行了初始化設(shè)置,現(xiàn)在運(yùn)行爬蟲程序不需要在控制臺中輸入命令:
scrapy crawl zc(爬蟲項(xiàng)目名)
直運(yùn)行start.py文件:得到如下結(jié)果:
對應(yīng)于頁面:
打開csv文件如下圖所示:(由于csv文件在word中亂碼了,此處我是用Notepad 打開)
沒有問題,數(shù)據(jù)采集完畢。
7.7. 總結(jié) 入門案例,需要細(xì)心,主要是基礎(chǔ)知識的鞏固,以便于為進(jìn)階學(xué)習(xí)做好準(zhǔn)備。
作者:飲馬長江