本文仅是获取验证码图片,python+selenium实现

图片的处理,算出偏移位置网上都有现成的;而由于b站的更新,图片的获取则与之前完全不同,不能直接从html中拿到

过程比较曲折所以记录一下,可能比较长

从分析的过程来展开,刚开始的分析最终发现有些问题,虽然可以拿到图片但与当前的验证码图片不一致;

经过前面的经历,找到了后面的方法,可以成功获取到当前图片

一、(可直接看二,测试可行的)

分析结果:两个参数challenge/gt,其中gt是固定的b6cc0fc51ec7995d8fd3c637af690de3,而challenge每次请求都不一样,所以关键在于challenge

1.故事的开始,combine接口,没有请求参数,返回challenge字符串

2.get.php接口,请求参数很多,但有用的只有challenge;返回了最重要的验证码图片地址

3.然鹅,看起来虽然通过combine接口→→get接口即可获得图片地址

但实际上get进行第二次请求时不返回数据,而返回了错误信息,错误信息是旧的参数,难道需要用新的challenge参数请求?可是新的在哪呢

4.就在被卡住时,验证码刷新了,发起了reset和refresh请求,而其中refresh与get一样,返回的是图片地址,所以出现了转机

refresh的请求参数正是get返回的challenge,最重要的是refresh接口可以重复请求,获得图片地址(故事在这里埋下了伏笔)

既然是用新的challenge,那么用refresh返回的试一下,结果是不行,依然显示old_challenge

那么换一下思路,用get的challenge参数,而接口用refresh,是不是就能返回get接口的图片的地址了,实际上还真获取到了

所以就以为图片实际的获取接口是refresh,现在只要拿到get接口的challenge就可以了,而这个之前就已经开始实现了

然后就是码代码了。。

coding。。。

完成

测试一下吧,图片确实保存在本地,过程也都没什么了问题了

然后就是点开看一下图片

咦!?不对啊,图片和页面上的不一样啊

然后才意识到是之前梳理的逻辑出问题了

分析ing。。。

测试发现,带着同一个challenge参数的refresh每次返回的图片地址都不一样,所以后台应该是随机返回图片,而且后台也不保存每次生成的图片,图片都是临时的,当然每次地址都不一样了;这样的话,图片的接口根本就不是refresh,之前的get和refresh本质是一样的,都是随机返回图片;

(至于为什么只能请求一次而且返回的错误信息是old_challeng就不知道了;其实这里面还有一条线,就是reset接口,伴随refresh出现,第一次带着combine返回的参数请求,返回新的challenge和s,推测是js根据这两个参数生成新的challenge,即new challenge,带着这个参数才能请求到数据)(c也很可疑,验证码被分成了52份,这些会不会和顺序有关系?)

所以这条路就走不通了,只能试试其他方法

二、直接通过selenium获取请求的响应

0.browsermob-proxy

先试了browsermob-proxy,即通过代理获取浏览器请求信息,但https无法请求成功,查了下,因为是由java写的,所以对Java实现的比较好,应该可以解决,但python查了很多资料都没有解决方法

但如果没有https问题,其实browsermob-proxy挺好用的,可以通过proxy.new_har(“”)创建har文件,一种json格式文件,之后请求的所有信息都会保存在proxy.har中,可以直接写入文件中,数据很也直观;

实现获取http请求信息:

from browsermobproxy import Server
from selenium import webdriver
import json

# browsermob-proxy.bat的路径
server = Server(r"xxx.\browsermob-proxy\bin\browsermob-proxy.bat")
server.start()
proxy = server.create_proxy()
# 创建har
proxy.new_har("google")
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument("--proxy-server={0}".format(proxy.proxy)) driver = webdriver.Chrome(options=chrome_options)
driver.get("http://www.xxx.com/")
proxy.wait_for_traffic_to_stop(1, 60)
# 保存har中的信息到本地
with open('1.har', 'w') as outfile:
json.dump(proxy.har, outfile,indent=2,ensure_ascii=False)

请求信息,全部在entries的列表中,每个请求保存为一个字典,其中的键主要有:request/response/timings

{
"log": {
"entries": [
{
"cache": {},
"time": 569,
"startedDateTime": "2019-09-10T16:32:33.342+0000",
"request": {
"method": "GET",
"url": "http://www.yiguo.com/",
"httpVersion": "HTTP",
"headersSize": 0,
"headers": [],
"queryString": [],
"cookies": [],
"bodySize": 0
},
"response": {
"content": {
"size": 10693,
"mimeType": "text/html; charset=utf-8"
},
"httpVersion": "HTTP",
"headersSize": 0,
"redirectURL": "",
"statusText": "OK",
"headers": [],
"status": 200,
"cookies": [
{
"name": "CityCSS",
"value": "UnitId=1&AreaId=7bc089fd-9d27-4e5f-a2e1-65907c5a5399&UnitName=%e4%b8%8a%e6%b5%b7",
"path": "/",
"domain": "yiguo.com",
"expires": "2020-09-10T16:32:35.000+0000"
}
],
"bodySize": 10693
},
"timings": {
"dns": 211,
"receive": 3,
"connect": 82,
"send": 0,
"blocked": 0,
"wait": 273
},
"serverIPAddress": "150.242.239.211",
"pageref": "baidu"
},

最终方案:通过webdriver自带的API

1.获取请求信息

参考:Browser performance tests through selenium—stackoverflow

第一版:

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities caps = DesiredCapabilities.CHROME
# 必须有这一句,才能在后面获取到performance
caps['loggingPrefs'] = {'performance': 'ALL'}
driver = webdriver.Chrome(desired_capabilities=caps) driver.get('https://stackoverflow.com')
 
# 重要:获取浏览器请求的信息,包括了每一个请求的请求方法/请求头,requestId等信息
logs = [json.loads(log['message'])['message'] for log in driver.get_log('performance')] with open('devtools.json', 'wb') as f:
json.dump(logs, f) driver.close()

但实际会报错:invalid argument: log type 'performance' not found,即get_log('performance')]出错

解决:Selenium Chrome can't see browser logs InvalidArgumentException

第二版:加上chrome_options.add_experimental_option('w3c', False)

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
 
chrome_options = webdriver.ChromeOptions()
chrome_options.add_experimental_option('w3c', False)
caps = DesiredCapabilities.CHROME
caps['loggingPrefs'] = {'performance': 'ALL'}
driver = webdriver.Chrome(desired_capabilities=caps,options=chrome_options)
driver.get('https://stackoverflow.com')

# 重要:获取浏览器请求的信息,包括了每一个请求的请求方法/请求头,requestId等信息
logs = [json.loads(log['message'])['message'] for log in driver.get_log('performance')] with open('devtools.json', 'wb') as f:
json.dump(logs, f) driver.close()

到这里就获取到了请求的的信息,包含着各个请求的url,和后面用到的requestId

(get_log('performance')的返回数据,参考:selenium 如何抓取请求信息

2.根据请求信息中的requestId获取响应

启发:selenium 获取请求返回内容的解决方案

他在文中提到了:

// 获取请求返回内容 session.getCommand().getNetwork().getResponseBody("requestIdxxxxx");

但没有获取到响应内容,最终发现可以通过ExecuteSendCommandAndGetResult来实现,只要传 cmd 与 params 命令就可以调用这个接口,最后自己通过代码实现,不过是Java的也看不太懂

但可以直接去python中的selenium源码看,是否有类似的接口,结果还真给找到了

selenium/webdriver/chrom/webdriver/下的WebDriver类的一个方法:execute_cdp_cmd()就实现了这样的功能;

而我们一般用的webdriver.Chrom(),返回的就是WebDriver的实例对象

源码如下:

def execute_cdp_cmd(self, cmd, cmd_args):
    """
Execute Chrome Devtools Protocol command and get returned result
    The command and command args should follow chrome devtools protocol domains/commands, refer to link
https://chromedevtools.github.io/devtools-protocol/ :Args:
- cmd: A str, command name
- cmd_args: A dict, command args. empty dict {} if there is no command args :Usage:
driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId}) :Returns:
A dict, empty dict {} if there is no result to return.
For example to getResponseBody: {'base64Encoded': False, 'body': 'response body string'} """


    return self.execute("executeCdpCommand", {'cmd': cmd, 'params': cmd_args})['value']
def execute(self, driver_command, params=None):
    """
Sends a command to be executed by a command.CommandExecutor.
    :Returns:
The command's JSON response loaded into a dictionary object.
"""
    response = self.command_executor.execute(driver_command, params)
    return response


def execute(self, command, params):

    return self._request(command_info[0], url, body=data)


def _request(self, method, url, body=None):
    """
    Send an HTTP request to the remote server.

    :Returns:
A dictionary with the server's parsed JSON response.
"""
    # 太长只看其中的逻辑部分
    resp = self._conn.request(method, url, body=body, headers=headers)
    data = resp.data.decode('UTF-8')
    return data
    

思路:

1.通过正则在前面拿到的请求信息中,匹配到想要获取的请求所对应的的requestId

2.然后直接调用execute_cdp_cmd()接口,传入requestId

pat = r"""https://api\.geetest\.com/get\.php\?is_next.*?\".*?\"requestId\": \"(\d+?\.\d+?)\","""
requestId = re.findall(pat, browser_log, re.S)[0]
response_dict = driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId})
# body即为之前提到的get接口返回的json数据,其中包含了验证码图片的地址
body = response_dict["body"]

3.拿到验证码url,即可用requests模块请求,最终保存在本地

最后附上完整代码:

import json
import requests
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import time
import re headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
} chrome_options = webdriver.ChromeOptions()
chrome_options.add_experimental_option('w3c', False)
caps = DesiredCapabilities.CHROME
caps['loggingPrefs'] = {'performance': 'ALL'}
driver = webdriver.Chrome(desired_capabilities=caps,options=chrome_options)
driver.get('https://passport.bilibili.com/login') def input_click_01():
input_name = driver.find_element_by_xpath("//input[@id='login-username']")
input_pwd = driver.find_element_by_xpath("//input[@id='login-passwd']") input_name.send_keys("username")
input_pwd.send_keys("passport") time.sleep(3)
login_btn = driver.find_element_by_class_name("btn-login")
login_btn.click()
time.sleep(5) def browser_log_02():
browser_log_list = driver.get_log("performance") # 先保存到文件,利于测试,和后面的正则匹配
logs = [json.loads(log['message'])['message'] for log in browser_log_list]
with open('devtools.json', 'w') as f:
json.dump(logs, f, indent=4, ensure_ascii=False) with open('devtools.json', 'r') as f:
browser_log = f.read()
print("浏览器日志获取完成")
return browser_log def get_response_img_url_03(browser_log):
# 获取requestId
# 获取到的有两种,取前者,暂时没出错,出现异常再进行筛选
pat = r"""https://api\.geetest\.com/get\.php\?is_next.*?\".*?\"requestId\": \"(\d+?\.\d+?)\","""
requestId = re.findall(pat, browser_log, re.S)[0]
# print(requestId) # 最重要的一步:调用接口,通过requestId获取请求的响应
response_dict = driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId})
body = response_dict["body"]
# print(body) # 从响应中获取图片链接
fullbg = re.findall(r"fullbg\":.\"(.*?)\",",body)
bg = re.findall(r"\"bg\":.\"(.*?)\",",body)
fullbg_url = "https://static.geetest.com/" + fullbg[0]
bg_url = "https://static.geetest.com/" + bg[0] return fullbg_url,bg_url def get_img_04(fullbg_url,bg_url):
# 请求
origin_img_data = requests.get(fullbg_url, headers=headers).content
fix_img_data = requests.get(bg_url, headers=headers).content # 先保存图片
with open("原图.jpg", "wb") as f:
f.write(origin_img_data)
with open("缺口图.png", "wb") as f:
f.write(fix_img_data)
print("保存图片完成") def main():
input_click_01()
log_data = browser_log_02()
url_tuple = get_response_img_url_03(log_data)
get_img_04(*url_tuple)
driver.close() if __name__ == '__main__':
main()

最后,也不知道是不是绕远了,有更简洁的方法可以获取到验证码url;不过也确实是有些收获的;还有对于selenium的进一步理解,包括代理模式,远程连接等

b站滑动验证码图片的获取-python的更多相关文章

  1. 爬虫(十二):图形验证码的识别、滑动验证码的识别(B站滑动验证码)

    1. 验证码识别 随着爬虫的发展,越来越多的网站开始采用各种各样的措施来反爬虫,其中一个措施便是使用验证码.随着技术的发展,验证码也越来越花里胡哨的了.最开始就是几个数字随机组成的图像验证码,后来加入 ...

  2. Js逆向-滑动验证码图片还原

    本文列举两个例子:某象和某验的滑动验证 一.某验:aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vZGVtby9zbGlkZS1mbG9hdC5odG1s 未还原图像: 还原后的图: ...

  3. 利用selenium库自动执行滑动验证码模拟登陆

    破解流程 #1.输入账号.密码,然后点击登陆 #2.点击按钮,弹出没有缺口的图 #3.针对没有缺口的图片进行截图 #4.点击滑动按钮,弹出有缺口的图 #5.针对有缺口的图片进行截图 #6.对比两张图片 ...

  4. day78:luffy:前端对于token的认证&滑动验证码的实现

    目录 1.前端对于token的认证 2.滑动验证码 1.滑动验证码实现的原理 2.滑动验证码的代码实现 1.配置文件 2.前端实现:Login.vue 3.后端实现:改写jwt代码 1.前端对于tok ...

  5. 对极验geetest滑块验证码图片还原算法的研究

    免责声明 本文章所提到的技术仅用于学习用途,禁止使用本文章的任何技术进行发起网络攻击.非法利用等网络犯罪行为,一切信息禁止用于任何非法用途.若读者利用文章所提到的技术实施违法犯罪行为,其责任一概由读者 ...

  6. python爬虫21 | 对于b站这样的滑动验证码,不好意思,照样自动识别

    今天 要来说说滑动验证码了 大家应该都很熟悉 点击滑块然后移动到图片缺口进行验证 现在越来越多的网站使用这样的验证方式 为的是增加验证码识别的难度 那么 对于这种验证码 应该怎么破呢 接下来就是 学习 ...

  7. 使用python实现滑动验证码

    首先安装一个需要用到的模块 pip install social-auth-app-django 安装完后在终端输入pip list会看到 social-auth-app-django social- ...

  8. python验证码识别(2)极验滑动验证码识别

    目录 一:极验滑动验证码简介 二:极验滑动验证码识别思路 三:极验验证码识别 一:极验滑动验证码简介   近些年来出现了一些新型验证码,不想旧的验证码对人类不友好,但是这种验证码对于代码来说识别难度上 ...

  9. C#获取网页中的验证码图片(转载)

    有时候我们需要获得网页上的图片,尤其是向验证码这样的图片.这个方法就是将网页上的图片获取到PictureBox中.效果入下图所示. 右边是使用Webbrowser控件装载的某网站的注册页面,其中包括了 ...

随机推荐

  1. strcasecmp()函数

    函数介绍: strcasecmp用忽略大小写比较字符串.,通过strcasecmp函数可以指定每个字符串用于比较的字符数,strncasecmp用来比较参数s1和s2字符串前n个字符,比较时会自动忽略 ...

  2. java1.8 AQS AbstractQueuedSynchronizer学习

    AQS concurrent并发包中非常重要的顶层锁类,往往用的比较多的是ReentrantLock,然而ReentrantLock的实现依赖AbstractQueuedSynchronizer在到上 ...

  3. 自动化API之一 自动生成Mysql数据库的微服务API

        本文演示如何利用Uniconnector平台,自动生成Mysql数据库的API,节约开发人员编写后台API的时间.使用生成API的前提是开发者有 自己的数据库,有数据库的管理权限,并能通过外网 ...

  4. pycharm报错:ImportError: libcusolver.so.8.0: cannot open shared object file: No such file or directory

    pycharm报错:ImportError: libcusolver.so.8.0: cannot open shared object file: No such file or directory ...

  5. .NET Core 学习笔记之 WebSocketsSample

    1. 服务端 代码如下: Program: using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; namespace WebS ...

  6. OpenGL入门1.1:窗口

    每一个小步骤的源码都放在了Github 的内容为插入注释,可以先跳过 测试GLFW 在我们的test.cpp中加入下面两个头文件 #include <glad/glad.h> #inclu ...

  7. Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'jdbc.username' in string value "${jdbc.username}"

    1.启动dubbo的引用dubbo服务时候报下面这个错误,这是由于去找dubbo的发布服务未找到报的错误,所以先启动dubbo的发布服务即可. [INFO] Scanning for projects ...

  8. 将VMWare中的虚拟机时间设定在一个固定值

    1.关闭虚拟机 2.用记事本打开虚拟机的.vmx文件 在末尾添加添加: tools.syncTime = "FALSE"  time.synchronize.continue = ...

  9. pip下载速度慢解决方法

    添加镜像链接 解决方式: 更改pip的数据源.目前国内比较知名的有豆瓣的,清华的.都是pipy官网的镜像. 清华:https://pypi.tuna.tsinghua.edu.cn/simple 阿里 ...

  10. IO流总结---- 字节流 ,字符流, 序列化 ,数据操作流,打印流 , Properties 集合

    笔记内容: 什么是流 字节流 字符流 序列化 数据操作流(操作基本数据类型的流)DataInputStream 打印流 Properties 集合 什么是流: 流是个抽象的概念,是对输入输出设备的抽象 ...