本文已实战为主,如果不熟悉selenium或playwright,建议补充相关知识点:

cookie、session、request、headers相关概念

selenium:get_log() 获取用户权限信息,打开指定浏览器,免登陆,伪造请求头

playwright:类方法-Page,Request,Route,Docs-Authentication,Network

本文使用的各个框架版本如下:


python-3.8.8

selenium-3.141.0

playwright-1.32.1

requests-2.27.1

其中selenium4与selenium3的操作有一些差异,这里不做研究。

吐槽一下,playwright的资料是真的很少(基础资料除外),只能自己去看官网,官网写的还是可以的,自己多试试还是可以搞出点东西来的,但真的好累,呜呜呜~

需求背景

1、登录google类web端,通过自动化手段登录会被google监控并屏蔽登录请求,严重有封号风险(在之前的文章有讲过)。

2、selenium或playwright打开指定已登录google账号的浏览器,获取用户鉴权信息。

3、伪造请求头,通过requests获取对应接口的信息,进行数据拉取。

本文实战背景以FireBase后台为列,https://console.firebase.google.com/

没有接触过的,可以用Gmail等其他系列的google应用,但重在思路和方法,详见后文一步步解析。

知识点:selenium获取已登录网站的用户鉴权信息

直接上代码


__author__ = "梦无矶小仔"

import json,time,requests

from datetime import datetime, timedelta

from selenium import webdriver

from selenium.webdriver.chrome.options import Options

from selenium.webdriver.common.desired_capabilities import DesiredCapabilities


def get_headers():

    # 关键步骤 1:下面两行代码是用来设置特性,获取request的信息前提步骤。

    d = DesiredCapabilities.CHROME

    d['loggingPrefs'] = {'performance': 'ALL'}

    options = webdriver.ChromeOptions()

    options.add_experimental_option('useAutomationExtension', False)

    # # 防止打印一些无用的日志

    options.add_experimental_option("excludeSwitches", ['enable-automation', 'enable-logging'])

    options.add_argument("--disable-software-rasterizer")


    chrome_options = Options()

    chrome_options.add_experimental_option('w3c', False)

    chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")

    chrome_driver = "./chromedriver.exe"  # 我是把chromedriver驱动放在项目根目录下

    driver = webdriver.Chrome(executable_path=chrome_driver, options=options, chrome_options=chrome_options)

    driver.get("https://console.firebase.google.com/")

    info = driver.get_log('performance')

    cookie_list = []

    for i in info:

        dic_info = json.loads(i["message"])  # 把json格式转成字典。

        infom = dic_info["message"]  # request 信息,在字典的 键 ["message"]['params'] 中。

        if infom['method'] == 'Network.requestWillBeSentExtraInfo' and infom["params"]["headers"].get(":authority"):

            if infom["params"]["headers"][":authority"] == "mobilesdk-pa.clients6.google.com" and \

                    infom["params"]["headers"][":method"] == 'POST':

                cookie_list.append(infom["params"]["headers"])


    authorization = cookie_list[0]["authorization"]

    cookie = cookie_list[0]["cookie"]

 

    # 伪造请求头

    headers = {

        "Host": "crashlytics-pa.clients6.google.com",

        "content-type": "application/json",

        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/96.0.1054.62",

        "referer": "https://console.firebase.google.com/",

        "cookie": cookie,

        "origin": "https://console.firebase.google.com",

        "authorization": authorization

    }


    return headers

「代码解析」

1、好像没啥解析的,就是通过performance的log去过滤我要的接口,拿到接口中的各项信息

2、哪里看不懂给我留言吧

「注意」

我使用的是selenium3,如果你是selenium4,你需要使用如下方法获取:


from seleniumwire import webdriver  #pip install selenium-wire

from selenium.webdriver.common.keys import Keys

from selenium.webdriver.common.by import By

browser = webdriver.Chrome()

browser.get('http://....') #打开

#获取Authorization_str

Authorization_str=''

for request in  browser.requests:  #遍历所有 请求

    # if request.method == 'POST' and \

    #    request.url == 'http://....': #找到这个请求

        if 'Authorization' in request.headers: #有这个标志

            Authorization_str = request.headers['Authorization']  #找到了结果

            break

「要点:」

1、装完selenium后,还得装selenium-wire


from seleniumwire import webdriver  #pip install selenium-wire

代替

from selenium import webdriver

2、仅browser 使用 seleniumwire, 其他都不变,例如 By、keys等还用selenium

知识点:playwright获取cookie

playwright官方cookie代码:BrowserContext | Playwright Python

「方法一:」

自动打开浏览器,手动登录后通过playwright保存cookie到本地,之后需要直接通过文件读取这个cookie。


__author__ = "梦无矶小仔"

from playwright.sync_api import sync_playwright

import json

# 先手动登录,保存Cooies到文件。

def saveCookies():

    with sync_playwright() as p:

        # 显示浏览器,每步操作等待100毫秒

        browser = p.chromium.launch(headless=False, slow_mo=100)

        context = browser.new_context()

        page = context.new_page()

        page.goto('https://cq.meituan.com/', timeout=50000)  # 设置超时时间为50s

        time.sleep(80)  # 此处手动登录,然后到个人信息页再获取cookie

        cookies = context.cookies()

        print(page.title())

        browser.close()

        f = open('cookies.txt', 'w+',,encoding="utf-8")

        json.dump(cookies, f)

        time.sleep(2)

        browser.close()

        print("cookie获取完毕")

saveCookies()#执行函数

「方法二:」

手动打开指定浏览器,使playwright指定改浏览器运行,获取已登录的cookie信息,保存到本地。


__author__ = "梦无矶小仔"

# 对已经打开的浏览器进行操作

import json

import subprocess

from pprint import pprint

from playwright.sync_api import Playwright,sync_playwright


playwright = sync_playwright().start()

# 连接已打开浏览器,指定端口

browser = playwright.chromium.connect_over_cdp("http://127.0.0.1:9222")

default_context = browser.contexts[0] # 注意这里不是browser.new_page()了

page = default_context.pages[0]

base_url = r"https://console.firebase.google.com/" # 我这里截去了项目网站的url进行脱敏

page.goto(base_url)

# page.wait_for_timeout(timeout=15000)

print(page.title()) #firebase标题

time.sleep(5)

cookies = default_context.cookies(urls=base_url) #指定url下的cookie值,不填则是所有的

pprint(cookies)

# 保存cookies到本地

filePath = r'cookies.txt'

with open(filePath,'w+',encoding="utf-8") as f:

    json.dump(cookies,f)


playwright.stop() 

知识点:playwright获取storage_state提取cookie

F12打开浏览器,在Aplication下可以看到本地存储的一些信息,比如cookie、session

官方教程:BrowserContext | Playwright Python


__author__ = "梦无矶小仔"

# 对已经打开的浏览器进行操作

import json

import subprocess

import time

from pprint import pprint

from playwright.sync_api import Playwright,sync_playwright


playwright = sync_playwright().start()

# 连接已打开浏览器,找好端口

browser = playwright.chromium.connect_over_cdp("http://127.0.0.1:9222")

default_context = browser.contexts[0] # 注意这里不是browser.new_page()了

page = default_context.pages[0]

base_url = r"https://console.firebase.google.com"  # 我这里截去了项目网站的url进行脱敏

page.goto(base_url)

print(page.title()) #firebase标题

filePath = r'storage_state.txt'

storage_state = default_context.storage_state(path=filePath)

pprint(storage_state)

playwright.stop()

# browser.close()

但这个方法会有个问题,这里面会把你当前浏览器所有的strong存储的内容拿出来。

如果是像我只是针对某个网站接口的cookie,这个方法就显得有点臃肿,还需要自己去整体过滤,而且本地的还存在一个及时刷新的问题(我遇到过有效期非常短的)。

秃发状况

2023.5.10这天,windows自动更新了,它更新就算了,还自动把我固定版本的chrome浏览器也给更新到了最新版本。

我原先就禁用了chrome自动更新的功能,这次windows更新竟然可以解禁,就很离谱。

这里更新一下windows禁止chrome浏览器自动更新。当前版本是 113.0.5672.93(正式版本) (64 位)

那么浏览器更新了我会遇到一个怎样的问题?

1、selenium是倚靠驱动进行浏览器操作,浏览器更新了我就得更新驱动,但我没有做自动更新驱动的功能

2、之前一直用的是固定某个版本,这样驱动一直用一个就行,现在不得不面临三个选择

  • 更新驱动,再次禁用更新,以后遇到再说(麻烦)

  • 增加自动更新驱动的功能(后续会意想不到的坑)

  • UI层面改为playwright,因为playwright无需依赖三方驱动(懒汉必备)

「于是这三个我都研究了一下,接下来一一解析一下。」

windows禁止chrome浏览器自动更新

1、找到C:\Users\xiaozai\AppData\Local\Google目录下的Update文件夹

2、右键属性,选择安全选项,点击编辑,把这些用户的权限全部改成拒绝

3、在安全选项下,点击高级,点击禁用继承,删除允许用户,点击确认

4、中途点击确认的时候,由于你禁止了权限,会有一堆弹窗,一直点确认就ok了

5、验证,之后你再双击Updata文件夹,发现是无权访问了

6、去chrome查看更新选项,发现已经无法更新了

selenium自动下载驱动

使用Drivers的方式

官方介绍:https://www.selenium.dev/documentation/webdriver/getting_started/install_drivers/

「先安装库:」

pip install webdriver-manager

webdriver-manager 支持selenium3.0、selenium4.0

具体可以看github上的说明:https://github.com/SergeyPirogov/webdriver_manager

基于 selenium3 的chrome示列


# pip install webdriver-manager

#selenium3

import time

from selenium import webdriver

from webdriver_manager.chrome import ChromeDriverManager


driver = webdriver.Chrome(ChromeDriverManager().install())

driver.get("https://www.baidu.com")

driver.maximize_window()

time.sleep(5)

driver.quit()

基于 selenium4 的chrome示列


import time

from selenium import webdriver

from selenium.webdriver.edge.service import Service

from webdriver_manager.chrome import ChromeDriverManager


service = Service(executable_path=ChromeDriverManager().install())

driver = webdriver.Ie(service=service)

driver.get("https://www.baidu.com")

driver.maximize_window()

time.sleep(5)

driver.quit()

是不是非常简单,非常nice?

官方的列子这里我就不放了,大家有兴趣自己去研究哈~

playwright无驱动操作已打开浏览器

详见我之前写的文章,这里就不赘述了,链接如下:

公众号:playwright连接已有浏览器操作 (qq.com)

CSDN:https://blog.csdn.net/qq_46158060/article/details/130429536?spm=1001.2014.3001.5501

Authorization鉴权

对于google类型的所有认证权限都含有一个Authorization,并且加密是SAPISIDHASH,这个我不会破解。

如果请求头不携带此鉴权字段,是无法访问相关接口的。


{

  "error": {

    "code": 401,

    "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",

    "status": "UNAUTHENTICATED",

    "details": [

      {

        "@type": "type.googleapis.com/google.rpc.ErrorInfo",

        "reason": "CREDENTIALS_MISSING",

        "domain": "googleapis.com",

        "metadata": {

          "service": "crashlytics-pa.googleapis.com",

          "method": "google.internal.crashlytics.dashboard.v1.CrashlyticsMetricsReadService.GetCrashStatistics"

        }

      }

    ]

  }

}

通过selenium知道可以在performance获取request请求信息(前文有demo),那么playwright是否有类似的方法?

通过查阅官方文档发现,确实有,它叫事件监听。

目前我们就需要拿到请求头的这些信息,从而通过接口进行获取数据。


Authorization

Cookie

Origin

Referer

User-Agent

playwright 事件监听

官方文档:

page.on事件监听:https://playwright.dev/python/docs/api/class-page#page-event-request

request拦截接口:https://playwright.dev/python/docs/api/class-request

事件监听我们主要使用的是page.on("request",my_request),其他监听事件可以参考官网。

Request事件里面有个all_headers方法,会以字典的形式返回我们请求的请求头信息。

request拦截接口代码示列:


def my_request(request):

    print(request.all_headers())


page.on("request",my_request) # 创建拦截请求,获取请求的hearders  

# 这里推荐使用requestfinished

注意:page.on在page实例后就要创建,代表监控之后page上发生的对应事件。如果在事件发生后创建page.on方法,则无法监控该事件,只能监控创建page.on之后的操作。

如我监控了FireBase后台数据页面,示列代码如下


import json

from pprint import pprint

import requests

from playwright.sync_api import sync_playwright


def my_request(request):

    print(request.all_headers())

    # 对headers进行劫持处理


playwright = sync_playwright().start()

# 连接已打开浏览器,找好端口

browser = playwright.chromium.connect_over_cdp("http://127.0.0.1:9222")

default_context = browser.contexts[0] # 注意这里不是browser.new_page()了

page = default_context.pages[0]

page.on("requestfinished",my_request) # 创建拦截请求,获取请求的hearders

base_url = r"https://console.firebase.google.com/"

page.goto(base_url)

page.wait_for_load_state('networkidle') #等待资源加载,直到没有网络请求,否则得到的资源不完整,拿不到想要的鉴权信息

控制台输出监控到了的所有请求头信息,authorization字段赫然在列,我们可以继续改造my_request方法,拿到我们所需要的headers信息。

image-20230509123703422

但这个时候还遇到个问题,这里获取到的authorization并不是我真正能够使用的,我还需要对referer字段进行过滤,但发现并没有我要找的,F12查看network发现前端是刷新出来了。打印出来的却全是https://console.firebase.google.com/

需要在请求后加上这句,表示等待资源加载,直到没有网络请求。详细可以看官网:https://playwright.dev/python/docs/api/class-page#page-wait-for-load-state

page.wait_for_load_state('networkidle')

但有时候也会有点不灵,所以我推荐使用强制等待。

page.wait_for_timeout(timeout=20000) # 这个timeout是毫秒

接下来需要对这些请求头进行过滤,我只需要拿到一个包含Authorization字段的headers字段的信息就可以结合前面拿到的cookie进行伪造了。

同时过滤:authority字段,注意,F12你看到的首字母是大写,playwright官方文档中有说明,headers返回的都是小写字段,所以我们拿的时候要以小写的方式进行提取。

改造后的my_request方法


# 全局变量

user_headers = {}

def my_request(request):

    all_headers_dict = request.all_headers()

    # 过滤请求(这里我对:path也进行了过滤,完整path我脱敏处理了)

    if all_headers_dict.get(':path') == "/metrics:getCrashFree.." and not user_headers:

        if all_headers_dict[":method"]=='POST' and all_headers_dict[":authority"]=='crashlytics-pa.clients6.google.com' :

            # 提取我需要的信息

            user_headers["cookie"] = all_headers_dict.get("cookie")

            user_headers["user-agent"] = all_headers_dict.get("user-agent")

            user_headers["authorization"] = all_headers_dict.get("authorization")

最终会将第一次获取到的headers信息记录到user_headers字典中。

接着我们就可以使用requests进行携带带有认证信息的请求头进行接口请求了。

使用route劫持

官方文档:Route | Playwright Python

用这个方法也可以获取到请求头的相关信息,它最终还是使用了request获取请求头。

我在使用的过程中发现有时候请求会被阻塞,不知道为啥,对这方面有研究的大佬请指教我一哈,万分感谢。


user_msg_list = []

def handler(route):

    headers_dict = {}

    all_headers_dict = route.request.headers

    if all_headers_dict.get('authorization') and all_headers_dict.get('cookie'):

        # if 'https://scone-pa.clients6.google.com/static/proxy' in all_headers_dict["referer"]:

            # 提取我需要的信息

            headers_dict["referer"] = all_headers_dict.get("referer")

            headers_dict["user-agent"] = all_headers_dict.get("user-agent")

            headers_dict["authorization"] = all_headers_dict.get("authorization")

            headers_dict["cookie"] = all_headers_dict.get("cookie")

            user_msg_list.append(headers_dict)

    route.continue_()


page.route(**/*,handler)

page.wait_for_load_state('networkidle')

最终代码

「注意:对敏感信息已脱敏」


# -*- coding: utf-8 -*-

'''

@Time : 2023/5/10 13:42

@Author : Vincent.xiaozai

@Email : Lvan826199@163.com

@File : demo06_整合请求伪造.py

'''

__author__ = "梦无矶小仔"


import json

import subprocess

import time

from datetime import datetime, timedelta

from pprint import pprint


import requests

from playwright.sync_api import Playwright,sync_playwright


# 全局变量

user_headers = {}

def my_request(request):

    all_headers_dict = request.all_headers()

    # 过滤请求

    if all_headers_dict.get(':path') == "/metrics:getCrashFreeTime..." and not user_headers:

        if all_headers_dict[":method"]=='POST' and all_headers_dict[":authority"]=='crashlytics-pa.clients6.google.com' :

            # 提取我需要的信息

            user_headers["cookie"] = all_headers_dict.get("cookie")

            user_headers["user-agent"] = all_headers_dict.get("user-agent")

            user_headers["authorization"] = all_headers_dict.get("authorization")

            user_headers["referer"] = all_headers_dict.get("referer")

            user_headers["origin"] = all_headers_dict.get("origin")


playwright = sync_playwright().start()

# 连接已打开浏览器,找好端口

browser = playwright.chromium.connect_over_cdp("http://127.0.0.1:9222")

default_context = browser.contexts[0] # 注意这里不是browser.new_page()了

page = default_context.pages[0]

page.on("requestfinished",my_request) # 创建拦截请求,获取请求的hearders

base_url = "https://console.firebase.google.com/u/0/project/..." #完整url已脱敏

page.goto(base_url) 

# 如果要保证刷新可以强制等待

page.wait_for_timeout(timeout=20000)


# 请求头伪造

headers = {

    "Host": "crashlytics-pa.clients6.google.com",

    "content-type": "application/json",

    'user-agent':user_headers["user-agent"],

    "referer": user_headers["referer"],

    'cookie':user_headers["cookie"],

    "origin": user_headers["origin"],

    'authorization': user_headers["authorization"]

}

print("---------------用户cookie及Authorization--------------------------")

print(f"伪造的请求头:{headers}")

print("---------------用户cookie及Authorization--------------------------")


## 执行request请求获取数据

crashAndUsersUrl = "https://crashlytics-pa.clients6.google.com/v1/projects" # 完整url已脱敏

crashAndUsersNum = get_all_crashAndUser(day=2,headers=headers,url=crashAndUsersUrl,the_latest="None",version_bt_list="None",platform='Android',eventType=["FATAL"])

print("----------接口信息打印-------------------")

print(crashAndUsersNum)

这里的get_all_crashAndUser就是我的业务代码,里面是对接口的请求进行了处理,这里就不放出来了。

可以看到最终我们拿到了这个接口的信息。

在之后的操作中,就可以一直使用requests进行接口请求了,如果cookie有使用有效期,那么每隔一段时间用playwright进行重新获取,重新伪造请求头就可以了。

最后作为一位过来人也是希望大家少走一些弯路,在这里我给大家分享一些软件测试的学习资料和我花了3个月整理的软件测试自学全栈,这些资料希望能给你前进的路上带来帮助。

​视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。

更多推荐