文章目录
声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责。
若有侵权,请在评论区联系作者立即删除!
前言
本文章主要是我结合自己的工作经验,讲解一下基于 DrissionPage
如何实现京东网页端登录流程,涉及到一些 DrissionPage
的简单使用,滑块算法和图像识别算法的说明和使用。如有更专业高效的方案欢迎在评论区探讨,有不正之处还望指正。
一、DrissionPage 启动浏览器
参考官方文档 DrissionPage 使用文档,先将浏览器启动起来。
1.1 安装 DrissionPage
pip install DrissionPage
我使用的版本
1.2 自动化打开浏览器
使用最简单的配置,成功打开一个新的浏览器。
import time
from DrissionPage import Chromium, ChromiumOptions
def new_browser():
co = ChromiumOptions()
# 指定其他端口,新开一个浏览器,不影响当前已打开的浏览器的使用
co.set_paths(local_port=10086)
# 设置启动时最大化
co.set_argument("--start-maximized")
# 设置浏览器可视窗口大小
co.set_argument("--window-size=1920,1080")
# 以该配置创建页面对象
browser = Chromium(addr_or_opts=co)
time.sleep(10)
# 关闭浏览器
browser.quit()
if __name__ == "__main__":
new_browser()
1.3 登录主流程实现
写好登录流程的主要逻辑,如下所示:
import time
from DrissionPage import Chromium, ChromiumOptions
class JDCrawler:
def new_browser(self):
co = ChromiumOptions()
# 指定其他端口,新开一个浏览器,不影响当前已打开的浏览器的使用
co.set_paths(local_port=10086)
# 设置启动时最大化
co.set_argument("--start-maximized")
# 设置浏览器可视窗口大小
co.set_argument("--window-size=1920,1080")
# 以该配置创建页面对象
browser = Chromium(addr_or_opts=co)
return browser
def login(self, page):
print("============= 账号密码登录页面加载完成 =============")
# TODO: 实现具体登录逻辑
if __name__ == "__main__":
print("============= 程序开始 =============")
jd_crawler = JDCrawler()
try:
browser = jd_crawler.new_browser()
page = browser.latest_tab
print("============= 准备进入登录页 =============")
page.get(JD_LOGIN_URL)
time.sleep(3)
jd_crawler.login(page)
except Exception as e:
print("执行报错:", e)
finally:
print("============= 准备关闭浏览器 =============")
page.close()
browser.quit()
print("============= 程序退出 =============")
二、分析登录流程
我们打开进入 京东登录页,输入账号密码会发现整体登录流程可以拆解为这几个步骤:
- 输入账号
- 输入密码
- 点击登录
- 滑块校验
- 校验通过,登录完成
2.1 输入账号
我这里根据 css 选择器
规则 #loginname
定位到账号输入框元素。
通过 DP
提取元素并输入账户名:page.ele("#loginname", timeout=DEFAULT_TIMEOUT).clear().input(JD_ACCOUNT)
,其中 clear()
是为了清除输入框内容。
2.2 输入密码
css 选择器
规则 #nloginpwd
定位到密码输入框元素。
通过 DP
输入密码:page.ele("#nloginpwd", timeout=DEFAULT_TIMEOUT).clear().input(JD_PASS)
。
2.3 点击提交
page.ele("#loginsubmit", timeout=DEFAULT_TIMEOUT).click()
实现点击操作,提交账号。
2.4 提交账户登录
点击登录后因为风控规则可能会出现滑块,需要检查一下是出现了滑块,再进行后续操作。
检查代码:
slider_ele = page.ele('xpath://div[@class="JDJRV-suspend-slide"]',timeout=DEFAULT_TIMEOUT)
if slider_ele and slider_ele.states.is_displayed:
# TODO: 处理滑块
三、滑块校验
3.1 获取背景图片
利用 xpath
规则://div[@class="JDJRV-bigimg"]/img
获取背景图片,DP
提取元素:page.ele('xpath://div[@class="JDJRV-bigimg"]/img')
3.2 获取缺口图片
page.ele('xpath://div[@class="JDJRV-smallimg"]/img')
3.3 定位滑块按钮位置
page.ele('xpath://div[@class="JDJRV-slide-inner JDJRV-slide-btn"]')
3.4 图像识别,计算出移动距离
3.4.1 转换文件格式
网页的图片是base64
格式,需要将图片转成 OpenCV
可以处理的图像格式。
@staticmethod
def img2cv(img_data):
"""openCV 读取 base64 图片"""
data = img_data.replace("data:image/png;base64,", "").replace(
"data:image/jpg;base64,", ""
)
img_data = base64.b64decode(data)
# 将图片的二进制数据转换为图像的原始字节数据;np.uint8 指定将数据解析为 8 位无符号整数
img_np = np.frombuffer(img_data, np.uint8)
# 图像的原始字节数据解码为 OpenCV 可以处理的图像格式
img = cv2.imdecode(img_np, cv2.IMREAD_COLOR)
return img
3.4.2 滑动距离识别
完成了对图片的处理之后,我们继续利用 OpenCV
来识别背景图片和缺口图片之间的距离。
def img2xy(bg_img, tp_img):
"""
识别图片缺口位置
Args:
bg_img : 背景图片
tp_img : 缺口图片
Returns:
返回缺口的左上角、右下角坐标
"""
# 边缘检测:利用边缘检测算法 cv2.Canny 识别图片的边缘
bg_edge = cv2.Canny(bg_img, 100, 200)
# 100 和 200 是 Canny 算法的阈值参数,用于控制边缘检测的灵敏度
tp_edge = cv2.Canny(tp_img, 100, 200)
# 转换图片格式:将边缘检测结果从灰度图转换为 RGB 格式
bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)
# 缺口匹配
res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
# 寻找最优匹配
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 左上角点的坐标
tl = max_loc
# th 和 tw 分别是缺口图片的高度和宽度
th, tw = tp_pic.shape[:2]
# 右下角点的坐标
br = (tl[0] + tw, tl[1] + th)
# 返回缺口区域的左上角坐标 (x1, y1) 和右下角坐标 (x2, y2)
return max_loc, br
上面是图片缺口识别的算法,最终返回左上角坐标和右下角坐标,在登录流程中,我们只需要左上角坐标 max_loc
的 x
坐标,这个就是我们要的滑动距离 distance
。
需要注意的是网页会存在对图片进行不同比例的渲染,这时候移动的距离也要等比例延长或者缩短,才能移动到目标位置,这个比例系数 coefficient
我们可以通过观察图片的 Rendered Size(渲染尺寸)
和 Intrinsic Size(固有尺寸)
的比值得出,比如这里 Rendered Size / Intrinsic Size = 0.95
,因此我们真实的滑动距离为 distance * 0.95
。
3.5 生成滑动轨迹
3.5.1 公式原理
- 位移公式:s = v₀t + ½at²
- 速度公式:v = v₀ + at
- s 表示距离;v₀ 表示初速度;v 表示末速度;t 表示时间;a 表示加速度
利用上面两个公式,模拟人在滑动滑块时先加速后减速的行为,s
的值则是上面步骤得出的distance
,加速度a
则根据我们调整不同的数值,观察滑动的情况来优化。
3.5.2 轨迹生成示例代码
数据需要根据实际滑动的情况来调整,直到效果跟人滑动出的效果差不多,则证明这个轨迹坐标是有效的。
def handle_distance(distance):
"""生成轨迹"""
v = 0 # 初始速度为 0
t = 2 # 每 2 秒生成一个坐标
forward_tracks = [] # 保存坐标值 x
current = 0 # 当前已滑动的距离
# 加速和减速的距离转折点
mid = "给一个距离"
while current < distance:
if current < mid:
a = "给一个加速数值"
else:
a = "给一个减速数值"
s = v * t + 0.5 * a * (t**2) # 位移公式
v = v + a * t # 速度公式
current += s
forward_tracks.append(round(s))
3.6 DrissionPage 实现拖拽
利用 DP
的动作链实现鼠标拖拽。
def slider(
self, page, bg_ele_xpath, patch_ele_xpath, button_ele_xpath, coefficient
):
"""
移动滑块
Args:
page (string): page对象
bg_ele_xpath (string): 背景图片选择器表达式
patch_ele_xpath (string): 缺口图片选择器表达式
button_ele_xpath (string): 拖动按钮选择器表达式
coefficient (float): 图片缩放比例系数
"""
print("============= 识别图片 =============")
# 识别缺口距离
picture_distance = self.recognize_position(page, bg_ele_xpath, patch_ele_xpath)
distance = picture_distance * coefficient # 调整滑动距离
tracks = self.handle_distance(distance) # 生成轨迹坐标
button = page.ele(button_ele_xpath) # 获取滑块按钮
ac = Actions(page) # 动作链对象
print("============= 开始移动滑块 =============")
ac.move_to(button) # 移动鼠标到滑块按钮位置
ac.hold(button) # 按住滑块
# 移动滑块
for pos in tracks:
y = random.uniform(-1, 2) # 模拟鼠标上下晃动
ac.move(pos, y) # 移到坐标位置
# 模拟停顿
time.sleep(random.uniform(0.5, 0.8))
ac.release(button) # 松开按钮
四、验证效果
滑块校验通过。
5、交流群
不会经常刷博客,有需要者可以加本人,搜索 LOVE_SELF_AD_LIFE,进逆向群聊,一起探讨技术