本文仅是获取验证码图片,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. Spring Cloud Alibaba Sentinel对Feign的支持

    Spring Cloud Alibaba Sentinel 除了对 RestTemplate 做了支持,同样对于 Feign 也做了支持,如果我们要从 Hystrix 切换到 Sentinel 是非常 ...

  2. 解决office365无法登录以及同步的问题

    解决office365无法登录以及同步的问题 You better need to test them one by one. You better need to test them one by ...

  3. ubuntu / zsh shell / oh-my-zsh / 常用插件

    记录一下 zsh 的下载与配置,省得每次重装系统都要上网到处查. 安装 zsh shell sudo apt install zsh 切换 shell chsh -s /bin/zsh 安装 oh-m ...

  4. Vue.js 源码分析(一) 代码结构

    关于Vue vue是一个兴起的前端js库,是一个精简的MVVM.MVVM模式是由经典的软件架构MVC衍生来的,当View(视图层)变化时,会自动更新到ViewModel(视图模型),反之亦然,View ...

  5. Rust从入门到放弃(1)—— hello,world

    安装及环境配置 特点:安全,性能,并发 rust源配置 RLS安装 cargo rust管理工具,该工具可以愉快方便的管理rust工程 #!/bin/bash mkdir learn cd learn ...

  6. mongodb实现文件存储系统

    前言:这种坑很深呀,要对应mongodb的版本跟php支持的版本,然后,如果要用composer安装第三方的库,一定要一一对应的 正片开始! 开发环境: 系统:window 开发语言:php+apac ...

  7. Triangulation by Ear Clipping(耳切法处理多边形三角划分)(转载)

    转载自: https://www.cnblogs.com/xignzou/p/3721494.html 使用EarClipping三角化多边形(翻译) ---Triangulation by Ear ...

  8. Python - 正则表达式2 - 第二十三天

    Python3 正则表达式 正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配. Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式. ...

  9. 春秋-SQLi题

    这道题挺好的 学到的知识 sprintf()构成的sql注入漏洞 题目环境今天做的时候坏了 留下这几篇博客学习 https://blog.csdn.net/nzjdsds/article/detail ...

  10. 7 CentOS 7网卡配置

    首先重中之重:修改前一定要进行系统备份,如果是虚拟机进行快照 查看虚拟机的网卡配置 注意桥接模式和NAT模式     桥接模式:网络层面,虚拟机和PC处于同级地位,虚拟机直接和路由器相连     NA ...