Python Selenium库模拟登录豆瓣【账号登录+滑块验证】-经验分享
·
使用Selenium库模拟登录豆瓣共分三个步骤:1.打开豆瓣网站,点击“密码登录”; 2.输入账号和密码,确认登录;3.拉动滑块完成验证[结合OpenCV库]。本文内容仅作学习交流用,希望为大家提供更多实操思路。有任何问题或建议,欢迎大家留言哦~
1.创建Chrome浏览器对象,打开豆瓣网站
#导入库
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
import time
import requests
from fake_useragent import UserAgent as ua
import cv2
from random import randint
#创建Options对象,设置启动参数
options = Options()
#关闭chrome自动化脚本,更像真人操作
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_argument('--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36')
#从selenium 4开始,须使用Service类来驱动浏览器并指明chromedriver路径
service = Service(executable_path='/usr/local/bin/chromedriver')
#创建driver
driver = webdriver.Chrome(service=service, options=options)
#打开豆瓣首页
driver.get('https://www.douban.com/')
#浏览器页面最大化
driver.maximize_window()
至此,运行代码将自动弹出页面:

2.切入iframe子框架
一般情况下网站的登录模块会以iframe的形式嵌入在主体框架中,因此我们需要先右键查看豆瓣网页的html代码,定位iframe所在位置。

找到iframe后,将driver切入iframe框架中。
#显式等待10秒,待找到iframe标签名后停止等待
iframe = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.TAG_NAME, 'iframe')))
#切入子框架
driver.switch_to.frame(iframe)
#可选:
#可用JavaScript判定当前窗口是否是顶层窗口,以判断是否成功切入子框架中:
is_in_iframe = driver.execute_script('return window.self != window.top;')
print(is_in_iframe) #返回True,证明在iframe中
3.定位至“密码登录”模块
鼠标悬浮在“密码登录”处,右键选择查看源代码。以Xpath方式锁定密码登录位置。

#定位‘密码登录’
login_btn = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, '/html/body/div[1]/div[1]/ul[1]/li[contains(text(), "密码登录")]')))
#此处也可选用By.CSS_SELECTOR方式
#点击
login_btn.click()
4.输入用户名和密码,点击登录豆瓣
#输入用户名和密码
username = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, 'username')))
username.send_keys('...') #此处请替换为你自己的用户名
password = driver.find_element(By.XPATH, '//*[@id="password"]')
password.send_keys('...') #此处请替换为你自己的密码
#点击“登录豆瓣”
submit = driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div[1]/div[5]/a[contains(text(), "登录豆瓣")]')
submit.click()
5.滑块验证
首先须切入滑块部分的iframe中,找到带有缺口的背景图和滑块碎片。利用Opencv库处理背景图和碎片图,用碎片图去弥合背景图的缺口处,定位缺口处位置。
5.1 下载背景图和碎片组合图,剪切出碎片图
#切换到滑块验证的iframe
slider_iframe = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '//iframe[@id="tcaptcha_iframe_dy"]')))
driver.switch_to.frame(slider_iframe)
#定位至滑块验证的背景图和被滑动的碎片
bg_image = driver.find_element(By.XPATH, '//*[@id="slideBg" and @class="tc-bg-img unselectable"]')
#print(bg_image.get_attribute('class')) #‘tc-bg-img unselectable’,验证元素class属性,定位成功
slider_image = driver.find_element(By.XPATH, '//*[@id="tcOperation"]/div[@class="tc-fg-item" and contains(@style, "left: 2")]')
#每次刷新时,碎片html代码段会变化位置,用xpath简单定位会存在偏失,因此要加入多个属性条件
#抓取背景图、被滑动的碎片的url
bg_url = bg_image.get_attribute('style').split('("')[1].split('")')[0]
slider_url = slider_image.get_attribute('style').split('("')[1].split('")')[0]
#下载背景图和碎片至本地
def download_pic(url, img_name):
with requests.get(url, headers={'User-Agent': ua().random}) as resp1:
with open(img_name+'.png', mode='wb') as f:
f.write(resp1.content)
download_pic(bg_url, 'background')
download_pic(slider_url, 'assembly_slider')
#此处发现下载的滑块碎片图是包含碎片、滑动轴和滑动按钮的组合图,因此需要单独剪切出滑块碎片部分
下载至本地的背景图和碎片图示例⬇️:
由于下载下来的碎片图是一个包含碎片、滑动按钮和滑动轴的组合图,而且碎片图的位置固定,因此我们可以利用cv2将碎片单独剪切出来。


#设计函数,用于剪切碎片:
def crop_slider(img_path):
#读取组合图
zuhe_img = cv2.imread(img_path)
#裁剪
x, y, w, h = 141, 491, 119, 119 #(x,y)是要被裁剪部分的左上角坐标,w和h是从(x,y)点处开始向右、向下延伸的长度
slider = zuhe_img[y:y+h, x:x+w] #直接剪切法
#保存至本地
cv2.imwrite('slider.png', slider)
#剪切出滑块碎片,并保存至本地
crop_slider('assembly_slider.png')
剪切下来的碎片:

5.2 利用cv2.matchTemplate获得背景图中缺口的左上角位置
#设计函数,定位背景图缺口的左上角位置
def locate_gap(bg_path, slider_path): #bg_path和slider_path分别为背景图和被剪切后滑块的图片本地路径
#读取背景图和被剪切后的滑块图,并转置为灰度图
bg_img = cv2.imread(bg_path, cv2.COLOR_RGB2GRAY)
slider_img = cv2.imread(slider_path, cv2.COLOR_RGB2GRAY)
#识别灰度图片边缘
edges_bg = cv2.Canny(bg_img, 100, 200)
edges_slider = cv2.Canny(slider_img, 100, 200)
#转化图片格式为RGB
rgb_bg = cv2.cvtColor(edges_bg, cv2.COLOR_GRAY2RGB)
rgb_slider = cv2.cvtColor(edges_slider, cv2.COLOR_GRAY2RGB)
#缺口匹配
res = cv2.matchTemplate(rgb_bg, rgb_slider, cv2.TM_CCOEFF_NORMED)
#cv2.TM_CCOEFF_NORMED:归一化相关匹配。返回-1至1的浮点数,1表示完全匹配;0表示没有相关性,-1是完全相反。
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
#max_val越接近1,表示匹配越好;max_loc是匹配区域左上角的坐标
top_left = max_loc #找到背景图中缺口的左上角
return top_left
#确认背景图中缺口左上角位置
top_left = locate_gap('background.png', 'slider.png')
5.3 计算缺口中心位置
#确认缺口中心位置
gap_center = (top_left[0]+119//2, top_left[1]+119//2) #(宽度, 高度)
#将中心位置匹配至网页中位置(原图像素(672x390)与网页中图片像素(278x199)存在缩放差异)
#新缺口x轴坐标=原缺口x轴坐标*新画布宽度/原画布宽度
gap_x = gap_center[0] * 278//672
5.4 定位滑块按钮,获得按钮中心点
#定位滑块按钮
swipe = driver.find_element(By.XPATH, '//*[@id="tcOperation"]/div[@class="tc-fg-item tc-slider-normal"]')
#获取滑块按钮初始位置的左侧点和按钮宽度
swipe_left = swipe.get_attribute('style').split('px;')[0].split(': ')[1]
swipe_width = swipe.get_attribute('style').split('px;')[2].split('th: ')[1]
#style="left: 18.6161px; top: 166.304px; z-index: 2; width: 53.7798px; height: 28.9583px; line-height: 28.9583px; background-color: rgb(65, 190, 87); box-shadow: rgba(65, 190, 87, 0.5) 0px 0px 4.1369px 0.41369px; cursor: pointer; opacity: 1;"
#获取滑块按钮初始位置的x轴中心点
swipe_center = float(swipe_left) + float(swipe_width) / 2
5.5计算滑块须滑动距离:滑动距离 = 背景图缺口中心点x坐标 - 滑块按钮中心点x坐标
#滑块按钮滑动距离
distance = int(gap_x - swipe_center)
5.6按住滑块模拟滑动,完成验证
#点击并按住滑块
ActionChains(driver).click_and_hold(swipe).perform()
#滑动滑块:模拟人类,逐步拉动且低速
accumulated_steps = 0
while True:
per_step = randint(10, 15)
ActionChains(driver).move_by_offset(xoffset=per_step, yoffset=0).perform()
accumulated_steps += per_step
if accumulated_steps > distance:
break
#松开滑块
ActionChains(driver).release().perform()
#等待加载页面
time.sleep(5)
#退出浏览器
driver.quit()
###完整代码为:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
import time
import requests
from fake_useragent import UserAgent as ua
import cv2
from random import randint
#设计函数,计算滑块须移动的x轴距离
#1 从组合图中裁剪出滑块碎片,保存至本地 'slider.png'
def crop_slider(img_path):
#读取组合图
zuhe_img = cv2.imread(img_path)
#裁剪
x, y, w, h = 141, 491, 119, 119 #(x,y)是要被裁剪部分的左上角坐标,w和h是从(x,y)点处开始向右、向下延伸的长度
slider = zuhe_img[y:y+h, x:x+w] #直接剪切法
#展示被裁剪部分
# cv2.imshow('slider', slider)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
#保存至本地
cv2.imwrite('slider.png', slider)
#2 定位背景图中缺口的左上角位置
def locate_gap(bg_path, slider_path): #bg_path和slider_path分别为背景图和被剪切后滑块的图片本地路径
#读取背景图和被剪切后的滑块图,转置为灰度图
bg_img = cv2.imread(bg_path, cv2.COLOR_RGB2GRAY)
slider_img = cv2.imread(slider_path, cv2.COLOR_RGB2GRAY)
#识别灰度图片边缘
edges_bg = cv2.Canny(bg_img, 100, 200)
edges_slider = cv2.Canny(slider_img, 100, 200)
#转化图片格式
rgb_bg = cv2.cvtColor(edges_bg, cv2.COLOR_GRAY2RGB)
rgb_slider = cv2.cvtColor(edges_slider, cv2.COLOR_GRAY2RGB)
#缺口匹配
res = cv2.matchTemplate(rgb_bg, rgb_slider, cv2.TM_CCOEFF_NORMED)
#cv2.TM_CCOEFF_NORMED:归一化相关匹配。返回-1至1的浮点数,1表示完全匹配;0表示没有相关性,-1是完全相反。
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
#max_val越接近1,表示匹配越好;max_loc是匹配区域左上角的坐标
top_left = max_loc #找到背景图中缺口的左上角
return top_left
#开始模拟登陆:
#创建Options对象,设置启动参数
options = Options()
options.add_argument('--disable-blink-features=AutomationControlled') #关闭chrome自动化脚本,更像真人操作
options.add_argument('--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36')
service = Service(executable_path='/usr/local/bin/chromedriver')
driver = webdriver.Chrome(service=service, options=options)
#1 打开豆瓣首页
driver.get('https://www.douban.com/')
#浏览器页面最大化
driver.maximize_window()
#切入iframe子框架
iframe = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.TAG_NAME, 'iframe')))
driver.switch_to.frame(iframe)
#2 用户名/密码输入阶段
#点击‘密码登录’
login_btn = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, '/html/body/div[1]/div[1]/ul[1]/li[contains(text(), "密码登录")]')))
login_btn.click()
#输入用户名和密码
username = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, 'username')))
username.send_keys('****')
password = driver.find_element(By.XPATH, '//*[@id="password"]')
password.send_keys('****')
#点击“登录豆瓣”
submit = driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div[1]/div[5]/a[contains(text(), "登录豆瓣")]')
submit.click()
#3 进入滑块验证阶段
#切换到滑块验证的iframe
slider_iframe = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '//iframe[@id="tcaptcha_iframe_dy"]')))
driver.switch_to.frame(slider_iframe)
#休眠一下
time.sleep(3)
#定位至滑块验证的背景图和被滑动的碎片
bg_image = driver.find_element(By.XPATH, '//*[@id="slideBg" and @class="tc-bg-img unselectable"]')
#print(bg_image.get_attribute('class')) #‘tc-bg-img unselectable’,验证元素class属性,定位成功
slider_image = driver.find_element(By.XPATH, '//*[@id="tcOperation"]/div[@class="tc-fg-item" and contains(@style, "left: 2")]')
#每次刷新时,碎片html代码段会变化位置,单用xpath定位存在偏失,因此要加入多个属性条件
#抓取背景图、被滑动的碎片的url
bg_url = bg_image.get_attribute('style').split('("')[1].split('")')[0]
slider_url = slider_image.get_attribute('style').split('("')[1].split('")')[0]
#下载背景图和碎片至本地
def download_pic(url, img_name):
with requests.get(url, headers={'User-Agent': ua().random}) as resp1:
with open(img_name+'.png', mode='wb') as f:
f.write(resp1.content)
download_pic(bg_url, 'background')
download_pic(slider_url, 'assembly_slider') #此处发现下载的滑块碎片图是包含碎片、滑动轴和滑动块的组合图,因此需要单独剪切出滑块碎片部分
#剪切出滑块碎片,并保存至本地
crop_slider('assembly_slider.png')
#确认背景图中缺口左上角位置
top_left = locate_gap('background.png', 'slider.png')
#确认缺口中心位置
gap_center = (top_left[0]+119//2, top_left[1]+119//2)
#将中心位置匹配至网页中位置(原图(672x390)与网页中图片(278x199)存在缩放差异)
#新缺口坐标=原缺口坐标*新画布宽度/原画布宽度
gap_x = gap_center[0] * 278//672
#定位滑块按钮
swipe = driver.find_element(By.XPATH, '//*[@id="tcOperation"]/div[@class="tc-fg-item tc-slider-normal"]')
#获取滑块按钮初始位置的左侧点和按钮宽度
swipe_left = swipe.get_attribute('style').split('px;')[0].split(': ')[1]
swipe_width = swipe.get_attribute('style').split('px;')[2].split('th: ')[1]
#获取滑块按钮初始位置的x轴中心点
swipe_center = float(swipe_left) + float(swipe_width) / 2
#滑块按钮滑动距离
distance = int(gap_x - swipe_center)
#点击并按住滑块
ActionChains(driver).click_and_hold(swipe).perform()
#滑动滑块:模拟人类,逐步拉动且低速
accumulated_steps = 0
while True:
per_step = randint(10, 15)
ActionChains(driver).move_by_offset(xoffset=per_step, yoffset=0).perform()
accumulated_steps += per_step
if accumulated_steps > distance:
break
#松开滑块
ActionChains(driver).release().perform()
#等待加载页面
time.sleep(5)
#退出浏览器
driver.quit()
更多推荐


所有评论(0)