为了写这篇文章,先写了两篇爬虫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来发布博客园随笔文章了。这回又转到咱们熟悉的请求分析啦。
查看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爬虫实战之路
也能够扫描下面二维码,添加个人微信公众号