1. 程式人生 > >pyspider的基本用法及原理

pyspider的基本用法及原理

Python爬蟲-pyspider框架的使用

2012061822064864.png

pyspider 是一個用python實現的功能強大的網路爬蟲系統,能在瀏覽器介面上進行指令碼的編寫,功能的排程和爬取結果的實時檢視,後端使用常用的資料庫進行爬取結果的儲存,還能定時設定任務與任務優先順序等。

本篇文章只是對這個框架使用的大體介紹,更多詳細資訊可見官方文件

<h3>安裝</h3>

首先是環境的搭建,網上推薦的各種安裝命令,如:

pip install pyspider

但是因為各種許可權的問題,博主安裝報錯了,於是採用了更為簡單粗暴的方式,直接把原始碼下下來run。

pyspider原始碼地址,直接download或者git clone都行,下載完成後,進入資料夾目錄。

系統預設用的Python是2.7版本,自己另外裝了個3.4的,原始碼用python3跑起來。

先進行安裝,在pyspider的路徑下敲命令:

python3 setup.py install

一堆的列印,完了之後沒什麼錯誤提示就是安裝完成了。

接下來跑起來:

python3 run.py 

執行結果如下圖所示

1.jpg

可以看到webui執行在5000埠處,在瀏覽器開啟127.0.0.1:5000或者localhost:5000,便能看到框架的UI介面,如下圖

2.jpg

這樣pyspider就算是跑起來了。有的文章會提到需要安裝phantomjs,這個暫時用不上,先忽略。

<h3>開始</h3>

3.jpg

<h4>新建任務</h4>

第一次跑起來的時候因為沒有任務,介面的列表為空,右邊有個Create按鈕,點選新建任務。

4.jpg

  • Project Name:任務的名字,可以任意填
  • Start URL(s):爬取任務開始的地址,這裡我們填目標網址的url

填寫完成後,點選Create,便建立成功並跳轉到了另一個介面,如下圖所示

5.jpg

介面右邊區域自動生成了初始預設的程式碼:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2016-11-02 09:27:35
# Project: reo

from pyspider.libs.base_handler import *


class Handler(BaseHandler):
    crawl_config = {
    }

    @every(minutes=24 * 60)
    def on_start(self):
        self.crawl('http://www.reeoo.com', 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(),
        }
  • on_start(self) 程式的入口,當點選左側綠色區域右上角的 run 按鈕時首先會呼叫這個函式

  • self.crawl(url, callback) pyspider庫主要的API,用於建立一個爬取任務,url 為目標地址,這裡為我們剛剛建立任務指定的起始地址,callback 為抓取到資料後的回撥函式

  • index_page(self, response) 引數為 Response 物件,response.doc 為 pyquery物件(具體使用可見pyquery官方文件),pyquery和jQuery類似,主要用來方便地抓取返回的html文件中對應標籤的資料

  • detail_page(self, response) 返回一個 dict 物件作為結果,結果會自動儲存到預設的 resultdb 中,也可以通過過載方法來講結果資料儲存到指定的資料庫,後面會再提到具體的實現

其他一些引數

  • @every(minutes=24 * 60) 通知 scheduler(框架的模組) 每天執行一次

  • @config(age=10 * 24 * 60 * 60) 設定任務的有效期限,在這個期限內目標爬取的網頁被認為不會進行修改

  • @config(priority=2) 設定任務優先順序

Ps. 需要注意的一個地方,前面跑的 run.py 不是下載的原始碼資料夾中的,而是在 pyspider 資料夾中的 run.py,如下圖,可以看到有兩個 run.py 檔案,雖然兩個都能跑起來,但我們用到的是圈出來的那個,否則不能通過 --config 配置。

6.jpg

成功跑起來之後可以看到在當前資料夾中生成了一個 data 資料夾,生成的結果預設會儲存到 result.db 中,爬取資料後可開啟看裡面儲存了執行的結果。

7.jpg

<h4>執行</h4>

點選左邊綠色區域右上角的 run 按鈕,執行之後頁面下冊的 follows 按鈕出現紅色角標

8.jpg

選中 follows 按鈕,看到 index_page 行,點選行右側的執行按鈕

9.jpg

執行完成後顯示如下圖,即 www.reeoo.com 頁面上所有的url

10.jpg

此時我們可以任意選擇一個結果執行,這時候呼叫的是 detail_page 方法,返回最終的結果。

結果為json格式的資料,這裡我們儲存的是網頁的 title 和 url,見左側黑色的區域

11.jpg

回到主頁面,此時看到任務列表顯示了我們剛剛建立的任務,設定 status 為 running,然後點選 Run 按鈕執行

12.jpg

執行過程中可以看到整個過程的列印輸出

13.jpg

執行完成後,點選 Results 按鈕,進入到爬取結果的頁面

14.jpg

15.jpg

右上方的按鈕選擇將結果資料儲存成對應的格式,例如:JSON格式的資料為:

16.jpg

以上則為pyspider的基本使用方式。

<h3>自定義爬取指定資料</h3>

接下來我們通過自定義來抓取我們需要的資料,目標為抓取這個頁面中,每個詳情頁內容的標題、標籤、描述、圖片的url、點選圖片所跳轉的url。

17.jpg

18.jpg

點選首頁中的 project name > reo,跳轉到指令碼的編輯介面

19.jpg

<h4>獲取所有詳情頁面的url</h4>

index_page(self, response) 函式為獲取到 www.reeoo.com 頁面所有資訊之後的回撥,我們需要在該函式中對 response 進行處理,提取出詳情頁的url。

通過檢視原始碼,可以發現 class 為 thum 的 div 標籤裡,所包含的 a 標籤的 href 值即為我們需要提取的資料,如下圖

20.jpg

程式碼的實現

def index_page(self, response): 
    for each in response.doc('div[class="thumb"]').items():
        detail_url = each('a').attr.href
        print (detail_url)
        self.crawl(detail_url, callback=self.detail_page)

response.doc('div[class="thumb"]').items() 返回的是所有 class 為 thumb 的 div 標籤,可以通過迴圈 for...in 進行遍歷。

each('a').attr.href 對於每個 div 標籤,獲取它的 a 標籤的 href 屬性。

可以將最終獲取到的url列印,並傳入 crawl 中進行下一步的抓取。

點選程式碼區域右上方的 save 按鈕儲存,並執行起來之後的結果如下圖,中間的灰色區域為列印的結果

21.jpg

注意左側區域下方的幾個按鈕,可以展示當前所爬取頁面的一些資訊,web 按鈕可以檢視當前頁面,html 顯示當前頁面的原始碼,enable css selector helper 可以通過選中當前頁面的元素自動生成對應的 css 選擇器方便的插入到指令碼程式碼中,不過並不是總有效,在我們的demo中就是無效的~

22.jpg

23.jpg

<h4>抓取詳情頁中指定的資訊</h4>

接下來開始抓取詳情頁中的資訊,任意選擇一條當前的結果,點選執行,如選擇第一個

24.jpg

實現 detail_page 函式,具體的程式碼實現:

def detail_page(self, response):
    header = response.doc('body > article > section > header')
    title = header('h1').text()
    
    tags = []
    for each in header.items('a'):
        tags.append(each.text())
    
    content = response.doc('div[id="post_content"]')
    description = content('blockquote > p').text()
    
    website_url = content('a').attr.href
    
    image_url_list = []
    for each in content.items('img[data-src]'):
        image_url_list.append(each.attr('data-src'))
    
    return {
        "title": title,
        "tags": tags,
        "description": description,
        "image_url_list": image_url_list,
        "website_url": website_url,
}

response.doc('body > article > section > header') 引數和CSS的選擇器類似,獲取到 header 標籤, .doc 函式返回的是一個 pyquery 物件。

header('h1').text() 通過引數 h1 獲取到標籤,text() 函式獲取到標籤中的文字內容,通過檢視原始碼可知道,我們所需的標題資料為 h1 的文字。

標籤頁包含在 header 中,a 的文字內容即為標籤,因為標籤有可能不為1,所以通過一個數組去儲存遍歷的結果 header.items('a'),具體html的原始碼如下圖:

25.jpg

response.doc('div[id="post_content"]') 獲取 id 值為 post_content 的 div 標籤,並從中取得詳情頁的描述內容,有的頁面這部分內容可能為空。

其餘資料分析抓取的思路基本一致。

最終將需要的資料作為一個 dict 物件返回,即為最終的抓取結果

{
    "title": title,
    "tags": tags,
    "description": description,
    "image_url_list": image_url_list,
    "website_url": website_url,
}

儲存之後直接點選左邊區域的 run 按鈕執行起來,結果如圖,中間灰色區域為分析抓取到的結果。

26.jpg

在主頁把任務重新跑起來,檢視執行結果,可以看到我們需要的資料都抓取下來

27.jpg

<h3>將資料儲存到本地的資料庫</h3>

抓取到的資料預設儲存到 resultdb 中,雖然很方便通過瀏覽器進行瀏覽和下載,但卻不太適合進行大規模的資料儲存。

所以最好的處理方式還是將資料儲存在常用的資料庫系統中,本例採用的資料庫為 mongodb

<h4>引數的配置</h4>

新建一個檔案,命名為 config.json,放在 pyspider 檔案目錄下,以 JSON 格式儲存配置資訊。檔案到時候作為 pyspider 配置命令的引數。

檔案具體內容如下:

{
    "taskdb": "mongodb+taskdb://127.0.0.1:27017/pyspider_taskdb",
    "projectdb": "mongodb+projectdb://127.0.0.1:27017/pyspider_projectdb",
    "resultdb": "mongodb+resultdb://127.0.0.1:27017/pyspider_resultdb",
    "message_queue": "redis://127.0.0.1:6379/db",
    "webui": {
        "port": 5001
    }
}

指定了資料庫的地址,"webui" 指定網頁的埠,這時候我們可以改成 5001 試試,不再用預設的 5000。

Ps. 在執行之前,你得保證開啟本地的資料庫 mongodb 和 redis,具體怎麼玩自行google,反正這兩個介面跑起來就對了~

28.jpg

29.jpg

通過設定引數的命令重新執行起來:

pyspider --config config.json 

<h4>資料庫儲存的實現</h4>

通過過載 on_result(self, result): 函式,將結果儲存到 mongodb 中,具體程式碼實現

import pymongo

...

def on_result(self, result):
    if not result:
        return
    
    client = pymongo.MongoClient(host='127.0.0.1', port=27017)
    db = client['pyspyspider_projectdb']
    coll = db['website']
    
    data = {
        'title': result['title'],
        'tags': result['tags'],
        'description': result['description'],
        'website_url': result['website_url'],
        'image_url_list': result['image_url_list']
    }
    
    data_id = coll.insert(data)
    print (data_id)

on_result(self, result) 在每一步抓取中都會呼叫,但只在 detail_page 函式呼叫後引數中的 result 才會不為 None,所以需要在開始的地方加上判斷。

db = client['pyspyspider_projectdb'] 中資料庫的名字 pyspyspider_projectdb 為之前在 config.json 配置檔案中的值。

coll = db['website'] 在資料庫中建立了一張名為 website 的表。

data_id = coll.insert(data) 將資料以我們制定的模式儲存到 mongodb 中。

(除了過載on_result方法外,也可以通過過載ResultWorker類來實行結果的處理,需要在配置檔案中加上對應的引數。)

重新新建一個任務,將完整的程式碼拷進去,在主介面完成的跑一遍。

執行過程中可以看到 mongodb 中的列印不斷有資料插入

30.jpg

執行完成後,瀏覽器檢視結果,因為設定了資料庫的儲存,不再儲存在預設的 resultdb 中,此時瀏覽器的result介面是沒有資料的

31.jpg

通過命令列進入資料庫查詢資料:

use pyspyspider_projectdb
db.website.find()

可看到儲存到的資料

{ "_id" : ObjectId("5819e422e8e70103751f0f4c"), "image_url_list" : [ "http://media.reeoo.com/MING Labs.png!main" ], "website_url" : "https://minglabs.com/en", "tags" : [ "design company", "onepage", "showcase" ], "description" : "MING Labs is a UX design and development company with offices in Germany, China and Singapore. We craft digital products for all screens and platforms.", "title" : "MING Labs" }
{ "_id" : ObjectId("581ad797e8e7010fa8ea85c1"), "tags" : [ "onepage" ], "title" : "SpaceTravellers", "website_url" : "https://www.totaltankstelle.de/spacetravellers/", "image_url_list" : [ "http://media.reeoo.com/SpaceTravellers.png!main" ], "description" : "Sie haben Treibstoff gesucht und viel mehr gefunden." }
{ "_id" : ObjectId("581ad8ede8e70112e51bd4e1"), "website_url" : "http://aftershock.cc/", "title" : "Aftershock", "tags" : [ "onepage" ], "description" : "Evento de design para aqueles que estão tentando viver (ou quase) de arte. Chega mais! Dia 22-23 de outubro. Botafogo-RJ", "image_url_list" : [ "http://media.reeoo.com/Aftershock.png!main" ] }
{ "_id" : ObjectId("581ad8ede8e70112e51bd4e3"), "website_url" : "http://www.proudandpunch.com.au/", "title" : "Proud & Punch", "tags" : [ "food", "ice cream", "onepage" ], "description" : "At Proud & Punch, we’re all about real flavours that pack a punch. We start with fresh ingredients and turn them into tasty treats with a whole lot of flair, right here in Australia. We don’t take shortcuts and have nothing to hide. We’re just proudly real, proudly delicious and proudly here to give you the feel-good treat you’ve been waiting for.", "image_url_list" : [ "http://media.reeoo.com/Proud & Punch.png!main" ] }
{ "_id" : ObjectId("581ad8ede8e70112e51bd4e5"), "website_url" : "http://www.mobil1.com.sg/theendlessrace-game/", "title" : "The Endless Race", "tags" : [ "game" ], "description" : "", "image_url_list" : [ "http://media.reeoo.com/The Endless Race.png!main" ] }
{ "_id" : ObjectId("581ad8eee8e70112e51bd4e7"), "website_url" : "http://www.maztri.com/en/", "title" : "Maztri", "tags" : [ "design agency", "showcase" ], "description" : "Maztri est une agence d’architecture intérieure et de design qui travaille sur la sensorialité des espaces et des objets qui nous entourent.", "image_url_list" : [ "http://media.reeoo.com/Maztri.png!main" ] }
...

至此,我們便已將所抓取到的結果儲存到了本地。