Python + selenium + chromeDrive 解析浏览器接口响应值

Python + selenium + chromedriver 解析浏览器接口响应值


本文章内容禁供学习和交流切勿用作非法用途!

本案例以自动化答题项目来讲解如何通过selenium调用chromedriver解析接口响应值

开发环境

  • Windows 10 x64
  • Python 3.8
  • Pycharm 2019.3
  • Chrome 97.0.4692.99
  • Python相关依赖
    • reqyests (用于发送HTTP请求)
    • PIL (用于处理图片)
    • selenium (用于驱动Chrome浏览器)

分析过程

1. 观察页面中资源的加载过程

目标页面的URLhttp://dati.mwr.gov.cn/dxsgltl/templates/dxsgltl/answer.jsp?answer=1

浏览器的开发者调试工具网络(NewWork)这个页面下可以查看到在加载这个页面时,页面中的的内容的加载信息,通过对响应内容的大小和名称的观察,可以看到存在一个接口**http://dati.mwr.gov.cn/dxsgltl/handle/dt/loadRandomQuestion**

不出意外的话就是这个接口的响应值完成了页面中题目信息的加载了

Python + selenium + chromeDrive 解析浏览器接口响应值_第1张图片

2. 分析接口的内容

查看接口的返回值,通过查看接口的返回值,我们很容易的发现这个接口的返回值设计也很直观

这不就是传说中的谜底就在谜面上

Python + selenium + chromeDrive 解析浏览器接口响应值_第2张图片

​ 按照这个接口中的answer对应的选项尝试着对题目进行了答题,果不其然,100分

​ 也就是说只要获取到这个接口中的响应值,然后在根据响应值的内容进行答题就可以完成满分答题

​ 自动答题的逻辑是捋清楚了,但是这个页面需要通过完成登录才可以到达

​ 所以在解决这个问题之前还要解决登录的问题

3. 实现自动登录

自动登录我们可以借助selenium驱动chromeDrive来完成

  1. 来到登录页面 URLhttp://xxxx/xxxxx/xxxxx/xxxxx/login.jsp

Python + selenium + chromeDrive 解析浏览器接口响应值_第3张图片
通过找到姓名身份证两个input框对应的Xpath通过selenium中的find_element_by_xpath(xpath).send_keys(key)就可以完成d对应信息的输入

但是 还有一个数字验证码,解决这个验证码的方式有主要有两种

  1. 手填
  2. 通过程序解决(图像识别等…)

具体怎么解决,自己选择

  1. 从获得接口的响应的数据

    上面我们提到了,谜底就谜面上只要我们获得了接口的响应值,我们就相当于获得了题目的答案

    通过查阅了一些资料,我发现可以利用chromeDrive通过network来获取response.body

    从而获得那个接口的响应数据,进而解析出接口中的答案,具体的实现过程见下面的代码

代码编写

1. 注册chrome的驱动

因为selenium中没有提供对查询接口数据的函数,但是可以通过webdriver提供的API进行查询

使用的函数是Network.getResponseBody

  • Network.getResponseBody的参数是requestid

  • requestidwebdriver在执行每个请求过程中自动生成的唯一ID

    拿到requestid就能拿到对应的接口的返回数据

  • requestid的获取

    • 创建webdriver对象时配置相关设置,即可获取每个请求的日志信息

    • 对日志信息的检索可以找到对应的requestid

# 配置webdriver的相关参数
caps = {
    'browserName': 'chrome',
    'loggingPrefs': {
        'browser': 'ALL',
        'driver': 'ALL',
        'performance': 'ALL',
 },
    'goog:chromeOptions': {
        'perfLoggingPrefs': {
            'enableNetwork': True,
        },
        'w3c': False,
    },
}
# executable_path 可以指定驱动文件 chromedriver.exe 的路径
driver = webdriver.Chrome(desired_capabilities=caps,executable_path="chromedriver.exe")
# 设置窗口最大化
driver.maximize_window()
# 设置隐式等待(10秒)
driver.implicitly_wait(10)

2. 编写登录功能函数

1. 自动化输入姓名身份证号

通过浏览器定位到具体input输入框的xpath

Python + selenium + chromeDrive 解析浏览器接口响应值_第4张图片

利用selenium 中的find_element_by_xpath()函数定位到元素后根据不同的事件调用不同的函数

  • 点击.click()

  • 填写文本.send_keys(key)

访问页面,输入姓名和身份证号环节代码如下

driver.get(login_url)
print('正在执行自动化登陆...')
# 用户名
driver.find_element_by_xpath('//*[@id="userName"]').send_keys(user_name)
# 身份证号
driver.find_element_by_xpath('//*[@id="idCard"]').send_keys(id_card_num)
2. 验证码的获取

验证码的获取可通过多种方式,我所使用的方式是调用baidu图像识别接口完成验证码的识别

调用baidu图像识别的接口的方式本文不进行详细说明,有空在写

建议手动输入完成验证码

3. 达到答题页面

验证码输入完成后就可以来到下一个页面

直接点击开始答题按钮即可

 # 开始答题
 click_by_xpath('/html/body/div/div[1]/div/div/div[2]/form/table/tbody/tr[7]/td/div/button')

如果已经完成过了一次答题的记录,在下一个页面上会出现证书下载再答一次

4. 进入答题页面

​ 进入答题页面后需要**等待当前所有被发出的请求都得到响应**才能获取日志

	# 这里需要等待一下,等所有的接口都收到响应,才可以抓取到数据
	time.sleep(5)
	# 得到页面的所有的接口的log数据
	log_list = driver.get_log('performance')

此时得到的log_list是一个list对象
其中每一个元素的格式如下

  {
    "level": "INFO",
    "message": "{\"message\":{\"method\":\"Network.responseReceived\",\"params\":{\"frameId\":\"7F38488422C362C23121B0032A63211B\",\"hasExtraInfo\":true,\"loaderId\":\"053A0A4B77B5104685F03154CFEE68DF\",\"requestId\":\"12360.179\",\"response\":{\"connectionId\":31,\"connectionReused\":true,\"encodedDataLength\":191,\"fromDiskCache\":false,\"fromPrefetchCache\":false,\"fromServiceWorker\":false,\"headers\":{\"Connection\":\"Keep-Alive\",\"Content-Length\":\"46668\",\"Content-Type\":\"application/json;charset=UTF-8\",\"Date\":\"Tue, 22 Feb 2022 07:29:49 GMT\",\"Keep-Alive\":\"timeout=5, max=90\",\"Server\":\"iis\"},\"mimeType\":\"application/json\",\"protocol\":\"http/1.1\",\"remoteIPAddress\":\"219.142.62.58\",\"remotePort\":80,\"responseTime\":1.645515025854593e+12,\"securityState\":\"insecure\",\"status\":200,\"statusText\":\"\",\"timing\":{\"connectEnd\":-1,\"connectStart\":-1,\"dnsEnd\":-1,\"dnsStart\":-1,\"proxyEnd\":-1,\"proxyStart\":-1,\"pushEnd\":0,\"pushStart\":0,\"receiveHeadersEnd\":167.98,\"requestTime\":2379.742195,\"sendEnd\":0.369,\"sendStart\":0.201,\"sslEnd\":-1,\"sslStart\":-1,\"workerFetchStart\":-1,\"workerReady\":-1,\"workerRespondWithSettled\":-1,\"workerStart\":-1},\"url\":\"http://dati.mwr.gov.cn/dxsgltl/handle/dt/loadRandomQuestion\"},\"timestamp\":2379.910729,\"type\":\"XHR\"}},\"webview\":\"7F38488422C362C23121B0032A63211B\"}",
    "timestamp": 1645515030579
 }

其中message字段对应的是另一个字符串格式的json对象

取出message对应的值进行转义后可得到如下格式的json对象

{
  "message": {
    "method": "Network.responseReceived",
    "params": {
      "frameId": "7F38488422C362C23121B0032A63211B",
      "hasExtraInfo": true,
      "loaderId": "053A0A4B77B5104685F03154CFEE68DF",
      "requestId": "12360.179",
      "response": {
        "connectionId": 31,
        "connectionReused": true,
        "encodedDataLength": 191,
        "fromDiskCache": false,
        "fromPrefetchCache": false,
        "fromServiceWorker": false,
        "headers": {
          "Connection": "Keep-Alive",
          "Content-Length": "46668",
          "Content-Type": "application/json;charset=UTF-8",
          "Date": "Tue, 22 Feb 2022 07:29:49 GMT",
          "Keep-Alive": "timeout=5, max=90",
          "Server": "iis"
        },
        "mimeType": "application/json",
        "protocol": "http/1.1",
        "remoteIPAddress": "219.142.62.58",
        "remotePort": 80,
        "responseTime": 1.645515025854593e+12,
        "securityState": "insecure",
        "status": 200,
        "statusText": "",
        "timing": {
          "connectEnd": -1,
          "connectStart": -1,
          "dnsEnd": -1,
          "dnsStart": -1,
          "proxyEnd": -1,
          "proxyStart": -1,
          "pushEnd": 0,
          "pushStart": 0,
          "receiveHeadersEnd": 167.98,
          "requestTime": 2379.742195,
          "sendEnd": 0.369,
          "sendStart": 0.201,
          "sslEnd": -1,
          "sslStart": -1,
          "workerFetchStart": -1,
          "workerReady": -1,
          "workerRespondWithSettled": -1,
          "workerStart": -1
        },
        "url": "http://dati.mwr.gov.cn/dxsgltl/handle/dt/loadRandomQuestion"
      },
      "timestamp": 2379.910729,
      "type": "XHR"
    }
  },
  "webview": "7F38488422C362C23121B0032A63211B"
}

对象中的message.method的值存在多种类型例如Network.requestWillBeSentNetwork.responseReceived

其中只有menthd的值为Network.responseReceived的对象才是对应的是接口响应数据的对象

根据在最开始的分析,我们可以确定我们要找的是请求路径为http://dati.mwr.gov.cn/dxsgltl/handle/dt/loadRandomQuestion的接口的响应数据

结合log对象中的message字段对应的对象的结构,可以找到message.params.response.url记录的数据就是接口的请求URL

根据以上的分析对log_list数据进行过滤即可,代码如下:

request_url = 'http://xxxxx/xxxxx/xxxxx/xxxxx/loadRandomQuestion'
# 遍历列表中的对象,寻找需要解析的接口数据
for item in log_list:
    # message同时也是一个json对象
    message = item['message']
    # 解析message
    message_dict = json.loads(message)['message']
    # 得到params对象
    params_dict = message_dict['params']
    network_method = message_dict.get('method')
    # 判断params中是否存在response
    if network_method == 'Network.responseReceived':
        response_dict = params_dict.get('response')
        url_info = response_dict['url']
        # 判断url是不是我们要找的题目的url
        if request_url == url_info:
            request_id = params_dict['requestId']
            print('题目数据接口的请求ID = '+request_id)

此时我们已经得到我们需要的接口对应的requestid

5. 根据requestid从日志中获取接口响应的数据
# 传入request_id 获得响应的数据
content = driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': request_id})
# 获取body对应的数据
response_body = content['body']
print("接口数据解析成功!")

截止目前我们已经获得到了http://xxxxx/xxxxx/xxxxx/xxxxx/loadRandomQuestion的响应数据

下一步需要将返回值的进行处理,结合selenium完成自动对正确选项的勾选

3.点击正确的选项

1. 分析接口中答案和题目类型之间的关系

接口的相应数据格式如下 (数据经过简化)

{
  "message": "ok",
  "result": {
    "answer": {
    },
    "questions": [
      {
        "answer": "D",
        "createTime": "2022-01-05 15:56:57",
        "createTimeStr": "2022-01-05 15:56:57",
        "difficulty": 3,
        "id": "5dbfb524d3c34019bd61d4057b15e3dc",
        "knowledgePoint": 12,
        "knowledgePointName": "",
        "name": "()的地下水,是指与大气降水和地表水体没有密切水力联系,无法补给或者补给非常缓慢的地下水。",
        "options": [
          {
            "content": "浅层以下",
            "createTime": "2022-01-05 15:56:58",
            "id": "0eca09b3c5c748efbf6a368318f9444f",
            "question": null,
            "sortNo": "A",
            "status": 0,
            "updateTime": "2022-01-05 15:56:58"
          },
          {
            "content": "深层",
            "createTime": "2022-01-05 15:56:58",
            "id": "4e7cb8e3e3b4458390e65f939cb1476e",
            "question": null,
            "sortNo": "B",
            "status": 0,
            "updateTime": "2022-01-05 15:56:58"
          },
          {
            "content": "缓慢更新",
            "createTime": "2022-01-05 15:56:58",
            "id": "864a10198cfb4978aa8e9e177df3df33",
            "question": null,
            "sortNo": "C",
            "status": 0,
            "updateTime": "2022-01-05 15:56:58"
          },
          {
            "content": "难以更新",
            "createTime": "2022-01-05 15:56:58",
            "id": "5bc380f665c64936ad246df5023dc34f",
            "question": null,
            "sortNo": "D",
            "status": 0,
            "updateTime": "2022-01-05 15:56:58"
          }
        ],
        "quType": 1,
        "sortNo": 0,
        "status": 0,
        "updateTime": "2022-01-05 15:56:57"
      },
      {
        "answer": "A|B|C|D",
        "createTime": "2022-01-05 15:56:57",
        "createTimeStr": "2022-01-05 15:56:57",
        "difficulty": 3,
        "id": "f359a081c4704598a2f011ae9357803f",
        "knowledgePoint": 12,
        "knowledgePointName": "",
        "name": "《xxxxx》中明确,坚持节水优先,统筹()、()、()、(),优化水资源配置格局,提升配置效率。",
        "options": [
          {
            "content": "地表水与地下水",
            "createTime": "2022-01-05 15:56:58",
            "id": "f191dd0bb2e34a0fa9d0e71eabff8ea4",
            "question": null,
            "sortNo": "A",
            "status": 0,
            "updateTime": "2022-01-05 15:56:58"
          },
          {
            "content": "天然水与再生水",
            "createTime": "2022-01-05 15:56:58",
            "id": "5bde2b80d17f4d24890c7050a10fcd5c",
            "question": null,
            "sortNo": "B",
            "status": 0,
            "updateTime": "2022-01-05 15:56:58"
          },
          {
            "content": "当地水与外调水",
            "createTime": "2022-01-05 15:56:58",
            "id": "96d5e55b102840dda36cb3ddf85a1258",
            "question": null,
            "sortNo": "C",
            "status": 0,
            "updateTime": "2022-01-05 15:56:58"
          },
          {
            "content": "常规水与非常规水",
            "createTime": "2022-01-05 15:56:58",
            "id": "89d422ae2a2f446d83612eb3e3d5c2e2",
            "question": null,
            "sortNo": "D",
            "status": 0,
            "updateTime": "2022-01-05 15:56:58"
          }
        ],
        "quType": 2,
        "sortNo": 0,
        "status": 0,
        "updateTime": "2022-01-05 15:56:57"
      },
      {
        "answer": "A",
        "createTime": "2022-01-05 15:56:57",
        "createTimeStr": "2022-01-05 15:56:57",
        "difficulty": 3,
        "id": "0b64195829b34442976acdbfc5268a5e",
        "knowledgePoint": 12,
        "knowledgePointName": "",
        "name": "xxxxx》提出,开展地下水超采综合治理行动,加大中下游地下水超采漏斗治理力度,逐步实现重点区域地下水采补平衡。",
        "options": [
          {
            "content": "正确",
            "createTime": "2022-01-05 15:56:58",
            "id": "125e797508564f1b978d48c20e337240",
            "question": null,
            "sortNo": "A",
            "status": 0,
            "updateTime": "2022-01-05 15:56:58"
          },
          {
            "content": "错误",
            "createTime": "2022-01-05 15:56:58",
            "id": "b522fbef81254f64906027534c4e6f8c",
            "question": null,
            "sortNo": "B",
            "status": 0,
            "updateTime": "2022-01-05 15:56:58"
          }
        ],
        "quType": 3,
        "sortNo": 0,
        "status": 0,
        "updateTime": "2022-01-05 15:56:57"
      }
    ]
  },
  "returncode": 0
}

其中result.questions字段的值为包含题目中的数据列表

该对象中answer字段为正确选项的结果

options为题目中的选项

quType题目的类型

其中不同的题目类型对应的其他数据的值也不同

题目类型 quType answer的值 options数量(选项数量)
单选 1 A 4
多选 2 A | B | C | D 4
判断 3 A 2

题目中全部选项的数量 = questions中每个对象的options的长度的累加

为了后续可以更加方便的用selenium对页面中的DOM元素数据进行遍历

先构造一个二位数组对每个答案进行封装

    如果在单选中答案为A
        则结果为 [1,0,0,0]
    如果多选答案为 A B C
        则结果为 [1,1,1,0]
    如果判断题答案为 对
        则结果为 [1,0]

为了让后续遍历跟家的顺利,将二维数组转换成一维数组

数组的长度 = 页面中全部的题目的数量

如果某个选项是正确选项则对应数组中值为1反之数组中的值为0

代码如下

response_body = json.loads(response_body)
# 得到result
result_dict = response_body['result']
# 题目列表
question_list = result_dict['questions']
# 遍历题目列表得到答案
answer_result = []
for item in question_list:
    # 得到答案
    answer_str = item['answer']
    # 对答案进行拆分
    answer_list = str(answer_str).split("|")
    # 得到题目的类型
    question_type = item['quType']
    # 得到选项
    question_option = item['options']
    # 构造选项结果
    item_answer_list = []
    # 遍历选项列表
    for option_index in range(len(question_option)):
        # 跳出循环的条件
        flag = True
        # 得到选项对象
        option_item = question_option[option_index]['sortNo']
        # 遍历答案列表
        for answer_index in range(len(answer_list)):
            # 得到答案对象
            answer_item = answer_list[answer_index]
            # 判断两个元素是否一致
            if option_item == answer_item:
                item_answer_list.append(1)
                flag = False
        # 判断跳出循环的情况
        if flag:
            item_answer_list.append(0)
    answer_result.append(item_answer_list)

# 将二维数组变成一维数组
answer_result_list = []
for item_list in answer_result:
    for item in item_list:
        answer_result_list.append(item)
return answer_result_list
2. 点击正确的选项

通过上面的操作已经得到了每个每个选项是否是正确答案的结果集

得到页面上的全部的选项的对象后就可开始对数据的遍历操作

代码如下

# 得到页面上所有的选项
check_list = driver.find_elements_by_tag_name("input")
"""
此时页面上的列表中的元素的个数
和answer_result_list的个数是一样的
所以answer_result_list的下标进行遍历即可
"""
for item_index in range(len(answer_result_list)):
    # 得到每个元素的值
    answer_item = answer_list[item_index]
    # 点击选项
    if answer_item:
        check_list[item_index].click()

代码运行结束后,页面中的所有的正确答案已经被选中

后续再次定位需要点击的页面元素调用selenium的点击事件即可完成剩余操作

selenium调用chromedriver接口获得响应数据的相关引用自文章

https://blog.csdn.net/mxdzchallpp/article/details/106475193

感谢博主分享

你可能感兴趣的:(python,selenium,chrome)