主要用selenium模塊模擬用戶登陸網(wǎng)站,編寫判斷兩張圖片相匹配的算法以及破解旋轉(zhuǎn)圖片驗(yàn)證碼的總體思路膏蚓,各個(gè)局部功能介紹实愚,比如下載圖片兼呵、切圖兔辅,圖片去重,起瀏覽器模擬用戶登陸等击喂,都有附上相應(yīng)的詳細(xì)代碼维苔,組裝起來(lái)就是實(shí)現(xiàn)登錄的完整代碼
本爬蟲程序功能:
功能 - ->> 用Chrome瀏覽器自動(dòng)登錄某漫畫網(wǎng)站,破解旋轉(zhuǎn)的圖片驗(yàn)證碼懂昂。難點(diǎn)有兩個(gè):一是編寫圖片匹配算法蕉鸳,判斷兩張圖片是否相同,二是要想到破解旋轉(zhuǎn)圖片驗(yàn)證碼的解決思路忍法;
成功破解的效果 - - >> 網(wǎng)站會(huì)自動(dòng)打開潮尝,自動(dòng)點(diǎn)擊頭像,彈出登錄框饿序,自動(dòng)填寫賬號(hào)密碼勉失,自動(dòng)點(diǎn)擊圖片驗(yàn)證碼旋轉(zhuǎn),正確后自動(dòng)點(diǎn)擊登錄原探。
功能演示如下:
準(zhǔn)備工作:
需要安裝Chrome瀏覽器乱凿,以及與瀏覽器版本相對(duì)應(yīng)的chromedriver(https://npm.taobao.org/mirrors/chromedriver),配置相對(duì)應(yīng)的環(huán)境變量咽弦;當(dāng)然徒蟆,肯定得有python,以及相應(yīng)的虛擬環(huán)境型型,安裝相應(yīng)的第三方庫(kù)(selenium段审,requests等)
在創(chuàng)建python項(xiàng)目配好虛擬環(huán)境后,需要?jiǎng)?chuàng)建的文件有(具體如何存放可以自行定義):
1.靜態(tài)文件夾static(放圖片)
2.保存下載的原始驗(yàn)證碼圖片的文件夾jisu_images(在static文件夾下)
3.保存原始圖片剪切下來(lái)的小圖文件夾ringht_img(在static文件夾下)
4.保存去掉重復(fù)后的正確狀態(tài)最終的圖片驗(yàn)證碼資源文件夾imgs(在static文件夾下)
5.創(chuàng)建py文件jisumanhua_rotate_picture.py闹蒜,編寫主要程序
6.創(chuàng)建py文件compare_picture.py寺枉,編寫圖片匹配算法
如下截圖(藍(lán)色部分):
代碼說(shuō)明:
本程序是基于python3,用pycharm開發(fā)的爬蟲程序绷落,包括圖片匹配算法和模擬用戶登陸兩個(gè)py文件姥闪,模擬用戶登陸文件中的代碼并沒有拆分出獲取圖片、裁剪圖片以及下載等等功能進(jìn)行模塊化砌烁,不完全符合高內(nèi)聚低耦合的編程思想筐喳,但已進(jìn)行函數(shù)式編碼處理;
功能實(shí)現(xiàn)的整體思路:
第一步:從網(wǎng)站requests圖片驗(yàn)證碼資源
第二步:crop處理原始驗(yàn)證碼圖片函喉,處理后保存到本地
注意:截圖中切下來(lái)的圖片還沒有去掉重復(fù)的圖片避归,可以先讓程序去重,再進(jìn)行手動(dòng)旋正函似,再次去重得到最后的圖片(哎槐脏,我是沒去重,先手動(dòng)旋正1200張圖片撇寞,再去重顿天,花了我20來(lái)分鐘)
第三步:用selenium模擬用戶打開chrome瀏覽器進(jìn)入該漫畫網(wǎng)站
第四步:用selenium模塊實(shí)現(xiàn)自動(dòng)填寫賬戶密碼
第五步:用selenium模塊切下網(wǎng)頁(yè)截圖(包含驗(yàn)證碼)
程序自動(dòng)截圖堂氯,類似下圖:
第六步:用PIL模塊切出網(wǎng)頁(yè)中的驗(yàn)證碼
效果如下截圖:
第七步:用自己寫的圖片匹配算法將網(wǎng)頁(yè)上切下來(lái)的驗(yàn)證碼圖片與本地圖片驗(yàn)證碼資源相匹配
第八步:匹配成功自動(dòng)點(diǎn)擊登錄
第九步: 匹配不成功,自動(dòng)點(diǎn)擊換一組繼續(xù)匹配
整體代碼布局(沒按嚴(yán)格順序排版牌废,請(qǐng)見諒):
布局截圖(便于截圖咽白,盡量緊湊的放在了一起,請(qǐng)忽略PEP8)鸟缕,下面一張是剩下部分(屏幕太小晶框,一屏截不完整,尷尬):
具體編碼實(shí)現(xiàn):
一:從網(wǎng)站獲取原始驗(yàn)證碼資源:
def get_picture():
"""下載300張?jiān)撀嬀W(wǎng)站的圖片驗(yàn)證碼原始圖片"""
url = 'http://www.1kkk.com//image3.ashx?t=1550843570000'
headers = {
'Referer': 'http://www.1kkk.com/',
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Mobile Safari/537.36'
}
for i in range(300):
response = requests.get(url, headers=headers)
with open('./static/jisu_images/img' + str(i), 'wb') as f:
f.write(response.content)
print(i)
二:編寫圖片匹配算法:
# 聲明一個(gè)圖片類懂从,對(duì)象屬性是圖片的PIL庫(kù)的Image對(duì)象
# compare對(duì)象方法可以判斷兩張圖片是否一樣授段,但是并不能百分百判斷正確,正確率在百分之九十左右
# 設(shè)置判斷兩張圖片相匹配的最低匹配度
compatible = 75
class PictureHash(object):
"""圖片類"""
def __init__(self, obj):
"""param obj: 圖片的Image對(duì)象"""
self.obj = obj
def compare(self, other_picture):
"""
比較兩個(gè)圖片是否一樣
:param other_picture: 另一張圖片的PictureHash對(duì)象
:return: 布爾值(1表示相同番甩,0表示不同)
"""
dist = 0
hash_s = self.get_hash()
hash_o = other_picture.get_hash()
for i in range(0, len(hash_s)):
if hash_s[i] == hash_o[i]:
dist = dist + 1
if dist >= compatible:
return 1
else:
return 0
def get_gray(self):
"""
獲取圖片灰度集合
:return: 灰度列表
"""
a = self.change_size_con()
tmpls = []
for h in range(0, a.size[1]):
for w in range(0, a.size[0]):
tmpls.append(a.getpixel((w, h)))
return tmpls
def change_size_con(self):
"""
改變圖片大小侵贵,轉(zhuǎn)255灰度圖(0到255的值)
:return: 新圖片Image對(duì)象
"""
return self.obj.resize((12, 12)).convert("L")
def get_hash(self):
"""生成圖片哈希表"""
bitls = ''
a = self.change_size_con()
b = self.get_gray()
avg = sum(b) / len(b)
for h in range(1, a.size[1] - 1):
for w in range(1, a.size[0] - 1):
if a.getpixel((w, h)) >= avg:
bitls = bitls + '1'
else:
bitls = bitls + '0'
return bitls
三:處理原始驗(yàn)證碼圖片(切下所有圖片后,需要手動(dòng)將所有圖片旋轉(zhuǎn)到正確狀態(tài)缘薛,然后再去除重復(fù)的圖片窍育,因?yàn)槌绦驘o(wú)法識(shí)別那張圖是正確狀態(tài))
def split_picture(img_path):
"""
從原始驗(yàn)證碼中切出有效圖片驗(yàn)證碼
:param img_path: 原始驗(yàn)證碼圖片地址
:return: 有效驗(yàn)證碼Image對(duì)象
"""
img_file = Image.open(img_path)
width = int(img_file.size[0])
height = int(img_file.size[1])
img1 = img_file.crop((0, 0, width/4, height/4))
img2 = img_file.crop((width/4, 0, width/2, height/4))
img3 = img_file.crop((width/2, 0, 3*width/4, height/4))
img4 = img_file.crop((3*width/4, 0, width, height/4))
return [img1, img2, img3, img4]
def save_img(img_obj_list, file_name):
"""
保存圖片
:param img_obj_list: 圖片Image對(duì)象
:param file_name: 目標(biāo)文件夾名
"""
num = 1
for img_obj in img_obj_list:
img_obj.save(f'./static/{file_name}/img{num}.png')
num += 1
def split_all_picture(file_name):
"""
剪切文件中所有原始驗(yàn)證碼圖片,并保存
:param file_name: 圖片存放地址
"""
img_list = os.listdir('./static/jisu_images/')
for img in img_list:
imgs = split_picture('./static/jisu_images/' + img)
save_img(imgs, file_name)
def compare_picture(picture1, picture2):
"""
判斷圖片是否相同
:param picture1: 第一張圖片Image對(duì)象
:param picture2: 第二張圖片Image對(duì)象
:return: 布爾值(True表示相同宴胧,F(xiàn)alse表示不同)
"""
img1 = PictureHash(picture1)
img2 = PictureHash(picture2)
if img1.compare(img2):
return 1
else:
return 0
def del_repeat():
"""
去除重復(fù)圖片
:return: 圖片Image對(duì)象列表
"""
right_list = os.listdir('./static/right_img/')
img1 = Image.open('./static/right_img/img1.png')
result_right_img = [img1]
for img in right_list[1:]:
img = Image.open(f'./static/right_img/{img}')
for right_img in result_right_img[:]:
return_img = compare_picture(img, right_img)
if return_img:
break
else:
result_right_img.append(img)
return result_right_img
四:selenium+chrome模擬用戶打開網(wǎng)站并登陸
# 聲明瀏覽器對(duì)象
browser = webdriver.Chrome()
# 設(shè)置網(wǎng)頁(yè)大惺ァ(可修改)
browser.set_window_size(1400, 700)
# 設(shè)置網(wǎng)頁(yè)最大等待時(shí)長(zhǎng)(可修改)
wait = WebDriverWait(browser, 5)
# 登錄的賬號(hào)、密碼(請(qǐng)自行修改)
username = '#############'
password = '#############'
# 設(shè)置點(diǎn)擊換一組驗(yàn)證碼的最大點(diǎn)擊次數(shù)(可修改)
max_change_times = 3
# 設(shè)置點(diǎn)擊登錄卻登錄不上的最大點(diǎn)擊次數(shù)(可修改)
max_login_times = 3
def simulate_user():
"""模擬用戶登陸極速漫畫網(wǎng)站"""
url = 'http://www.1kkk.com'
browser.get(url)
# 點(diǎn)擊頭像登錄恕齐,進(jìn)入圖片驗(yàn)證碼界面
img_login = wait.until(EC.element_to_be_clickable((By.XPATH, '//img[@class="header-avatar"]')))
img_login.click()
time.sleep(1)
# 填入賬戶和密碼
input_user = wait.until(EC.presence_of_element_located((By.XPATH, '//input[@name="txt_name"]')))
input_password = wait.until(EC.presence_of_element_located((By.XPATH, '//input[@name="txt_password"]')))
input_user.send_keys(username)
time.sleep(1)
input_password.send_keys(password)
global login_num # 點(diǎn)擊登錄卻登錄不上的次數(shù)
login_num = 0
global change_num # 點(diǎn)擊換一組的次數(shù)
change_num = 0
while change_num < max_change_times and login_num < max_login_times:
time.sleep(1)
# 獲取網(wǎng)站頁(yè)面截圖(有驗(yàn)證碼)
screen_img = browser.get_screenshot_as_png()
# 將byte類型數(shù)據(jù)轉(zhuǎn)換成Image對(duì)象
screen = Image.open(BytesIO(screen_img))
# 定位并獲取4個(gè)圖片驗(yàn)證碼節(jié)點(diǎn)元素
pictures = [wait.until(
EC.element_to_be_clickable(
(By.XPATH, f'//div[@class="form-wrap"]/div//div[{i+2}]'))) for i in range(4)]
picture_num = 1
for picture in pictures:
img = get_cut_img(screen, picture)
times = confirm_click_times(img, picture_num)
picture_num += 1
if click_picture(picture, times):
# 圖片正確乞娄,結(jié)束本次循環(huán),換下一張圖片
continue
else:
# 每點(diǎn)一次換一組檐迟,統(tǒng)計(jì)次數(shù)加一
change_num += 1
# 結(jié)束for循環(huán)补胚,回到while循環(huán)開始地方開始執(zhí)行
break
else:
# for循環(huán)正常結(jié)束,說(shuō)明驗(yàn)證碼已經(jīng)完全正確追迟,點(diǎn)擊登錄
login = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#btnLogin')))
login.click()
login_num += 1
time.sleep(2)
if check_login_status():
break
其中,實(shí)現(xiàn)功能的函數(shù)封裝:
1.從需要點(diǎn)擊的驗(yàn)證碼的網(wǎng)頁(yè)截圖中切出驗(yàn)證碼圖片
def get_cut_img(screen_img, img):
"""
剪切網(wǎng)頁(yè)上的驗(yàn)證碼圖片
:param screen_img: 整個(gè)網(wǎng)頁(yè)大圖(需包含驗(yàn)證碼)
:param img: 圖片驗(yàn)證碼在網(wǎng)頁(yè)中的節(jié)點(diǎn)對(duì)象
:return: 切下的圖片數(shù)據(jù)
"""
location = img.location
size = img.size
cut_image = screen_img.crop(
(location['x'], location['y'], location['x'] + size['width'], location['y'] + size['height']))
return cut_image
2.匹配驗(yàn)證碼圖片,計(jì)算要得到正確驗(yàn)證碼應(yīng)該點(diǎn)擊的次數(shù)
def confirm_click_times(browser_img, picture_num):
"""
確認(rèn)圖片驗(yàn)證碼點(diǎn)擊次數(shù)
:param browser_img: 圖片驗(yàn)證碼Image對(duì)象
:return: 點(diǎn)擊次數(shù)(-1除外骚腥,-1表示無(wú)法在本地資源匹配驗(yàn)證碼)
"""
i = 0
browser_p = PictureHash(browser_img)
right_imgs = os.listdir('./static/imgs/')
for img_path in right_imgs:
i += 1
img = Image.open('./static/imgs/' + img_path)
if PictureHash(img).compare(browser_p):
print(f'第{picture_num}張驗(yàn)證碼與imgs文件中的第{i}張圖片匹配敦间,并且不用點(diǎn)擊')
return 0
if PictureHash(img.rotate(90)).compare(browser_p):
# 本地資源圖片逆時(shí)針旋轉(zhuǎn)90度(相當(dāng)于驗(yàn)證碼點(diǎn)擊一次)
print(f'第{picture_num}張驗(yàn)證碼與imgs文件中的第{i}張圖片匹配,模擬點(diǎn)擊一次')
return 1
if PictureHash(img.rotate(180)).compare(browser_p):
# 本地資源圖片逆時(shí)針旋轉(zhuǎn)180度(相當(dāng)于驗(yàn)證碼點(diǎn)擊二次)
print(f'第{picture_num}張驗(yàn)證碼與imgs文件中的第{i}張圖片匹配束铭,模擬點(diǎn)擊兩次')
return 2
if PictureHash(img.rotate(270)).compare(browser_p):
# 本地資源圖片逆時(shí)針旋轉(zhuǎn)270度(相當(dāng)于驗(yàn)證碼點(diǎn)擊三次)
print(f'第{picture_num}張驗(yàn)證碼與imgs文件中的第{i}張圖片匹配廓块,模擬點(diǎn)擊三次')
return 3
else:
print(f'第{picture_num}張驗(yàn)證碼在imgs中沒有相匹配的資源,'
f'也可能是圖片匹配算法對(duì)這張圖片無(wú)效契沫,模擬點(diǎn)擊換一組')
if change_num < max_change_times:
print('正在嘗試換一組驗(yàn)證碼進(jìn)行匹配带猴,請(qǐng)稍等!...')
return -1
3.點(diǎn)擊圖片驗(yàn)證碼懈万,讓其變正確
def click_picture(picture, times):
"""
點(diǎn)擊圖片驗(yàn)證碼
:param picture: 圖片驗(yàn)證碼節(jié)點(diǎn)元素
:param times: 點(diǎn)擊次數(shù)
:return: 布爾值(1代表圖片已點(diǎn)擊至正確狀態(tài)拴清,0代表無(wú)法匹配圖片)
"""
if times == 0:
return 1
elif times == -1:
# 點(diǎn)擊換一組驗(yàn)證碼
change_img = wait.until(EC.element_to_be_clickable((By.XPATH, '//a[@class="rotate-refresh"]')))
change_img.click()
return 0
else:
# 點(diǎn)擊相應(yīng)的次數(shù)靶病,讓驗(yàn)證碼變正確
for _ in range(times):
picture.click()
return 1
4.檢查登錄的狀態(tài),看賬號(hào)是否已經(jīng)登錄成功
def check_login_status():
"""
查看是否登錄成功
:return: 布爾值(1代表已登錄口予,0代表未登錄)
"""
try:
# 如果網(wǎng)頁(yè)右上方頭像標(biāo)簽有onmouseover="getuserinfo(this);"屬性娄周,說(shuō)明已登陸
wait.until(EC.presence_of_element_located((By.XPATH, '//img[@onmouseover="getuserinfo(this);"]')))
print('恭喜,成功登錄沪停!')
return 1
except Exception as e:
print('圖片匹配成功煤辨,但登錄不了')
print('可能原因一:圖片匹配算法不能百分之百確認(rèn)兩張圖片是否匹配,存在算法漏洞木张;')
print('可能原因二:賬號(hào)或者密碼錯(cuò)誤众辨。')
if login_num < max_login_times:
print('正在重新嘗試!請(qǐng)稍后...')
return 0
五:主函數(shù)如下(主函數(shù)中實(shí)現(xiàn)多個(gè)功能舷礼,并沒有分開鹃彻,將不需要執(zhí)行的功能注釋掉就可以了)
def main():
start_time = time.time()
# # 從網(wǎng)站下載驗(yàn)證碼到本地保存到j(luò)isu_images
# get_picture()
# # 切圖,并存入right_img
# split_all_picture('right_img')
# # 去掉旋轉(zhuǎn)到正確位置后,重復(fù)的圖片,并保存到imgs
# result_list = del_repeat()
# save_img(result_list, 'imgs')
# 模擬用戶訪問網(wǎng)站,自動(dòng)破解驗(yàn)證碼并登陸
simulate_user()
end_time = time.time()
print(f'''
判斷圖片相匹配的最低匹配度設(shè)置為:{compatible}%
模擬點(diǎn)擊換一組的最大次數(shù)設(shè)置為:{max_change_times}次
模擬點(diǎn)擊登錄的最大次數(shù)設(shè)置為:{max_login_times}次
模擬點(diǎn)擊換一組次數(shù):{change_num}次
模擬點(diǎn)擊登錄次數(shù):{login_num}次
程序主動(dòng)休眠時(shí)間:{5+change_num+3*login_num}s
程序?qū)崿F(xiàn)該功能的運(yùn)行時(shí)間:{end_time-start_time}s
''')
if change_num == max_change_times:
print(f'已點(diǎn)擊{max_change_times}次換一組(程序設(shè)置的極限)且轨,依然無(wú)法破解浮声,'
f'請(qǐng)重啟爬蟲,或者降低圖片匹配算法的匹配度旋奢,或者下載適量該網(wǎng)站的原始驗(yàn)證碼圖片泳挥,處理后,更新到本地圖片資源庫(kù)imgs文件夾中')
if login_num == max_login_times:
print(f'已點(diǎn)擊{max_login_times}次登錄((程序設(shè)置的極限))至朗,依然不能登錄屉符,'
f'請(qǐng)檢查賬號(hào)、密碼是否正確锹引,或者重啟爬蟲程序, 或者優(yōu)化圖片匹配算法')
if __name__ == '__main__':
main()
需要導(dǎo)入的庫(kù)如下(compare_picture是自己編寫的圖片匹配算法矗钟,單獨(dú)放在一個(gè)py文件中,所以需要導(dǎo)入):
import time
import os
import requests
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from compare_picture import *