为了写这篇文章,先写了两篇爬虫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.
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来发布博客园随笔文章了。这回又转到我们熟悉的请求分析啦。
- 打开chrome,登录博客园,打开我的博客
- 打开Charles,点击“新随笔”
- 添加随笔并发布
查看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爬虫实战之路
也可以扫描下面二维码,添加我的微信公众号