Python之Selenium神坑踩记录

6 篇文章 0 订阅
3 篇文章 0 订阅

Python之Selenium神坑踩记录


在之前,我总结过一些关于selenium的常用操作。本以为这就是它的全部了,然而I am wrong!这东西比我想象的要强大的多。
本期带来的知识主要有:

  • 关于代理认证框的处理问题
  • 浏览器获取后台日志
  • 浏览器Cookie操作跳过登录

没错,就是这些东西,每一个问题都让我将近快要放弃。因为查到的东西千篇一律,但又行不通。如果你有以上问题的烦恼,这篇博客将会是你的福音。OK,我们开始吧!

一、关于代理认证框的处理问题

你可能会有这样的烦恼,当你挂了代理,但是出现跨域访问的时候,会需要输入用户名密码进行验证。从而打断了selenium的自动执行。像下面这样:
在这里插入图片描述
由于这种弹框既不属于js弹框,不能使用switch_to_alert()方法定位处理;也不属于页面元素构造的弹出框,所以更不能使用元素定位处理。

另外,我还试过其它的方法,比如:模拟按键输入,利用Tab建切换输入并回车。
这种方式以失败告终,原因是这种弹出框根本无法获框焦点,看着好像有焦点的样子。

再比如,模拟鼠标点击到文本框输入。同样是以失败告终。原因不详。

到这里就只能转思路了,这种认证弹框能不能不弹出来,而不是弹出以后去处理。网上去查了一下,果真。还是有的,代理插件启动

原理很简单,以manifest.json和background.js构造一个插件,配置上用户名密码,之后只要是需要认证的地方都会帮我们自动处理并且不会弹框。当然这是我自己总结的,可能没那么官方,但也就这么个事儿。

这里给出代理插件生成的方法:

# 打包Google代理插件
def create_proxyauth_extension(proxy_host, proxy_port, proxy_username, proxy_password, scheme='http', plugin_path=None):
	"""检测是否设置代理
		Args:
            proxy_host: 代理地址
            proxy_port: 端口号
            proxy_username: 用户名
            proxy_password: 密码
        Returns:
            plugin_path:插件路径
    """
    if plugin_path is None:
        # 插件生成路径
        plugin_path = 'plugins/vimm_chrome_proxyauth_plugin.zip'
    manifest_json = """
        {
            "version": "1.0.0",
            "manifest_version": 2,
            "name": "Chrome Proxy",
            "permissions": [
                "proxy",
                "tabs",
                "unlimitedStorage",
                "storage",
                "<all_urls>",
                "webRequest",
                "webRequestBlocking"
            ],
            "background": {
                "scripts": ["background.js"]
            },
            "minimum_chrome_version":"22.0.0"
        }
        """
    background_js = string.Template(
        """
        var config = {
                mode: "fixed_servers",
                rules: {
                  singleProxy: {
                    scheme: "${scheme}",
                    host: "${host}",
                    port: parseInt(${port})
                  },
                  bypassList: ["foobar.com"]
                }
              };
        chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});
        function callbackFn(details) {
            return {
                authCredentials: {
                    username: "${username}",
                    password: "${password}"
                }
            };
        }
        chrome.webRequest.onAuthRequired.addListener(
                    callbackFn,
                    {urls: ["<all_urls>"]},
                    ['blocking']
        );
        """
    ).substitute(
        host=proxy_host,
        port=proxy_port,
        username=proxy_username,
        password=proxy_password,
        scheme=scheme,
    )
    # 打包插件
    with zipfile.ZipFile(plugin_path, 'w') as zp:
        zp.writestr("manifest.json", manifest_json)
        zp.writestr("background.js", background_js)
    return plugin_path

代码什么的还是挺简单的,仔细看应该都能看懂。接下来就是带插件启动了。

def get_chrome_by_proxy():
    """
    获取谷歌浏览器对象(代理启动)
        :return:谷歌浏览器对象
    """
    service.command_line_args()
    # 启动浏览器驱动
    service.start()
    # 创建谷歌浏览器插件
    plugin_path = create_proxyauth_extension(
        proxy_host=CONFIG["proxy_host"],
        proxy_port=int(CONFIG["proxy_port"]),
        proxy_username=CONFIG["proxy_user"],
        proxy_password=CONFIG["proxy_pwd"]
    )
    options = Options()
    options.add_argument("--start-maximized")
    # options.add_argument('--incognito')                     # 配置浏览器主题为黑色
    options.add_argument('--ignore-certificate-errors')     # 忽略连接警告信息
    options.add_experimental_option("detach", True)         # 不自动关闭浏览器
    options.add_argument("--disable-gpu")                 # 禁用gpu
    options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2}) # 禁止加载图片
    options.add_experimental_option('excludeSwitches', ['enable-automation']) # 禁用浏览器正在被自动化程序控制的提示
    # options.add_experimental_option('useAutomationExtension', False) # 禁用扩展插件
    options.add_extension(plugin_path)
    return webdriver.Chrome(options=options)

这里就是一个特别坑的地方,我试了N次才发现的,但也可能只有我这样的才会入坑。
关于浏览器主题配置为黑色这项配置:

options.add_argument(’–incognito’)

个人建议最好不要使用,用了直接导致插件无效,我不知道理由,但这是我踩坑的结果。可能是这项配置有什么特殊的含义,不单单只是改浏览器主题那么简单。

我就为了自己觉得好看一点,活生生把自己搞到快要放弃的边缘。我是做梦都想不到问题会出现在这一块儿。所以给大家个建议,别搞那些花里胡哨的。~~

二、浏览器获取后台日志

需求是这样的,公司的某些作业需要频繁去网站上进行操作。通过分析浏览器后台发现,其实有更加详细的数据,而且通过修改后台请求的参数,可以拿到更多的结果。所以可以通过requests模块构造请求去获取我们想要的数据。

但是分析发现在构造headers时必须要携带token才能获得返回值,手动复制构造是完全OK的。我们并不知道,token很长,并且很难分析出token的构造规则。所以考虑能不能拿到浏览器后台的请求数据,从而拿到我们需要的token?

这也是一个心酸的历程,百度给到的最多的方式是 browsermob-proxy插件

这种方式我尝试过,未果。它的原理是将后台请求打包成一个har文件,然后可以过滤出我们想要的东西。但可能是我哪儿出了问题,不论我怎么尝试,都没法拿到我想要的结果。而且如果在公司,浏览器同时也需要处理认证框的情况的这种情况,显然行不通。几经周折,放弃。

然后突然注意到有这样一个方式,ajax-hook.js。好像是通过一个js脚本直接能够抓取json数据。这岂不美哉?可是,即使这位博主 https://zhuanlan.zhihu.com/p/158394821 讲的很详细,我还是没有成功。执行js时一直提示js语法错误,尝试了很多不同的版本。最终还是选择放弃!

最后一种方式是浏览器自带的日志功能,可以使用get_logs()方法获取浏览器日志,这个日志很详细包含了每一条请求的详细构造与返回值。能够拿到我们想要的数据!下面是代码:

浏览器对象的构造,有些参数自己可以适当删减,这里采用上边的构造。有些细微的差异,请仔细观察。

def get_chrome_by_proxy():
    """
    获取谷歌浏览器对象(代理启动)
        :return:谷歌浏览器对象
    """
    service.command_line_args()
    # 启动浏览器驱动
    service.start()
    # 创建谷歌浏览器插件
    plugin_path = create_proxyauth_extension(
        proxy_host=CONFIG["proxy_host"],
        proxy_port=int(CONFIG["proxy_port"]),
        proxy_username=CONFIG["proxy_user"],
        proxy_password=CONFIG["proxy_pwd"]
    )
    options = Options()
    options.add_argument("--start-maximized")
    # options.add_argument('--incognito')                     # 配置浏览器主题为黑色
    options.add_argument('--ignore-certificate-errors')     # 忽略连接警告信息
    options.add_experimental_option("detach", True)         # 不自动关闭浏览器
    options.add_argument("--disable-gpu")                 # 禁用gpu
    options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2}) # 禁止加载图片
    options.add_experimental_option('excludeSwitches', ['enable-automation']) # 禁用浏览器正在被自动化程序控制的提示
    options.add_experimental_option('useAutomationExtension', False) # 禁用扩展插件
    options.add_experimental_option('w3c', False)
    caps = {
        'loggingPrefs': {
            'performance': 'ALL',
        }
    }
    options.add_extension(plugin_path)
    # options.add_experimental_option('prefs', prefs)
    return webdriver.Chrome(options=options, desired_capabilities=caps)

这里又是神奇的一个坑,里面有一个参数必须要有:

options.add_experimental_option(‘w3c’, False)

如果没有,会导致后面获取下面的代码报错:

chrome = driver.get_chrome()
chrome.get_log('performance')
selenium.common.exceptions.InvalidArgumentException: Message: invalid argument: log type 'performance' not found
  (Session info: chrome=84.0.4147.68)

这折腾的我是整个人都不好了,明明定义了performance,硬是提示我没找到这个类型。关键,一搜索一大片全是这样写的。几经崩溃的边缘,突然发现拿到了返回结果。我去,这是个什么鬼,对比了一下才发现是这个鬼东西options.add_experimental_option('w3c', False)。还是那句话,虽然我不懂,但这是我踩坑的结果。
如果你成功了,你会拿到类似这样一个结果:

[{'level': 'INFO', 'message': '{"message":{"method":"Network.requestWillBeSent","params":{"documentURL":"https://www.baidu.com/","frameId":"3791AB5772F75D7BCFD86443B37239D9","hasUserGesture":false,"initiator":{"type":"other"},"loaderId":"B01BE6CD6AE25E589D833A7B8AA16036","request":{"headers":{"Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.68 Safari/537.36","sec-ch-ua":"\\"\\\\\\\\Not\\\\\\"A;Brand\\";v=\\"99\\", \\"Chromium\\";v=\\"84\\", \\"Google Chrome\\";v=\\"84\\"","sec-ch-ua-mobile":"?0"},"initialPriority":"VeryHigh","method":"GET","mixedContentType":"none","referrerPolicy":"no-referrer-when-downgrade","url":"https://www.baidu.com/"},"requestId":"B01BE6CD6AE25E589D833A7B8AA16036","timestamp":613438.216976,"type":"Document","wallTime":1619428501.838738}},"webview":"3791AB5772F75D7BCFD86443B37239D9"}', 'timestamp': 1619428501840}]

这里面包含了请求url,headers,params,有返回值的会包含response等我们常用的信息。并且是标准的json格式,所以可以很方便的过滤出我们想要的数据。唯一缺点是数据量特别大。 但我最终还是只能拿这种方式实现了。

三、浏览器Cookie操作跳过登录

由于selenium每次启动都是构造了一个全新的浏览器,所以需要登陆的网站,每一次启动都需要重新登陆。我这个就更繁琐,需要邮件验证码。也就是说每次登录还得发封邮件获取验证码。好家伙,这能忍?

携带cookie跳过登录在很早以前就这样去试过,但是一直没什么效果。感觉然并卵。
这次也是在快放弃的时候找到了一篇博客,抱着试一试的心态,却成功了。这个故事告诉我们,不能太紧张我们的结果。有时候放松一点,反而更容易达到我们的目标。

其实总结一点,cookie不生效就是构造少了。我们打开浏览器开发者工具看一下:
在这里插入图片描述
就我当前画面的cookie就有这么多需要构造。但一条一条的构造得到猴年马月,于是用到了这个插件 EditThisCookie。这个插件能快速生成cookies列表并复制。
在这里插入图片描述
点击导出就能生成cookies列表并复制到剪切板。
数据是这样的:

[
{
    "domain": ".docusign.com",
    "expirationDate": 1682127180,
    "hostOnly": false,
    "httpOnly": false,
    "name": "__utma",
    "path": "/",
    "sameSite": "unspecified",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "29959850.2106214803.1618810739.1619055204.1619055204.1",
    "id": 1
},
{
    "domain": ".docusign.com",
    "expirationDate": 1634823180,
    "hostOnly": false,
    "httpOnly": false,
    "name": "__utmz",
    "path": "/",
    "sameSite": "unspecified",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "29959850.1619055204.1.1.utmcsr=support.docusign.com|utmccn=(referral)|utmcmd=referral|utmcct=/en/guides/ndse-admin-guide-delegated-admin",
    "id": 2
},
{
    "domain": ".docusign.com",
    "expirationDate": 1626761640,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_fbp",
    "path": "/",
    "sameSite": "unspecified",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "fb.1.1618985362069.319608315",
    "id": 3
},
...
]

然后配合循环添加cookie就可以了。

cookies = [
{
    "domain": ".docusign.com",
    "expirationDate": 1682127180,
    "hostOnly": false,
    "httpOnly": false,
    "name": "__utma",
    "path": "/",
    "sameSite": "unspecified",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "29959850.2106214803.1618810739.1619055204.1619055204.1",
    "id": 1
},
...
]
chrome.get("https://appdemo.docusign.com/home")
for cookie in cookies:
    chrome.add_cookie(cookie)
chrome.refresh() # 刷新
chrome.get("https://appdemo.docusign.com/home") # 重新访问

这里要注意,添加完cookie后需要刷新浏览器,然后重新访问就可以了。

以上就是本次踩坑记录,愿大家少踩坑。关于ajax-hook.js的那种方式,有知道怎么使用的还请教一下,我的问题是:边执行js报语法错误。

欢迎大家一起浏览探讨!我会这条路上一直踩坑前行。

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anesthesia丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值