1. 程式人生 > >python網路爬蟲學習(六)利用Pyspider+Phantomjs爬取淘寶模特圖片

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按鈕,就可以看到海量的照片奔向你的電腦了。
這裡寫圖片描述
這裡寫圖片描述

四.後續

隨著學習的深入,打算增添一些更有趣的功能,比如將模特的三圍,居住地,身高體重或者風格等資料建立資料庫,便於檢索,甚至可以建立一個網站。不過一切都依賴於我的繼續學習。