Python3+scrapy+selenium+BaiduAI识别并下载花瓣网高颜值妹子图片

一、说明

1.1 背景说明

上周在“Python3使用百度人脸识别接口识别高颜值妹子图片”中自己说到在成功判断颜值后,下截图片并不是什么难点。

直观感觉上确实如此,你判断的这个url适不适合下载,适合我就去下不适合就不去下,这算什么难点呢。

但事实经常没有想象的那么简单,所以决定去验证一下。结果再次证实自己想简单了,程序的编写和调试花了一周的业余时间,好在总算完成了。

1.2 程序编写过程说明

我以花瓣网http://huaban.com/favorite/beauty/入手,首先确定从beauty这个页面提取形如http://huaban.com/pins/1715655164/的浏览页面(//a/@herf),在这类界面中才真正提取图片src(//img/@src),然后将src提交到百度AI人脸识别接口,如果返回的颜值达标就下载;如果不达标就跳过。

第一个遇到的问题是ImagesPipeline找不到PIL,那就安装PIL,但PyCharm中安装PIL提示没有与当前python匹配的版本;然后自接用conda安装,最后看到环境中的python3.6直接被降成了python2.7,程序报了很多错,百度发现PIL只支持到Python2.7,Python3要安装Pillow,重新配置环境安装Pillow这个问题算处理了。

第二个遇到的问题是ImagesPipeline下载报错,再百度网上说PIL处理压缩图片有问题;ImagesPipeline也就是针对收到的url下载图片面已,自己写个保存函数(程序中的save_image)这个问题也就处理了,但这是有打击的因为我断言的“下载图片Scrapy的ImagesPipeline是绝配”太自以为然了

第三个遇到的问题是花瓣网不是直接整个返回页面,而是使用ajax取回各种元素再构造出的页面;ajax网上看了一般说用selenium处理,好那就去研究

第四个遇到的问题在下拉几个页面后花瓣网要求登录;首先考虑的是借助scrapy的FormRequest.from_response()取回Set-Cookie然后加到selenium的Cookie中去,但后来又想我分明selenium自己可以登录为什么要再多用个FormRequest.from_response()来取Cookie这么麻烦,所以登录这个问题也用selenium处理了

第五个问题是要模拟人看完一页才下拉滚动条加载下一页,异步加载相当于在上一页的后面追加,这样造成的问题是当前抽取出来的浏览页面包含了之前已经加载的所有的浏览页面,由于没有了scrapy我们如何去避免这里的重复问题;这个问题我这里的处理办法是使用for i in range(5):模拟五次异步加载,然后使用viewed_page_count这个变量记录之前异步加载已抽取的浏览页面数量,本次遍历从image_view_page_urls[viewed_page_count]开始(这种处理办法正确的前提是Seletor抽取浏览页面是严格从头向尾抽取的,这我并不确定但应该来说很大可能是这样)

第六个遇到的问题是http://huaban.com/favorite/beauty/,这个界面因为要下拉滚动条异步加载下一页所以这个界面应该保存,而http://huaban.com/pins/1715655164/这些链接需要不断打开,selenium又不支持多选项卡只用selenium构造一个浏览器是不够的;这个问题的处理办法是构造一个浏览器不够,那就构造两个浏览器

第七个遇到的问题是程序跑了大半个小时识别了很多符合下载条件的图片但都还没有下载任何一张图片;这个问题思路是降低程序的复杂度,首先花瓣网是用瀑布流形式的就http://huaban.com/favorite/beauty/一个页面,并不需要爬行所以直接使用Spider而不使用CrawlSpider这样可以去掉Rule;但依然还是没有下载,追踪了半天也没弄清楚不进入下载的原因,又一想既然我不用爬行那Spider的在程序并没有启什么作用而且其导致了我不清楚为什么没有下载图片。所以没有开始下载图片这个问题以弃用scrapy进行处理(我的“下载图片Scrapy的ImagesPipeline是绝配”彻底被否定了)

当然就功能实现来说是有一些捷径可以走的,比如首选登录这个问题如果通过修改User-Agent伪装成手机浏览器是不需要登录的。再比如瀑布流网站异步加载,直接找到ajax接口访问从返回的json内容中就可以提取接链接,或者调大limit一次就可以获取更多的返回结果,并不需要真去下拉滚动条。但,我们就是要正面硬刚,奇技淫巧是不要的。

程序的流程是:

login_in()登录花瓣网

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

在open_main_page()中使用for i in range(5):模拟用户五次下拉滚动条到底部

在for i in range(5):内每次都抽取新加载的浏览页面交给get_img_url_page()获取图片的src

get_img_url_page()将src传到BaiduFaceIdentify进行鉴别,如果评分超过50分就将src传到save_image()保存图片,如果不达标则跳过

1.3 程序运行结果展示

运行输出:

下载图片(我们是在认真地探讨技术,图片这种东西长什么样我根本没注意,我这么说你相信的吧(>_<))

文件夹:

二、程序源代码

使用时三件事:

1) 将以下两个文件保存在同级目录然后建个pic目录

2) huaban.py找到self.browser_main_page.find_element_by_name('email').send_keys('[email protected]')修改成自己花瓣网的用户名密码

3)BaiduFaceIdentify.py找到将client_id赋值成自己的API Key,client_secret赋值成自己的Secret Key

2.1 主程序----huaban.py

使用时,记得找到self.browser_main_page.find_element_by_name('email').send_keys('[email protected]')修改成自己的用户名密码

import re
import time
import logging
import urllib.request
from scrapy import Selector
from selenium import webdriver
from BaiduFaceIdentify import BaiduFaceIdentify


class HuabanDownloader():
    # 初始化函数
    def __init__(self):
        # 百度识别实例
        self.bfi = BaiduFaceIdentify()
        # 用于打开view_page的浏览器
        self.browser_view_page = webdriver.Firefox()
        self.browser_view_page.set_page_load_timeout(10)
        # 用于打开main_page的浏览器
        self.browser_main_page = webdriver.Firefox()
        self.browser_main_page.set_page_load_timeout(20)

    # 此函数用于登录花瓣网
    def login_in(self):
        try:
            # main_page浏览器打开花瓣网,以准备登录
            self.browser_main_page.get('http://huaban.com/')
        except Exception:
            # 如果到时间还没加载完成那就终止还没完成的加载,直接进行后续步骤
            self.browser_main_page.execute_script('window.stop()')
        # 点击登录按钮,唤出登录对话框
        self.browser_main_page.find_element_by_css_selector('.login').click()
        # 填写用户名,改成自己的用户名,我贴上来时乱改的
        self.browser_main_page.find_element_by_name('email').send_keys('[email protected]')
        # 填写密码,改成自己的密码,我贴上来时乱改的
        self.browser_main_page.find_element_by_name('password').send_keys('yourpassword')
        # 点击登录
        self.browser_main_page.find_element_by_css_selector('a.btn:nth-child(4)').click()
        time.sleep(2)

    # 此函数负责打开main_page
    def open_main_page(self,main_page_url):
        # viewed_page_count保存上次异步加载返回的self.browser_main_page.page_source中已有的view_page数量
        viewed_page_count = 0
        try:
            # 登录后,手动重定向到main_page
            self.browser_main_page.get(main_page_url)
        except Exception:
            # 如果到时间还没加载完成那就终止还没完成的加载,直接进行后续步骤
            self.browser_main_page.execute_script('window.stop()')
        # 这个for模拟用户五次将浏览器滚动条拉到了底部(即五次触发加载下一页的ajax)
        # 五次可以爬半天
        for i in range(5):
            try:
                # 浏览器滚动条拉到底部
                self.browser_main_page.execute_script("window.scrollTo(0,document.body.scrollHeight)")
            except Exception:
                # 如果到时间还没加载完成那就终止还没完成的加载,直接进行后续步骤
                self.browser_main_page.execute_script('window.stop()')
            # 获取当前浏览器界面的html源代码
            content = self.browser_main_page.page_source
            # 将html源代码传到选择器
            sel = Selector(text=content)
            # 使用选择器抽取出浏览页面
            image_view_page_urls = sel.xpath('//a/@href').extract()
            # 遍历浏览页面
            for image_view_page_url in image_view_page_urls[viewed_page_count:]:
                # 匹配pins+6位以上数字形式的连接才是我们想访问浏览页面
                # 如果匹配那么传到get_img_url_from_view_page()去抽取图片src
                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)
            # 记录本次self.browser_main_page.page_source中已有的view_page数量
            viewed_page_count = len(image_view_page_urls)

    # 此函数负责从view_page中抽取图片src,并将本次view_page的所有src传到百度识别接口,获取检测结果
    def get_img_url_from_view_page(self, image_view_page_url):
        try:
            # view_page浏览器打开传来的view_page
            self.browser_view_page.get(image_view_page_url)
        except Exception:
            # 如果到时间还没加载完成那就终止还没完成的加载,直接进行后续步骤
            self.browser_view_page.execute_script('window.stop()')
        content = self.browser_view_page.page_source.encode('utf-8')
        sel = Selector(text=content)
        # 从view_page中抽取图片src
        img_urls = sel.xpath('//img/@src').extract()

        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('img_url格式不匹配,将不调用百度识别:http' + img_url)

    # 此函数用于保存颜值达标的图片到本地硬盘
    def save_image(self, img_url,beauty_value):
        # 图片名称使用“颜值”-“下载日期”形式
        image_name = str(beauty_value) + '-' + time.strftime("%Y%m%d%H%M%S", time.localtime()) + '.jpg'
        # 保存图片
        urllib.request.urlretrieve(img_url, 'pic\\' + image_name)


    # 此函数负责在程序退出时将两个浏览器关闭
    def __del__(self):
        self.browser_view_page.close()
        self.browser_main_page.close()


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

 2.2 百度识别接口----BaiduFaceIdentify.py

import base64
import urllib
import requests
import json
import logging


class BaiduFaceIdentify():
    #此函数用于获取access_token,返回access_token的值
    #此函数被parse_face_pic调用
    def get_access_token(self):
        client_id = 'KuLRFhTzX3zBFBSrbQBsl6Q4'                #此变量赋值成自己API Key的值
        client_secret = '8ahbIb2hEOePzXhehw9ZDL9kGvbzIHTU'    #此变量赋值成自己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 Exception:
            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()
        # access_token = '24.1c602a1fc8adbb9edc82d3bee89b229a.2592000.1532370445.282335-11407672'
        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
import base64
import urllib
import requests
import json
import logging


class BaiduFaceIdentify():
#此函数用于获取access_token,返回access_token的值
#此函数被parse_face_pic调用
def get_access_token(self):
client_id = 'KuLRFhTzX3zBFBSrbQBsl6Q4' #此变量赋值成自己API Key的值
client_secret = '8ahbIb2hEOePzXhehw9ZDL9kGvbzIHTU' #此变量赋值成自己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 Exception:
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()
access_token = '24.1c602a1fc8adbb9edc82d3bee89b229a.2592000.1532370445.282335-11407672'
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)

猜你喜欢

转载自www.cnblogs.com/lsdb/p/9221095.html
今日推荐