Python爬蟲進階四之PySpider的用法

審時度勢

PySpider 是一個我個人認為非常方便並且功能強大的爬蟲框架,支持多線程爬取、JS動態解析,提供了可操作界面、出錯重試、定時爬取等等的功能,使用非常人性化。

本篇內容通過跟我做一個好玩的 PySpider 項目,來理解 PySpider 的運行流程。

招兵買馬

具體的安裝過程請查看本節講述

安裝

嗯,安裝好了之後就與我大幹一番吧。

鴻鵠之志

我之前寫過的一篇文章

抓取淘寶MM照片

由於網頁改版,爬取過程中需要的 URL 需要 JS 動態解析生成,所以之前用的 urllib2 不能繼續使用了,在這裡我們利用 PySpider 重新實現一下。

所以現在我們需要做的是抓取淘寶MM的個人信息和圖片存儲到本地。

審時度勢

爬取目標網站:mm.taobao.com/json/requ,大家打開之後可以看到許多淘寶MM的列表。

列表有多少?

mm.taobao.com/json/requ,第10000頁都有,看你想要多少。我什麼也不知道。

隨機點擊一位 MM 的姓名,可以看到她的基本資料。

可以看到圖中有一個個性域名,我們複製到瀏覽器打開。mm.taobao.com/tyy6160

嗯,往下拖,海量的 MM 圖片都在這裡了,怎麼辦你懂得,我們要把她們的照片和個人信息都存下來。

P.S. 注意圖中進度條!你猜有多少圖片~

利劍出鞘

安裝成功之後,跟我一步步地完成一個網站的抓取,你就會明白 PySpider 的基本用法了。

命令行下執行

pyspider alln

這句命令的意思是,運行 pyspider 並 啟動它的所有組件。

可以發現程序已經正常啟動,並在 5000 這個埠運行。

一觸即發

接下來在瀏覽器中輸入 http://localhost:5000,可以看到 PySpider 的主界面,點擊右下角的 Create,命名為 taobaomm,當然名稱你可以隨意取,繼續點擊 Create。

這樣我們會進入到一個爬取操作的頁面。

整個頁面分為兩欄,左邊是爬取頁面預覽區域,右邊是代碼編寫區域。下面對區塊進行說明:

左側綠色區域:這個請求對應的 JSON 變數,在 PySpider 中,其實每個請求都有與之對應的 JSON 變數,包括回調函數,方法名,請求鏈接,請求數據等等。

綠色區域右上角Run:點擊右上角的 run 按鈕,就會執行這個請求,可以在左邊的白色區域出現請求的結果。

左側 enable css selector helper: 抓取頁面之後,點擊此按鈕,可以方便地獲取頁面中某個元素的 CSS 選擇器。

左側 web: 即抓取的頁面的實時預覽圖。

左側 html: 抓取頁面的 HTML 代碼。

左側 follows: 如果當前抓取方法中又新建了爬取請求,那麼接下來的請求就會出現在 follows 里。

左側 messages: 爬取過程中輸出的一些信息。

右側代碼區域: 你可以在右側區域書寫代碼,並點擊右上角的 Save 按鈕保存。

右側 WebDAV Mode: 打開調試模式,左側最大化,便於觀察調試。

乘勝追擊

依然是上一節的那個網址,mm.taobao.com/json/requ,其中 page 參數代表頁碼。所以我們暫時抓取前 30 頁。頁碼到最後可以隨意調整。

首先我們定義基地址,然後定義爬取的頁碼和總頁碼。

from pyspider.libs.base_handler import *nnnclass Handler(BaseHandler):n crawl_config = {n }nndef __init__(self):n self.base_url = https://mm.taobao.com/json/request_top_list.htm?page=n self.page_num = 1n self.total_num = 30nn @every(minutes=24 * 60)ndef on_start(self):nwhile self.page_num <= self.total_num:n url = self.base_url + str(self.page_num)nprint urln self.crawl(url, callback=self.index_page)n self.page_num += 1nn @config(age=10 * 24 * 60 * 60)ndef index_page(self, response):nfor each in response.doc(a[href^="http"]).items():n self.crawl(each.attr.href, callback=self.detail_page)nn @config(priority=2)ndef detail_page(self, response):nreturn {n"url": response.url,n"title": response.doc(title).text(),n }n

點擊 save 保存代碼,然後點擊左邊的 run,運行代碼。

運行後我們會發現 follows 出現了 30 這個數字,說明我們接下來有 30 個新請求,點擊可查看所有爬取列表。另外控制台也有輸出,將所有要爬取的 URL 列印了出來。

然後我們點擊左側任意一個綠色箭頭,可以繼續爬取這個頁面。例如點擊第一個 URL,來爬取這個 URL

點擊之後,再查看下方的 web 頁面,可以預覽實時頁面,這個頁面被我們爬取了下來,並且回調到 index_page 函數來處理,目前 index_page 函數我們還沒有處理,所以是繼續構件了所有的鏈接請求。

好,接下來我們怎麼辦?當然是進入到 MM 到個人頁面去爬取了。

如火如荼

爬取到了 MM 的列表,接下來就要進入到 MM 詳情頁了,修改 index_page 方法。

def index_page(self, response):nfor each in response.doc(.lady-name).items():n self.crawl(each.attr.href, callback=self.detail_page)n

其中 response 就是剛才爬取的列表頁,response 其實就相當於列表頁的 html 代碼,利用 doc 函數,其實是調用了 PyQuery,用 CSS 選擇器得到每一個MM的鏈接,然後重新發起新的請求。

比如,我們這裡拿到的 each.attr.href 可能是 mm.taobao.com/self/model_card.htm?user_id=687471686,在這裡繼續調用了 crawl 方法,代表繼續抓取這個鏈接的詳情。

self.crawl(each.attr.href, callback=self.detail_page)n

然後回調函數就是 detail_page,爬取的結果會作為 response 變數傳過去。detail_page 接到這個變數繼續下面的分析。

好,我們繼續點擊 run 按鈕,開始下一個頁面的爬取。得到的結果是這樣的。

哦,有些頁面沒有載入出來,這是為什麼?

在之前的文章說過,這個頁面比較特殊,右邊的頁面使用 JS 渲染生成的,而普通的抓取是不能得到 JS 渲染後的頁面的,這可麻煩了。

然而,幸運的是,PySpider 提供了動態解析 JS 的機制。

友情提示:可能有的小夥伴不知道 PhantomJS,可以參考

爬蟲JS動態解析

因為我們在前面裝好了 PhantomJS,所以,這時候就輪到它來出場了。在最開始運行 PySpider 的時候,使用了pyspider all命令,這個命令是把 PySpider 所有的組件啟動起來,其中也包括 PhantomJS。

所以我們代碼怎麼改呢?很簡單。

def index_page(self, response):nfor each in response.doc(.lady-name).items():n self.crawl(each.attr.href, callback=self.detail_page, fetch_type=js)n

只是簡單地加了一個 fetch_type=』js』,點擊綠色的返回箭頭,重新運行一下。

可以發現,頁面已經被我們成功載入出來了,簡直不能更帥!

看下面的個性域名,所有我們需要的 MM 圖片都在那裡面了,所以我們需要繼續抓取這個頁面。

勝利在望

好,繼續修改 detail_page 方法,然後增加一個 domain_page 方法,用來處理每個 MM 的個性域名。

def detail_page(self, response):n domain = https: + response.doc(.mm-p-domain-info li > span).text()nprint domainn self.crawl(domain, callback=self.domain_page)nndef domain_page(self, response):npassn

好,繼續重新 run,預覽一下頁面,終於,我們看到了 MM 的所有圖片。

嗯,你懂得!

只欠東風

好,照片都有了,那麼我們就偷偷地下載下來吧~

完善 domain_page 代碼,實現保存簡介和遍歷保存圖片的方法。

在這裡,PySpider 有一個特點,所有的 request 都會保存到一個隊列中,並具有去重和自動重試機制。所以,我們最好的解決方法是,把每張圖片的請求都寫成一個 request,然後成功後用文件寫入即可,這樣會避免圖片載入不全的問題。

曾經在之前文章寫過圖片下載和文件夾創建的過程,在這裡就不多贅述原理了,直接上寫好的工具類,後面會有完整代碼。

import osnnclass Deal:ndef __init__(self):n self.path = DIR_PATHnif not self.path.endswith(/):n self.path = self.path + /nif not os.path.exists(self.path):n os.makedirs(self.path)nndef mkDir(self, path):n path = path.strip()n dir_path = self.path + pathn exists = os.path.exists(dir_path)nif not exists:n os.makedirs(dir_path)nreturn dir_pathnelse:nreturn dir_pathnndef saveImg(self, content, path):n f = open(path, wb)n f.write(content)n f.close()nndef saveBrief(self, content, dir_path, name):n file_name = dir_path + "/" + name + ".txt"n f = open(file_name, "w+")n f.write(content.encode(utf-8))nndef getExtension(self, url):n extension = url.split(.)[-1]nreturn extensionn

這裡面包含了四個方法。

mkDir:創建文件夾,用來創建 MM 名字對應的文件夾。

saveBrief: 保存簡介,保存 MM 的文字簡介。

saveImg: 傳入圖片二進位流以及保存路徑,存儲圖片。

getExtension: 獲得鏈接的後綴名,通過圖片 URL 獲得。

然後在 domain_page 中具體實現如下

def domain_page(self, response):n name = response.doc(.mm-p-model-info-left-top dd > a).text()n dir_path = self.deal.mkDir(name)n brief = response.doc(.mm-aixiu-content).text()nif dir_path:n imgs = response.doc(.mm-aixiu-content img).items()n count = 1n self.deal.saveBrief(brief, dir_path, name)nfor img in imgs:n url = img.attr.srcnif url:n extension = self.deal.getExtension(url)n file_name = name + str(count) + . + extensionn count += 1n self.crawl(img.attr.src, callback=self.save_img,n save={dir_path: dir_path, file_name: file_name})nndef save_img(self, response):n content = response.contentn dir_path = response.save[dir_path]n file_name = response.save[file_name]n file_path = dir_path + / + file_namen self.deal.saveImg(content, file_path)n

以上方法首先獲取了頁面的所有文字,然後調用了 saveBrief 方法存儲簡介。

然後遍歷了 MM 所有的圖片,並通過鏈接獲取後綴名,和 MM 的姓名以及自增計數組合成一個新的文件名,調用 saveImg 方法保存圖片。

爐火純青

好,基本的東西都寫好了。

接下來。繼續完善一下代碼。第一版本完成。

版本一功能:按照淘寶MM姓名分文件夾,存儲MM的 txt 文本簡介以及所有美圖至本地。

可配置項:

  • PAGE_START: 列表開始頁碼
  • PAGE_END: 列表結束頁碼
  • DIR_PATH: 資源保存路徑

#!/usr/bin/env pythonn# -*- encoding: utf-8 -*-n# Created on 2016-03-25 00:59:45n# Project: taobaommnnfrom pyspider.libs.base_handler import *nnPAGE_START = 1nPAGE_END = 30nDIR_PATH = /var/py/mmnnnclass Handler(BaseHandler):n crawl_config = {n }nndef __init__(self):n self.base_url = https://mm.taobao.com/json/request_top_list.htm?page=n self.page_num = PAGE_STARTn self.total_num = PAGE_ENDn self.deal = Deal()nndef on_start(self):nwhile self.page_num <= self.total_num:n url = self.base_url + str(self.page_num)n self.crawl(url, callback=self.index_page)n self.page_num += 1nndef index_page(self, response):nfor each in response.doc(.lady-name).items():n self.crawl(each.attr.href, callback=self.detail_page, fetch_type=js)nndef detail_page(self, response):n domain = response.doc(.mm-p-domain-info li > span).text()nif domain:n page_url = https: + domainn self.crawl(page_url, callback=self.domain_page)nndef domain_page(self, response):n name = response.doc(.mm-p-model-info-left-top dd > a).text()n dir_path = self.deal.mkDir(name)n brief = response.doc(.mm-aixiu-content).text()nif dir_path:n imgs = response.doc(.mm-aixiu-content img).items()n count = 1n self.deal.saveBrief(brief, dir_path, name)nfor img in imgs:n url = img.attr.srcnif url:n extension = self.deal.getExtension(url)n file_name = name + str(count) + . + extensionn count += 1n self.crawl(img.attr.src, callback=self.save_img,n save={dir_path: dir_path, file_name: file_name})nndef save_img(self, response):n content = response.contentn dir_path = response.save[dir_path]n file_name = response.save[file_name]n file_path = dir_path + / + file_namen self.deal.saveImg(content, file_path)nnnimport osnnclass Deal:ndef __init__(self):n self.path = DIR_PATHnif not self.path.endswith(/):n self.path = self.path + /nif not os.path.exists(self.path):n os.makedirs(self.path)nndef mkDir(self, path):n path = path.strip()n dir_path = self.path + pathn exists = os.path.exists(dir_path)nif not exists:n os.makedirs(dir_path)nreturn dir_pathnelse:nreturn dir_pathnndef saveImg(self, content, path):n f = open(path, wb)n f.write(content)n f.close()nndef saveBrief(self, content, dir_path, name):n file_name = dir_path + "/" + name + ".txt"n f = open(file_name, "w+")n f.write(content.encode(utf-8))nndef getExtension(self, url):n extension = url.split(.)[-1]nreturn extensionn

粘貼到你的 PySpider 中運行吧~

其中有一些知識點,我會在後面作詳細的用法總結。大家可以先體會一下代碼。

保存之後,點擊下方的 run,你會發現,海量的 MM 圖片已經湧入你的電腦啦~

需要解釋?需要我也不解釋!

項目代碼

TaobaoMM – GitHub

尚方寶劍

如果想了解 PySpider 的更多內容,可以查看官方文檔。

官方文檔

-------------------------------------

作者:崔慶才

出處:崔慶才的個人博客

最近很多人私信問我問題,平常知乎評論看到不多,如果沒有及時回復,大家也可以加小編微信:tszhihu,進知乎大數據分析挖掘交流群,可以跟各位老師互相交流。謝謝。


推薦閱讀:

TAG:Python | 大数据分析 | Python框架 |