博客園自動發帖--圖像處理極驗驗證碼

爲了寫這篇文章,先寫了兩篇爬蟲cookies詳解selenium+requests進行cookies保存讀取操做,感興趣的朋友能夠看看前兩篇文章。html

這篇文章我主要是提供另外一種滑動驗證碼的處理方式,看過我文章的朋友應該知道那篇極驗驗證碼破解之selenium,在那篇文章中咱們經過分析元素中的圖片信息拼接完整圖片和缺口圖片,而後經過像素對比計算移動距離,使用selenium模擬拖動完成驗證。python

爲何要用圖像處理的方式

在上一篇極驗驗證碼破解的文章中,咱們能找到圖片拼接信息還原原來的圖片,可是後來我發如今不少網站中極驗驗證碼的顯示都是使用canvas進行渲染的,在網頁元素中是找不到圖片信息的,例如咱們要說的博客園登陸web

那麼針對這種方式咱們怎麼獲取圖片進行缺口計算呢?很簡單,截圖chrome

截圖處理

這是彈出框顯示的圖片json

這是點擊拖動按鈕顯示的圖片canvas

那麼咱們只要把這兩塊圖片截下來,而後把滑塊部分過濾掉,其餘部分進行像素對比,便可獲取拖動距離。使用selenium進行截圖保存很方便,可是要注意不一樣的瀏覽器截圖方式不一樣,若是使用Firefox瀏覽器,能夠直接獲取圖片元素,進行元素截圖;若是使用chrome瀏覽器,此功能有BUG,咱們能夠進行瀏覽器截屏,而後把整個圖片中圖像部分進行裁剪處理,獲得全圖和缺陷圖。瀏覽器

使用get_screenshot_as_file(filename)接口,將登陸頁面截圖保存下來,而後獲取canvas元素微信

獲得x、y座標和大小cookie

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

使用Image庫打開保存的截圖文件,而後使用crop函數進行截圖,再使用灰度處理(灰度處理主要是爲了減小像素點的處理,不是必須的)session

# -*- 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爬蟲實戰之路
也能夠掃描下面二維碼,添加個人微信公衆號

公衆號

讚揚碼

相關文章
相關標籤/搜索