最近刚接触python爬虫,跟大多数人一样网上视频+书籍的形式学习,不过有java基础,是直接跳过前几章内容,上手就是撸selenium模拟登陆知乎,过程倒是不难,这让我信心大增。
于是就想尝试有极验验证码的B站,然而网上包括书上都已经是过时的方法,懵逼的我回头一看买的这本《Python3 网络爬虫开发实战》,上面写的2018年4月第一版,不得不感叹技术的变化,你不进步就别想追上它
在百度了N种关键词后,我发现了这位信田玉大大写的**Python爬虫实践-破解哔哩哔哩滑动验证登录**
二话不说贴下来尝试一遍,详细代码全在这位大大那,我这边就说说在代码执行过程中出现的问题以及解决方式
1、加载chrome浏览器问题
# 初始化
def init():# 定义为全局变量,方便其他模块使用global url, browser, username, password, wait# 登录界面的urlurl = 'https://passport.bilibili.com/login'chromedriver_path = 'C:/Users/machenike/Anaconda3/Scripts/chromedriver.exe'# 此步骤很重要,设置为开发者模式,防止被各大网站识别出来使用了Seleniumoptions = webdriver.ChromeOptions()options.add_experimental_option('excludeSwitches', ['enable-automation'])# 实例化一个chrome浏览器,并设置成窗口最大化browser = webdriver.Chrome(executable_path=chromedriver_path, options=options)browser.maximize_window()# 用户名username = 'XXXXXXXXXX'# 密码password = 'XXXXXXX'# 设置等待超时wait = WebDriverWait(browser, 20)
因为在用selenium登录知乎的时候发现有识别selenium的问题存在,于是在加载浏览器时设置成开发者模式,不怕一万就怕万一
2、裁剪验证码问题
#对某元素截图
def save_pic(obj,name):try:pic_url=browser.save_screenshot('.\\bilibili.png')print("%s:截图成功!" % pic_url)#获取元素位置信息left = obj.location['x'] top = obj.location['y']right = left + obj.size['width']bottom = top + obj.size['height']print('图:'+name)print('Left %s' % left)print('Top %s' % top)print('Right %s' % right)print('Bottom %s' % bottom)print('')im = Image.open('.\\bilibili.png')im = im.crop((left, top, right, bottom)) #元素裁剪file_name='bili_'+name+'.png'im.save(file_name) #元素截图except BaseException as msg:print("%s:截图失败!" % msg)
采用 left = obj.location[‘x’] top = obj.location[‘y’]方式获取的x,y位置总是不对头,至少验证码的模块用css选择器寻找正确,猜测是电脑分辨率的问题,会导致最终图片裁剪出现下面的情况,那没有准确的上、下、左、右坐标,可如何是好?
补充:已经找到分辨率问题,我的分辨率是1920*1080,设置的是125%显示,然而这样显示比100%更好,不信你可以试试,成功率接近100%
没办法这块我更加懵逼,于是采取曲线救国的方式,前方极其骚操作出现,非战斗人员憋着别笑
#修改裁剪代码,保存带有验证码的图片
#对某元素截图
def save_pic(obj,name):try:pic_url=browser.save_screenshot('.\\bilibili.png')print("%s:截图成功!" % pic_url)except BaseException as msg:print("%s:截图失败!" % msg)def cut():c_background = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'canvas.geetest_canvas_bg.geetest_absolute')))c_slice = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute')))c_full_bg = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')))print(c_background)#保存一张带有验证码的图片save_pic(c_slice, 'slice')
然后在本项目文件夹中出现了这样一张图片,当然也可以用微信截图截一个,类似就行
啊,万恶的验证码,让我们用Photoshop打开它,点击视图->标尺
然后…按住上下两个边拖出对齐线,拖动的时候可以看见XX厘米,用公式换算1厘米=28像素
我的电脑得出
left=1093
top=317
right=1405
bottom = 511
将代码还原并修改坐标,验证码图片裁剪成功
# 对某元素截图
def save_pic(obj, name):try:pic_url = browser.save_screenshot('.\\bilibili.png')print("%s:截图成功!" % pic_url)# 获取元素位置信息left = 1093top = 317right = 1405bottom = 511print('图:' + name)print('Left %s' % left)print('Top %s' % top)print('Right %s' % right)print('Bottom %s' % bottom)im = Image.open('.\\bilibili.png')im = im.crop((left, top, right, bottom)) # 元素裁剪file_name = 'bili_' + name + '.png'im.save(file_name) # 元素截图except BaseException as msg:print("%s:截图失败!" % msg)
3、滑块移动问题
三个地方可做修改,分别是计算滑块移动距离,构造滑块轨迹,和最后拖动滑块,代码如下
1、计算滑块移动距离
我理解就是 滑块最左边-缺口的最左边 这一段距离 ,滑块初始位置上方Phtotshop骚操作可计算出来 但是在实际移动过程中猜测还是因为分辨率的问题,要稍作调整,例如k = i-9 不减9我压根对不齐
2、构造滑块轨迹
不管是匀加速再匀减速,还是滑过头再回来,我都尝试过,均宣告失败,本着不服输的精神,考虑到模拟人动作还有一种可能,前面快速滑动到接近缺口马上停下来,再慢慢划过去,最后左右对一对齐
3、拖动滑块
代码跟一般的都一样,只是在滑动到缺口后又加了几下左右对一对齐的动作
# 计算滑块移动距离
def get_distance(bg_image, fullbg_image):''':param bg_image: (Image)缺口图片:param fullbg_image: (Image)完整图片:return: (Int)缺口离滑块的距离'''# 滑块的初始位置distance = 21# 遍历像素点横坐标for i in range(distance, fullbg_image.size[0]):# 遍历像素点纵坐标for j in range(fullbg_image.size[1]):# 如果不是相同像素if not is_pixel_equal(fullbg_image, bg_image, i, j):# 返回此时横轴坐标就是滑块需要移动的距离k = i-9return k# 构造滑动轨迹
def get_trace(distance):''':param distance: (Int)缺口离滑块的距离:return: (List)移动轨迹'''#滑动70%的距离后停下,再滑动剩下的30%lu = [0.7,0.3]# 创建存放轨迹信息的列表trace = []# 设置加速的距离faster_distance = distancefor i in lu:the_distance = i*faster_distance# 设置初始位置、初始速度、时间间隔start, v0, t = 0, 0, 0.2# 当尚未移动到终点时while start < the_distance:# 如果处于加速阶段if start < 0.5*the_distance:# 设置加速度为2a = 3# 如果处于减速阶段else:# 设置加速度为-3a = -3# 移动的距离公式move = v0 * t + 1 / 2 * a * t * t# 此刻速度v = v0 + a * t# 重置初速度v0 = v# 重置起点start += move# 将移动的距离加入轨迹列表trace.append(round(move,2))# 返回轨迹信息return trace# 模拟拖动
def move_to_gap(trace):# 得到滑块标签slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button')))# 使用click_and_hold()方法悬停在滑块上,perform()方法用于执行ActionChains(browser).click_and_hold(slider).perform()for x in trace:# 使用move_by_offset()方法拖动滑块,perform()方法用于执行ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()#小范围震荡一下,进一步迷惑极验后台,这一步可以极大地提高成功率ActionChains(browser).move_by_offset(xoffset=3, yoffset=0).perform()ActionChains(browser).move_by_offset(xoffset=-3, yoffset=0).perform()ActionChains(browser).move_by_offset(xoffset=-2, yoffset=0).perform()ActionChains(browser).move_by_offset(xoffset=2, yoffset=0).perform()# 模拟人类对准时间time.sleep(0.5)# 释放滑块ActionChains(browser).release().perform()
最后,经我实际调整测试,成功率极高,妈妈再也不用担心我的拼图被怪物吃了