Scrapy框架
Scrapy是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。 可以應用在包括數據挖掘,信息處理或存儲歷史數據等一系列的程序中。其最初是爲了 頁面抓取 (更確切來說, 網絡抓取 )所設計的, 也可以應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。Scrapy用途廣泛,可以用於數據挖掘、監測和自動化測試。
Scrapy 使用了 Twisted 異步網絡庫來處理網絡通訊。整體架構大致如下:
組件
Scrapy Engine
引擎負責控制數據流在系統中所有組件中流動,並在相應動作發生時觸發事件。 詳細內容查看下面的數據流(Data Flow)部分。
Scheduler(調度器)
調度器從引擎接受request並將他們入隊,以便之後引擎請求他們時提供給引擎。
Spiders
Spider是Scrapy用戶編寫用於分析response並提取item(即獲取到的item)或額外跟進的URL的類。 每個spider負責處理一個特定(或一些)網站。
Item Pipeline
Item Pipeline負責處理被spider提取出來的item。典型的處理有清理、 驗證及持久化(例如存取到數據庫中)。
下載器中間件(Downloader middlewares)
下載器中間件是在引擎及下載器之間的特定鉤子(specific hook),處理Downloader傳遞給引擎的response。 其提供了一個簡便的機制,通過插入自定義代碼來擴展Scrapy功能。
Spider中間件(Spider middlewares)
Spider中間件是在引擎及Spider之間的特定鉤子(specific hook),處理spider的輸入(response)和輸出(items及requests)。 其提供了一個簡便的機制,通過插入自定義代碼來擴展Scrapy功能。
數據流(Data flow)
Scrapy中的數據流由執行引擎控制,其過程如下:
- 引擎打開一個網站(open a domain),找到處理該網站的Spider並向該spider請求第一個要爬取的URL(s)。
- 引擎從Spider中獲取到第一個要爬取的URL並在調度器(Scheduler)以Request調度。
引擎向調度器請求下一個要爬取的URL。 - 調度器返回下一個要爬取的URL給引擎,引擎將URL通過下載中間件(請求(request)方向)轉發給下載器(Downloader)。
- 一旦頁面下載完畢,下載器生成一個該頁面的Response,並將其通過下載中間件(返回(response)方向)發送給引擎。
- 引擎從下載器中接收到Response並通過Spider中間件(輸入方向)發送給Spider處理。
Spider處理Response並返回爬取到的Item及(跟進的)新的Request給引擎。 - 引擎將(Spider返回的)爬取到的Item給Item Pipeline,將(Spider返回的)Request給調度器。
(從第二步)重複直到調度器中沒有更多地request,引擎關閉該網站。
Scrapy安裝以及生成項目
下載方式
windows:
從 https://pip.pypa.io/en/latest/installing.html 安裝 pip
打開命令行窗口,確認 pip 被正確安裝:
pip --verison
安裝scrapy:
pip install scrapy
項目初始化
startproject
語法: scrapy startproject <project_name>
是否需要項目: no
在 project_name 文件夾下創建一個名爲 project_name 的Scrapy項目。
打開命令行窗口,切換到自己想要創建的項目的目錄下,初始化一個名叫myproject的項目
scrapy startporject myproject
創建好後進入到我們所創建的項目中去,裏面的目錄結構如下所示:
scrapy.cfg
myproject/
__init__.py
items.py
pipelines.py
middlewares.py
settings.py
spiders/
__init__.py
...
接下來,就可以使用 scrapy 命令來管理和控制您的項目了。比如,創建一個新的spider,我們以MOOC網爲例:
genspider
語法: scrapy genspider [-t template] <name> <domain>
是否需要項目: yes
在當前項目中創建spider。
這僅僅是創建spider的一種快捷方法。該方法可以使用提前定義好的模板來生成spider。您也可以自己創建spider的源碼文件。
scrapy genspider course https://www.icourse163.org/
創建好後spider目錄下會生成一個course .py的文件:
接下來我們看下項目的配置文件,項目的配置文件在目錄下的setting.py
BOT_NAME:項目名稱
USER_AGENT:用戶代理
ROBOTSTXT_OBEY:是否遵循機器人協議,默認是true
CONCURRENT_REQUESTS:最大併發數
DOWNLOAD_DELAY:下載延遲時間,單位是秒,控制爬蟲爬取的頻率,根據你的項目調整,不要太快也不要太慢,默認是3秒,即爬一個停3秒,設置爲1秒性價比較高,如果要爬取的文件較多,寫零點幾秒也行
COOKIES_ENABLED:是否保存COOKIES,默認關閉
DEFAULT_REQUEST_HEADERS:默認請求頭,上面寫了一個USER_AGENT,其實這個東西就是放在請求頭裏面的,這個東西可以根據你爬取的內容做相應設置。
ITEM_PIPELINES:項目管道,300爲優先級,越低越爬取的優先度越高,默認是註釋掉的
在使用Scrapy時,可以聲明所要使用的設定。這可以通過使用環境變量: SCRAPY_SETTINGS_MODULE 來完成。
SCRAPY_SETTINGS_MODULE 必須以Python路徑語法編寫, 如 myproject.settings 。 注意,設定模塊應該在 Python import search path 中。
接下來可以開始爬取任務,打開命令行輸入scrapy crawl 開始爬取任務
crawl
語法: scrapy crawl <spider>
是否需要項目: yes
使用spider進行爬取。
scrapy crawl course
日誌模塊
Scrapy提供了log功能。您可以通過 scrapy.log 模塊使用。當前底層實現使用了 Twisted logging ,不過可能在之後會有所變化。
log服務必須通過顯示調用 scrapy.log.start() 來開啓。
Scrapy提供5層logging級別:
CRITICAL - 嚴重錯誤(critical)
ERROR - 一般錯誤(regular errors)
WARNING - 警告信息(warning messages)
INFO - 一般信息(informational messages)
DEBUG - 調試信息(debugging messages)
可以通過終端選項(command line option) –loglevel/-L 或 LOG_LEVEL 來設置log級別。設置log級別爲WARNING,就只會WARNING等級之下的ERROR和CRITICAL
用WARNING記錄
from scrapy import log
log.msg("This is a warning", level=log.WARNING)
在spider中添加log的推薦方式是使用Spider的 log() 方法。該方法會自動在調用 scrapy.log.msg() 時賦值 spider 參數。其他的參數則直接傳遞給 msg() 方法。
也可自己封裝一個日誌模塊
在使用的調用日誌模塊
from .course_logger import logger
實際案例
接下來以中國大學MOOC爲例,爬取免費公開課的一些信息
以高等數學(三)爲例,課程的地址必須類似以下兩種格式:
https://www.icourse163.org/course/NUDT-42002
https://www.icourse163.org/course/NUDT-42002?tid=1462924451
首先在item.py中,定義好你要提取的內容,比如我們提取的是課程的名稱、ID、講師、簡介、學校,相應創造這幾個變量。Field方法實際上的做法是創建一個字典,給字典添加一個建,暫時不賦值,等待提取數據後再賦值
import scrapy
class MoocItem(scrapy.Item):
term_id = scrapy.Field()
title = scrapy.Field()
description = scrapy.Field()
college = scrapy.Field()
lector = scrapy.Field()
思路
我們需要爬取的只是一個單頁面,主要就是分析HTML元素,利用xpath編寫路徑表達式來獲取節點/節點集,和常規電腦文件路徑類似。
re — 正則表達式操作
re模塊是python獨有的匹配字符串的模塊,該模塊中提供的很多功能是基於正則表達式實現的,而正則表達式是對字符串進行模糊匹配,提取自己需要的字符串部分,他對所有的語言都通用
yield
scrapy框架會根據 yield 返回的實例類型來執行不同的操作:
a. 如果是 scrapy.Request 對象,scrapy框架會去獲得該對象指向的鏈接並在請求完成後調用該對象的回調函數
b. 如果是 scrapy.Item 對象,scrapy框架會將這個對象傳遞給 pipelines.py做進一步處理
def parse(self, response):
item = {
'term_id': re.search(r'termId : "(\d+)"', response.text).group(1),
'title': response.xpath("//meta[@name= 'description']/@content").extract_first().split(',')[0],
'description': response.xpath("//meta[@name= 'description']/@content").extract_first().split(',')[1][
10:],
'college': response.xpath("//meta[@name= 'keywords']/@content").extract_first().split(',')[1],
}
lectors = []
script = response.css('script:contains("window.staffLectors")::text').get()
chiefLector_str = ''.join(re.findall('chiefLector = \\{([^}]*)\\}', script))
chiefLector_list = re.sub('\s+', '', ' '.join(chiefLector_str.split())).strip()
chiefLector = demjson.decode("{" + chiefLector_list + "}")
lectors.append(chiefLector)
staffLectors_str = ''.join(re.findall('staffLectors = \[([^\[\]]+)\]', script))
staffLectors_list = re.sub('\s+', '', ' '.join(staffLectors_str.split())).strip()
staffLector = demjson.decode("[" + staffLectors_list + "]")
if staffLector:
for staff in staffLector:
lectors.append(staff)
item['lector'] = lectors
yield item
在提取講師內容的時候有一點麻煩,講師內容是在script標籤中
利用正則表達式去匹配變量名,然後再去匹配中括號或是大括號中的內容,還需要藉助demjson處理JSON
demjson
python處理json是需要第三方json庫來支持,工作中遇到處理json數據,是沒有安裝第三方的json庫。demjson模塊提供用於編碼或解碼用語言中性JSON格式表示的數據的類和函數(這在ajax Web應用程序中通常被用作XML的簡單替代品)。此實現儘量儘可能遵從JSON規範(RFC 4627),同時仍然提供許多可選擴展,以允許較少限制的JavaScript語法。它包括完整的Unicode支持,包括UTF-32,BOM,和代理對處理。
pipline.py管道可以處理提取的數據,如存Mongo數據庫,代碼敲好後不要忘記在settings裏開啓pipelines。
from pymongo import MongoClient
class MyprojectPipeline:
MONGO_URL = "mongodb://localhost:27017"
MONGO_DB = "mooc"
MONGO_TABLE = "course"
client = MongoClient(MONGO_URL)
db = client[MONGO_DB]
def process_item(self, item, spider):
self.save_to_mongo(item)
return item
def save_to_mongo(self, data):
if self.db[self.MONGO_TABLE].insert(data):
print("SAVE SUCCESS", data)
return True
return False
存儲成功