pyspider框架的使用
介紹
- 用Python編寫指令碼
- 功能強大的WebUI,包括指令碼編輯器,任務監視器,專案管理器和結果檢視器
- MySQL,MongoDB,Redis,SQLite,Elasticsearch ; PostgreSQL使用SQLAlchemy作為資料庫後端
- RabbitMQ,Beanstalk,Redis和Kombu作為訊息佇列
- 任務優先順序,重試,定期,按年齡重新抓取等...
- 分散式架構,抓取Javascript頁面,Python 2和3等...
pyspider的架構
pyspider的架構主要分為Scheduler(排程器)、Fetcher(抓取器)、Processer(處理器)三個部分,整個爬取過程受到Monitor(監視器)的控制,抓取的結果被Result Worker(結果處理器)處理,如下圖所示。
Scheduler發起任務排程,Fecher負責抓取網頁內容,Processer負責解析網頁內容,然後將新生成的Request發給Scheduler進行排程,將生成的提取結果輸出儲存。
pyspider的基本使用
1.開啟cmd,輸入命令
pyspider all
這樣可以啟動pyspider的所有元件,包括PhantomJS、ResultWorker、Processer、Fetcher、Scheduler、WebUI,這些都說pyspider執行必備的元件。可以開啟瀏覽器,輸入連結http://localhost:5000,這時我們會看到頁面,如圖所示:
此頁面便是pyspider的WebUI,我們可以用它來管理專案、編寫程式碼、線上除錯、監控任務等。
建立專案
新建一個專案,點選右邊的create按鈕,在彈出的浮窗裡輸入專案名稱和爬取連結,在點選create按鈕,這樣就成功建立了一個專案,如圖所示
接下來會看到pyspider的專案編輯和除錯頁面,如圖所示;
左側就是程式碼的除錯頁面,點選左側右上角的run單步除錯爬蟲程式,在左側下半部分可以預覽當前的爬取頁面。右側是程式碼編輯頁面,我們可以直接編輯程式碼和儲存程式碼。
注意右側,pyspider生成了一些程式碼,程式碼如下所示:
#!/usr/bin/env python # -*- encoding: utf-8 -*- # Created on 2018-09-22 16:46:39 # Project: q from pyspider.libs.base_handler import * class Handler(BaseHandler): crawl_config = { } @every(minutes=24 * 60) def on_start(self): self.crawl('http://travel.qunar.com/travelbook/list.htm', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('a[href^="http"]').items(): self.crawl(each.attr.href, callback=self.detail_page) @config(priority=2) def detail_page(self, response): return { "url": response.url, "title": response.doc('title').text(), }
這樣的Handler就是pyspider爬蟲的主類,我們可以在此處定義爬取,解析、儲存的邏輯。整個爬蟲的功能只需要一個Handler即可完成。
接下來我們可以看到一個crawl_config屬性。我們可以將本專案的所有爬取配置統一定義道這裡,如定義Headers、設定代理等,配置之後全域性生效。
然後,on_start()方法是爬取入口,初始的爬取請求會在這裡產生,該方法通過呼叫crawl()方法即可新建一個爬取請求,第一個引數是爬取的URL,這裡自動替換成我們定義的URL。crawl()方法還有一個引數callback,它指定了這個頁面爬取成功後用哪個方法進行解析,程式碼中指定為index_page()方法,即如果這個URL對應的頁面爬取成功了,那Response將交給index_page()方法進行解析。
index_page()方法恰好接收這個Response引數,Response對接了pyquery。我們直接呼叫doc()方法傳入相應的CSS選擇器,就可以像pyquery一樣解析此頁面,程式碼中預設是a[href^="http"],也就是說該方法解析了頁面的所有連結,然後將連結遍歷,再次呼叫了crawl()方法生成了新的爬取請求,同時指定了callback和detail_page,意識是說這些頁面爬取成功了就呼叫detail_page()方法解析。這裡,index_page()實現了兩個功能,一是將爬取的結果進行解析,二是生成新的爬取請求。
detail_page()同樣接收Response作為引數。detail_page()抓取的就是詳情頁的資訊,就不會生成新的請求,只對Response物件做解析,解析之後將結果以字典的形式返回。當然我們也可以進行後續處理,如將資料儲存到資料庫中。
爬取首頁
點選左欄右上角的run按鈕,即可看到頁面下方follows便會出現一個標註,其中包含數字1,這代表有新的爬取請求產生,如圖所示。
左欄左上角會出現當前run的配置檔案,這裡有一個callback為on_start,這說明點選run之後實際是執行了on_start()方法。在on_start()方法中,我們利用crawl()方法生成一個爬取請求,那下方follows部分的數字1就代表了一個爬取請求。
點選下方的follows按鈕,即可以看到生成的爬取請求連結。如圖所示:
上方的callback已經變成了index_page,這就代表當前運行了index_page()方法。index_page()接收到的response引數就是剛才生成的第一個爬取請求的Response物件。index_page()方法通過呼叫doc()方法,傳入提取所有a節點的CSS選擇器,然後獲取a節點的屬性href,這樣實際上就是獲取第一個爬取頁面中的所有連結。然後在index_page()方法裡遍歷了所有連結,同時呼叫了crawl()方法,就把這一個個的連結構造成新的爬取請求了。所以最下方follows按鈕部分有231的數字標記,這就代表生成了217個爬取請求,同時這些請求的URL都呈現在當前頁面了。
再點選下方的web按鈕,即可預覽當前爬取結果的頁面,如圖所示:
點選html按鈕即可檢視當前頁面的原始碼,如圖所示:
剛才在index_page()方法中提取了所以的連結並生成了新的爬取請求。
當我們要爬取攻略詳情頁面連結的時候我們就使用下方enable css selector helper這個工具即可,點選它,然後選擇標題就會多一個紅框,上方出現了一個CSS選擇器,這就是當前標題對應的CSS選擇器,如圖所示:
在右側程式碼選擇要更改的區域,點選左欄的右箭頭,此時在上方出現的標題的CSS選擇器就會被替換到右側程式碼中,如圖所示:
這樣就完成了CSS選擇器的替換,非常便捷。
重新點選左欄右上角的run按鈕,即可重新執行index_page()方法,此時的follows就變成了10個,也就是說現在我們提取的只有當前10個攻略,如圖所示:
我們現在抓取的只是第一頁的內容,還需要抓取後續頁面,所以還需要一個爬取連結,即爬取下一頁的攻略列表頁面。我們再利用crawl()方法新增下一頁的爬取請求,在index_page()方法裡新增如下程式碼,然後點選save儲存,如下圖所示:
現在我們就把所以列表頁的解析過程完成了。
爬取詳情頁
任意選取一個詳情頁進入,點選前10個爬取請求中的任意一個的右箭頭,執行詳情頁的爬取,如圖所示
切換到Web頁面預覽效果,頁面下拉之後,頭圖正文中的一些圖片一直顯示載入中
此現象的原因是pyspider預設傳送HTTP請求,請求的HTML文件本身就不包含img節點。但是在瀏覽器中我們看到了圖片,這時因為這張圖片是後期經過JavaScript出現的。
我們可以將index_page()中生成抓取詳情頁的請求方法新增一個引數fetch_type,改寫的index_page()變為如下內容:
for each in response.doc('li > .tit > a').items():
self.crawl(each.attr.href, callback=self.detail_page,fetch_type='js')
next=response.doc('.next').attr.href
self.crawl(next,callback=self.index_page)
接下來看效果:
圖片被成功渲染出來了,這就是啟動了PhantomJS渲染後的結果。只需要新增一個fetch_type引數即可,這非常的方便。
最好在detail_page()方法改寫如下所示:
def detail_page(self, response):
return {
"url": response.url,
"title": response.doc('#booktitle').text(),
"date":response.doc('.when .data').text(),
"day":response.doc('.howlong .data').text(),
"who":response.doc('.who .data').text(),
"text":response.doc('#b_panel_schedule').text(),
"image":response.doc('.cover_img').attr.src
}
我們分別提取了頁面的連結、標題、出行日期、出行天數、人物、攻略正文、頭圖資訊、將這些資訊構造成一個字典。
啟動爬蟲
返回爬蟲的主頁面,將爬蟲的status設定成DEBUG或RUNNING,點選右側的Run按鈕即可開始爬取,如圖所示
在最左側我們可以定義專案的分組,以方便管理。rate/burst代表當前的爬取速率,rate代表1秒發出多少個請求,burst相當於流量控制中的令牌桶演算法的令牌數,rate和burst設定的越大,爬取速率越快,當然速率需要考慮本機效能和爬取過快被封的問題。process中的5m、1h、1d指的是最近5分、1小時、1天內的請求情況,all代表所有的請求情況。請求由不同顏色表示,藍色代表等待被執行的請求,綠色的代表成功的請求,黃色的代表請求失敗後等待重試的請求,紅色的代表失敗次數過多而被忽略的請求,這樣可以直觀知道爬取的進度和請求情況,如圖所示
點選Active Tasks,即可檢視最近請求的詳細情況,如圖所示:
點選Results,即可檢視所有爬取的結果,如圖所示:
點選右上角的按鈕,即可獲得資料的JSON、CSV格式。
pyspider用法
self.crawl
self.crawl(url, **kwargs)
self.crawl
是告訴pyspider應該抓取哪個url的主介面。
引數:
-
url
url是爬取時的URL,可以定義為單個URL字串,也可以定義成URL列表。
-
callback
callback是回撥函式,指定了該URL對應的響應內容用哪個方法來解析,如下所示:
def on_start(self):
self.crawl('http://travel.qunar.com/travelbook/list.htm', callback=self.index_page)
這裡指定了callback為index_page,就代表爬取http://travel.qunar.com/travelbook/list.htm'連結得到的響應會用index_page()方法來解析。
-
age
age是任務的有效時間。如果某個任務在有效時間內且已經被執行,則它不會重複執行,如下所示:
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
預設的有效時間為10天。
-
priority
priority是爬取任務的優先順序,其預設值是0,priority的數值越大,對應的請求會越優先被排程,如下所示:
def index_page(self):
self.crawl('http://www.example.org/page2.html', callback=self.index_page)
self.crawl('http://www.example.org/233.html', callback=self.detail_page,priority=1)
第二個任務會優先被呼叫,233.html這個連結優先爬取。
-
exetime
exetime引數可以設定定時任務,其值是時間戳,預設是0,即代表立即執行,如下所示:
import time def on_start(self):
self.crawl('http://www.example.org/', callback=self.callback,exetime=time.time()+30*60)
這樣該任務會在30分鐘之後執行。
-
retries
retries可以定義重試次數,其預設值是3.
-
itag
itag引數設定判定網頁是否發生變化的節點值,在爬取時會判定次當前節點是否和上次爬取到的節點相同。如果節點相同,則證明頁面沒有更新,就不會重複爬取,如下所示:
def index_page(self, response):
for item in response.doc('.item').items():
self.crawl(item.find('a').attr.url, callback=self.detail_page, itag=item.find('.update-time').text())
class Handler(BaseHandler):
crawl_config = { 'itag': 'v223' }
修改全域性引數itag,使所有任務都重新執行(需要點run按鈕來啟動任務)
-
auto_recrawl
當開啟時,爬取任務在過期後會重新執行,迴圈時間即定義的age時間長度,如下所示"
def on_start(self):
self.crawl('http://www.example.org/', callback=self.callback,age=5*60*60, auto_recrawl=True)
這裡定義了age有效期為5個小時,設定了auto_recrawl為True,這樣任務就會每5個小時執行一次。
-
method
method是HTTP的請求方式,它預設是GET。如果想發起POST請求,可以將method設定為POST。
-
params
我們可以方便地使用params來定義GET請求引數,如下所示:
def on_start(self):
self.crawl('http://httpbin.org/get', callback=self.callback,params={'a': 123, 'b': 'c'})
self.crawl('http://httpbin.org/get?a=123&b=c', callback=self.callback)
這裡兩個爬取任務是等價的
-
data
data是POST表單資料。當請求方式為POST時,我們可以通過此引數傳遞表單資料,如下所示:
def on_start(self):
self.crawl('http://httpbin.org/post', callback=self.callback,method='POST', data={'a': 123, 'b': 'c'})
-
files
files是上傳的檔案,需要指定檔名,如下所示:
def on_start(self):
self.crawl('http://httpbin.org/post', callback=self.callback,method='POST', files={field:{filename:'content'}})
-
user_agent
user_agent是爬取使用的User_Agent.
-
headers
headers是爬取時使用的Headers,即Request Headers。
-
cookies
cookies是爬取時使用的Cookies,為字典格式。
-
connect_timeout
connect_timeout是在初始化連線時的最長等待時間,它預設是20秒。
-
timeout
timeout是爬取網頁時的最長等待時間,它預設是120秒。
-
allow_redirects
確定是否自動處理重定向,它預設為True。
-
validate_cert
確定是否驗證證書,此選項對HTTPS請求有效,預設是True。
-
proxy
proxy是爬取時使用的代理,它支援使用者名稱密碼的配置,格式是username:[email protected]:port,如下所示:
class Handler(BaseHandler):
crawl_config = { 'proxy': 'localhost:8080' }
-
fetch_type
fetch_type開啟PhantomJS渲染。如果遇到JavaScript渲染的頁面,指定欄位即可實現PhantomJS的對接,pyspider將會使用PhantomJS進行網頁的抓取,如下所示:
def on_start(self):
self.crawl('http://www.taobao.com', callback=self.callback,method='POST', fetch_type='js')
這樣我們就可以實現淘寶頁面的抓取了,得到的結果就是瀏覽器中看到的效果。
-
js_script
js_script是頁面載入完畢後執行的Javascript指令碼,如下所示:
def on_start(self):
self.crawl('http://www.example.org/', callback=self.callback,fetch_type='js', js_script='''
function() {
window.scrollTo(0,document.body.scrollHeight);
return 123;
}
''')
頁面載入成功後將執行頁面混動的JavaScript程式碼,頁面會下拉到最底部。
-
load_images
在載入JavaScript頁面時確定是否載入圖片,它預設是否
-
save
save引數非常有用,可以在不同的方法之間傳遞引數,如下所示:
def on_start(self):
self.crawl('http://www.example.org/', callback=self.callback,save={'page': 123}) def callback(self, response): return response.save['page']
在on_start()方法中生成Request並傳遞額外的引數page,在回撥函式裡可以通過response遍歷的save欄位接收到這些引數值
-
cancel
取消任務,如果一個任務是ACTIVE狀態的,則需要force_update設定為True。
-
force_update
即使任務處於ACTIVE狀態,也會強制更新狀態。
專案狀態
每個專案都有6個狀態,分別是TODO、STOP、CHECKING、DEBUG、RUNNING、PAUSE。
TODO:專案剛剛被建立還未實現時的狀態。
STOP:如果想停止某專案的抓取,可以將專案的狀態設定成STOP
CHECKING:正在執行的專案被修改後就會變成CHECKING狀態,專案在中途出錯需要調整的時候會遇到這種情況。
DEBUG/RUNNING:這兩個狀態對專案的執行沒有影響,狀態設定成任意一個,專案都可以執行,但是可以用二者來區分專案是否已經測試通過。
PAUSE:當爬取過程中出現連續多次錯誤時,專案會自動設定為PAUSE狀態,並等待一定時間後繼續抓取。