1. 程式人生 > >pyspider框架的使用

pyspider框架的使用

介紹

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狀態,並等待一定時間後繼續抓取。