某容器管理平台模拟登录(Go语言版本)

大家好,我是TheWeiJun;最近疫情很严重,大家都要做好防护。转眼间,距离上次发文又快一个月了。今天给大家带来一个Go语言版本的某容器管理平台模拟登录的案例,目的是解放双手,实现自动部署和重启操作,欢迎各位读者多多阅读与交流!

特别声明:本公众号文章只作为学术研究,不用于其它不法用途;如有侵权请联系作者删除。

 目录

一、前言介绍

二、模拟登录

三、参数分析

四、代码实现

五、思路总结


趣味模块

老六是一名spider工程师,最近老六遇到了一个棘手的问题,老六每次写完代码上传git后,希望某容器管理平台能够自动同步git代码并且实现代码自动同步重启。老六一番研究后发现某容器平台除了代码宕机重启和定时重启,没有自动重启这么一说!老六很苦恼,后来老六想了想自己的职业,于是决定给他实现自动快速重启功能,彻底解放自己的双手,继续自己的复制粘贴高端操作!接下来,让我们一起去看看老六如何实现的自动登录和快速重启操作吧。


一、前言介绍

1.  什么是模拟登录?

解释:网页上以及app中输入账号密码的过程称为登录,不经过登录有些页面是无法直接跳转的。而模拟登录就是模拟手动输入账号密码的过程,获得登录成功后的cookie,以此cookie直接访问需要登录才能进入的页面,从而获取数据。

2. 模拟登录能做什么?

解释:模拟登录成功后,我们可以模拟人的行为下发一些操作指令,程序会根据我们下发的指令达到真实的人为效果,彻底解放我们的双手。

3.  什么是Cookie?

解释:cookies一种保存在电脑上的一种文件,当我们使用电脑进行浏览网页的时候,服务器就会生成一个证书,并且返回给我们的电脑,这个证书就是cookie,一般情况下,cookie是服务器写入客户端的文件,我们也可以叫浏览器缓存。


二、模拟登录

1、首先打开我们本次需要模拟登录的网站,浏览器中打开网页,截图如下所示:

2、点击账号密码,触发模拟登录操作,打开浏览器开发者工具查看相关请求包,截图如下所示:

环节说明:由于我们想要获取登录的协议接口,故我们随便输入内容,可查看到模拟登录所需要的data参数主要为上图六个。接下来,我们只需要模拟这几个参数并结合headers参数分析即可实现模拟登录!

3、一般网站开发人员为了安全,都会有csrf攻击防护,我们查看请求headers,截图如下所示:

总结:接下来我们只需要模拟data、headers参数即可实现模拟登录访问网站,达到我们最终的目的。


三、参数分析

1、参数初判断,需要我们模拟的参数主要有以下:

   Data参数分析如下:

  • username:登录所需要的账号。

  • password:登录所需要的密码。

  • description:固定值,经过多次验证。

  • responseType:固定值,同上。

  • ttl:网站cookie过期时间,同redis过期时间类似。

  • labels:固定值,同上。

Headers参数分析如下:

  • x-api-csrf:登录所需要csrf参数。

  • Cookie:登录成功后需要保留的参数。

2、为了获取csrf参数,我们再次刷新页面,分析每一个可疑请求包,截图如下所示:

3、从前到后分析每一个请求包,我们发现csrf参数并不是通过response或者Set-Cookie的参数返回,这种情况很少见,于是我们模拟请求,查看响应体,果然有意外收获,截图如下所示:

总结:查看js代码后,我发现了问题关键,这个是由于通过浏览器访问时,js代码会将set-cookie给remove掉,这样对我们在分析该网站时,就会定位不到该参数如何下发的,从而放弃操作。那么接下来,我们用Golang去实现模拟登录,并下发操作重启代码服务吧。


四、代码实现

1、使用http包忽略证书认证,并提取登录所需的csrf参数,代码如下所示:

package mainimport (  "crypto/tls"  "fmt"  "log"  "net/http")func main() {  tr := &http.Transport{    // 处理x509: certificate signed by unknown authority    TLSClientConfig: &tls.Config{InsecureSkipVerify: true},  }  client := &http.Client{Transport: tr}  req, err := http.NewRequest("GET", "https://xxxx/v3/settings/ui-pl", nil)  if err != nil {    log.Fatal(err)  }  req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")  req.Header.Set("Cache-Control", "no-cache")  req.Header.Set("Connection", "keep-alive")  req.Header.Set("Cookie", "R_USERNAME=")  req.Header.Set("Pragma", "no-cache")  req.Header.Set("Referer", "https://xxxx/login")  req.Header.Set("Sec-Fetch-Dest", "empty")  req.Header.Set("Sec-Fetch-Mode", "cors")  req.Header.Set("Sec-Fetch-Site", "same-origin")  req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")  req.Header.Set("accept", "application/json")  req.Header.Set("content-type", "application/json")  req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)  req.Header.Set("sec-ch-ua-mobile", "?0")  req.Header.Set("sec-ch-ua-platform", `"macOS"`)  req.Header.Set("x-api-action-links", "actionLinks")  req.Header.Set("x-api-no-challenge", "true")  resp, err := client.Do(req)  if err != nil {    log.Fatal(err)  }  defer resp.Body.Close()  CsrfStr := resp.Header.Get("Set-Cookie")  fmt.Printf("%s\n", CsrfStr)}

Goland中代码打印截图如下:

2、接下来我们使用上面获取的参数来完成模拟登录操作,完整代码如下:

package mainimport (  "crypto/tls"  "fmt"  "log"  "net/http"  "strings")var tr = &http.Transport{  // 处理x509: certificate signed by unknown authority  TLSClientConfig: &tls.Config{InsecureSkipVerify: true},}var client = &http.Client{Transport: tr}func GetCsrf() string {  req, err := http.NewRequest("GET", "https://xxxx/v3/settings/ui-pl", nil)  if err != nil {    log.Fatal(err)  }  req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")  req.Header.Set("Cache-Control", "no-cache")  req.Header.Set("Connection", "keep-alive")  req.Header.Set("Cookie", "R_USERNAME=xxx")  req.Header.Set("Pragma", "no-cache")  req.Header.Set("Referer", "https://xxxx/login")  req.Header.Set("Sec-Fetch-Dest", "empty")  req.Header.Set("Sec-Fetch-Mode", "cors")  req.Header.Set("Sec-Fetch-Site", "same-origin")  req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")  req.Header.Set("accept", "application/json")  req.Header.Set("content-type", "application/json")  req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)  req.Header.Set("sec-ch-ua-mobile", "?0")  req.Header.Set("sec-ch-ua-platform", `"macOS"`)  req.Header.Set("x-api-action-links", "actionLinks")  req.Header.Set("x-api-no-challenge", "true")  resp, err := client.Do(req)  if err != nil {    log.Fatal(err)  }  defer resp.Body.Close()  SetCookie := resp.Header.Get("Set-Cookie")  splitStr := strings.Split(SetCookie, ";")[0]  CsrfStr := strings.Replace(splitStr, "CSRF=", "", 1)  return CsrfStr}func Login(CsrfStr string) {  var data = strings.NewReader(`{"username":"xxxx","password":"xxxxx","description":"UI Session","responseType":"cookie","ttl":57600000,"labels":{"ui-session":"true"}}`)  req, err := http.NewRequest("POST", "https://xxxx/v3-public/localProviders/local?action=login", data)  if err != nil {    log.Fatal(err)  }  req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")  req.Header.Set("Cache-Control", "no-cache")  req.Header.Set("Connection", "keep-alive")  req.Header.Set("Origin", "https://xxxx")  req.Header.Set("Pragma", "no-cache")  req.Header.Set("Referer", "https://xxxxx/login")  req.Header.Set("Sec-Fetch-Dest", "empty")  req.Header.Set("Cookie", "R_USERNAME=; CSRF="+CsrfStr)  req.Header.Set("Sec-Fetch-Mode", "cors")  req.Header.Set("Sec-Fetch-Site", "same-origin")  req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")  req.Header.Set("accept", "application/json")  req.Header.Set("content-type", "application/json")  req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)  req.Header.Set("sec-ch-ua-mobile", "?0")  req.Header.Set("sec-ch-ua-platform", `"macOS"`)  req.Header.Set("x-api-action-links", "actionLinks")  req.Header.Set("x-api-csrf", CsrfStr)  req.Header.Set("x-api-no-challenge", "true")  resp, err := client.Do(req)  if err != nil {    log.Fatal(err)  }  defer resp.Body.Close()  SetCookie := resp.Header.Get("Set-Cookie")  fmt.Println(SetCookie)}func main() {  CsrfStr := GetCsrf()  Login(CsrfStr)}

Goland中打印登录后的token截图如下:

浏览器中登录后的token截图如下:

3、模拟登录流程实现后,我们开始下发重启代码操作,最后完整代码如下:

package mainimport (  "crypto/tls"  "fmt"  "log"  "net/http"  "strings")var tr = &http.Transport{  // 处理x509: certificate signed by unknown authority  TLSClientConfig: &tls.Config{InsecureSkipVerify: true},}var client = &http.Client{Transport: tr}func GetCsrf() string {  req, err := http.NewRequest("GET", "https://xxxx/v3/settings/ui-pl", nil)  if err != nil {    log.Fatal(err)  }  req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")  req.Header.Set("Cache-Control", "no-cache")  req.Header.Set("Connection", "keep-alive")  req.Header.Set("Cookie", "R_USERNAME=xxx")  req.Header.Set("Pragma", "no-cache")  req.Header.Set("Referer", "https://xxxx/login")  req.Header.Set("Sec-Fetch-Dest", "empty")  req.Header.Set("Sec-Fetch-Mode", "cors")  req.Header.Set("Sec-Fetch-Site", "same-origin")  req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")  req.Header.Set("accept", "application/json")  req.Header.Set("content-type", "application/json")  req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)  req.Header.Set("sec-ch-ua-mobile", "?0")  req.Header.Set("sec-ch-ua-platform", `"macOS"`)  req.Header.Set("x-api-action-links", "actionLinks")  req.Header.Set("x-api-no-challenge", "true")  resp, err := client.Do(req)  if err != nil {    log.Fatal(err)  }  defer resp.Body.Close()  SetCookie := resp.Header.Get("Set-Cookie")  splitStr := strings.Split(SetCookie, ";")[0]  CsrfStr := strings.Replace(splitStr, "CSRF=", "", 1)  return CsrfStr}func Login(CsrfStr string) {  var data = strings.NewReader(`{"username":"xxxx","password":"xxxxx","description":"UI Session","responseType":"cookie","ttl":57600000,"labels":{"ui-session":"true"}}`)  req, err := http.NewRequest("POST", "https://xxxx/v3-public/localProviders/local?action=login", data)  if err != nil {    log.Fatal(err)  }  req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")  req.Header.Set("Cache-Control", "no-cache")  req.Header.Set("Connection", "keep-alive")  req.Header.Set("Origin", "https://xxxx")  req.Header.Set("Pragma", "no-cache")  req.Header.Set("Referer", "https://xxxxx/login")  req.Header.Set("Sec-Fetch-Dest", "empty")  req.Header.Set("Cookie", "R_USERNAME=weijun; CSRF="+CsrfStr)  req.Header.Set("Sec-Fetch-Mode", "cors")  req.Header.Set("Sec-Fetch-Site", "same-origin")  req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")  req.Header.Set("accept", "application/json")  req.Header.Set("content-type", "application/json")  req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)  req.Header.Set("sec-ch-ua-mobile", "?0")  req.Header.Set("sec-ch-ua-platform", `"macOS"`)  req.Header.Set("x-api-action-links", "actionLinks")  req.Header.Set("x-api-csrf", CsrfStr)  req.Header.Set("x-api-no-challenge", "true")  resp, err := client.Do(req)  if err != nil {    log.Fatal(err)  }  defer resp.Body.Close()  SetCookie := resp.Header.Get("Set-Cookie")  fmt.Println(SetCookie)}func Redeploy(token, CsrfStr string) {  req, err := http.NewRequest("POST", "https://xxxxx/v3/project/c-djhlx:p-vnncq/workloads/deployment:default:task5?action=redeploy", nil)  if err != nil {    log.Fatal(err)  }  req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")  req.Header.Set("Cache-Control", "no-cache")  req.Header.Set("Connection", "keep-alive")  req.Header.Set("Content-Length", "0")  req.Header.Set("Cookie", "R_USERNAME=xxxx; CSRF="+CsrfStr+"; "+token)  req.Header.Set("Origin", "https://xxxx")  req.Header.Set("Pragma", "no-cache")  req.Header.Set("Referer", "xxxx")  req.Header.Set("Sec-Fetch-Dest", "empty")  req.Header.Set("Sec-Fetch-Mode", "cors")  req.Header.Set("Sec-Fetch-Site", "same-origin")  req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")  req.Header.Set("accept", "application/json")  req.Header.Set("content-type", "application/json")  req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)  req.Header.Set("sec-ch-ua-mobile", "?0")  req.Header.Set("sec-ch-ua-platform", `"macOS"`)  req.Header.Set("x-api-action-links", "actionLinks")  req.Header.Set("x-api-csrf", CsrfStr)  req.Header.Set("x-api-no-challenge", "true")  resp, err := client.Do(req)  if err != nil {    log.Fatal(err)  }  defer resp.Body.Close()  Options := resp.Header.Get("X-Content-Type-Options")  if Options != "" {    fmt.Println("Redeploy successful!")  }}func main() {  CsrfStr := GetCsrf()  token := Login(CsrfStr)  Redeploy(token, CsrfStr)}

代码运行后,Goland运行截图如下所示:

代码执行完毕后,浏览器中服务状态截图如下:

总结:对比上面两张截图,我们发现我们的代码已经能够重启指定的woker服务了,这样就实现了我们想要的自动化重启功能,整个流程到此就完全结束了!


五、思路总结

通过本次案例分析,我们不仅可以通过Python实现模拟登录,也可以用Go去实现模拟登录。本篇分享到这里就结束了,欢迎大家关注下一期,我们不见不散☀️☀️😊。最后希望大家多多转发、点赞、在看支持一波


关注我们获得更多精彩内容

逆向与爬虫的故事

不止于爬虫开发,还有Js逆向、App逆向!

往期推荐

革新之路:重新设计Scrapy调度器,让爬虫速度翻倍

猿人学逆向比赛第四题-gRPC题解 | Go版本

DX滑块验证码别乱捅!一不小心就反爬了。

某安网别逆向,一不小心就......

微信自动聊天机器狗,配置chatGPT,比Siri还智能!

被魔改md5加密坑了?某网站魔改md5加密逆向还原 (多种语言还原)

作者简介

我是TheWeiJun,有着执着的追求,信奉终身成长,不定义自己,热爱技术但不拘泥于技术,爱好分享,喜欢读书和乐于结交朋友,欢迎扫我微信与我交朋友💕

分享日常学习中关于爬虫及逆向分析的一些思路,文中若有错误的地方,欢迎大家多多交流指正💕

文章来源:逆向与爬虫的故事

原文链接:某容器管理平台模拟登录(Go语言版本)

微信搜公众号:逆向与爬虫的故事;给我一个关注!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/20132.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零开始制作STM32F103RCT6小车(一)

前言: 仅以此系列给实验室的学弟学妹作为小车制作教程来使用,后续的内容我会在这个暑假陆续更新出来,本篇的内容是新建一个适用于STM32F103RCT6的工程 准备工作: 接下来的操作几乎是基于STM32F1xx系列的固件库,这里我…

STM32F103RCT6

STM32F103RCT6是一款由STMicroelectronics公司生产的基于ARM Cortex-M3内核的32位微控制器。 它具有高性能、低功耗和广泛的应用领域。 包括ADC(模数转换器) DAC(数字模拟转换器) TIM(定时器) USART&#x…

STM32(二)STM32F103RCT6板载资源介绍

大家好,今天我跟大家分享一下STM32F103RCT6的板载资源,希望对初学STM32的同学有所启示。当然,本人也是一名初学者,如果有谈论不妥之处,还望您能够批评指正,不吝赐教,本人将非常感激。如果有什么…

STM32F103RCT6——定时器简单用法

STM32F10xx参考手册英文和中文版 百度网盘:https://pan.baidu.com/s/1Z2nB0WVJIxvm3VOI9MQiiw 提取码:lxlx STM32F103RCT6数据手册 链接:https://pan.baidu.com/s/1tRchgf-5C1MN4W58vQ9zPg 提取码:lxlx 定时器分类 STM32F103RCT6…

STM32F103RCT6 介绍

1、STM32F103RCT6 命名含义 ST:意法半导体公司 M:MCU 32:32位单片机 F:通用型单片机 103:产品系列号码 R: 芯片有64个引脚 C:256Kb flash T:LQFP封装 6:芯片工作温度 -40℃…

学STM32(STM32F103RCT6)

系列文章目录 第一章 了解STM32 文章目录 系列文章目录前言一、STM32基本系统1.STM32基本(最小)系统板2.使用STM32为核心器件的基本(最小)系统构成单元 二、主控制器(CPU)1.STM32F103RCT6微控制器参数2.ST…

简单:Windows中的文本与Linux中CentOS的vim编辑器相互复制粘贴方法

简单:Windows中的文本与Linux中vim编辑器中的文本相互复制粘贴的方法 1.从Windows下到vim中: 1,选中windows中文档内容按Ctrlc。 2,进入vi编辑器,在插入模式下,用鼠标点击右键再点击粘贴。 3&#xff0…

linux中vim命令详解(操作大全)

光标的移动 1.用h, j,k,l 来表示光标的移动 vim的金如何退出 2.如何退出vim编辑器操作行 先打出esc推出选项然后shift冒号 ,然后输入q!就可以了 3.如果想进虚拟机中编辑器看操作的话,需要输入vimtutor命令就可以进入vim虚拟器教程了 vim的编辑 4.x代…

Vim的配置 和 windows与Linux之间的复制粘贴

# 在Linux下编程对vim的简单配置,便于编程! 在终端下使用vim进行编辑时,默认情况下,编辑的界面上是没有显示行号、语法高亮度显示、智能缩进 等功能的。为了更好的在vim下进行工作,需要手动设置一个配置文件&#xff1…

如何将剪贴板中的内容复制到linux的vim中

1.按i进入insert模式 2.使用shiftinsert键,即可将你在windows系统中复制的内容粘贴到linux系统中

如何修改linux下的vim复制行数限制

vim-如何修改linux下的vim复制行数限制 来自北方的小强 正在努力成为资深数字IC搬砖工的搬砖工 个人根目录下的.vimrc (~/.vimrc)添加如下内容: set viminfo1000,<600 linux下&#xff0c;默认最多复制50行&#xff0c;上述修改即将限制改为600行。 注意&#xff1a; 1 如果…

Linux vim多行操作

同时注释多行 首先&#xff0c;将光标放在要注释的行首&#xff0c;然后按CTRLV进入列(也叫区块)模式&#xff1b;在行首使用上下键选择要注释的多行&#xff1b;用大写“I”(shifti)进入插入模式&#xff1b;然后输入注释符#(任一符号、字母、数字都可以插入)&#xff1b;最后…

【每日一题】缓存穿透、缓存击穿、缓存雪崩及解决方案

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 当下ChatGPT很火&#xff0c;让人心痒痒想试一试好不好用&#xff0c;因此我就试着借它写一篇文章&#xff0c;但是试了几次最终还是没有…

只需这一篇博客就能完全弄懂LSM树

早期LSM树 为什么需要LSM树 B树的数据都存储在叶子节点中&#xff0c;而叶子节点一般都存储在磁盘中。因此&#xff0c;每次插入的新数据都需要随机写入磁盘&#xff0c;而随机写入的性能非常慢。如果是一个日志系统&#xff0c;每秒钟要写入上千条甚至上万条数据&#xff0c…

数据库/MySQL - 深入探究 - 1

1.应用场景 主要用于了解和掌握数据库/MySQL - 更新操作详细流程。 2.学习/操作 1.文档阅读 主要来自于AI的对话【geek chat&#xff0c; chatgpt】 以及官方文档资料&#xff0c;以及其他技术文章&#xff0c;专栏等。 2.整理输出 抛出问题&#xff1a; 数据库【这里以mysql…

EMQX在Windows系统下的开机自启与异常自动重启脚本

目录 0.前言 1.介绍 2.运行与停止 2.1 运行批处理程序 2.2 停止批处理程序 2.3 开机自启动 3.运行结果 4.详细介绍 5.前台运行版本 0.前言 由于为某万年老项目做运维&#xff0c;但源码遗失以及项目遗留问题导致emqx经常崩溃&#xff0c;故无法追根溯源&#xff0c;迫于…

量化工具使用介绍——Tushare

Tushare ID:497485 今年年初的时候&#xff0c;我和几位小伙伴一起合作打花旗杯&#xff0c;项目和量化交易有关。不可避免地会使用到一些常规的量化工具&#xff08;尤其是python的第三方库&#xff09;&#xff0c;虽然决赛还没有开始&#xff0c;我们已经确定进入了二十强。…

BigQuant策略做量化真的能赚钱吗?

BigQuant策略做量化可以赚钱&#xff0c;但是是建立在一些前提条件基础之上的。量化策略本身存在的意义就是通过数量化模型建立科学投资体系&#xff0c;获取稳定收益&#xff0c;相比传统投资&#xff0c;其具备纪律性、系统性、及时性、准确性等诸多优势&#xff0c;所以一个…

自己做量化交易软件(45)小白量化实战18--直接使用通达信自编指标公式进行分析绘图和回测

自己做量化交易软件(45)小白量化实战18–直接使用通达信自编指标公式进行分析绘图和回测 小白量化一代提供了Python公式算法模式来写量化程序。 小白量化二代提供了仿通达信公式的模式来写量化程序。 小白量化三代除了仿通达信公式的模式来写量化程序外&#xff08;见前几篇博客…

自己做量化交易软件(16)用小白通通量化AI框架打造自己的量化平台

最近一段时间&#xff0c;我主要学习python3和tkinter的窗口开发&#xff0c;对tkinter编程逐步了解。 此外&#xff0c;应广大朋友要求&#xff0c;我写了 一本学习python3学习书籍<小白学Python3实战搭建量化投资平台>. <小白学Python3实战搭建量化投资平台>内容…