专栏首页进击的Coder教你轻松截获 Selenium 中的 Ajax 数据

教你轻松截获 Selenium 中的 Ajax 数据

之前我们介绍了 ajax-hook 来实现爬虫的过程中截获 Ajax 请求,在这里再另外介绍一个工具 BrowserMob Proxy,利用它我们同样可以实现 Selenium 爬虫过程中 Ajax 请求的获取。

下面我们来简单介绍一下。

BrowserMob Proxy

BrowserMob Proxy,简称 BMP,它是一个 HTTP 代理服务,利用它我们可以截获 HTTP 请求和响应内容,另外还可以把 Performance data 输出成一个 HAR 文件。

其 GitHub 链接为:https://github.com/lightbody/browsermob-proxy/。

大家可以点击进去看看详情介绍。

实际上其原理就是开了一个代理服务器,然后抓包,同时对接了 Java、Python API,以方便我们可以直接通过代码来获取到内容。

案例

官方的一些介绍比较复杂,而且大多数都是 Java 的对接,在这里我们使用 Python 来实验一下。

这里我们就直接通过一个案例来测试下吧,废话不多说。

还是拿我自己的一个测试网站为案例,链接为:https://dynamic2.scrape.center/。

页面如图所示:

其数据都是通过 Ajax 加载的,同时带着一些加密参数:

这个网站通过 Selenium 爬的话一点问题也没有,但是由于数据本身就是从 Ajax 加载的,所以如果能直接截获 Ajax 请求的话,连页面解析都省了。

所以这里我们要利用 BrowserMob Proxy 来截获一下试试。

代码实现

要用 Python 实现,我们需要先安装一个 BrowserMob Proxy 的包,命令如下:

pip3 install browsermob-proxy

另外我们还需要下载 browsermob-proxy 的二进制文件,以便于启动 BrowserMob Proxy。

下载的地址见:https://github.com/lightbody/browsermob-proxy/releases

直接下载 build 过的版本即可:

比如这里我就下载 browsermob-proxy-2.1.4.zip 文件,直接放到我的项目目录下。

好,接着呢,我们就可以实现如下代码:

from browsermobproxy import Server
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# 启动代理
server = Server('./browsermob-proxy-2.1.4/bin/browsermob-proxy')
server.start()
proxy = server.create_proxy()
print('proxy', proxy.proxy)

# 启动浏览器
chrome_options = Options()
chrome_options.add_argument('--ignore-certificate-errors')
chrome_options.add_argument('--proxy-server={0}'.format(proxy.proxy))
driver = webdriver.Chrome(options=chrome_options)

# 监听结果
base_url = 'https://dynamic2.scrape.center/'
proxy.new_har(options={
    'captureContent': True,
    'captureHeaders': True
})
driver.get(base_url)
time.sleep(3)

# 读取结果
result = proxy.har
for entry in result['log']['entries']:
    print(entry['request']['url'])
    print(entry['response']['content'])

在这里呢,一共分了四步:

•第一步便是启动 BrowserMob Proxy,它会在本地启动一个代理服务,这里注意 Server 的第一个参数需要指定 BrowserMob Proxy 的可执行文件路径,这里我就指定了下载下来的 BrowserMob Proxy 的 bin 目录的 browsermob-proxy 的路径。•第二步便是启动 Selenium 了,它可以设置 Proxy Server 为 BrowserMob Proxy 的地址。•第三步便是访问页面同时监听结果,这里我们需要调用 new_har 方法,同时指定捕获 Resopnse Body 和 Headers 信息,紧接着调用 Selenium 的 get 方法访问一个页面,这时候浏览器便会加载这个页面,同时所有的请求和响应信息都会被记录到 HAR 中。•第四步便是读取 HAR 到内容了,我们调用 log 到 entries 字段,里面便包含了请求和响应的具体结果,这样所有的请求和响应信息我们便能获取到了,Ajax 的内容也不在话下。

运行结果类似如下:

这里可以看到所有的数据都能获取到了,包括 Ajax 结果、JavaScript、CSS 文件内容等等。

这里 har 的内容其实是一个 JSON 对象,里面记录了在访问页面的过程中发生的所有请求和响应内容,一般内容都会记录在 logs 的 entries 字段里面,还有其他的信息如有需要也可以读取。

所以,这样我们就能从 Selenium 中获取 Ajax 请求内容了。

优化

不过像上面这种代码还是不方便啊,不好复用,不好扩展,我们来稍微改写下,代码如下:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from browsermobproxy import Server
import time
import json

class BaseFramework(object):

    def __init__(self):
        self.server = Server('./browsermob-proxy-2.1.4/bin/browsermob-proxy')
        self.server.start()
        self.proxy = self.server.create_proxy()
        chrome_options = Options()
        chrome_options.add_argument('--ignore-certificate-errors')
        chrome_options.add_argument('--proxy-server={0}'.format(self.proxy.proxy))
        self.browser = webdriver.Chrome(options=chrome_options)

    def process_request(self, request, response):
        pass

    def process_response(self, response, request):
        pass

    def run(self, func, *args):
        self.proxy.new_har(options={
            'captureContent': True,
            'captureHeaders': True
        })
        func(*args)
        result = self.proxy.har
        for entry in result['log']['entries']:
            request = entry['request']
            response = entry['response']
            self.process_request(request, response)
            self.process_response(response, request)

    def __del__(self):
        self.proxy.close()
        self.browser.close()


class MovieFramework(BaseFramework):

    def process_request(self, request, response):
        print(request)

    def process_response(self, response, request):
        if '/api/movie/' in request['url']:
            print(response['content'])
            text = response['content']['text']
            results = json.loads(text)['results']
            for item in results:
                name = item.get('name')
                with open(f'{name}.json', 'w', encoding='utf-8') as f:
                    json.dump(item, f, ensure_ascii=False, indent=2)

    def load(self, url):
        self.browser.get(url)
        time.sleep(3)

if __name__ == '__main__':
    f = MovieFramework()
    for page in range(1, 5):
        url = f'https://dynamic2.scrape.center/page/{page}'
        f.run(f.load, url)

这里框架写的很基础,还有很多需要完善的地方,就只借着这雏形说说大体思路:

•这里我先定义了一个 BaseFramework,就是基础框架,然后里面定义了几个关键方法,__init__ 方法不多说了,就是把一些初始化的工作放进去。然后定义了 run 方法,把 HAR 的声明、访问、读取的操作封装了一下。然后定义了 process_response 和 process_request 的回调,这里就没实现任何操作,可以在子类实现。•如果我们要写一个爬虫的话,可以新建一个子类来继承刚才定义的 BaseFramework,然后可以自己实现 process_request 和 process_response 来处理请求和响应的结果,比如这里我就实现了一个 MovieFramework,然后实现了 process_response 处理响应信息,里面判断了 Ajax 请求的 URL,然后进行了提取和保存处理。里面 load 方法就是自行定义的,里面正常定义逻辑即可。•最后运行的时候使用 run 方法运行自定义的 load 方法即可,传入 load 方法的参数,即可完成页面的加载。同时加载的过程中 process_response 方法就会被回调,对结果进行处理。这里我们就提取了 Ajax 数据,然后保存下来了。

最终运行下,我们就可以看到一条条的电影数据就被保存下来了,如图所示:

是不是方便多了?有了它我们连页面解析的那一步都直接省略了,直接拿到了原始 Ajax 数据,舒服。

当然上面的框架还有很多很多需要优化的地方,大家可以参考思路自己实现。

总结

本节我们就讲解了利用 BrowserMob Proxy 来截获和处理 Ajax 数据的方法,实现简单方便。有了这个我们就不需要非得等页面加载出来之后再根据页面渲染结果提取信息了,Ajax 请求直接拿原始数据,爽歪歪!

本文分享自微信公众号 - 进击的Coder(FightingCoder)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Scrapy-Redis分布式爬虫源码解析

    Scrapy-Redis库已经为我们提供了Scrapy分布式的队列、调度器、去重等功能,其GitHub地址为:https://github.com/rmax/s...

    崔庆才
  • ScrapyRedis源码解析

    ScrapyRedis 这个库已经为我们提供了 Scrapy 分布式的队列、调度器、去重等功能,其 GitHub 地址为:https://github.com/...

    崔庆才
  • Scrapy 和 Pyppeteer 更优雅的对接方案

    现在越来越多的网页都已经演变为 SPA 页面,而且越来越多的网站采用了各种 JavaScript 混淆和加密技术,这使得 JavaScript 逆向难度变得很大...

    崔庆才
  • Cypress系列(40)- viewport() 命令详解

    我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite...

    小菠萝测试笔记
  • 给UICollectionView设置组背景和组圆角-Swift

    最近由于我们的UI设计钟情于圆角搞得我很方,各种圆角渐变,于是就有了下面这篇给UICollectionView的组设置圆角和背景色的一个小封装,拿出...

    Mr.RisingSun
  • python写的监控mysql主从的ap

    [global] user = cmVwbsdfsdfA== passwd = SGMxNzVBcEdEZ0ZRTGsfdfV6aA== time = 1...

    py3study
  • Java进阶面试精选系列:RabbitMQ+Kafka+Zookeeper+MySql+Redis+JVM

    小编精心收集:为金三银四准备,以下面试题先过一遍,为即将到了的面试做好准备,也过一遍基础知识点。

    慕容千语
  • Hash 冲突的一般解决方案与字符串查找中 hash 的使用

    h(k)=[(ak+b)mod p]mod m 其中a,b是{0,..,p-1}中的随机值,P是一个大的质数

    爬蜥
  • 如何避免单例模式被破坏

    这里不再讨论单例的模式的n种写法,仅仅讨论如何避免单例模式被破坏,看下面的一个例子:

    我是攻城师
  • 一段神奇的代码全自动删除QQ空间说说

     打开QQ空间:http://user.qzone.qq.com/你的QQ号/311 右键-审查元素-Console  (看到那个小箭头了吗?)

    似水的流年

扫码关注云+社区

领取腾讯云代金券