DC-01:爬蟲框架scrapy入門
本主題主要是scrapy入門,包含內容如下:
??1. Scrapy框架環境搭建;
??2. 理解scrapy框架結構;
??3. 理解并能處理簡單的數據流;
??
如果想關注爬蟲的高級技術與應用場景,請關注后繼內容與馬哥教育。這個系列包括數據采集,數據分析與數據可視化。
一、Scrapy安裝
??Scrapy的安裝是比較簡單的,直接使用pip可以完成最新版本的安裝。
??
??目前最新版本是:1.6。
1.官網地址
??Scrapy的官方地址是:https://scrapy.org !
2. 安裝
??安裝指令
pip install scrapy
??安裝過程
3. 測試安裝
??只需要啟動python交互式編程終端,看看能否import模塊scrapy即可;
4. scrapy幫助
??在交互式編程終端,使用import引入scrapy模塊,并使用help(scrapy)獲取scrapy框架API整體模塊結構(其他更加詳細的幫助可以使用help與dir獲?。?div id="rvzptob" class="image-package">
scrapy幫助使用截圖
5. 教程與參考資料
??最好的教程我個人認為還是官方的教程+API幫助。
二、Scrapy組件結構與工作流程
1. 核心組件介紹
1.1. 組件01:引擎(Scrapy Engine)
??Scrapy Engine引擎作用有兩個:
????1. 控制Scrapy框架中所有組件之間的數據流;
????2. 并在某些爬取動作發生的時候觸發數據處理事件。
1.2. 組件02:調度器(Scheduler)
??調度器(Scheduler)接收來自引擎的請求,并將它們排隊,以便在引擎請求時向引擎提供這些請求。
1.3. 組件03:下載器(Downloader)
??下載者(Downloader)負責下載網頁并將其發送給引擎,引擎把下載的網頁發送給蜘蛛/爬蟲處理。
1.4. 組件04:蜘蛛/爬蟲(Spiders)
??蜘蛛/爬蟲(Spider)負責解析下載器下載的網頁,并從中提取數據項(ITEM)(也稱為爬取項)或后續的爬取請求。
??蜘蛛/爬蟲(Spider)一般由用戶實現,用來實現用戶的爬取邏輯。一般繼承Spider類來定制實現。
1.5. 組件05:數據項管道(Item Pipeline)
??數據項管道(Item Pipeline)負責處理被蜘蛛/爬蟲提?。ɑ蚺廊。┑臄祿?。典型的任務包括:
????1. 清理;
????2. 驗證;
????3. 持久存儲(如將數據項存儲在數據庫關系數據庫或者NoSQL數據庫中)。
1.6. 組件06:下載器中間件(Downloader middlewares)
??下載器中間件(Downloader middlewares)是位于引擎和下載器之間的特定功能的回調Hook,負責處理從引擎傳遞到下載器時處理請求,以及從下載器傳遞到引擎的響應。
??使用下載器中間件(Downloader middlewares)的幾種情況:
????1. 在將請求發送給下載者之前處理該請求(即在Scrapy將請求發送到網站之前);
????2. 在傳遞給spider之前改變接受到的響應;
????3. 重新發送新的請求,而不是將收到的響應傳遞給spider;
????4. 在沒有爬取到網頁的情況下,發送一個響應給spider;
????5. 需要根據條件放棄一些請求。
1.7. 組件07:蜘蛛/爬蟲中間件(Spider middlewares)
??蜘蛛/爬蟲中間件(Spider middlewares)是位于引擎和蜘蛛之間的特定功能的Hook,負責處理Spider的輸入(響應)和輸出(數據項或者請求)。
??蜘蛛/爬蟲中間件(Spider middlewares)的幾種情況:
????1. spider回調的輸出后的處理:包含:更改/添加/刪除請求或數據項;
????2. 開始請求的后處理;
????3. 處理spider異常;
????4. 對一些基于響應內容的請求調用errback,而不是回調。
2. 核心工作流程
??Scrapy的工作流程是按照爬取的數據設計的流程,并據此設計組件結構。(下圖是來自Scrapy的官方文檔)
2.1. 流程01-獲取請求
??引擎從蜘蛛獲取需要爬取的初始請求。
????源:Spider
????目標:Engine
????數據:請求
2.2. 流程02-請求調度安排
??引擎調度爬取請求到調度器,并申請下一個需要爬取的爬取請求。
????源:Engine
????目標:Scheduler
????數據:請求
2.3. 流程03-調度爬取請求
??引擎器返回下一個爬取請求給引擎。(為什么不直接爬取,而是需要經過調度器處理呢?調度的好處在于:多任務爬取,還可以處理爬取請求與爬取過程的時間不一致的時間差。)
????源:Scheduler
????目標:Engine
????數據:請求
2.4. 流程04-發送請求給下載器
??引擎將請求發送到下載器,并通過下載器中間軟件傳遞(process_request回調函數可以處理請求數據)。
????源:Engine
????目標:Downloader
????數據:請求
2.5. 流程05-下載器完成下載
??一旦頁面下載器完成頁面下載,下載器將使用下載好的頁面生成一個響應(使用該頁面),并將其發送到引擎,通過下載器中間軟件(process_response回調函數完成下載后的頁面數據處理)。
????源:Downloader
????目標:Engine
????數據:響應
2.6. 流程06-數據項抽取
??引擎從下載器接收響應并將其發送到spider進行處理,并通過spider中間件進行處理(process_spider_input回調函數處理爬取的網頁數據)。
????源:Engine
????目標:Spider
????數據:響應
2.7. 流程07-返回抽取的數據與請求
??Spider處理響應(從爬取的網頁中抽取需要的數據項),并通過spider中間件(process_spider_output回調函數處理Spider處理過的數據)向引擎返回抽取的數據項或者新的附加請求。
????源:Spider
????目標:Engine
????數據:數據項(附加的請求)
2.8. 流程08-存儲抽取的數據項
??引擎將已處理的數據項發送到數據項管道,然后將已附加的請求發送到調度程序,并請求可能的下一個請求進行爬取。
????源:Engine
????目標:Item Pipeline/Scheduler
????數據:數據項/附加請求
2.9. 流程09-結束爬取
??重復01-08,直到調度器中沒有請求調度為止。
三、Scrapy入門
??這個入門是按照官方的教程組織。
??不是上面介紹的每個組件都需要我們開發,實際只需要我們開發業務部分,爬蟲的通用功能部分都封裝到框架中,所以我們需要一個框架的環境,并理解整個工作流程,并關注需要開發的部分,以及開發部分與整個框架組件的關系。
1. 創建一個爬蟲項目
??爬蟲項目使用scrapy框架提供的一個工具創建:scrapy,該工具創建的項目會提供業務部分運行的環境與配置。
1.1. scrapy工具介紹
1.1.1. 獲取scrapy工具幫助
??直接在終端輸入scrapy,可以直接輸出scrapy工具的幫助。
命令:
localhost:~ yangqiang$ scrapy
??其中startproject命令項就是我們馬上要使用來創建項目的。
1.1.2. 獲取startproject命令項幫助
??獲取幫助的指令:
localhost:~ yangqiang$ scrapy startproject -h
創建爬蟲項目指令startproject幫助獲取截圖
1.2. 使用scrapy工具創建一個爬蟲項目
??創建爬蟲項目的兩個重要參數:
????1. 項目名稱
????2. 項目存放目錄(可選,默認當前目錄)
1.2.1. 創建項目
localhost:codes yangqiang$ scrapy startproject FirstScrapy
1.2.2. 創建好的項目文件
??可以查看創建的項目,其中的文件結合上面的組件與工作流程,大致也知道其用途與作用。
1.3. 使用pycharn打開創建的項目
??可以使用pycharm IDE工具打開創建的爬蟲項目:
2. 實現爬蟲業務
??因為在框架在開發,為了保證框架能順利工作,需要按照設計的結構,繼承scrapy.Spider類,并發起一個爬取請求,并處理。
2.1. 創建爬蟲代碼模塊
??在項目的spiders包路徑下,創建一個python源代碼文件:spiders.home_spider.py
。
2.2. 繼承Spider
??由于Spider是抽象類,需要override其中的抽象函數。
def parse(self, response):
raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))
??繼承Spider類后的子類:
# coding = utf-8
import scrapy
class HomeSpider(scrapy.Spider):
def parse(self, response):
pass
2.3. 爬蟲中兩個重要的屬性
2.3.1. name屬性
??name屬性,用來在執行爬蟲的時候指定爬蟲。屬性類型是字符串。
2.3.2. start_urls屬性
??start_urls屬性用來發起一個爬蟲任務請求。屬性類型是列表。
2.3.3. 屬性實現代碼
# coding = utf-8
import scrapy
class HomeSpider(scrapy.Spider):
name = 'home'
start_urls = [
'https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1',
]
def parse(self, response):
pass
3. scrapy工具與運行爬蟲項目
3.1. 在項目目錄下的scrapy工具的幫助
??在scrapy工具創建的爬蟲項目頂層目錄下,執行scrapy工具獲取的幫助會更多。
??命令:
localhost:FirstScrapy yangqiang$ scrapy
??執行效果:
在爬蟲項目目錄下獲取scrapy工具與在其他地方得到的幫助內容存在不同
??與項目相關的命令有(6個):
????list:列出項目中爬蟲列表。
????check:檢查爬蟲。
????crawl:啟動爬蟲任務。
????edit:編輯爬蟲。
????fetch:獲取。
????parse:解析。
??上述命令的使用,使用幫助可以獲取。具體的使用在后面會介紹。
3.2. list爬蟲
??命令:
localhost:FirstScrapy yangqiang$ scrapy list
??效果:
3.3. edit爬蟲
??命令:
localhost:FirstScrapy yangqiang$ scrapy edit home
??一般不使用這個指令在字符界面下編輯,而是使用IDE工具編輯。不過遠程維護使用字符界面是非常方便的,
??效果:
3.4. crawl爬蟲-運行爬蟲
??命令:
localhost:FirstScrapy yangqiang$ scrapy crawl home
??使用該命令啟動爬蟲任務,其中home是爬蟲程序中name指定的爬蟲名。
??效果:
3.5. check爬蟲
??命令:
localhost:FirstScrapy yangqiang$ scrapy check home
??檢查爬蟲代碼中的錯誤。
??下面是沒有錯誤的例子:
??下面是有錯誤的例子(隨便在代碼中設計幾個錯誤即可)。
3.6. parse爬蟲
??parse命令用來測試爬蟲的parse函數。
??parse命令的幫助:
??命令:
localhost:FirstScrapy yangqiang$ scrapy parse --spider=home https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1
??效果(當沒有指定爬蟲名,這會使用url直接創建一個爬蟲,但是scrapy存在bug,會報錯,在github上已經有人修正這個bug,可以通過百度找到這個帖子):
3.7. fetch爬蟲
??直接下載頁面,并顯示在終端。
??命令:
localhost:FirstScrapy yangqiang$ scrapy fetch https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1
??效果:
3.8. genspider生成爬蟲
??genspider可以查看爬蟲模板,創建爬蟲,編輯爬蟲等功能。其幫助如下:
3.8.1. 查看模板
??一般默認的模板是basic。
??命令:
localhost:FirstScrapy yangqiang$ scrapy genspider -l
??效果:
3.8.2. 創建爬蟲
??命令:
localhost:FirstScrapy yangqiang$ scrapy genspider -t crawl myspider https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1
??效果:
??創建后,在項目工程中能看見這個爬蟲代碼文件:!
3.8.3. 創建并編輯爬蟲
??命令:
localhost:FirstScrapy yangqiang$ scrapy genspider -e myspider 'https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1'
??這個命令是先創建,后編輯。注意:編輯中需要項目的模塊導入,否則編輯不會正常。不過一般創建好以后,也不會在終端下編輯。
3.9. view查看爬取頁面
??該命令首先下載頁面,然后使用瀏覽器打開。
??命令:
localhost:FirstScrapy yangqiang$ scrapy view https://www.baidu.com
??效果:
3.10. shell交互式爬蟲處理
??使用交互式處理爬蟲數據處理。與代碼一樣,只是交互式開發模式。
??命令:
localhost:FirstScrapy yangqiang$ scrapy shell 'https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1'
??效果(綠色的表示代碼輸入):
??交互式編程示例:
3.11. settings獲取settings.py中的配置
??settings指令用來獲取與設置settings.py中的配置值。
??命令:
localhost:FirstScrapy yangqiang$ scrapy settings --get=SPIDER_MODULES
??效果:
使用settings指令獲取settings文件中定義的配置
??下面是settings.py文件:
3.12. runspider運行爬蟲文件
??這個命令是直接執行爬蟲代碼文件,與crawl的區別在于,crawl執行的是爬蟲項目spiders目錄下的有爬蟲名的爬蟲。
??命令:
localhost:FirstScrapy yangqiang$ scrapy runspider ./FirstScrapy/spiders/home_spider.py
??效果:
四、爬蟲中的數據流與數據處理
1. 創建一個測試項目
??使用scrapy工具創建一個爬蟲數據流與數據處理的測試項目。
??創建命令:
localhost:codes yangqiang$ scrapy startproject Scra_DataFlow
??創建過程:
??下面主要關注點在數據上,所以其他scarpy工具等使用細節可以參考上面的幫助。
??同時,scrapy爬蟲框架提供了一些快捷實現方式,下面都采用傳統的思路實現,這樣容易理解,快捷方式的介紹不作為重點,甚至不在這里介紹。
2. 爬蟲目標
2.1. 爬取站點
??爬取騰訊課堂上的數據。爬取騰訊課堂基礎課程中的Python付費課程信息。
2.2. 爬取數據
??課程名稱,培訓機構,購買人數,課程價格,開課方式等。
2.3. 理解domain與url
??domain:https://ke.qq.com
??urls:https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1
3. 使用命令創建爬蟲
??使用scrapy的genspider指令創建spider代碼模板。
??命令:
localhost:Scra_DataFlow yangqiang$ scrapy genspider -t basic course ke.qq.com
使用genspider用basic模板創建一個爬蟲模塊
??創建好的代碼如下:
# -*- coding: utf-8 -*-
import scrapy
class CourseSpider(scrapy.Spider):
name = 'course'
allowed_domains = ['ke.qq.com']
start_urls = ['http://ke.qq.com/']
def parse(self, response):
pass
??在Pycharm中,代碼在項目中的截圖:
4. 爬取URL
??實際需要爬取的頁面URL為:
????https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1
??這是scrapy框架數據流的第01步。從爬蟲發起一個請求。
??該請求,使用迭代器的方式返回給爬蟲引擎:
????方式一:是引擎調用def start_requests(self)函數,通過返回值得到請求。
????方式二:覆蓋父類的start_urls屬性,用來替代默認的 start_requests函數,返回請求。
??然后爬蟲引擎,把請求發送給調度器(流程02),根據處理資源的情況,引擎從調度器獲取請求(流程03),再發送給下載器(流程04)。
??下載器使用引擎發送過來的請求,完成下載任務,并把下載的響應返回給引擎(流程05)。引擎把響應通過parse函數傳遞給爬蟲程序(流程06),爬蟲通過parse函數參數,得到下載響應。其中想用通過response對象獲取,該響應對象的類型是:<class 'scrapy.http.response.html.HtmlResponse'>。
??注意:流程01,02,03,04,05,06都是框架自動完成,如果不做特別的處理,可以不干預框架的流程,直接在流程06的結束得到一個響應對象。
??scrapy框架的數據流,注意其中的編號,對應這我們這里的一樣的編號。
5. 通過parse函數的參數,獲取下載響應
5.1. scrapy.http.response.html.HtmlResponse類
??數據成員:
????|- encoding
????|- selector
????|- text
????|- body
????|- meta
????|- url
??函數成員:
????|- body_as_unicode(self)
??????|- 返回一個unicode的body
????|- css(self, query)
????follow(self, url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding=None, priority=0, dont_filter=False, errback=None)
??????|- 返回Request類型的對象。
????|- replace(self, *args, **kwargs)
????|- urljoin(self, url)
????|- xpath(self, query, **kwargs)
??獲取解碼后的文本頁面內容。
????|- text
??數據解析接口:
????|- css(self, query)
????|- xpath(self, query, **kwargs)
5.2. 直接處理(中斷數據流)
5.2.1. 直接保存成文件
??這類直接保存成html文本文件。
# -*- coding: utf-8 -*-
import scrapy
class CourseSpider(scrapy.Spider):
name = 'course'
allowed_domains = ['ke.qq.com']
start_urls = ['https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1']
def parse(self, response):
# 2019-02-14 15:00:14 [course] DEBUG: <class 'scrapy.http.response.html.HtmlResponse'>
# self.log(type(response))
# utf-8
# self.log(response.encoding)
# <Selector xpath=None data='<html lang="zh">\n<head>\n <meta charse'>
# self.log(response.selector)
# self.log(response.text) # unicode的文本內容
# self.log(response.body) # 二進制內容
# {'download_timeout': 180.0, 'download_slot': 'ke.qq.com', 'download_latency': 0.592094898223877}
# self.log(response.meta)
# https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1
# self.log(response.url)
# 保存下載的頁面到文件,護或者到數據庫(可以考慮文檔數據庫或者mysql關系數據庫)
with open('page.html', 'w') as fd:
fd.write(response.text)
??下載后保存的文件內容。
5.2.2. 解析保存成csv文件
??從瀏覽器獲取XPATH格式(/html/body/section[1]/div/div[3]/ul):
??把瀏覽器中獲取的XPATH按照我們的需求修改:
????| - 瀏覽器XPATH:/html/body/section[1]/div/div[3]/ul
????| - 修改后XPATH://section[1]/div/div[3]/ul/li
??
??因為XPATH是從body文檔開始解析的,同時我們希望獲取頁面上24門課程的內容。
??代碼如下:
# -*- coding: utf-8 -*-
import scrapy
class CourseSpider(scrapy.Spider):
name = 'course'
allowed_domains = ['ke.qq.com']
start_urls = ['https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1']
def parse(self, response):
# 解析數據,并保存成csv文件。
result = response.xpath('//section[1]/div/div[3]/ul/li')
self.log('XXXX:{}'.format(len(result)))
??運行爬蟲的輸出結果是:
使用xpath獲取到我們需要爬取得24們課程列表截圖
??xpath返回的是一個list列表,其中元素的類型是:<class 'scrapy.selector.unified.Selector'>,該類型的API幫助可以使用help獲取。
??下面我們可以直接解析得到需要的字段(保存到csv的實現,這里就略掉,其中使用了xpath或者css,這個知識點,下面專門講解)。
# -*- coding: utf-8 -*-
import scrapy
class CourseSpider(scrapy.Spider):
name = 'course'
allowed_domains = ['ke.qq.com']
start_urls = ['https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1']
def parse(self, response):
# 解析數據,并保存成csv文件。
result = response.xpath('//section[1]/div/div[3]/ul/li')
for course_ in result:
# self.log(type(course_))
# 課程名稱
course_name = course_.xpath('h4[@class="item-tt"]/a/text()').get()
self.log('課程名稱:{}'.format(course_name.strip() if course_name else ''))
# 培訓機構
course_organization = course_.xpath(
'div[@class="item-line item-line--middle"]/span[@class="item-source"]/a/text()').get()
self.log('培訓機構:{}'.format(course_organization.strip() if course_organization else ''))
# 課程連接
course_link = course_.xpath('h4[@class="item-tt"]/a/@href').get()
self.log('課程連接:{}'.format(course_link.strip() if course_link else ''))
# 報名人數
course_number = course_.xpath(
'div[@class="item-line item-line--middle"]/span[@class="line-cell item-user"]/text()').get()
self.log('報名人數:{}'.format(course_number.strip() if course_number else ''))
# 課程狀態
course_status = course_.xpath('div[@class="item-status"]/text()').get()
self.log('課程狀態:{}'.format(course_status.strip() if course_status else ''))
# 課程價格
course_price = course_.xpath('div[@class="item-line item-line--bottom"]/span/text()').get()
self.log('課程價格:{}'.format(course_price.strip() if course_price else ''))
??解析的效果:
5.3. 返回數據項到管道(流程07與流程08)
??在parse函數中,實際是可以返回數據的:
????| - 返回的數據是Items,則引擎接受到數據后,會發送給管道Pipeline。
????| - 返回Request請求,則把Request發送給調度器,繼續爬取數據。
??
??這里只關注返回Items,返回Request的情路,后面說明。
5.3.1. 定義Items的數據項
??繼承scrapy.Item類,對應需要接續的字段定義數據項。
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class ScraDataflowItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# 課程名稱
course_name = scrapy.Field()
# 培訓機構
course_organization = scrapy.Field()
# 課程連接
course_link = scrapy.Field()
# 報名人數
course_number = scrapy.Field()
# 課程狀態
course_status = scrapy.Field()
# 課程價格
course_price = scrapy.Field()
5.3.2. 使用數據項,緩存爬取的數據字段
??使用數據項比較模式化,比較容易理解。 下面是實現代碼:
# -*- coding: utf-8 -*-
import scrapy
from Scra_DataFlow.items import ScraDataflowItem
class CourseSpider(scrapy.Spider):
name = 'course'
allowed_domains = ['ke.qq.com']
start_urls = ['https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1']
def parse(self, response):
result = response.xpath('//section[1]/div/div[3]/ul/li')
items = [] # 數據項數組列表
for course_ in result:
# 數據項
item_ = ScraDataflowItem()
course_name = course_.xpath('h4[@class="item-tt"]/a/text()').get()
item_['course_name'] = '{}'.format(course_name.strip() if course_name else '')
# 培訓機構
course_organization = course_.xpath(
'div[@class="item-line item-line--middle"]/span[@class="item-source"]/a/text()').get()
item_['course_organization'] = course_organization.strip() if course_organization else ''
# 課程連接
course_link = course_.xpath('h4[@class="item-tt"]/a/@href').get()
item_['course_link'] = course_link.strip() if course_link else ''
# 報名人數
course_number = course_.xpath(
'div[@class="item-line item-line--middle"]/span[@class="line-cell item-user"]/text()').get()
item_['course_number'] = course_number.strip() if course_number else ''
# 課程狀態
course_status = course_.xpath('div[@class="item-status"]/text()').get()
item_['course_status'] = course_status.strip() if course_status else ''
# 課程價格
course_price = course_.xpath('div[@class="item-line item-line--bottom"]/span/text()').get()
item_['course_price'] = course_price.strip() if course_price else ''
items.append(item_)
# 返回數據項到管道
return items
5.3.3. 使用管道保存數據
??使用命令:
localhost:Scra_DataFlow yangqiang$ scrapy crawl course -o course.csv
??執行過程:
??提示:pycharm可以安裝csv的插件,用來顯示csv文件
??使用管道保存的文件(不使用插件在pycharm顯示):
使用管道保存的文件顯示截圖(pychar無插件文本顯示)
??使用管道保存的文件(使用插件在pycharm顯示)
附錄
??需要掌握爬蟲的高級應用,以及一些經典場景應用,可以關注后繼內容與馬哥教育。
最后編輯于 :
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。