• 0基础也能教会你如何搭建Python+Selenium环境以及元素定位


    前言

    不用我说现在很多人出去面试,基本上面试官都会问你会不会自动化这个问题,显而易见自动化测试已经成为一种趋势,今后较好的测试岗位一定会竞争的更加激烈,既然岗位如此的内卷,那我们还不得赶紧学起来。

    今天主要给大家讲讲如何搭建Python+Selenium环境以及八大元素定位法,话不多说我们直接进入主题。

    一、Selenium 的环境搭建

    在 Windows 搭建和部署 Selenium 工具
    主要包括两个步骤:
    安装 Python 语言
    Python的官方网站:http://www.python.org
    Python 目前并行了两套版本,2.x 和 3.x。如果你之前没有 Python 的使用经验,建议使用 Python 3.x 版本。两套版本互相不兼容,并且 Python 从 3.5(含)开始,不再支持 Windows XP 系统,请注意。
    选择安装目录
    3.4或者3.4以下的版本,都是 C:\python34
    3.5以上的目录,默认装个人文件夹,建议用类似上面的目录,比如C:\python35
    勾选添加环境变量
    勾选Add Python.exe to PATH
    安装过程中不要关闭弹出来的命令行窗口
    关于 Python 的安装,也可以选择一些第三方的Python 安装包,典型的有 Anaconda3,这样的包有丰富的第三方库,在使用 Python 的过程中会更加方便。
    Anaconda 的官网:https://www.continuum.io/anaconda-overview
    安装 Selenium 工具包
    由于 安装好的 Python 默认有 pip Python 包管理工具,可以通过 pip 非常方便的安装 Selenium。
    启动命令行工具:Win+R | 输入 cmd | 回车
    输入命令:
    pip install selenium
    该命令的执行需要有互联网联网环境。此外该命令有以下几种选项可以使用
    安装指定的版本,例如安装指定的 Selenium 3.4.3
    pip install selenium==3.4.3

    安装最新版的 Selenium
    pip install -U selenium # -U 也可以用 --upgrade pip install --upgrade selenium

    卸载安装当前的 Selenium
    pip uninstall selenium
    当然,如果您的机器处于非接入互联网的环境,您可以事先下载 Selenium 的 Python 安装包,再进行手动安装。
    官方下载地址:https://pypi.python.org/pypi/selenium
    上述地址会下载最新版的 Selenium,目前最先版的是 3.4.3,您也可以根据以下路径下载指定的 3.4.3
    Selenium 3.4.3 下载地址:https://pypi.python.org/pypi/selenium/3.4.3#downloads
    下载后,解压该压缩包
    然后用命令行进入该压缩包的根目录,输入命令进行安装
    python setup.py install

    配置 浏览器 和 驱动
    Selenium 2 可以默认支持Firefox 46.0或者更低版本,对于其他浏览器需要额外安装驱动。
    Selenium 3 对于所有的浏览器都需要安装驱动,本文以 Chrome 和 Firefox、IE为例设置浏览器和驱动。
    ChromeDriver下载地址:http://chromedriver.storage.googleapis.com/index.html
    ChromeDriver 与 Chrome 对应关系表:
    ChromeDriver版本 支持的Chrome版本 v2.31 v58-60 v2.30 v58-60 v2.29 v56-58 v2.28 v55-57 v2.27 v54-56 v2.26 v53-55 v2.25 v53-55 v2.24 v52-54 v2.23 v51-53 v2.22 v49-52 v2.21 v46-50 v2.20 v43-48
    GeckoDriver下载地址:https://github.com/mozilla/geckodriver/releases
    GeckoDriver 与 Firefox 的对应关系表:
    GeckoDriver版本 支持的Firefox版本 v0.18.0 v56 v0.17.0 v55 v0.16.0 v54,需要Selenium 3.4或者以上 v0.15.0 v53,需要Selenium 3.3或者以上
    IEDriverServer下载地址:http://selenium-release.storage.googleapis.com/index.html
    IEDriverServer 的版本需要与 Selenium 保持严格一致。
    浏览器驱动的配置
    首先,将下载好的对应版本的浏览器安装。
    其次,在 Python 的根目录中,放入浏览器驱动。
    最好再重启电脑,一般情况下不重启也可以的。

    二、 Selenium 的最简脚本

    通过上一节的环境安装成功以后,我们可以进行第一个对Selenium 的使用,就是最简脚本编写。脚本如下:

    # 声明一个司机,司机是个Chrome类的对象
    driver = webdriver.Chrome()
    
    # 让司机加载一个网页
    driver.get("http://demo.ranzhi.org")
    
    # 给司机3秒钟去打开
    sleep(3)
    
    # 开始登录
    # 1. 让司机找用户名的输入框
    we_account = driver.find_element_by_css_selector('#account')
    we_account.clear()
    we_account.send_keys("demo")
    
    # 2. 让司机找密码的输入框
    we_password = driver.find_element_by_css_selector('#password')
    we_password.clear()
    we_password.send_keys("demo")
    
    # 3. 让司机找 登录按钮 并 单击
    driver.find_element_by_css_selector('#submit').click()
    sleep(3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    实际上一段20行的代码,也不能算太少了。但是这段代码的使用,确实体现了 Selenium 的最简单的使用。我们在下面内容进行阐述。

    关于面向对象编程

    通过前面的介绍,我们知道 Selenium 支持多种语言,并且推荐使用面向对象的方式进行编程。接下来我们将着重介绍如何使用面向对象的方式进行编程。

    我们利用 Python 进行面向对象编程,需要首先了解一个概念:类

    类是任何面向对象编程的语言的基本组成,描述了使用的基本方法。我们可能在目前,还不是特别明白类的含义,但是我们可以通过类的使用,来进一步了解。

    类的使用

    类,通过实例化进行使用。比如有一个类: Driver,该类有一个方法: head(road)
    那么关于这个类的使用,只需要两个步骤:
    实例化该类:d = Driver()
    调用类的方法:d.head(“中山路”)
    了解上述例子和使用以后,我们来看具体的 Selenium 的使用。

    具体的对象的使用

    在面向对象的理念看来,任何的编码,都是由对象而来的,这里也不例外。和之前介绍 WebDriver 时候的描述对应,我们需要用到两种主要的类,并将其实例化。
    WebDriver 类:主要靠直接实例化该类为对象,然后用其对象直接调用该类的方法和属性
    WebElement 类:主要通过 WebDriver 类实例化的对象,通过对页面元素的查找,得到 WebElement 类的对象,然后调用该类的方法和属性。
    在这里插入图片描述
    上述代码中,使用了一个 WebDriver 类 的对象,即第2行,声明了该类的对象,并赋值给变量 driver,接着变量 driver 作为 WebDriver 类的对象,使用了多个 WebDriver 类的方法。
    注意:Chrome 是 WebDriver 的子类,是 WebDriver 类的一种

    get(url): 第5行,打开网址

    find_element_by_css_selector(selector): 第12、17、22行都使用了该方法,同时通过对该方法的调用,分别各产生了一个 WebElement类的对象,we_account,we_password和最后一个匿名的对象,并通过产生的三个对象,调用 WebElement 类的方法 clear():清理页面元素中的文字 send_keys(text):给页面元素中,输入新的文字 click():鼠标左键点击页面元素

    正是通过这样的面向对象的方式,产生 Web司机(WebDriver类的对象),并且通过 Web司机不懈的努力,寻找到各种 Web元素(WebElement类的对象)进行操作,这样便实现了 Selenium WebDriver 作为一款出色的浏览器测试工具,进行浏览器UI界面的自动化测试的代码编写和用例执行。

    三、Selenium WebDriver API 的使用

    通过上述最简脚本的使用,我们可以来进一步了解 Selenium 的使用。事实上,上一节用的,便是 Selenium 的 WebDriver API。API(Application Programming Interface,应用程序编程接口,即通过编程语言,操作 WebDriver 的方法集合)

    Selenium WebDriver API 官方参考:http://seleniumhq.github.io/selenium/docs/api/py/

    具体API文档地址:https://seleniumhq.github.io/selenium/docs/api/py/api.html

    API 使用: 用现成的类(大部分情况)的方法进行编程 WebDriver WebElement
    API 文档 编程使用说明 介绍了每个方法的使用 方法的作用 方法的参数 方法的返回值

    四、 控制浏览器

    浏览器的控制也是自动化测试的一个基本组成部分,我们可以将浏览器最大化,设置浏览器的高度和宽度以及对浏览器进行导航操作等。

    # 浏览器打开网址
    driver.get("https://www.baidu.com")
    
    # 浏览器最大化
    driver.maximize_window()
    
    # 设置浏览器的高度为800像素,宽度为480像素
    driver.set_window_size(480, 800)
    
    # 浏览器后退
    driver.back()
    
    # 浏览器前进
    driver.forward()
    
    # 浏览器关闭
    driver.close()
    
    # 浏览器退出
    driver.quit()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    五、 元素定位操作

    WebDriver提供了一系列的定位符以便使用元素定位方法。常见的定位符有以下几种:

    id
    name
    class name
    tag
    link text
    partial link text
    xpath
    css selector
    那么我们以下的操作将会基于上述的定位符进行定位操作。

    对于元素的定位,WebDriver API可以通过定位简单的元素和一组元素来操作。在这里,我们需要告诉Selenium如何去找元素,以至于他可以充分的模拟用户行为,或者通过查看元素的属性和状态,以便我们执行一系列的检查。

    在Selenium2中,WebDriver提供了多种多样的find_element_by方法在一个网页里面查找元素。这些方法通过提供过滤标准来定位元素。当然WebDriver也提供了同样多种多样的find_elements_by的方式去定位多个元素。

    尽管上述的方式,可以进行元素定位,实际上我们也是更多的用组合的方式进行元素定位。
    在这里插入图片描述
    接下来的列表将会详细展示find_elements_by的方法集合。这些方法依据匹配的具体标准返回一系列的元素。
    在这里插入图片描述
    1、依据ID查找

    请查看如下HTML的代码,以便实现通过ID的属性值去定义一个查找文本框的查找:

    <input id="search" type="text" name="q" value=""
           class="input-text" maxlength="128" autocomplete="off"/>
    
    • 1
    • 2

    根据上述代码,这里我们使用find_element_by_id()的方法去查找搜索框并且检查它的最大长度maxlength属性。我们通过传递ID的属性值作为参数去查找,参考如下的代码示例:

    def test_search_text_field_max_length(self):
        # get the search textbox
        search_field = self.driver.find_element_by_id("search")
        # check maxlength attribute is set to 128
        self.assertEqual("128", search_field.get_attribute("maxlength"))
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果使用find_elements_by_id()方法,将会返回所有的具有相同ID属性值的一系列元素。

    2、依据名称name查找

    这里还是根据上述ID查找的HTML代码,使用find_element_by_name的方法进行查找。参考如下的代码示例:

    # get the search textbox
    self.search_field = self.driver.find_element_by_name("q")
    
    • 1
    • 2

    同样,如果使用find_elements_by_name()方法,将会返回所有的具有相同name属性值的一系列元素。

    3、依据class name查找

    除了上述的ID和name的方式查找,我们还可以使用class name的方式进行查找和定位。

    事实上,通过ID,name或者类名class name查找元素是最提倡推荐的和最快的方式。当然Selenium2 WebDriver也提供了一些其他的方式,在上述三类方式条件不足,查找无效的时候,可以通过这些其他方式来查找。这些方式将会在后续的内容中讲述。
    请查看如下的HTML代码,通过改代码进行练习和理解.

    <button type="submit" title="Search" class="button">
      <span><span>Search</span></span>
    </button>
    
    • 1
    • 2
    • 3

    根据上述代码,使用find_element_by_class_name()方法去定位元素。

    def test_search_button_enabled(self):
        # get Search button
        search_button = self.driver.find_element_by_class_name("button")
        # check Search button is enabled
        self.assertTrue(search_button.is_enabled())
    
    • 1
    • 2
    • 3
    • 4
    • 5

    同样的如果使用find_elements_by_class_name()方法去定位元素,将会返回所有的具有相同name属性值的一系列元素。

    4、依据标签名tag name查找

    利用标签的方法类似于利用类名等方法进行查找。我们可以轻松的查找出一系列的具有相同标签名的元素。例如我们可以通过查找表中的来获取行数。

    下面有一个HTML的示例,这里在无序列表中使用了标签。

    <ul class="promos">
        <li>
            <a href="http://demo.magentocommerce.com/home-decor.html">
                <img src="/media/wysiwyg/homepage-three-column-promo-
            01B.png" alt="Physical &amp; Virtual Gift Cards">
            </a>
        </li>
        <li>
            <a href="http://demo.magentocommerce.com/vip.html">
                <img src="/media/wysiwyg/homepage-three-column-promo-
            02.png" alt="Shop Private Sales - Members Only">
            </a>
        </li>
        <li>
            <a href="http://demo.magentocommerce.com/accessories/
            bags-luggage.html">
                <img src="/media/wysiwyg/homepage-three-columnpromo-
            03.png" alt="Travel Gear for Every Occasion">
            </a>
        </li>
    </ul>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    这里面我们使用find_elements_by_tag_name()的方式去获取全部的图片,在此之前,我们将会使用find_element_by_class_name()去获取到指定的

    具体代码如下:

    def test_count_of_promo_banners_images(self):
        # get promo banner list
        banner_list = self.driver.find_element_by_class_name("promos")
        # get images from the banner_list
        banners = banner_list.find_elements_by_tag_name("img")
        # check there are 20 tags displayed on the page
        self.assertEqual(20, len(banners))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5、依据链接文字link查找

    链接文字查找通常比较简单。使用find_element_by_link_text请查看以下示例

    <a href="#header-account" class="skip-link skip-account">
        <span class="icon"></span>
        <span class="label">ACCOUNT Description</span>
    </a>
    
    • 1
    • 2
    • 3
    • 4

    测试代码如下:

    def test_my_account_link_is_displayed(self):
        # get the Account link
        account_link =
        self.driver.find_element_by_link_text("ACCOUNT Description")
        # check My Account link is displayed/visible in
        # the Home page footer
        self.assertTrue(account_link.is_displayed())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6、依据部分链接文字partial text查找

    这里依旧使用上述的列子进行代码编写:

    def test_account_links(self):
        # get the all the links with Account text in it
        account_links = self.driver.\
        find_elements_by_partial_link_text("ACCOUNT")
        # check Account and My Account link is
        # displayed/visible in the Home page footer
        self.assertTrue(2, len(account_links))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    7、依据XPath进行查找

    XPath是一种在XML文档中搜索和定位节点node的一种查询语言。所有的主流Web浏览器都支持XPath。Selenium2可以用强大的XPath在页面中查找元素。

    常用的XPath的方法有starts-with(),contains()和ends-with()等

    若想要了解更多关于XPath的内容,请查看http://www.w3schools.com/XPath/
    如下有一段HTML代码,其中里面的没有使用ID,name或者类属性,所以我们无法使用之前的方法。亚这里我们可以通过的alt属性,定位到指定的tag。

    <ul class="promos">
        <li>
            <a href="http://demo.magentocommerce.com/home-decor.html">
                <img src="/media/wysiwyg/homepage-three-column-promo-
            01B.png" alt="Physical &amp; Virtual Gift Cards">
            </a>
        </li>
        <li>
            <a href="http://demo.magentocommerce.com/vip.html">
                <img src="/media/wysiwyg/homepage-three-column-promo-
            02.png" alt="Shop Private Sales - Members Only">
            </a>
        </li>
        <li>
            <a href="http://demo.magentocommerce.com/accessories/
            bags-luggage.html">
                <img src="/media/wysiwyg/homepage-three-columnpromo-
            03.png" alt="Travel Gear for Every Occasion">
            </a>
        </li>
    </ul>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    具体代码如下:

    def test_vip_promo(self):
        # get vip promo image
        vip_promo = self.driver.\
        find_element_by_xpath("//img[@alt='Shop Private Sales - Members Only']")
        # check vip promo logo is displayed on home page
        self.assertTrue(vip_promo.is_displayed())
        # click on vip promo images to open the page
        vip_promo.click()
        # check page title
        self.assertEqual("VIP", self.driver.title)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当然,如果使用find_elements_by_xpath()的方法,将会返回所有匹配了XPath查询的元素。

    8、依据CSS选择器进行查找

    CSS是一种设计师用来描绘HTML文档的视觉的层叠样式表。一般来说CSS用来定位多种多样的风格,同时可以用来是同样的标签使用同样的风格等。类似于XPath,Selenium2也可以使用CSS选择器来定位元素。

    请查看如下的HTML文档。

    <div class="minicart-wrapper">
        <p class="block-subtitle">Recently added item(s)
            <a class="close skip-link-close" href="#" title="Close">×</a>
        </p>
        <p class="empty">You have no items in your shopping cart.
        </p>
    </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们来创建一个测试,验证这些消息是否正确。

    def test_shopping_cart_status(self):
        # check content of My Shopping Cart block on Home page
        # get the Shopping cart icon and click to open the
        # Shopping Cart section
        shopping_cart_icon = self.driver.\
        find_element_by_css_selector("div.header-minicart
                                     span.icon")
        shopping_cart_icon.click()
        # get the shopping cart status
        shopping_cart_status = self.driver.\
        find_element_by_css_selector("p.empty").text
        self.assertEqual("You have no items in your shopping cart.",
        shopping_cart_status)
        # close the shopping cart section
        close_button = self.driver.\
        find_element_by_css_selector("div.minicart-wrapper
                                     a.close")
        close_button.click()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    特殊 iframe 操作

    iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。
    iframe: 紫禁城
    在一个中,包含了另一个

    示例

    <html>
      <head>
        <title>iframe示例</title>
      </head>
      <body>
        <h1>
          这里是H1,标记了标题
        </h1>
        <p>
          这里是段落,标记一个段落,属于外层
        </p>
        <div>
          <iframe id="iframe-1">
            <html>
              <body>
                <p>
                  这里是个段落,属于内层,内联框架中的
                </p>
                <div id="div-1">
                  <p class="hahahp">
                    这里是div中的段落,需要被定位
                  </p>
                </div>
              </body>
            </html>
          </iframe>
        </div>
      </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    需要定位上面示例中的

    :这里是div中的段落,需要被定位

    如下是selenium WebDiriver的代码

    ## 查找并定位 iframe
    element_frame = driver.find_element_by_css_selector('#iframe-1')
    ## 切换到刚刚查找到的 iframe
    driver.switch_to.frame(element_frame)
    ## 定位 <p>
    driver.find_element_by_css_selector('#div-1 > p')
    ## TODO....
    ## 退出刚刚切换进去的 iframe
    driver.switch_to.default_content()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    特殊 Select 操作

    <select> 是选择列表
    Select 是个selenium的类selenium.webdriver.support.select.Select
    Select 类的路径:
    C:\Python35\Lib\site-packages\selenium\webdriver\support\select.py
    
    • 1
    • 2
    • 3
    • 4
    <select id="brand">
      <option value ="volvo">Volvo</option>
      <option value ="saab">Saab</option>
      <option value="opel">Opel</option>
      <option value="audi">Audi</option>
    </select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    示例,选择 Audi

    ## 查找并定位到 select
    element_select = driver.find_element_by_css_selector('#brand')
    ## 用Select类的构造方法,实例化一个对象 object_select
    object_select = Select(element_select)
    ## 操作 object_select
    object_select.select_by_index(3)
    ## 也可以这样
    object_select.select_by_value('audi')
    ## 还可以这样
    object_select.select_by_visible_text('Audi')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    组合操作

    自动化经验的积累,需要100%按照手工的步骤进行操作。

    比如步骤如下:

    点击一个
    自动产生了一个


    • 点击
      • 的第五个

    代码示例

    driver.find_element_by_css_selector('#customer_chosen').click()
    sleep(1)
    driver.find_element_by_css_selector('#customer_list > li:nth-child(5)')
    
    • 1
    • 2
    • 3

    作为过来人的一些忠告

    最后,作为过来人,给所有测试员一些忠告~~一名测试员,你真的不该只会点点点~随着体系的改变,对于现在的测试人员来说,不是自动化或者代码有多重要,而是懂自动化,懂代码,能够理解系统的实现,已经变成了必备技能。

    自动化测试到底应该学什么?

    1、建议是学 selenium,开源的,免费的,你可以下载源码研究,去了解其原理,再者 selenium 测试思路和手工测试类似,学起来比较轻松。

    2、不建议学 seleniumIDE 录制,当然你可以通过录制一些然后转换为相应的脚本去学习,等你学好了 selenium,再去学 appium 你会有一种天然的似曾相识。为什么不建议学 QTP/UFT?因为收费/臃肿/现在做桌面软件测试的工作已经不太多了。

    3、关于买书,看书能够提高一定的理论知识,但是解决不了实际问题,自动化测试的能力还是要靠一行一行代码敲出来的。

    学习安排上!

    感谢每一个认真阅读我文章的人,下面这个网盘链接也是我费了几个月时间整理的非常全面的,希望也能帮助到有需要的你!
    在这里插入图片描述
    这些资料,对于想转行做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。希望对大家有所帮助……

    如果你不想一个人野蛮生长,找不到系统的资料,问题得不到帮助,坚持几天便放弃的感受的话,可以点击下方小卡片加入我们群,大家可以一起讨论交流,里面会有各种软件测试资料和技术交流,同时我也把上面花几个月整理的资料放里边了,赶快加入吧。

    敲字不易,如果此文章对你有帮助的话,点个赞收个藏来个关注,给作者一个鼓励。也方便你下次能够快速查找。

  • 相关阅读:
    百战RHCE(第四十一战:linux高级应用-重置root密码)
    房产网小程序源码 房产中介小程序源码 房产网源码
    华钜同创:亚马逊卖家培训如何追溯流量变化
    python案例:百钱买鸡
    Vite2 + Vue3 + TypeScript + Pinia 搭建一套企业级的开发脚手架
    go-zero&go web集成redis实战
    【已解决】Linux服务器安装JDK
    MMdetection瑕疵检测环境搭建
    shell脚本之find命令
    y79.第四章 Prometheus大厂监控体系及实战 -- prometheus的服务发现机制(十)
  • 原文地址:https://blog.csdn.net/weixin_57805858/article/details/127651032