python網路爬蟲學習(六)利用Pyspider+Phantomjs爬取淘寶模特圖片
一.新的問題與工具
平時在淘寶上剁手的時候,總是會看到各種各樣的模特。由於自己就讀於一所男女比例三比一的工科院校……寫程式碼之餘看看美女也是極好的放鬆方式。但一張一張點右鍵–另存為又顯得太過麻煩而且不切實際,畢竟圖片太多了。於是,我開始考慮用萬能的python來解決問題。
要用爬蟲去爬,就得先分析頁面,我們開啟第一個淘女郎的頁面,如圖
看起來好像和我們之前爬過的站沒什麼不同嘛,直接上urllib和urllib2不就好了?不過,當我開啟頁面原始碼時,發現並非如此,原始碼如圖
這個頁面的很多內容並沒有出現在原始碼中,很明顯,使用js加載出來的。那麼,我們常用的urllib2庫也就起不了什麼作用了,因為這個庫只能獲取到HTML中的內容。於是,在經過查詢資料後, 我找到了另外兩個好用的工具:Pyspider和Phantomjs
Pyspider是一個爬蟲框架,具有webUI,CSS選擇器等實用的功能,支援多執行緒爬取、JS動態解析,提供了可操作介面、出錯重試、定時爬取等等的功能,使用非常人性化。
PhantomJS 是一個基於 WebKit 的伺服器端 JavaScript API。它全面支援web而不需瀏覽器支援,其快速、原生支援各種Web標準:DOM 處理、CSS 選擇器、JSON、Canvas 和 SVG。 PhantomJS 可以用於頁面自動化、網路監測、網頁截圖以及無介面測試等。
在介紹完我們的主角之後,我會對我配置開發環境的過程中遇到的問題進行記錄。
二.開發環境的配置
系統環境:Ubuntu 14.04 -i386(注意一定要使用32位的Ubuntu,因為經過我的實際測試phantomjs無法在64位系統上執行,另外,也無法再16.04版本的Ubuntu上執行,建議系統環境與我保持一致)
step 1:安裝pip
sudo apt-get install python-pip
step 2:安裝Phantomjs
sudo apt-get install phantomjs
step 3:安裝Pyspider
根據官方文件,在在安裝pyspider之前,你需要安裝以下類庫
sudo apt-get install python python-dev python-distribute python-pip libcurl4-openssl-dev libxml2-dev libxslt1-dev python-lxml
安裝過程完成之後,執行
sudo pyspider all
不報錯即為安裝成功,如果有錯誤,請善用google和baidu,都可以找到答案。
之後進入圖形介面,開啟瀏覽器在位址列中輸入localhost:5000即可
下面記錄一個似乎是隻有我遇到的問題,在使用ubuntu自帶的firefox時,在之後的一步操作中會出現問題,至於問題是什麼待會兒陳述,強烈建議各位使用應用商店中別的瀏覽器。
之後開啟目標網站https://mm.taobao.com/json/request_top_list.htm?page=1
開啟第一位模特的頁面,如圖
注意到個性域名了嗎?進去看看~
原來模特的圖片都在這裡,這樣我們大致確定了爬取思路:
1.在某一頁中先爬取每位模特的詳情頁
2.在詳情頁中取出我們需要的個性域名的url
3.從個性域名中篩選出圖片並儲存。
三.開始爬取
(1)建立專案
在瀏覽器中輸入 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: 開啟除錯模式,左側最大化,便於觀察除錯。
(2)進行簡單的爬取操作
from pyspider.libs.base_handler import *
class Handler(BaseHandler):
crawl_config = {
}
def __init__(self):
self.base_url = 'https://mm.taobao.com/json/request_top_list.htm?page='
self.page_num = 1
self.total_num = 30
@every(minutes=24 * 60)
def on_start(self):
while self.page_num <= self.total_num:
url = self.base_url + str(self.page_num)
print url
self.crawl(url, callback=self.index_page)
self.page_num += 1
@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(),
}
其實只是在原先的程式碼中加入了一個初始化的方法,沒有做其他改變,儲存後執行,我們看到
我們看到控制檯中給出了30個url,同時看到follow上的數字為30,這表示之後有30個請求,我們點選某一個URL右側的綠色三角,繼續爬取該頁面。
我們點選左下角的web按鈕,就可以預覽到網頁的內容,網頁內容被回撥給index.page方法處理,由於此時還沒有編寫index.page的具體方法,所以只是繼續構建了連結請求。
(2)獲取模特個性域名
在上一步中,我們已經看到了模特的列表,但是該如何進入詳情頁呢?老方法,分析頁面原始碼
注意高亮部分,模特的詳情頁的URL是在一個class為”lady-name”的a標籤中。那麼我們就要對index.page方法加以修改如下:
def index_page(self, response):
for each in response.doc('.lady-name').items():
self.crawl(each.attr.href, callback=self.detail_page)
其中的response就是指剛才的模特列表頁,呼叫doc函式其實是用CSS選擇器得到每個模特的連結,然後用self.crawl方法繼續發起請求。回撥函式是 detail_page,爬取的結果會作為 response 變數傳過去。detail_page 接到這個變數繼續下面的分析。
繼續點選小三角爬取下一個頁面,得到的頁面如下
似乎和我們在瀏覽器中看到的有點不一樣,圖片沒有被加載出來。什麼原因呢?因為這個頁面比較特殊,它是用JS動態加載出來的,所以我們看不到任何內容。怎麼辦呢?我們就要使用神器Phantomjs
了。我們對index.page方法作出如下的修改:
def index_page(self, response):
for each in response.doc('.lady-name').items():
self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js')
只需要在self.crawl方法中加上 fetch=’js’即可,這表示使用phantomjs來載入頁面。
請各位注意,在文章開始時,我提到過我遇到一個難以解決的問題,當我使用firefox瀏覽器開啟pyspider的webUI時,這一步即使我加上 fetch=’js’依然沒有加載出頁面,直到後來我換用了chromeium瀏覽器才解決這個問題,請各位注意!!我在這個問題上浪費了兩天時間,希望各位不要在這個問題上耽誤太多時間
現在點擊向左的箭頭返回到上一頁,重新爬取模特的詳情頁,頁面被成功載入,phantomjs名不虛傳
看到個性域名了嗎?我們離成功只差一步,下面我們解決如何從詳情頁中獲取到模特的個性域名。
老規矩,F12開啟chrome的開發者模式,搜尋個性域名的URL,找到其所在的標籤。
找到後,增加一個domain_page方法用來處理個性域名,並修改detail_page方法如下:
def detail_page(self, response):
domain = 'https:' + response.doc('.mm-p-domain-info li > span').text()
print domain
self.crawl(domain, callback=self.domain_page)
def domain_page(self, response):
pass
mm-p-domain-info li > span指的是從域名所在的div標籤到span標籤的路徑上的所有祖先節點。再加上”https:”就組成了個性域名的URL。之後使用self.crawl來繼續對domain這個頁面進行爬取。
再次點選run按鈕,我們就可以看到模特的主頁了
接下來該怎麼辦?你懂得~~~~
(3)儲存照片和簡介
進入了模特的個人頁面,我們再次分析原始碼,尋找簡介和照片的位置。
找到模特姓名所在的標籤,那麼要使用CSS選擇器進行解析的話,路徑應該是這樣的: .mm-p-model-info-left-top dd > a
同樣的方法找到簡介為: .mm-aixiu-content
圖片為: .mm-aixiu-content img
這樣我們就定位了我們所需要的資訊在頁面上的位置。
之後我們完善domain_page方法如下:
def domain_page(self, response):
name=response.doc('.mm-p-model-info-left-top dd > a').text()
dir_path=self.deal.mkDIR(name)
brief=response.doc('.mm-aixiu-content').text()
if dir_path:
imgs=response.doc('.mm-aixiu-content img').items()
count=1
self.deal.save_brief(brief,name,dir_path)
for img in imgs:
url = img.attr.src
if url:
extension=self.deal.getextension(url)
file_name=name+str(count)+'.'+extension
#self.deal.save_Img(img.attr.src,file_name)
count += 1
self.crawl(img.attr.src, callback=self.save_img, save={'save_path':dir_path,'file_name':file_name})
def save_img(self,response):
content=response.content
dir_path=response.save['save_path']
file_name=response.save['file_name']
file_path=dir_path+'/'+file_name
self.deal.save_Img(content,file_path)
上述程式碼首先用save_brief方法儲存頁面所有文字,之後遍歷模特所有圖片,用save_Img方法儲存所有照片,並使用一個自增變數+模特姓名來構造檔名。
(3)儲存照片至本地
下面定義檔案處理類
class Deal:
def __init__(self):
self.dir_path=DIR_PATH
if not self.dir_path.endswith('/'):
self.dir_path=self.dir_path+'/'
if not os.path.exists(self.dir_path):
os.makedirs(self.dir_path)
def mkDIR(self,name):
name=name.strip()
#dir_name=self.dir_path+'/'+name
dir_name=self.dir_path+name
exists=os.path.exists(dir_name)
if not exists:
os.makedirs(dir_name)
return dir_name
else:
return dir_name
def save_Img(self,content,file_name):
file=open(file_name,'wb')#這個地方將圖片以二進位制流寫入檔案
file.write(content)
file.close()
def save_brief(self,brief,name,path):
file_name=path+'/'+name+'.txt'
file=open(file_name,'w+')
file.write(brief.encode('utf-8'))
file.close()
def getextension(self,url):
extension=url.split('.')[-1]
return extension
檔案處理類主要使用os模組的一些方法,都是基礎內容,沒什麼好說的,有不懂的地方請使用搜索引擎或者翻閱python教程。
三.大功告成
到這裡,我們就完成了基本的功能,下面貼出完整程式碼,version 1.0.0就算基本完成。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2016-06-15 10:48:25
# Project: taobaomm_v1
# Version:1.0.0
# Function:儲存模特照片至本地,以模特姓名建立資料夾來分類儲存
from pyspider.libs.base_handler import *
import os
PAGE_NUM=1#起始頁碼
MAX_PAGE=30#終止頁碼
DIR_PATH='/home/kelvinmao/Music/'#定義儲存位置(這三個變數都可以自己填)
class Handler(BaseHandler):
crawl_config = {
}
def __init__(self):
self.base_url='https://mm.taobao.com/json/request_top_list.htm?page='
self.page_num=PAGE_NUM
self.max_page=MAX_PAGE
self.deal=Deal()
@every(minutes=24 * 60)
def on_start(self):
while self.page_num<=self.max_page:
url=self.base_url+str(self.page_num)
print url
self.crawl(url, callback=self.index_page)
self.page_num+=1
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('.lady-name').items():
self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js')
@config(priority=2)
def detail_page(self, response):
domain = response.doc('.mm-p-domain-info li > span').text()
if domain:
domain_name = 'https:' + domain
self.crawl(domain_name, callback=self.domain_page)
def domain_page(self, response):
name=response.doc('.mm-p-model-info-left-top dd > a').text()
dir_path=self.deal.mkDIR(name)
brief=response.doc('.mm-aixiu-content').text()
if dir_path:
imgs=response.doc('.mm-aixiu-content img').items()
count=1
self.deal.save_brief(brief,name,dir_path)
for img in imgs:
url = img.attr.src
if url:
extension=self.deal.getextension(url)
file_name=name+str(count)+'.'+extension
#self.deal.save_Img(img.attr.src,file_name)
count += 1
self.crawl(img.attr.src, callback=self.save_img, save={'save_path':dir_path,'file_name':file_name})
def save_img(self,response):
content=response.content
dir_path=response.save['save_path']
file_name=response.save['file_name']
file_path=dir_path+'/'+file_name
self.deal.save_Img(content,file_path)
class Deal:
def __init__(self):
self.dir_path=DIR_PATH
if not self.dir_path.endswith('/'):
self.dir_path=self.dir_path+'/'
if not os.path.exists(self.dir_path):
os.makedirs(self.dir_path)
def mkDIR(self,name):
name=name.strip()
#dir_name=self.dir_path+'/'+name
dir_name=self.dir_path+name
exists=os.path.exists(dir_name)
if not exists:
os.makedirs(dir_name)
return dir_name
else:
return dir_name
def save_Img(self,content,file_name):
file=open(file_name,'wb')
file.write(content)
file.close()
def save_brief(self,brief,name,path):
file_name=path+'/'+name+'.txt'
file=open(file_name,'w+')
file.write(brief.encode('utf-8'))
file.close()
def getextension(self,url):
extension=url.split('.')[-1]
return extension
貼上這些程式碼到你的pyspider中,儲存
點選STOP或者TODO狀態,將其切換為RUNNING,點選右側的run按鈕,就可以看到海量的照片奔向你的電腦了。
四.後續
隨著學習的深入,打算增添一些更有趣的功能,比如將模特的三圍,居住地,身高體重或者風格等資料建立資料庫,便於檢索,甚至可以建立一個網站。不過一切都依賴於我的繼續學習。