接上篇

上篇的最后我贴出了一份代码,是测试用的代码。我们大概改一下,让它变得可以生产用。

import cv2

def locate():
    img = cv2.imread('./src.jpg', 0)
    re, img1 = cv2.threshold(img, 125, 255, 0)
    contours, b = cv2.findContours(img1.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

    for j in range(0, len(contours) - 1):
        M = cv2.moments(contours[j])  # 计算第一条轮廓的各阶矩,字典形式
        try:
            center_x = int(M["m10"] / M["m00"])
            center_y = int(M["m01"] / M["m00"])
        except:
            continue
        area = cv2.contourArea(contours[j])
        if area < 6000 or area > 8000 or center_x < 500:
            continue
        return center_x

该代码用上篇的思路,识别同目录下的src.jpg。将分析结果的x坐标返回,即我们所需要移动的距离。

正文

现在我们新建一个文件,我给它命名为qzone.py,首先我们import可能需要用到的库和模块

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from location import locate
from selenium.webdriver.common.action_chains import ActionChains
import time
import requests
import random

然后我们定义第一个函数,即打开无头浏览器。

def loginBefore(qq, pwd):
    opt = Options()
    opt.add_argument('--headless')
    opt.add_argument('log-level=3')
    driver = webdriver.Chrome(chrome_options=opt)
    try:
        cookie = login(qq, pwd, driver)
    except:
        pass
    finally:
        driver.quit()
    return cookie

前三行为配置参数,第四行打开浏览器。

在执行完login函数后,退出driver并且返回cookie。

然后咱们来看login函数:

def login(qq, pwd, driver, con=1):
    # 打开网页
    openAndWait(qq, pwd, driver)
    # 保存图片
    save_pic(driver)
    # 判断位置
    x = locate()
    # 转换为实际长度 /1.954
    xBegin = 119.194
    try:
        distance = x - xBegin
    except:
        print('未捕捉到拼图位置,重新载入页面')
        login(qq, pwd, driver, con)
    else:
        distance = distance / 1.72
        # 模拟鼠标移动
        return move(distance, qq, pwd, driver, con)

注释写的比较完整,我唯一要说的一点是xBegin是拼图碎片的初始位置距离图片左侧边界的距离。

当图片缺口捕捉失败时,我们递归执行login函数重新加载网页。

距离捕捉完成后,由于浏览器的缩放等原因,我们需要重新调整distance,即distance = distance / 1.72。后面的1.72需要你慢慢调整到一个适合你自己的值。

随后我们便可以开始调用move函数来进行模拟移动操作。

def move(distance, qq, pwd, driver, con=1):
    for n in range(3):
        track = get_track(distance)
        url_login = driver.current_url
        slider = driver.find_element_by_xpath('/html/body/div[1]/div[3]/div[2]/div[2]/div[2]/div[1]')
        ActionChains(driver).click_and_hold(slider).perform()
        time.sleep(1)
        i = 0
        for x in track:
            y = random.choice([-2, -1, 0, 1, 2])
            ActionChains(driver).move_by_offset(xoffset=x, yoffset=y).perform()
            t = random.choice([0.007, 0.008, 0.009, 0.010, 0.011])
            if i < 20:
                time.sleep(t * 10)
            else:
                time.sleep(t)
            i += 1
        time.sleep(1)
        ActionChains(driver).release(on_element=slider).perform()
        for j in range(5):
            url_now = driver.current_url
            if url_now != url_login:
                print('登录成功,返回cookies')
                cookies = driver.get_cookies()
                # driver.quit()
                return cookies
            time.sleep(1)
        if n != 2: print('尝试失败,正在进行再次尝试')
    print('多次失败,重新载入页面')
    if con >= 3:
        print('多次登录失败,程序停止')
        return 'more'
    login(qq, pwd, driver, con + 1)

track = get_track(distance)语句通过get_track函数获得步长列表,我们通过使用这个随机的步长列表来模拟人的拖拽动作。

首先记录当前的url,用于以后判断是否登录成功。然后我们捕捉到拼图碎片并且点住它ActionChains(driver).click_and_hold(slider).perform()

随后,读取track列表,随机一个纵向的移动距离,进行一小格移动。然后随机一个休息时间再进行下一个track的移动。

其中

if i < 20:
                time.sleep(t * 10)
            else:
                time.sleep(t)
            i += 1

用来实现先慢后快的拖拽。

拖拽完成后,ActionChains(driver).release(on_element=slider).perform()来松开鼠标。

后面我们通过检测url判断是否成功登录,如果成功则返回cookie,失败则进行几次尝试。尝试次数过多后返回错误信息。

最后我们看到生成track的函数

def get_track(distance):
    # 移动轨迹
    track = []
    # 当前位移
    current = 0
    # 减速阈值
    mid = distance * 4 / 5
    # 计算间隔
    t = 0.15
    # 初速度
    v = 1
    r = [0.9, 0.95, 0.975, 1, 1.025, 1.05, 1.1]
    i = 0
    while current < distance:
        if current < mid:
            a = 1
        else:
            a = -3.5
        v0 = v
        v = v0 + a * t
        r1 = random.choice(r)
        move = v * t * r1
        current += move
        track.append(move)
        if distance - current <= move:
            track.append(distance - current)
            return track
        i = i + 1
    return track

同样,也是随机,具体我也就不在讲述了。

至此,通过selenium来模拟鼠标通过qq空间拼图验证码也算是成功完成。

后话

首先确实感谢几位朋友的赞扬,本来写上篇时因为太耗费时间所以一直没动力写续集。前几天倒腾个人博客偶然想起自己还有个csdn,登录上来居然看到了2个评论呜呜呜真是眼眶都红了。

不过上次详细讲述了思路过程,虽然可能对于喜欢仔细阅读的朋友可以有很大的连贯性,但是确实非常耗费精力,再加上时隔已久我确实也想不清自己的思路了哈哈哈哈。所以这次采用了源码讲解的方式,希望这样也可以给大家提供些许帮助吧。

以及,因为整个项目我是用flask部署到服务器上的,所以其实还有第三个文件我没有讲述——基于flask框架的qq空间自动点赞后端程序。

为了避免填一坑挖一坑的恶性循环,我会整理下我的代码全部放到porn啊不是,全部放到github上。地址在这里:qq空间自动点赞

此外,如果有机会我会再写这个项目的第三篇——基于flask框架的qq空间自动点赞后端程序