Python3 爬蟲學習筆記第十七章 —— 【爬蟲框架 pyspider — 基本使用】
文章目錄
【17.1】初識 pyspider
pyspider 是由國人 Binux 編寫的一個 Python 爬蟲框架
- GitHub:https://github.com/binux/pyspider
- 官方文檔(英文):http://docs.pyspider.org/
- 非官方文檔(中文):http://book.crifan.com/books/python_spider_pyspider/website/
- 非官方文檔(中文):https://www.cntofu.com/book/156/index.md
pyspider 特性:
- python 腳本控制,可以使用任何 html 解析包(內置 pyquery)
- WEB 界面編寫調試腳本,起停腳本,監控執行狀態,查看活動歷史,獲取結果產出
- 支持 MySQL、MongoDB、Redis、SQLite、Elasticsearch、PostgreSQL
- 對接了 PhantomJS,支持抓取 JavaScript 的頁面
- 組件可替換,支持單機和分佈式部署,支持 Docker 部署
- 提供優先級控制、失敗重試、定時抓取等功能
Windows 系統安裝 pyspider:
使用命令 pip install pyspider
安裝,若報 PyCurl 相關錯誤,可訪問 https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycurl 下載對應 wheel 文件並使用命令 pip install whl文件名
安裝即可
如果要爬取 JavaScrip 渲染的頁面,還要下載 PhantomJS,並將 PhantomJS 的路徑配置到環境變量裏,或者直接複製到 Python 安裝目錄的 Scripts 文件夾,需要用到數據庫儲存的話,同樣要安裝好相應的數據庫
準備就緒後,使用 pyspider all
命令可啓動 pyspider,瀏覽器打開:http://localhost:5000/ 可以看到 pyspider 的 WebUI 管理界面
【17.2】使用 pyspider
【17.2.1】主界面
當成功創建了一個爬蟲項目後,主界面如下所示:
-
Recent Active Tasks:查看最近活動的任務,會跳轉到一個頁面有列表顯示
-
Create:創建一個新的爬蟲項目
-
group:定義項目的分組,以方便管理,若 group 設置爲 delete,則該項目將會在24小時之後刪除
-
project name:爬蟲項目名稱
-
status:項目狀態,各狀態如下:
TODO:一個爬蟲項目剛剛創建時的狀態,此狀態下可以編輯 Python 代碼
STOP:中止項目的運行
CHECKING:當一個運行中的項目被編輯時項目狀態會被自動設置成此狀態並中止運行
DEBUG:會運行爬蟲,顧名思義找 BUG,一般來說用於調試階段
RUNNING:運行爬蟲項目
PAUSED:當爬取過程中出現連續多次錯誤時,項目會自動設置爲 PAUSE 狀態,並等待一定時間後繼續爬取,中途突然斷網也會自動設置爲此狀態 -
rate/burst:當前的爬取速率,rate 代表 1 秒發出多少個請求,burst 相當於流量控制中的令牌桶算法的令牌數,rate 和 burst 設置的越大,爬取速率越快,速率的設定需要考慮本機性能和爬取過快被封的問題
-
avg time:任務平均時間
-
process:5m、1h、1d 分別指的是最近 5 分、1 小時、1 天內的請求情況,all 代表所有的請求情況,請求由不同顏色表示,藍色的代表等待被執行的請求,綠色的代表成功的請求,黃色的代表請求失敗後等待重試的請求,紅色的代表失敗次數過多而被忽略的請求
-
actions:對爬蟲項目的操作,各操作如下:
Run:立即執行任務,需要 status 爲 RUNNING 或者 DEBUG 狀態;假如在配置的調度執行時間內已經執行過,再點 run 是無效的,需要刪除 task.db 裏的數據才行
Active Tasks:查看當前爬蟲項目的活動任務
Results:查看項目運行結果
【17.2.2】項目界面
創建一個爬蟲項目,界面如下所示:
- 創建項目:點擊 Create 即可新建一個爬蟲項目
- Project Name:爬蟲項目名稱
- Start URL(s) :爬蟲入口地址,選填,可在項目中更改
項目創建完成進入調試界面:
-
調試界面右邊:編寫代碼的區域
-
調試界面左邊:調試的區域,用於執行代碼,顯示輸出信息等用途
-
run:單步調試爬蟲程序,點擊就可運行當前任務
-
< > 箭頭:上一步、下一步,用於調試過程中切換到上一步驟或者下一步驟
-
save:保存當前代碼,當代碼變更後只有保存了再運行才能得到最新結果
-
enable css selector helper: CSS 選擇器輔助程序
-
web:頁面預覽
-
html:可以查看頁面源代碼
-
follows:表示爬取請求,點擊可查看所有的請求
在新建一個爬蟲項目的時候,pyspider 已經自動生成了如下代碼:
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2019-09-17 21:18:13
# Project: 2
from pyspider.libs.base_handler import *
class Handler(BaseHandler):
crawl_config = {
}
@every(minutes=24 * 60)
def on_start(self):
self.crawl('__START_URL__', 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(),
}
-
class Handler():pyspider 爬蟲的主類,可以在此處定義爬取、解析、存儲的邏輯。整個爬蟲的功能只需要一個 Handler 即可完成
-
crawl_config 屬性:項目的所有爬取配置將會統一定義到這裏,如定義 headers、設置代理等,配置之後全局生效
-
on_start() 方法:爬取入口,初始的爬取請求會在這裏產生,該方法通過調用
crawl()
方法即可新建一個爬取請求,第一個參數是爬取的 URL,另一個參數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 對象做解析,解析之後將結果以字典的形式返回。當然也可以進行後續處理,如將結果保存到數據庫等操作
PS:pyspider 默認的 web 預覽頁面窗口較小,可以找到 pyspider 文件夾有個 debug.min.css 文件(如:E:\Python\Lib\site-packages\pyspider\webui\static\debug.min.css),搜索 iframe,將原樣式:iframe{border-width:0;width:100%}
改爲 iframe{border-width:0;width:100%;height:400px !important}
即可,清除瀏覽器緩存後就會生效!
【17.3】使用 pyspider 爬取去哪兒網
爬取地址:http://travel.qunar.com/travelbook/list.htm
爬取目標:去哪兒網旅遊攻略,發帖作者、標題、正文等
【17.3.1】爬取首頁
創建一個名爲 qunar 的爬蟲項目,Start URL 設置爲 http://travel.qunar.com/travelbook/list.htm ,點擊 run 出現一個爬取請求
左邊調試區域出現以下代碼:
{
"process": {
"callback": "on_start"
},
"project": "qunar",
"taskid": "data:,on_start",
"url": "data:,on_start"
}
callback 爲 on_start,表示此時執行了 on_start()
方法。在 on_start()
方法中,利用 crawl()
方法即可生成一個爬取請求,點擊 index_page 鏈接後面的箭頭會出現許多新的爬取請求,即首頁所包含的所有鏈接
此時左邊調試區域代碼變爲:
{
"fetch": {},
"process": {
"callback": "index_page"
},
"project": "qunar",
"schedule": {
"age": 864000
},
"taskid": "73a789f99528a2bdc3ab83a13902962a",
"url": "http://travel.qunar.com/travelbook/list.htm"
}
callback 變爲了 index_page,表示此時執行了 index_page()
方法。傳入 index_page()
方法的 response 參數爲剛纔生成的第一個爬取請求的 response 對象,然後調用 doc()
方法,傳入提取所有 a 節點的 CSS 選擇器,獲取 a 節點的屬性 href,實現了頁面所有鏈接的提取,隨後遍歷所有鏈接,調用 crawl()
方法,把每個鏈接構造成新的爬取請求,可以看到 follows 新生成了 229 個爬取請求。點擊 web 按鈕可以直接預覽當前頁面,點擊 html 按鈕可以查看此頁面源代碼
【17.3.2】信息匹配
代碼 for each in response.doc('a[href^="http"]').items():
實現了對整個頁面鏈接的獲取,我們需要提取網頁的攻略的標題,內容等信息,那麼直接替換 doc()
方法裏的匹配語句即可,pyspider 提供了非常方便的 CSS 選擇器,點擊 enable css selector helper
按鈕後,選擇要匹配的信息並點擊,再點擊箭頭 add to editor 即可得到匹配語句
完成了 CSS 選擇器的替換,點擊 save
保存,再次點擊 run
重新執行 index_page()
方法,可以看到 follows 變爲了 10 個,即抓取到了 10 篇攻略
【17.3.3】抓取下一頁數據
每一頁只有 10 篇攻略,想要爬取所有頁面的攻略,必須要得到下一頁的數據,優化 index_page()
方法:
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('li > .tit > a').items():
self.crawl(each.attr.href, callback=self.detail_page)
next = response.doc('.next').attr.href
self.crawl(next, callback=self.index_page)
匹配下一頁按鈕,獲取下一頁按鈕的 URL 並賦值給 next,將該 URL 傳給 crawl()
方法,指定回調函數爲 index_page()
方法,這樣會再次調用 index_page()
方法,提取下一頁的攻略標題
【17.3.4】抓取JS渲染數據
隨便點擊一個獲取到的攻略,預覽該頁面,可以觀察到頭圖一直在加載中,切換到 html 查看源代碼頁面,可以觀察到沒有 img 節點,那麼此處就是後期經過 JavaScript 渲染後纔出現的
針對 JavaScript 渲染頁面,可以通過 PhantomJS 來實現,具體到 pyspider 中,只需要在 index_page()
的 crawl()
抓取方法中添加一個參數 fetch_type
即可:
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
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)
保存之後再次運行即可看到正常頁面
【17.3.5】抓取所有數據
改寫 detail_page()
方法,同樣通過 CSS 選擇器提取 URL、標題、日期、作者、正文、圖片等信息:
@config(priority=2)
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
}
【17.3.6】啓動爬蟲項目
該爬蟲項目完整代碼如下:
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2019-09-18 09:48:29
# Project: qunar
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('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)
@config(priority=2)
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 修改爲 RUNNING ,點擊 actions 的 run 按鈕即可啓動爬蟲
點擊 Active Tasks,即可查看最近請求的詳細狀況:
點擊 Results,即可查看所有的爬取結果:
另外,右上角還可以選擇 JSON、CSV 格式