利用selenium抓取网页的ajax请求

部门需要一个自动化脚本,完成web端界面功能的冒烟,并且需要抓取加载页面时的ajax请求,从接口层面判断请求是否成功。查阅了很多资料都没有人有过相关问题的处理经验,在处理过程中也踩了很多坑,所以如果你也有这个需要,就继续往下看吧~

环境及语言:

Python

selenium3.14

 

为什么selenium不能直接拦截请求body呢?这是Chrome官方故意而为之的,详情可参考这个网址:

https://bugs.chromium.org/p/chromedriver/issues/detail?id=2267&q=performance%20response%20body&can=1

 

上网引擎搜索后,找到的解决方案有以下三种:

1、走代理,在代理层面截取日志请求;这个如果代理过期,请求就会变得巨慢,所以没有采用,有需要可以自行研究。

2、用selenium-wire,这个是一个GitHub上的开源项目,可以直接截取response_code和body,原理大概翻了一下源码,应该走的也是代理,只不过这个项目替你封装好了。

当时看到的时候真是激动坏了,pip install一下, 问题全部解决。但是当我兴致满满的运行代码时,发现网页报错: err_proxy_connection_failed.心凉了一大半,去开源项目下寻求答案,这个原来不止我这边发生过,可以说是这个开源项目的一个bug,至今为止未被close。不死心的调试了很久,都没能走通这条路,所以含泪放弃。事实说明,还是不要随便相信star只有一两百颗星的项目。开源代码放在下面,万一你那里能走通呢:

https://github.com/wkeeling/selenium-wire

3、开启selenium的性能抓取,在性能日志里面可以做改动,以拦截response_body:

整体的一个思路先概括一下:

结合selenium与Chrome devtool:selenium可开启性能日志,根据性能日志中的Network.responseReceived事件抓取requestId和对应的url ,然后结合selenium提供的execute方法,传入requestId参数,即可获得对应的body回参。

execute_cdp_cmd()方法的源码里面有示例,写的很清楚,可以去看一下,本文也会给出代码示例:

  • 3.1 selenium截取开启日志的代码如下:
1 caps = DesiredCapabilities.CHROME
2 caps['goog:loggingPrefs'] = {'performance': 'ALL'}
3 
4 def driver():
5     global driver
6     driver = webdriver.Chrome(desired_capabilities=caps)
7     driver.maximize_window()

请注意第二行代码,这是我踩得第一个坑:

很多教程里面给的代码如下:

caps['loggingPrefs'] = {'performance': 'ALL'},用这段代码去打开性能日志,但是这样运行起来是会报错的,原因是自chromedriver, 75.0.3770.8起,就必须这样去运行了。

注意,只打开性能日志还是不能抓取body的,如果你的需求只是判断状态码,那么上面的解决方式已经足够了。

  • 3.2 下面记录怎么让我们抓取到返回的消息体:

1、为什么需要获取requestid:

Chrome性能日志的获取需要配合Chrome DevTool的方法一起使用,这个文档详细列出了Chrome提供的domains,定位到domains——network下,可以选取需要的methods:https://chromedevtools.github.io/devtools-protocol/tot/Network/

在这个里面,我选到了自己需要的方法,如下图,可以看到,这里需要传入一个参数,叫做requestId,它是唯一的请求ID,获得这个我们就能抓取到想要的body了。但是这个requestId真是听都没听过,要去从哪里获取呢?这时候就需要去分析一下我们抓取的性能日志里面的事件了。

 

 2、 关于Chrome返回的日志事件,可以参加这个博客,里面有很详细介绍,我就是基于这个博客(https://blog.csdn.net/zhuyiquan/article/details/80148767#networkrequestwillbesent。),分析出自己要用到的事件的——Network.responseReceived,里面含有requestid的返回值。

1  def parse_response_body(driver):
2   """获取requestid"""
3      browser_log = driver.get_log('performance')
4      events = [_process_browser_log_entry(entry) for entry in browser_log]
5     events_response = [event for event in events if 'Network.responseReceived' == event['method']] # 根据Network.responseReceived这个network,解析出requestId
6    for res in events_response:
7       requestId = res["params"]["requestId"]

 

 

3、综上,再结合selenium中的方法即可:

 

1 def execute_cdp_cmd(driver, cmd, cmd_args):
2     return driver.execute("executeCdpCommand", {'cmd': cmd, 'params': cmd_args})['value']
3  
4 
5 response_body = driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId}

 

  

 

接下来的事情就非常简单了,抓取body,然后利用json进行解析即可。就在我信心满满的时候,现实又给了沉重的一击,在抓取请求的body过程中,程序报错

No resource with given identifier found

这是踩得最大的坑,整整困扰了我五六个小时,以为是自己代码哪里处理的有问题,一直在调试,在网上搜索问题,最后在一个不太相关的关于js的博客里面,看到这么一句话:getResponseBody will error occur when the query returns nothing...

回头去界面手动获取了一下当前运行页面的ajax请求,果然都是没有返回体的请求ORZ。

果断上了try..except..,然后程序就华丽丽的运行起来了!此刻的心情真是又激动又想哭。

 

后记:

这种需求感觉还是很常见的,毕竟界面元素能否加载成功,看接口返回是最直接最可靠的方法,但是不知道为什么国内好像没有相关的博客(也可能是我手残没有搜到),所以谨以此文做记录,希望有需要的小伙伴不要走我这么多弯路,能够轻轻松松解决问题~~

 

PS:当时在FQ的过程中,也搜到了一种解决方法是升级selenium至4,因为selenium 4开始支持与Chrome DevTools 一起获取。但是搜到的只有Java示例,因为对于Java不是很熟悉,所以弃用,在这里一并提供给大家,有需要的可以自取:

https://medium.com/@ohanaadi/chrome-devtools-and-selenium-4-eadab5d755b7

 

-----------------------我是分割线2021/02/01---------------------------

 

今天翻阅技术博客的时候,发现了一个开源项目,是通过代理的手段截取的ajax请求,先记录一下资料:

https://github.com/lightbody/browsermob-proxy

 

posted on 2020-06-20 18:54  101欢欢鱼  阅读(12051)  评论(7编辑  收藏  举报

导航