Python 爬蟲 + 人臉檢測 —— 知乎高顏值圖片抓取
1 資料來源
知乎 話題『美女』下所有問題中回答所出現的圖片
2 抓取工具
Python 3,並使用第三方庫 Requests、lxml、AipFace,程式碼共 100 + 行
3 必要環境
Mac / Linux / Windows (Linux 沒測過,理論上可以。Windows 之前較多反應出現異常,後查是 windows 對本地檔名中的字元做了限制,已使用正則過濾),無需登入知乎(即無需提供知乎帳號密碼),人臉檢測服務需要一個百度雲帳號(即百度網盤 / 貼吧帳號)
4 人臉檢測庫
AipFace,由百度雲 AI 開放平臺提供,是一個可以進行人臉檢測的 Python SDK。可以直接通過 HTTP 訪問,免費使用
http://ai.baidu.com/ai-doc/FACE/fk3co86lr
5 檢測過濾條件
- 過濾所有未出現人臉圖片(比如風景圖、未露臉身材照等)
- 過濾所有非女性(在抓取中,發現知乎男性圖片基本是明星,故不考慮;存在 AipFace 性別識別不準的情況)
- 過濾所有非真實人物,比如動漫人物 (AipFace Human 置信度小於 0.6)
- 過濾所有顏值評分較低圖片(AipFace beauty 屬性小於 45,為了節省儲存空間;再次宣告,AipFace 評分無任何客觀性)
在這裡還是要推薦下我自己建的Python開發學習群:810735403
6 實現邏輯
- 通過 Requests 發起 HTTP 請求,獲取『美女』下的部分討論列表
- 通過 lxml 解析抓取到的每個討論中 HTML,獲取其中所有的 img 標籤相應的 src 屬性
- 通過 Requests 發起 HTTP 請求,下載 src 屬性指向圖片(不考慮動圖)
- 通過 AipFace 請求對圖片進行人臉檢測
- 判斷是否檢測到人臉,並使用 『4 檢測過濾條件』過濾
- 將過濾後的圖片持久化到本地檔案系統,檔名為 顏值 + 作者 + 問題名 + 序號
- 返回第一步,繼續
7 抓取結果
直接存放在資料夾中(angelababy 實力出境)。另外說句,目前抓下來的圖片,除 baby 外,88 分是最高分。個人對其中的排序表示反對,老婆竟然不是最高分
8 程式碼
- 8.1 直接使用 百度雲 Python-SDK 程式碼 —— 已移除
- 8.2不使用 SDK,直接構造 HTTP 請求版本。直接使用這個版本有個好處,就是不依賴於 SDK 的版本(百度雲現在有兩個版本的介面 —— V2 和 V3。現階段,百度雲同時支援兩種介面,所以直接使用 SDK 是沒問題的。等以後哪一天百度不支援 V2 了,就務必升級
SDK 或使用這個直接構造 HTTP 版本)
1 #coding: utf-8 2 3 import time 4 import os 5 import re 6 7 import requests 8 from lxml import etree 9 10 from aip import AipFace 11 12 #百度雲 人臉檢測 申請資訊 13 #唯一必須填的資訊就這三行 14 APP_ID = "xxxxxxxx" 15 API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxx" 16 SECRET_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 17 18 # 檔案存放目錄名,相對於當前目錄 19 DIR = "image" 20 # 過濾顏值閾值,儲存空間大的請隨意 21 BEAUTY_THRESHOLD = 45 22 23 #瀏覽器中開啟知乎,在開發者工具複製一個,無需登入 24 #如何替換該值下文有講述 25 AUTHORIZATION = "oauth c3cef7c66a1843f8b3a9e6a1e3160e20" 26 27 #以下皆無需改動 28 29 #每次請求知乎的討論列表長度,不建議設定太長,注意節操 30 LIMIT = 5 31 32 #這是話題『美女』的 ID,其是『顏值』(20013528)的父話題 33 SOURCE = "19552207" 34 35 #爬蟲假裝下正常瀏覽器請求 36 USER_AGENT = "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.5 Safari/534.55.3" 37 #爬蟲假裝下正常瀏覽器請求 38 REFERER = "https://www.zhihu.com/topic/%s/newest" % SOURCE 39 #某話題下討論列表請求 url 40 BASE_URL = "https://www.zhihu.com/api/v4/topics/%s/feeds/timeline_activity" 41 #初始請求 url 附帶的請求引數 42 URL_QUERY = "?include=data%5B%3F%28target.type%3Dtopic_sticky_module%29%5D.target.data%5B%3F%28target.type%3Danswer%29%5D.target.content%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%3Bdata%5B%3F%28target.type%3Dtopic_sticky_module%29%5D.target.data%5B%3F%28target.type%3Danswer%29%5D.target.is_normal%2Ccomment_count%2Cvoteup_count%2Ccontent%2Crelevant_info%2Cexcerpt.author.badge%5B%3F%28type%3Dbest_answerer%29%5D.topics%3Bdata%5B%3F%28target.type%3Dtopic_sticky_module%29%5D.target.data%5B%3F%28target.type%3Darticle%29%5D.target.content%2Cvoteup_count%2Ccomment_count%2Cvoting%2Cauthor.badge%5B%3F%28type%3Dbest_answerer%29%5D.topics%3Bdata%5B%3F%28target.type%3Dtopic_sticky_module%29%5D.target.data%5B%3F%28target.type%3Dpeople%29%5D.target.answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics%3Bdata%5B%3F%28target.type%3Danswer%29%5D.target.content%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%3Bdata%5B%3F%28target.type%3Danswer%29%5D.target.author.badge%5B%3F%28type%3Dbest_answerer%29%5D.topics%3Bdata%5B%3F%28target.type%3Darticle%29%5D.target.content%2Cauthor.badge%5B%3F%28type%3Dbest_answerer%29%5D.topics%3Bdata%5B%3F%28target.type%3Dquestion%29%5D.target.comment_count&limit=" + str(LIMIT) 43 44 #指定 url,獲取對應原始內容 / 圖片 45 def fetch_image(url): 46 try: 47 headers = { 48 "User-Agent": USER_AGENT, 49 "Referer": REFERER, 50 "authorization": AUTHORIZATION 51 } 52 s = requests.get(url, headers=headers) 53 except Exception as e: 54 print("fetch last activities fail. " + url) 55 raise e 56 57 return s.content 58 59 #指定 url,獲取對應 JSON 返回 / 話題列表 60 def fetch_activities(url): 61 try: 62 headers = { 63 "User-Agent": USER_AGENT, 64 "Referer": REFERER, 65 "authorization": AUTHORIZATION 66 } 67 s = requests.get(url, headers=headers) 68 except Exception as e: 69 print("fetch last activities fail. " + url) 70 raise e 71 72 return s.json() 73 74 #處理返回的話題列表 75 def process_activities(datums, face_detective): 76 for data in datums["data"]: 77 78 target = data["target"] 79 if "content" not in target or "question" not in target or "author" not in target: 80 continue 81 82 #解析列表中每一個元素的內容 83 html = etree.HTML(target["content"]) 84 85 seq = 0 86 87 #question_url = target["question"]["url"] 88 question_title = target["question"]["title"] 89 90 author_name = target["author"]["name"] 91 #author_id = target["author"]["url_token"] 92 93 print("current answer: " + question_title + " author: " + author_name) 94 95 #獲取所有圖片地址 96 images = html.xpath("//img/@src") 97 for image in images: 98 if not image.startswith("http"): 99 continue 100 s = fetch_image(image) 101 102 #請求人臉檢測服務 103 scores = face_detective(s) 104 105 for score in scores: 106 filename = ("%d--" % score) + author_name + "--" + question_title + ("--%d" % seq) + ".jpg" 107 filename = re.sub(r'(?u)[^-\w.]', '_', filename) 108 #注意檔名的處理,不同平臺的非法字元不一樣,這裡只做了簡單處理,特別是 author_name / question_title 中的內容 109 seq = seq + 1 110 with open(os.path.join(DIR, filename), "wb") as fd: 111 fd.write(s) 112 113 #人臉檢測 免費,但有 QPS 限制 114 time.sleep(2) 115 116 if not datums["paging"]["is_end"]: 117 #獲取後續討論列表的請求 url 118 return datums["paging"]["next"] 119 else: 120 return None 121 122 def get_valid_filename(s): 123 s = str(s).strip().replace(' ', '_') 124 return re.sub(r'(?u)[^-\w.]', '_', s) 125 126 import base64 127 def detect_face(image, token): 128 try: 129 URL = "https://aip.baidubce.com/rest/2.0/face/v3/detect" 130 params = { 131 "access_token": token 132 } 133 data = { 134 "face_field": "age,gender,beauty,qualities", 135 "image_type": "BASE64", 136 "image": base64.b64encode(image) 137 } 138 s = requests.post(URL, params=params, data=data) 139 return s.json()["result"] 140 except Exception as e: 141 print("detect face fail. " + url) 142 raise e 143 144 def fetch_auth_token(api_key, secret_key): 145 try: 146 URL = "https://aip.baidubce.com/oauth/2.0/token" 147 params = { 148 "grant_type": "client_credentials", 149 "client_id": api_key, 150 "client_secret": secret_key 151 } 152 s = requests.post(URL, params=params) 153 return s.json()["access_token"] 154 except Exception as e: 155 print("fetch baidu auth token fail. " + url) 156 raise e 157 158 def init_face_detective(app_id, api_key, secret_key): 159 # client = AipFace(app_id, api_key, secret_key) 160 # 百度雲 V3 版本介面,需要先獲取 access token 161 token = fetch_auth_token(api_key, secret_key) 162 def detective(image): 163 #r = client.detect(image, options) 164 # 直接使用 HTTP 請求 165 r = detect_face(image, token) 166 #如果沒有檢測到人臉 167 if r is None or r["face_num"] == 0: 168 return [] 169 170 scores = [] 171 for face in r["face_list"]: 172 #人臉置信度太低 173 if face["face_probability"] < 0.6: 174 continue 175 #顏值低於閾值 176 if face["beauty"] < BEAUTY_THRESHOLD: 177 continue 178 #性別非女性 179 if face["gender"]["type"] != "female": 180 continue 181 scores.append(face["beauty"]) 182 183 return scores 184 185 return detective 186 187 def init_env(): 188 if not os.path.exists(DIR): 189 os.makedirs(DIR) 190 191 init_env() 192 face_detective = init_face_detective(APP_ID, API_KEY, SECRET_KEY) 193 194 url = BASE_URL % SOURCE + URL_QUERY 195 while url is not None: 196 print("current url: " + url) 197 datums = fetch_activities(url) 198 url = process_activities(datums, face_detective) 199 #注意節操,爬蟲休息間隔不要調小 200 time.sleep(5) 201 202 203 # vim: set ts=4 sw=4 sts=4 tw=100 et:
9 執行準備
- 安裝 Python 3,Download Python
- 安裝 requests、lxml、baidu-aip 庫,都可以通過 pip 安裝,一行命令
- 申請百度雲檢測服務,免費。人臉識別-百度AI
將 AppID ApiKek SecretKey 填寫到 程式碼 中
- (可選)配置自定義資訊,如圖片儲存目錄、顏值閾值、人臉置信度等
- (可選)若請求知乎失敗,返回如下。需填寫
AUTHORIZATION,可從開發者工具中獲取(如下圖,換了幾個瀏覽器,目前沒登入情況該值都是一樣的。知乎對爬蟲的態度比較開放,不知道後續是否會更換)
1 { 2 "error": { 3 "message": "ZERR_NO_AUTH_TOKEN", 4 "code": 100, 5 "name": "AuthenticationInvalidRequest" 6 } 7 }
Chrome 瀏覽器;找一個知乎連結點進去,開啟開發者工具,檢視 HTTP 請求 header;無需登入
1 - 執行 ^*^
10 結語
因是人臉檢測,所以可能有些福利會被篩掉。百度影象識別 API 還有一個叫做色情識別。這個 API 可以識別不可描述以及性感指數程度,可以用這個 API 來找福利
https://cloud.baidu.com/product/imagecensoring
- 如果實在不想申請百度雲服務,可以直接把人臉檢測部分註釋掉,當做單純的爬蟲使用
- 人臉檢測部分可以替換成其他廠商服務或者本地模型,這裡用百度雲是因為它不要錢
- 抓了幾千張照片,效果還是挺不錯的。有興趣可以把程式碼貼下來跑跑試試
- 這邊文章只是基礎爬蟲 + 資料過濾來獲取較高質量資料的示例,希望有興趣者可以 run
下,程式碼裡有很多地方可以很容易的修改,從最簡單的資料來源話題變更、抓取資料欄位增加和刪除到圖片過濾條件修改都很容易。如果再稍微花費時間,變更為抓取某人動態(比如輪子哥,資料質量很高)、探索
HTTP 請求中哪些 header 和 query
是必要的,文中程式碼都只需要非常區域性性的修改。至於人臉探測,或者其他機器學習介面,可以提供非常多的功能用於資料過濾,但哪些過濾是具備高可靠性,可信賴的且具備可用性,這個大概是經驗和反覆試驗,這就是額外的話題了;順便希望大家有良好的編碼習慣 - 最後再次宣告,顏值得分以及性別過濾存在 bad case,請勿認真對待
在這裡還是要推薦下我自己建的Python開發學習群:810735403
,群裡都是學Python開發的,如果你正在學習Python ,歡迎你加入,大家都是軟體開發黨,不定期分享乾貨(只有Python軟體開發相關的),包括我自己整理的一份2020最新的Python進階資料和高階開發教程,歡迎進階中和進想深入Python的小夥伴!