博客园自动发帖--图像处理极验验证码

为了写这篇文章,先写了两篇爬虫cookies详解selenium+requests进行cookies保存读取操作,感兴趣的朋友可以看看前两篇文章。

这篇文章我主要是提供另一种滑动验证码的处理方式,看过我文章的朋友应该知道那篇极验验证码破解之selenium,在那篇文章中我们通过分析元素中的图片信息拼接完整图片和缺口图片,然后通过像素对比计算移动距离,使用selenium模拟拖动完成验证。

为什么要用图像处理的方式

在上一篇极验验证码破解的文章中,我们能找到图片拼接信息还原原来的图片,但是后来我发现在很多网站中极验验证码的显示都是使用canvas进行渲染的,在网页元素中是找不到图片信息的,例如我们要说的博客园登录

那么针对这种方式我们怎么获取图片进行缺口计算呢?很简单,截图

截图处理

这是弹出框显示的图片

这是点击拖动按钮显示的图片

那么我们只要把这两块图片截下来,然后把滑块部分过滤掉,其他部分进行像素对比,即可获取拖动距离。使用selenium进行截图保存很方便,但是要注意不同的浏览器截图方式不同,如果使用Firefox浏览器,可以直接获取图片元素,进行元素截图;如果使用chrome浏览器,此功能有BUG,我们可以进行浏览器截屏,然后把整个图片中图像部分进行裁剪处理,得到全图和缺陷图。

使用get_screenshot_as_file(filename)接口,将登录页面截图保存下来,然后获取canvas元素

得到x、y坐标和大小

left = element.location.get("x")
top = element.location.get("y")
right = left + element.size.get("width")
bottom = top + element.size.get("height")

使用Image库打开保存的截图文件,然后使用crop函数进行截图,再使用灰度处理(灰度处理主要是为了减少像素点的处理,不是必须的)

# -*- coding: utf-8 -*-
import random
import time

from PIL import Image
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains

class CNBlogSelenium(object):
    def __init__(self):
        opt = webdriver.ChromeOptions()
        # 设置无头模式,调试的时候可以注释这句
        # opt.set_headless()
        self.driver = webdriver.Chrome(executable_path=r"/usr1/webdrivers/chromedriver", chrome_options=opt)
        self.driver.set_window_size(1440, 900)

    def visit_login(self):
        try:
            self.driver.get("https://passport.cnblogs.com/user/signin")

            WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//*[@id="input1"]')))
            username = self.driver.find_element_by_xpath('//*[@id="input1"]')
            username.clear()
            username.send_keys("账号")

            WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//*[@id="input2"]')))
            password = self.driver.find_element_by_xpath('//*[@id="input2"]')
            password.clear()
            password.send_keys("密码")

            WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//*[@id="signin"]')))
            signin = self.driver.find_element_by_xpath('//*[@id="signin"]')
            signin.click()

            WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//*[@class="geetest_radar_tip_content"]')))
            geetest = self.driver.find_element_by_xpath('//*[@class="geetest_radar_tip_content"]')
            geetest.click()

            #点击滑动验证码后加载图片需要时间
            time.sleep(3)

            self.analog_move()

        except :
            pass

        self.driver.quit()

    # 截图处理
    def screenshot_processing(self):
        WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable(
            (By.XPATH, '//canvas[@class="geetest_canvas_fullbg geetest_fade geetest_absolute"]')))
        element = self.driver.find_element_by_xpath(
            '//canvas[@class="geetest_canvas_fullbg geetest_fade geetest_absolute"]')

        # 保存登录页面截图
        self.driver.get_screenshot_as_file("login.png")
        image = Image.open("login.png")

        # 打开截图,获取element的坐标和大小
        left = element.location.get("x")
        top = element.location.get("y")
        right = left + element.size.get("width")
        bottom = top + element.size.get("height")

        # 对此区域进行截图,然后灰度处理
        cropImg = image.crop((left, top, right, bottom))
        full_Img = cropImg.convert("L")
        full_Img.save("fullimage.png")

        WebDriverWait(self.driver, 10, 0.5).until(
            EC.element_to_be_clickable((By.XPATH, '//*[@class="geetest_slider_button"]')))
        move_btn = self.driver.find_element_by_xpath('//*[@class="geetest_slider_button"]')

        ActionChains(self.driver).move_to_element(move_btn).click_and_hold(move_btn).perform()

        WebDriverWait(self.driver, 10, 0.5).until(
            EC.element_to_be_clickable((By.XPATH, '//canvas[@class="geetest_canvas_slice geetest_absolute"]')))
        element = self.driver.find_element_by_xpath('//canvas[@class="geetest_canvas_slice geetest_absolute"]')

        self.driver.get_screenshot_as_file("login.png")
        image = Image.open("login.png")
        left = element.location.get("x")
        top = element.location.get("y")
        right = left + element.size.get("width")
        bottom = top + element.size.get("height")
        cropImg = image.crop((left, top, right, bottom))
        cut_Img = cropImg.convert("L")
        cut_Img.save("cutimage.png")

图片分析

通过观察图片我们发现每个缺口图片的都是处于最左侧,即最左侧部分为滑块,无需进行像素对比,对滑动块进行截图查看,宽度基本在60像素左右,我们可以直接越过前面这部分,但是保险起见我还是从开始进行像素计算,在得到第一个不同像素后,向后加+60像素,继续进行像素对比。

def calc_cut_offset(self, cut_img, full_img):
    x, y = 1, 1
    find_one = False
    top = 0
    left = 0
    right = 0
    while x < cut_img.width:
        y = 1
        while y < cut_img.height:
            cpx = cut_img.getpixel((x, y))
            fpx = full_img.getpixel((x, y))
            if abs(cpx - fpx) > 50:
                if not find_one:
                    find_one = True
                    x += 60
                    y -= 10
                    continue
                else:
                    if left == 0:
                        left = x
                        top = y
                    right = x
                    break
            y += 1
        x += 1
    return left, right - left

移动处理

这里的移动处理同极验验证码破解之selenium中一样,具体解释可以查看上篇文章

def start_move(self, distance, element, click_hold=False):
    # 这里就是根据移动进行调试,计算出来的位置不是百分百正确的,加上一点偏移
    distance -= 7
    print(distance)

    # 按下鼠标左键
    if click_hold:
        ActionChains(self.driver).click_and_hold(element).perform()

    while distance > 0:
        if distance > 10:
             # 如果距离大于10,就让他移动快一点
            span = random.randint(5, 8)
        else:
            time.sleep(random.randint(10, 50) / 100)
            # 快到缺口了,就移动慢一点
            span = random.randint(2, 3)
        ActionChains(self.driver).move_by_offset(span, 0).perform()
        distance -= span

    ActionChains(self.driver).move_by_offset(distance, 1).perform()
    ActionChains(self.driver).release(on_element=element).perform()

移动处理这里识别率不是很高,当我们移动失败后,要进行重试,如果验证成功后面提示显示登录成功,我们通过查看tip_btn元素的文本信息即可

进行多次尝试以后,拖动框会消失,点触式按钮显示点击重试,我们同样检测点触式按钮上是否显示点击重试字样,如果存在就执行一次点击事件

在进行极验验证码处理的过程中一定要进行失败重试的处理,因为我们很难做到百分百验证成功。

# 判断是否登录成功
tip_btn = self.driver.find_element_by_xpath('//*[@id="tip_btn"]')
if tip_btn.text.find("登录成功") == -1:
    try:
        WebDriverWait(self.driver, 3, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//*[@class="geetest_reset_tip_content"]')))
        reset_btn = self.driver.find_element_by_xpath('//*[@class="geetest_reset_tip_content"]')
        #判断是否需要重新打开滑块框
        if reset_btn.text.find("重试") != -1:
            reset_btn.click()
    except:
        pass
    else:
        time.sleep(1)
    # 刷新滑块验证码图片
    refresh_btn = self.driver.find_element_by_xpath('//*[@class="geetest_refresh_1"]')
    refresh_btn.click()
    time.sleep(0.5)

    # 重新进行截图、分析、计算、拖动处理
    self.analog_move()
else:
    print("登录成功")

登录完成处理

登录完成以后,我们保存cookies到本地,以供requests使用,具体使用方式请参看selenium+requests进行cookies保存读取操作

cookies = self.driver.get_cookies()
with open("cookies.txt", "w") as fp:
    json.dump(cookies, fp)

自动发布博客园随笔文章

登录完成保存了cookies我们就可以使用requests来发布博客园随笔文章了。这回又转到我们熟悉的请求分析啦。

  1. 打开chrome,登录博客园,打开我的博客
  1. 打开Charles,点击“新随笔”
  1. 添加随笔并发布

查看POST请求,form值中title、body还有两个__开头的变量,其他的都是固定值,找一下__VIEWSTATE/__VIEWSTATEGENERATOR的值

url = "https://i.cnblogs.com/EditPosts.aspx?opt=1"
r = self.session.get(url)
html = etree.HTML(r.text)
__VIEWSTATE = html.xpath('//*[@id="__VIEWSTATE"]')[0].attrib.get('value')
__VIEWSTATEGENERATOR = html.xpath('//*[@id="__VIEWSTATEGENERATOR"]')[0].attrib.get('value')

data = {
    "__VIEWSTATE":__VIEWSTATE,
    "__VIEWSTATEGENERATOR":__VIEWSTATEGENERATOR,
    "Editor$Edit$txbTitle":title,
    "Editor$Edit$EditorBody":content,
    "Editor$Edit$Advanced$ckbPublished":"on",
    "Editor$Edit$Advanced$chkDisplayHomePage":"on",
    "Editor$Edit$Advanced$chkComments":"on",
    "Editor$Edit$Advanced$chkMainSyndication":"on",
    "Editor$Edit$Advanced$txbEntryName":"",   
    "Editor$Edit$Advanced$txbExcerpt":"",
    "Editor$Edit$Advanced$txbTag":"",
    "Editor$Edit$Advanced$tbEnryPassword":"",
    "Editor$Edit$lkbPost":"发布"
}
self.session.post(url, data=data)

# 访问我的博客首页,查看是否有些发布的文章
url = "http://www.cnblogs.com/small-bud/"
r = self.session.get(url)
if r.text.find(title) != -1:
    print("发布成功")

博客园的自动发布搞定了,还有其他的,以后就可以一键发布到其他网站再也不需要手动去搞啦


如果你觉得我的文章还可以,可以关注我的微信公众号,查看更多实战文章:Python爬虫实战之路
也可以扫描下面二维码,添加我的微信公众号

公众号 赞赏码

本文作者:星星在线

本文链接:https://www.cnblogs.com/small-bud/p/9064786.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   星星在线  阅读(408)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑
  1. 1 Victory REOL
Victory - REOL
00:00 / 00:00
An audio error has occurred.

作曲 : Reol

作词 : Reol

fade away...do over again...

fade away...do over again...

歌い始めの一文字目 いつも迷ってる

歌い始めの一文字目 いつも迷ってる

どうせとりとめのないことだけど

伝わらなきゃもっと意味がない

どうしたってこんなに複雑なのに

どうしたってこんなに複雑なのに

噛み砕いてやらなきゃ伝わらない

ほら結局歌詞なんかどうだっていい

僕の音楽なんかこの世になくたっていいんだよ

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.

目の前 広がる現実世界がまた歪んだ

目の前 広がる現実世界がまた歪んだ

何度リセットしても

僕は僕以外の誰かには生まれ変われない

「そんなの知ってるよ」

気になるあの子の噂話も

シニカル標的は次の速報

麻痺しちゃってるこっからエスケープ

麻痺しちゃってるこっからエスケープ

遠く遠くまで行けるよ

安定なんてない 不安定な世界

安定なんてない 不安定な世界

安定なんてない きっと明日には忘れるよ

fade away...do over again...

fade away...do over again...

そうだ世界はどこかがいつも嘘くさい

そうだ世界はどこかがいつも嘘くさい

綺麗事だけじゃ大事な人たちすら守れない

くだらない 僕らみんなどこか狂ってるみたい

本当のことなんか全部神様も知らない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.