Python3+selenium+BaiduAI識別並下載花瓣網高顏值妹子圖片

1、說明

1.1 背景說明

上週在「Python3使用百度人臉識別接口識別高顏值妹子圖片」中本身說到在成功判斷顏值後,下截圖片並非什麼難點。css

直觀感受上確實如此,你判斷的這個url適不適合下載,適合我就去下不適合就不去下,這算什麼難點呢。html

但事實常常沒有想象的那麼簡單,因此決定去驗證一下。結果再次證明本身想簡單了,程序的編寫和調試花了一週的業餘時間,好在總算完成了。python

 

1.2 程序編寫過程說明

我以花瓣網http://huaban.com/favorite/beauty/入手,首先肯定從beauty這個頁面提取形如http://huaban.com/pins/1715655164/的瀏覽頁面(//a/@herf),在這類界面中才真正提取圖片src(//img/@src),而後將src提交到百度AI人臉識別接口,若是返回的顏值達標就下載;若是不達標就跳過。git

第一個遇到的問題是ImagesPipeline找不到PIL,那就安裝PIL,但PyCharm中安裝PIL提示沒有與當前python匹配的版本;而後自接用conda安裝,最後看到環境中的python3.6直接被降成了python2.7,程序報了不少錯,百度發現PIL只支持到Python2.7,Python3要安裝Pillow,從新配置環境安裝Pillow這個問題算處理了。github

第二個遇到的問題是ImagesPipeline下載報錯,再百度網上說PIL處理壓縮圖片有問題;ImagesPipeline也就是針對收到的url下載圖片面已,本身寫個保存函數(程序中的save_image)這個問題也就處理了,但這是有打擊的由於我斷言的「下載圖片Scrapy的ImagesPipeline是絕配」太自覺得然了web

第三個遇到的問題是花瓣網不是直接整個返回頁面,而是使用ajax取回各類元素再構造出的頁面;ajax網上看了通常說用selenium處理,好那就去研究ajax

第四個遇到的問題在下拉幾個頁面後花瓣網要求登陸;首先考慮的是藉助scrapy的FormRequest.from_response()取回Set-Cookie而後加到selenium的Cookie中去,但後來又想我分明selenium本身能夠登陸爲何要再多用個FormRequest.from_response()來取Cookie這麼麻煩,因此登陸這個問題也用selenium處理了chrome

第五個問題是要模擬人看完一頁才下拉滾動條加載下一頁,異步加載至關於在上一頁的後面追加,這樣形成的問題是當前抽取出來的瀏覽頁面包含了以前已經加載的全部的瀏覽頁面,因爲沒有了scrapy咱們如何去避免這裏的重複問題;這個問題我這裏的處理辦法是使用for i in range(5):模擬五次異步加載,而後使用viewed_page_count這個變量記錄以前異步加載已抽取的瀏覽頁面數量,本次遍歷從image_view_page_urls[viewed_page_count]開始(這種處理辦法正確的前提是Seletor抽取瀏覽頁面是嚴格從頭向尾抽取的,這我並不肯定但應該來講很大多是這樣)express

第六個遇到的問題是http://huaban.com/favorite/beauty/,這個界面由於要下拉滾動條異步加載下一頁因此這個界面應該保存,而http://huaban.com/pins/1715655164/這些連接須要不斷打開,selenium又不支持多選項卡只用selenium構造一個瀏覽器是不夠的;這個問題的處理辦法是構造一個瀏覽器不夠,那就構造兩個瀏覽器json

第七個遇到的問題是程序跑了大半個小時識別了不少符合下載條件的圖片但都尚未下載任何一張圖片;這個問題思路是下降程序的複雜度,首先花瓣網是用瀑布流形式的就http://huaban.com/favorite/beauty/一個頁面,並不須要爬行因此直接使用Spider而不使用CrawlSpider這樣能夠去掉Rule;但依然仍是沒有下載,追蹤了半天也沒弄清楚不進入下載的緣由,又一想既然我不用爬行那Spider的在程序並無啓什麼做用並且其致使了我不清楚爲何沒有下載圖片。因此沒有開始下載圖片這個問題以棄用scrapy進行處理(個人「下載圖片Scrapy的ImagesPipeline是絕配」完全被否認了)

第八個問題是出現了大量的重複圖片;這個問題一是限制main_page只能抽取中間的那些href,二是限制view-page只能抽取右側邊欄的src,三是圖片採用「顏值」-「下載日期」-「url的md5值」形式命名確保圖片不會被重複下載。可是經過這樣限制後發現程序運行一段時間後大量提示「圖片已存在」,下載下來的圖片卻又不多,最後觀察發現是firefox只能打開第一個view_page後再調用get請求下一個view_page時頁面並無成功刷新,這就使得每次獲取的page_source其實都是每個view_page的提取出來的url固然都重複。這個問題經過更換成chrome在必定程度上緩解,但並沒能徹底解決由於chrome在運行一段時間後會出現強制退出的狀況,因此這個問題的根本緣由多是selenium對開兩個瀏覽器支持不完善形成的。

固然就功能實現來講是有一些捷徑能夠走的,好比首選登陸這個問題若是經過修改User-Agent假裝成手機瀏覽器是不須要登陸的。再好比瀑布流網站異步加載直接找到ajax接口訪問從返回的json內容中就能夠提取接連接,或者調大limit一次就能夠獲取更多的返回結果並不須要真去下拉滾動條,又再或者乾脆直接換個網站。但,咱們就是要正面硬剛,奇技淫巧是不要的。

20180627更新:

瀏覽器運行一段時間後掛掉這個問題,首先想多是瀏覽器失去焦點就失效因此將使用兩個瀏覽器改成使用一個瀏覽器兩個選項卡,可是使用選項卡一段時間後同樣仍是出錯,懷疑是選項卡一段時間後就失效因此想打開view_page取完內容後就關閉但仍是出錯,懷疑是跨函數切換選項卡引起的問題將全部代碼都放內一個函數仍是出錯。最後肯定問題是:selenium一段時間(觀察大可能是5秒)不操做後瀏覽器就會失效。處理辦法是在main_page方面將程序原先設想的五次下拉滾動條第次解析各自的view_page改成連續五次下拉滾動條一次性抽取解析全部view_page,在view_page處理方面將程序由原先設想的一個瀏覽器不斷打開新view_page調整爲每次都實例化一個瀏覽器去打開傳來的view_page取得其實內後就將瀏覽器關閉。

程序修改基本到這就定型了,比較惋惜因爲selenium的bug沒能將流程徹底擬人化(固然也多是我不知道該怎麼配置)。而後感謝花瓣網對爬蟲的寬容,感謝百度對識別請求的「來者不拒」。最後對於AI,單就百度人臉檢測這一項來講還有很多問題,好比說有時人臉識不到又好比說人不是站立正面直面前方(側臉、躺着等)評分誤差較大等;總的來講首先AI是一個很好的方向,而後AI比較難百度算中國AI的領導級別了但搞了這麼幾年產品可用性實用性也只能說差強人意,最後就是說對AI的見解計算機行業這麼多年來不少概念都是資本的炒做很不幸AI也就包括在其中。AI恐怕是計算機行業技術最爲密集的領域,百度都是些什麼級別的人才?搞了多久?出了什麼產品?有多大市場?對應的如今一堆搞AI的都是些什麼人?學了多久?你能指望他們能搞出什麼產品?你準備指望他們能佔領什麼市場?或者怎麼定義AI,像我這裏同樣去調個別人的接口就是搞AI嗎。就計算機行業而言政府重視企業投錢社會承認美滋滋不必去說破,只但願輸的人不要太慘。

當前程序的流程是:

login_in()----登陸花瓣網

open_main_page()----手動重定向到http://huaban.com/favorite/beauty/

open_main_page()----使用for i in range(5):模擬用戶4次下拉滾動條到底部

open_main_page()----從五次下拉滾動條後的頁面抽取抽取出全部view_page交給get_img_url_page()獲取圖片的src

get_img_url_page()----將src傳到BaiduFaceIdentify進行鑑別,若是評分超過50分就將src傳到save_image()保存圖片,若是不達標則跳過

save_image()----接收傳過來的圖片url,判斷圖片是否已存在若是未存在則下載保存

 

1.3 程序運行結果展現

運行輸出:

下載圖片(咱們是在認真地探討技術,圖片這種東西長什麼樣我根本沒注意,我這麼說你相信的吧(>_<))

文件夾:

 

2、程序源代碼

使用時五件事:

1) 下載安裝firefox

2) 配置好python環境----python3.x、selenium、requests、lxml

3) 下載geckodriver(chrome下載chromedriver),將之與下面兩個文件保存在同個文件夾下

4) huaban.py----找到self.browser_main_page.find_element_by_name('email').send_keys('youemail@qq.com')修改爲本身花瓣網的用戶名密碼

5) BaiduFaceIdentify.py----找到將client_id賦值成本身的API Key,client_secret賦值成本身的Secret Key

github地址:https://github.com/PrettyUp/BaiduAI

 

2.1 主程序----huaban.py

import hashlib
import os
import re
import time
import logging
import urllib.request
from lxml import etree
from selenium import webdriver
from BaiduFaceIdentify import BaiduFaceIdentify


class HuabanDownloader():
    # 構造函數,實例化bfi、browser、pic_download_count
    def __init__(self):
        # 百度人臉檢測實例
        self.bfi = BaiduFaceIdentify()
        # 用於打開首頁、登陸花瓣並五次下拉滾動條到底部的瀏覽器
        # 在調試時建議使用正常呈現界面的瀏覽器模式,但在程序發佈時建議使用無頭模式
        # 這樣能夠加快速度、減少資源消耗及防止誤關瀏覽器致使程序停止
        # 無頭模式和普通模式在功能和使用上毫無區別,只是在實例化時加入如下--headless和--disable-gpu兩個參數
        self.browser_options = webdriver.FirefoxOptions()
        self.browser_options.add_argument('--headless')
        self.browser_options.add_argument('--disable-gpu')
        self.browser = webdriver.Firefox(firefox_options=self.browser_options)
        # 設置瀏覽器頁面加載超時時間,這裏設置15秒
        self.browser.set_page_load_timeout(15)
        # 類成員變量,用於保存共下載的圖片張數
        self.pic_download_count = 0


    # 此函數用於登陸花瓣網
    def login_in(self, login_page_url):
        try:
            # 使用瀏覽器打開花瓣網,以準備登陸
            self.browser.get(login_page_url)
        except Exception:
            # 若是加載超時,直接停止加載未完成內容運行後續代碼
            self.browser.execute_script('window.stop()')
        time.sleep(1)
        # 找到登陸按鈕並點擊,喚出登陸對話框
        self.browser.find_element_by_xpath('//a[@class="login bounce btn wbtn"]').click()
        # 找到用戶名輸入框,填寫用戶名(我貼上來時亂改的,運行時改爲本身花瓣網的用戶名)
        self.browser.find_element_by_name('email').send_keys('youremail@qq.com')
        # 找到密碼輸入框,填寫密碼(我貼上來時亂改的,運行時改爲本身的密碼)
        self.browser.find_element_by_name('password').send_keys('yourpasswd')
        # 找到登陸按鈕並點擊登陸
        self.browser.find_element_by_css_selector('a.btn:nth-child(4)').click()
        time.sleep(2)

    # 此函數用於打開main_page,下拉五次滾動條,而後提取頁面全部目標a標籤的href
    def open_main_page(self,main_page_url):
        try:
            # 登陸後會自動重定向到我的主頁,咱們手動重定向到main_page
            self.browser.get(main_page_url)
        except Exception:
            # 若是加載超時,直接停止加載未完成內容運行後續代碼
            self.browser.execute_script('window.stop()')
        # for模擬用戶4次將瀏覽器滾動條拉到了底部,因爲打開時直接展現一個版面,因此下拉4次後最終是五個版面
        # 這設置在我電腦大概跑了兩小時下載554張圖片
        for i in range(1,5):
            logging.warning('開始第'+ str(i) +'次下拉滾動條')
            # 瀏覽器執行js將滾動條拉到底部
            self.browser.execute_script("window.scrollTo(0,document.body.scrollHeight)")
            time.sleep(2)

        # 獲取當前瀏覽器界面的html源代碼
        content = self.browser.page_source
        # 使用lxml解析內容構建選擇器
        sel = etree.HTML(content)
        # 提取目標a標籤的href屬性值
        image_view_page_urls = sel.xpath('//div[@id="waterfall"]//a[@class="img x layer-view loaded"]/@href')
        # 關閉瀏覽器,實際使用發現瀏覽器5秒以上沒操做後面就沒用了,因此不關留着後面也用不了
        self.browser.quit()
        # 遍歷瀏覽頁面a標籤的href屬性值,也就是view_page
        for image_view_page_url in image_view_page_urls:
            # 匹配「pins/」+6位以上數值的url纔是咱們的目標view_page
            if re.search('pins/\d{6,}',image_view_page_url):
                logging.warning('view_page url格式匹配,即將進入:' + image_view_page_url)
                image_view_page_url_temp = 'http://huaban.com' + image_view_page_url
                self.get_img_url_from_view_page(image_view_page_url_temp)
            else:
                logging.warning('view_page url格式不匹配,將不進入:'+ image_view_page_url)


    # 此函數負責從view_page中抽取圖片src,並將本次view_page的全部src傳到百度識別接口,獲取檢測結果
    def get_img_url_from_view_page(self, image_view_page_url):
        # 每次都實例化一個瀏覽器來打開傳來的url
        browser_view_page_options = webdriver.FirefoxOptions()
        browser_view_page_options.add_argument('--headless')
        browser_view_page_options.add_argument('--disable-gpu')
        browser_view_page = webdriver.Firefox(firefox_options=browser_view_page_options)
        browser_view_page.set_page_load_timeout(5)
        try:
            # 打開url
            browser_view_page.get(image_view_page_url)
        except Exception:
            # 若是到時間還沒加載完成那就終止還沒完成的加載,直接進行後續步驟
            browser_view_page.execute_script('window.stop()')
        # 獲取當前瀏覽器界面的html源代碼
        content = browser_view_page.page_source
        # 使用lxml解析內容構建選擇器
        sel = etree.HTML(content)
        # 從view_page中抽取圖片src
        img_urls = sel.xpath('//div[@id="board_pins_waterfall"]//img/@src')
        # 關閉瀏覽器,實際使用發現瀏覽器5秒以上沒操做後面就沒用了,因此不關留着後面也用不了
        browser_view_page.quit()
        # 遍歷當前view_page抽取到的圖片src
        for img_url in img_urls:
            # 排除gif及確保圖片不是網站相對圖徑
            if 'gif' not in img_url and 'aicdn.com' in img_url:
                logging.warning('\r\nimg_url格式匹配,即將調用百度識別:http:' + img_url)
                img_url_tmp = 'http:' + img_url[:img_url.find('_')]
                try:
                    # 調用百度識別接口進行識別,固然這個接口是咱們本身封裝的BaiduFaceIdentify類
                    beauty_value = self.bfi.parse_face_pic(img_url_tmp)
                except Exception:
                    logging.error('百度識別遇到了一個錯誤:' + img_url_tmp)
                    continue
                # 對返回的顏值進行判斷,以決定如何處理圖片
                if beauty_value > 50.0:
                    logging.warning('顏值' + str(beauty_value) +'達標,準備確認圖片是否已存在:' + img_url_tmp)
                    self.save_image(img_url_tmp, beauty_value)
                elif beauty_value == 1.0:
                    logging.warning('不是妹子,將不保存該圖片:' + img_url_tmp)
                elif beauty_value == 0.0:
                    logging.warning('沒有人臉,將不保存該圖片:' + img_url_tmp)
                else:
                    logging.warning('顏值' + str(beauty_value) +'不達標,將不保存該圖片:' + img_url_tmp)
            else:
                logging.warning('\r\nimg_url格式不匹配,將不調用百度識別:http' + img_url)


    # 此函數用於將顏值達標的圖片保存到當前路徑的pic目錄下
    def save_image(self, img_url,beauty_value):
        # 圖片名稱使用「顏值」-「下載日期」-「url的md5值」形式
        image_name = str(beauty_value) + '-' + time.strftime("%Y%m%d", time.localtime()) + '-' + hashlib.md5(img_url.encode()).hexdigest()+'.jpg'
        # 判斷pic目錄是否存在,不存在則先建立
        if not os.path.exists('pic'):
            logging.warning('pic目錄尚不存在,即將建立')
            os.mkdir('pic')
        # 判斷圖片是否以前已保存過
        if not os.path.isfile('pic\\' + image_name):
            logging.warning('圖片尚不存在,即將保存:'+ image_name)
            # 保存圖片
            urllib.request.urlretrieve(img_url, 'pic\\' + image_name)
            self.pic_download_count += 1
            logging.warning('當前已保存'+ str(self.pic_download_count) + '張圖片')
        else:
            logging.warning('圖片已存在,將不保存:'+ image_name)


if __name__ == '__main__':
    login_page_url = 'http://huaban.com/'
    main_page_url = 'http://huaban.com/favorite/beauty/'
    huaban_downloader = HuabanDownloader()
    huaban_downloader.login_in(login_page_url)
    huaban_downloader.open_main_page(main_page_url)
View Code

 

 2.2 百度識別接口----BaiduFaceIdentify.py

import base64
import urllib
import json
import logging
import requests


class BaiduFaceIdentify():
    #此函數用於獲取access_token,返回access_token的值
    #此函數被parse_face_pic調用
    def get_access_token(self):
        client_id = 'KuLRFhTzX3zBFBSrbQBsl6Q5'                #此變量賦值成本身API Key的值
        client_secret = '8ahbIb2hEOePzXhehw9ZDL9kGvbzIHTV'    #此變量賦值成本身Secret Key的值
        auth_url = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=' + client_id + '&client_secret=' + client_secret

        response_at = requests.get(auth_url)
        json_result = json.loads(response_at.text)
        access_token = json_result['access_token']
        return access_token

    #此函數進行人臉識別,返回識別到的人臉列表
    #此函數被parse_face_pic調用
    def identify_faces(self,url_pic,url_fi):
        headers = {
            'Content-Type' : 'application/json; charset=UTF-8'
        }
        # 由於提交URL讓baidu去讀取圖片,老是返回圖片下載錯了
        # 因此咱們這裏將讀取URL指向的圖片,將圖片轉成BASE64編碼,改用提交BASE64編碼而不是提交URL
        pic_obj = urllib.request.urlopen(url_pic)
        pic_base64 = base64.b64encode(pic_obj.read())
        post_data = {
            # 'image': url_pic,
            # 'image_type' : 'URL',
            'image': pic_base64,
            'image_type': 'BASE64',
            'face_field' : 'facetype,gender,age,beauty', #expression,faceshape,landmark,race,quality,glasses
            'max_face_num': 1
        }

        response_fi = requests.post(url_fi,headers=headers,data=post_data)
        json_fi_result = json.loads(response_fi.text)
        # 有些圖片是沒有人臉的,或者識別有問題,這個咱們不細管直接捕獲異常就返回空列表
        try:
            # if json_fi_result['result'] is None:
            #     return []
            # else:
                return json_fi_result['result']['face_list']
        except:
            return []
        #下邊的print也許是最直觀,你最想要的
        #print(json_fi_result['result']['face_list'][0]['age'])
        #print(json_fi_result['result']['face_list'][0]['beauty'])

    #此函數用於解析進行人臉圖片,返回圖片中人物顏值
    #此函數調用get_access_token、identify_faces
    def parse_face_pic(self,url_pic):
        #調用get_access_token獲取access_token
        access_token = self.get_access_token()
        url_fi = 'https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=' + access_token
        #調用identify_faces,獲取人臉列表
        json_faces = self.identify_faces(url_pic,url_fi)
        # 若是沒有人臉,那麼就以0.0爲顏值評分返回
        if len(json_faces) == 0:
            logging.warning('未識別到人臉')
            return 0.0
        else:
            for json_face in json_faces:
                logging.debug('種類:'+json_face['face_type']['type'])
                logging.debug('性別:'+json_face['gender']['type'])
                logging.debug('年齡:'+str(json_face['age']))
                logging.debug('顏值:'+str(json_face['beauty']))
                # 若是識別到的不是妹子,也以1.0爲顏值評分返回
                # 若是識別到的是妹子,直接以值顏值返回
                if json_face['gender']['type'] != 'female':
                    logging.info('圖片不是妹子')
                    return 1.0
                else:
                    return json_face['beauty']

if __name__ == '__main__':
    #uil_pic賦值成本身要測試的圖片的url地址
    url_pic = 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1357154930,886228461&fm=27&gp=0.jpg'
    bfi = BaiduFaceIdentify()
    bfi.parse_face_pic(url_pic)
View Code
相關文章
相關標籤/搜索