半纸鸢 發表於 2022-4-27 17:47:00

python自动化测试工具selenium使用指南

<h2 id="概述">概述</h2>
<p><code>selenium</code>是网页应用中最流行的自动化测试工具,可以用来做自动化测试或者浏览器爬虫等。官网地址为:https://www.selenium.dev/。相对于另外一款web自动化测试工具QTP来说有如下优点:</p>
<ul>
<li>免费开源轻量级,不同语言只需要一个体积很小的依赖包</li>
<li>支持多种系统,包括Windows,Mac,Linux</li>
<li>支持多种浏览器,包括Chrome,FireFox,IE,safari,opera等</li>
<li>支持多语言,包括Java,C,python,c#等主流语言</li>
<li>支持分布式测试用例执行</li>
</ul>
<h3 id="pythonselenium环境安装">python+selenium环境安装</h3>
<p>首先需要安装python(推荐3.7+)环境,然后直接用<code>pip install selenium</code>安装依赖包即可。</p>
<p>另外还需要下载浏览器相应的<code>webdriver</code>驱动程序,<strong>注意下载的驱动版本一定要匹配浏览器版本</strong>。</p>
<ul>
<li>Firefox浏览器驱动:geckodriver</li>
<li>Chrome浏览器驱动:chromedriver</li>
<li>IE浏览器驱动:IEDriverServer</li>
<li>Edge浏览器驱动:MicrosoftWebDriver</li>
<li>Opera浏览器驱动:operadriver</li>
</ul>
<p>下载以后可以把驱动程序加到环境变量,这样使用时就不用手动指定驱动程序路径。</p>
<h2 id="使用selenium启动浏览器">使用selenium启动浏览器</h2>
<p>可以在python中使用下面的代码启动一个<code>Chrome</code>浏览器,然后控制这个浏览器的行为或者读取数据。</p>
<pre><code class="language-python">from selenium import webdriver

# 启动Chrome浏览器,要求chromedriver驱动程序已经配置到环境变量
# 将驱动程序和当前脚本放在同一个文件夹也可以
driver = webdriver.Chrome()

# 手动指定驱动程序路径
driver = webdriver.Chrome(r'D:/uusama/tools/chromedriver.exe')

driver = webdriver.Ie()      # Internet Explorer浏览器
driver = webdriver.Edge()      # Edge浏览器
driver = webdriver.Opera()   # Opera浏览器
driver = webdriver.PhantomJS()   # PhantomJS

driver.get('http://uusama.com')# 打开指定路径的页面
</code></pre>
<p>启动的时候还可以设置启动参数,比如下面的代码实现启动时添加代理,并且忽略<code>https</code>证书校验。</p>
<pre><code class="language-python">from selenium import webdriver

# 创建chrome启动选项对象
options = webdriver.ChromeOptions()


options.add_argument("--proxy-server=127.0.0.1:16666")# 设置代理
options.add_argument("---ignore-certificate-errors")# 设置忽略https证书校验
options.add_experimental_option("excludeSwitches", ["enable-logging"])# 启用日志

# 设置浏览器下载文件时保存的默认路径
prefs = {"download.default_directory": get_download_dir()}
options.add_experimental_option("prefs", prefs)
driver = webdriver.Chrome(options=options)
</code></pre>
<p>一些非常有用的启动选项,下面使用的<code>options = webdriver.ChromeOptions()</code>:</p>
<ul>
<li><code>options.add_argument("--proxy-server=127.0.0.1:16666")</code>: 设置代理,可以结合<code>mitmproxy</code>进行抓包等</li>
<li><code>option.add_experimental_option('excludeSwitches', ['enable-automation'])</code>: 设置绕过<code>selenium</code>检测</li>
<li><code>options.add_argument("---ignore-certificate-errors")</code>: 设置忽略https证书校验</li>
<li><code>options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})</code>: 设置不请求图片模式加快页面加载速度</li>
<li><code>chrome_options.add_argument('--headless')</code>: 设置无头浏览器</li>
</ul>
<h2 id="selenium页面加载等待和检测">selenium页面加载等待和检测</h2>
<p>使用selenium打开页面以后,还不能立刻操作,需要等到待处理页面元素加载完成,这时就需要检测和等待页面元素加载。</p>
<h3 id="使用timesleep等待">使用<code>time.sleep()</code>等待</h3>
<p>最简单的方法就是打开页面以后,使用<code>time.sleep()</code>强制等待一定时间,该方法只能设置一个固定时间等待,如果页面提前加载完成,则会空等阻塞。</p>
<pre><code class="language-python">from time import sleep
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('http://uusama.con')
time.sleep(10)
print('load finish')
</code></pre>
<h3 id="使用implicitly_wait设置最长等待时间">使用<code>implicitly_wait</code>设置最长等待时间</h3>
<p>另外还可以使用<code>implicitly_wait</code>设置最长等待时间,如果在给定时间内页面加载完成或者已经超时,才会执行下一步。该方法会等到所有资源全部加载完成,也就是浏览器标签栏的loading图表不再转才会执行下一步。有可能页面元素已经加载完成,但是js或者图片等资源还未加载完成,此时还需要等待。</p>
<p>另需注意使用<code>implicitly_wait</code>只需设置一次,且对整个<code>driver</code>生命周期都起作用,凡是遇到页面正在加载都会阻塞。</p>
<p>示例如下:</p>
<pre><code class="language-python">from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(30)   # 设置最长等30秒
driver.get('http://uusama.com')
print(driver.current_url)

driver.get('http://baidu.com')
print(driver.current_url)
</code></pre>
<h3 id="使用webdriverwait设置等待条件">使用<code>WebDriverWait</code>设置等待条件</h3>
<p>使用<code>WebDriverWait</code>(selenium.webdriver.support.wait.WebDriverWait)能够更加精确灵活地设置等待时间,<code>WebDriverWait</code>可在设定时间内每隔一段时间检测是否满足某个条件,如果满足条件则进行下一步操作,如果超过设置时间还不满足,则抛出<code>TimeoutException</code>异常,其方法声明如下:</p>
<p><code>WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)</code></p>
<p>其中各参数含义如下:</p>
<ul>
<li><code>driver</code>:浏览器驱动</li>
<li><code>timeout</code>:最长超时时间,默认以秒为单位</li>
<li><code>poll_frequency</code>:检测的间隔(步长)时间,默认为<code>0.5秒</code></li>
<li><code>ignored_exceptions</code>:忽略的异常,即使在调用<code>until()</code>或<code>until_not()</code>的过程中抛出给定异常也不中断</li>
</ul>
<p><code>WebDriverWait()</code>一般配合<code>until()</code>或<code>until_not()</code>方法使用,表示等待阻塞直到返回值为<code>True</code>或者<code>False</code>,需要注意这两个方法的参数都需是可调用对象,即方法名称,可以使用<code>expected_conditions</code>模块中的方法或者自己封装的方法。</p>
<pre><code class="language-python">from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions

driver = webdriver.Chrome()
driver.get("http://baidu.com")

# 判断id为`input`的元素是否被加到了dom树里,并不代表该元素一定可见,如果定位到就返回WebElement
element = WebDriverWait(driver, 5, 0.5).until(expected_conditions.presence_of_element_located((By.ID, "s_btn_wr")))

# implicitly_wait和WebDriverWait都设置时,取二者中最大的等待时间
driver.implicitly_wait(5)

# 判断某个元素是否被添加到了dom里并且可见,可见代表元素可显示且宽和高都大于0
WebDriverWait(driver,10).until(EC.visibility_of_element_located((By.ID, 'su')))

# 判断元素是否可见,如果可见就返回这个元素
WebDriverWait(driver,10).until(EC.visibility_of(driver.find_element(by=By.ID, value='kw')))
</code></pre>
<p>下面列出<code>expected_conditions</code>常用的一些方法:</p>
<ul>
<li><code>title_is</code>: 判断当前页面title是否精确等于预期</li>
<li><code>title_contains</code>:判断当前页面title是否包含预期字符串</li>
<li><code>presence_of_element_located</code>: 判断某个元素是否被加到了dom树里,并不代表该元素一定可见</li>
<li><code>visibility_of_element_located</code>: 判断某个元素是否可见(元素非隐藏,并且元素的宽和高都不等于0)</li>
<li><code>visibility_of</code>: 跟上面的方法做一样的事情,只是上面的方法要传入locator,这个方法直接传定位到的element就好了</li>
<li><code>presence_of_all_elements_located</code>: 判断是否至少有1个元素存在于dom树中。举个例子,如果页面上有n个元素的class都是'column-md-3',那么只要有1个元素存在,这个方法就返回True</li>
<li><code>text_to_be_present_in_element</code>: 判断某个元素中的text是否包含了预期的字符串</li>
<li><code>text_to_be_present_in_element_value</code>: 判断某个元素中的value属性是否包含了预期的字符串</li>
<li><code>frame_to_be_available_and_switch_to_it</code>: 判断该frame是否可以switch进去,如果可以的话,返回True并且switch进去,否则返回False</li>
<li><code>invisibility_of_element_located</code>: 判断某个元素中是否不存在于dom树或不可见</li>
<li><code>element_to_be_clickable</code>: 判断某个元素中是否可见并且是enable的,这样的话才叫clickable</li>
<li><code>staleness_of</code>: 等某个元素从dom树中移除,注意,这个方法也是返回True或False</li>
<li><code>element_to_be_selected</code>: 判断某个元素是否被选中了,一般用在下拉列表</li>
<li><code>element_selection_state_to_be</code>: 判断某个元素的选中状态是否符合预期</li>
<li><code>element_located_selection_state_to_be</code>: 跟上面的方法作用一样,只是上面的方法传入定位到的element,而这个方法传入locator</li>
</ul>
<h3 id="检测document是否加载完成">检测<code>document</code>是否加载完成</h3>
<p>另外还可以使用<code>driver.execute_script('return document.readyState;') == 'complete'</code>来检测<code>document</code>是否加载完成。</p>
<p>注意<code>document</code>加载完成,是不包括那种异步加载ajax请求动态渲染dom的,这种需要使用<code>WebDriverWait</code>检测某个元素是否渲染完成。</p>
<h2 id="selenium元素定位和读取">selenium元素定位和读取</h2>
<h3 id="查找元素">查找元素</h3>
<p>selenium提供了一系列api方便获取chrome中的元素,这些API都返回<code>WebElement</code>对象或其列表,如:</p>
<ul>
<li><code>find_element_by_id(id)</code>: 查找匹配id的第一个元素</li>
<li><code>find_element_by_class_name()</code>: 查找匹配<code>class</code>的第一个元素</li>
<li><code>find_elements_by_xpath()</code>: 查找匹配<code>xpath</code>的所有元素</li>
<li><code>find_elements_by_css_selector()</code>: 查找匹配css选择器的所有元素</li>
</ul>
<p>其实可以看<code>WebDriver</code>类里面的实现源码,其核心实现都是调用两个基本函数:</p>
<ul>
<li><code>find_element(self, by=By.ID, value=None)</code>: 查找匹配策略的第一个元素</li>
<li><code>find_elements(self, by=By.ID, value=None)</code>: 查找匹配策略的所有元素</li>
</ul>
<p>其中<code>by</code>参数可以是<code>ID</code>, <code>CSS_SELECTOR</code>, <code>CLASS_NAME</code>, <code>XPATH</code>等。下面举几个简单的例子:</p>
<ul>
<li>通过xpath查询包含文本<code>登录</code>的第一个元素: <code>find_element_by_xpath("//*")</code></li>
<li>查询包含类名<code>refresh</code>的所有元素: <code>find_elements_by_class_name('refresh')</code></li>
<li>查询<code>table</code>表格的第二行: <code>find_element_by_css_selector('table tbody &gt; tr:nth(2)')</code></li>
</ul>
<h3 id="dom元素交互">dom元素交互</h3>
<p>上面介绍的元素查找结果<code>WebElement</code>对象,常用的api有:</p>
<ul>
<li><code>element.text</code>: 返回元素的文本内容(包括所有后代节点的内容),注意如果元素<code>display=none</code>则返回为空字符串</li>
<li><code>element.screenshot_as_png</code>: 元素的截图</li>
<li><code>element.send_keys("input")</code>: 元素输入框输入<code>input</code>字符串</li>
<li><code>element.get_attribute('data-v')</code>: 获取<code>data-v</code>名称属性值,除了自定义节点属性,还可以获取如<code>textContent</code>等属性</li>
<li><code>element.is_displayed()</code>: 判断元素是否用户可见</li>
<li><code>element.clear()</code>: 清除元素文本</li>
<li><code>element.click()</code>: 点击元素,如果元素不可点击会抛出<code>ElementNotInteractableException</code>异常</li>
<li><code>element.submit()</code>: 模拟表单提交</li>
</ul>
<h3 id="查找元素失败处理">查找元素失败处理</h3>
<p>如果找不到指定元素,则会抛出<code>NoSuchElementException</code>异常,而且需要注意,<code>display=none</code>的元素是可以获取到的,凡是在<code>dom</code>节点中的元素都可以获取到。</p>
<p>而且实际使用的时候要注意一些js代码动态创建的元素,可能需要轮询获取或者监控。</p>
<p>一个检查是否存在指定元素的方法如下:</p>
<pre><code class="language-python">def check_element_exists(xpath):
    try:
      driver.find_element_by_xpath(xpath)
    except NoSuchElementException:
      return False
    return True
</code></pre>
<h2 id="selenium交互控制">selenium交互控制</h2>
<h3 id="actionchains动作链"><code>ActionChains</code>动作链</h3>
<p>webdriver通过<code>ActionChains</code>对象来模拟用户操作,该对象表示一个动作链路队列,所有操作会依次进入队列但不会立即执行,直到调用<code>perform()</code>方法时才会执行。其常用方法如下:</p>
<ul>
<li><code>click(on_element=None)</code>: 单击鼠标左键</li>
<li><code>click_and_hold(on_element=None)</code>: 点击鼠标左键,不松开</li>
<li><code>context_click(on_element=None)</code>: 点击鼠标右键</li>
<li><code>double_click(on_element=None)</code>: 双击鼠标左键</li>
<li><code>send_keys(*keys_to_send)</code>: 发送某个键到当前焦点的元素</li>
<li><code>send_keys_to_element(element, *keys_to_send)</code>: 发送某个键到指定元素</li>
<li><code>key_down(value, element=None)</code>: 按下某个键盘上的键</li>
<li><code>key_up(value, element=None)</code>: 松开某个键</li>
<li><code>drag_and_drop(source, target)</code>: 拖拽到某个元素然后松开</li>
<li><code>drag_and_drop_by_offset(source, xoffset, yoffset)</code>: 拖拽到某个坐标然后松开</li>
<li><code>move_by_offset(xoffset, yoffset)</code>: 鼠标从当前位置移动到某个坐标</li>
<li><code>move_to_element(to_element)</code>: 鼠标移动到某个元素</li>
<li><code>move_to_element_with_offset(to_element, xoffset, yoffset)</code>: 移动到距某个元素(左上角坐标)多少距离的位置</li>
<li><code>perform()</code>: 执行链中的所有动作</li>
<li><code>release(on_element=None)</code>: 在某个元素位置松开鼠标左键</li>
</ul>
<h3 id="模拟鼠标事件">模拟鼠标事件</h3>
<p>下面代码模拟鼠标移动,点击,拖拽等操作,注意操作时需要等待一定时间,否则页面还来不及渲染。</p>
<pre><code class="language-python">from time import sleep
from selenium import webdriver
# 引入 ActionChains 类
from selenium.webdriver.common.action_chains import ActionChains

driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")
action_chains = ActionChains(driver)

target = driver.find_element_by_link_text("搜索")
# 移动鼠标到指定元素然后点击
action_chains.move_to_element(target).click(target).perform()
time.sleep(2)

# 也可以直接调用元素的点击方法
target.click()
time.sleep(2)

# 鼠标移动到(10, 50)坐标处
action_chains.move_by_offset(10, 50).perform()
time.sleep(2)

# 鼠标移动到距离元素target(10, 50)处
action_chains.move_to_element_with_offset(target, 10, 50).perform()
time.sleep(2)

# 鼠标拖拽,将一个元素拖动到另一个元素
dragger = driver.find_element_by_id('dragger')
action.drag_and_drop(dragger, target).perform()
time.sleep(2)

# 也可以使用点击 -&gt; 移动来实现拖拽
action.click_and_hold(dragger).release(target).perform()
time.sleep(2)
action.click_and_hold(dragger).move_to_element(target).release().perform()
</code></pre>
<h3 id="模拟键盘输入事件">模拟键盘输入事件</h3>
<p>通过<code>send_keys</code>模拟键盘事件,常用有:</p>
<ul>
<li><code>send_keys(Keys.BACK_SPACE)</code>: 删除键(BackSpace)</li>
<li><code>send_keys(Keys.SPACE)</code>: 空格键(Space)</li>
<li><code>send_keys(Keys.TAB)</code>: 制表键(Tab)</li>
<li><code>send_keys(Keys.ESCAPE)</code>: 回退键(Esc)</li>
<li><code>send_keys(Keys.ENTER)</code>: 回车键(Enter)</li>
<li><code>send_keys(Keys.F1)</code>: 键盘 F1</li>
<li><code>send_keys(Keys.CONTROL,'a')</code>: 全选(Ctrl+A)</li>
<li><code>send_keys(Keys.CONTROL,'c')</code>: 复制(Ctrl+C)</li>
<li><code>send_keys(Keys.CONTROL,'x')</code>: 剪切(Ctrl+X)</li>
<li><code>send_keys(Keys.CONTROL,'v')</code>: 粘贴(Ctrl+V)</li>
</ul>
<p>示例:定位到输入框,然后输入内容</p>
<pre><code class="language-python"># 输入框输入内容
driver.find_element_by_id("kw").send_keys("uusamaa")

# 模拟回车删除多输入的一个字符a
driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)
</code></pre>
<h3 id="警告框处理">警告框处理</h3>
<p>用于处理调用<code>alert</code>弹出的警告框。</p>
<ul>
<li><code>driver.switch_to_alert()</code>: 切换到警告框</li>
<li><code>text</code>:返回<code>alert/confirm/prompt</code>中的文字信息,比如js调用<code>alert('failed')</code>则会获取<code>failed</code>字符串</li>
<li><code>accept()</code>:接受现有警告框</li>
<li><code>dismiss()</code>:关闭现有警告框</li>
<li><code>send_keys(keysToSend)</code>:将文本发送至警告框</li>
</ul>
<h2 id="selenium浏览器控制">selenium浏览器控制</h2>
<h3 id="基本常用api">基本常用api</h3>
<p>下面列出一些非常实用的浏览器控制api:</p>
<ul>
<li><code>driver.current_url</code>: 获取当前活动窗口的url</li>
<li><code>driver.switch_to_window("windowName")</code>: 移动到指定的标签窗口</li>
<li><code>driver.switch_to_frame("frameName")</code>: 移动到指定名称的<code>iframe</code></li>
<li><code>driver.switch_to_default_content()</code>: 移动到默认文本内容区</li>
<li><code>driver.maximize_window()</code>: 将浏览器最大化显示</li>
<li><code>driver.set_window_size(480, 800)</code>: 设置浏览器宽480、高800显示</li>
<li><code>driver.forword()</code>, <code>driver.back()</code>: 浏览器前进和后退</li>
<li><code>driver.refresh()</code>: 刷新页面</li>
<li><code>driver.close()</code>: 关闭当前标签页</li>
<li><code>driver.quiit()</code>: 关闭整个浏览器</li>
<li><code>driver.save_screenshot('screen.png')</code>: 保存页面截图</li>
<li><code>driver.maximize_window()</code>: 将浏览器最大化显示</li>
<li><code>browser.execute_script('return document.readyState;')</code>: 执行js脚本</li>
</ul>
<h3 id="selenium读取和加载cookie">selenium读取和加载cookie</h3>
<p>使用<code>get_cookies</code>和<code>add_cookie</code>可以实现将cookie缓存到本地,然后启动时加载,这样可以保留登录态。实现如下</p>
<pre><code class="language-python">import os
import json
from selenium import webdriver

driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")

# 读取所有cookie并保存到文件
cookies = driver.get_cookies()
cookie_save_path = 'cookie.json'
with open(cookie_save_path, 'w', encoding='utf-8') as file_handle:
    json.dump(cookies, file_handle, ensure_ascii=False, indent=4)

# 从文件读取cookie并加载到浏览器
with open(cookie_save_path, 'r', encoding='utf-8') as file_handle:
    cookies = json.load(file_handle)
    for cookie in cookies:
      driver.add_cookie(cookie)
</code></pre>
<h3 id="selenium打开新的标签页窗口">selenium打开新的标签页窗口</h3>
<p>使用<code>driver.get(url)</code>会默认在第一个标签窗口打开指定连接,点击页面中的<code>_blank</code>的链接时也会打开一个新的标签窗口。</p>
<p>还可以用下面的方式手动打开一个指定页面的标签窗口,需要注意打开新窗口或者关闭以后,还需要手动调用<code>switch_to.window</code>切换当前活动的标签窗口,否则会抛出<code>NoSuchWindowException</code>异常。</p>
<pre><code class="language-python">from selenium import webdriver

driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")

new_tab_url = 'http://uusama.com'
driver.execute_script(f'window.open("{new_tab_url}", "_blank");')
time.sleep(1)

# 注意:必须调用switch_to.window手动切换window,否则会找不到tab view
# 聚焦到新打开的tab页面,然后关闭
driver.switch_to.window(driver.window_handles)
time.sleep(2)
driver.close()   # 关闭当前窗口

# 手动回到原来的tab页面
driver.switch_to.window(driver.window_handles)
time.sleep(1)
</code></pre>
<p>除了使用<code>execute_script</code>外,还可以使用模拟打开新tab页按键的方式新建一个标签页窗口:</p>
<ul>
<li><code>driver.find_element_by_tag_name('body').send_keys(Keys.CONTROL + 't')</code></li>
<li><code>ActionChains(driver).key_down(Keys.CONTROL).send_keys('t').key_up(Keys.CONTROL).perform()</code></li>
</ul>
<h2 id="selenium一些问题记录">selenium一些问题记录</h2>
<h3 id="获取隐藏元素的文本内容">获取隐藏元素的文本内容</h3>
<p>如果一个元素是隐藏的,即<code>display=none</code>,虽然可以通过<code>find_element</code>查找到该元素,但是用<code>element.text</code>属性是获取不到该元素文本内容的,其值是空字符串,这时可以用下面的方式获取:</p>
<pre><code class="language-python">element = driver.find_element_by_id('uusama')
driver.execute_script("return arguments.textContent", element)
driver.execute_script("return arguments.innerHTML", element)

# 相应的也可以把隐藏的元素设置为非隐藏
driver.execute_script("arguments.style.display = 'block';", element)
</code></pre>
<h3 id="浏览器崩溃webdriverexception异常处理">浏览器崩溃<code>WebDriverException</code>异常处理</h3>
<p>比如在<code>Chrome</code>中长时间运行一个页面会出现<code>Out Of Memory</code>内存不足的错误,此时<code>WebDriver</code>会抛出<code>WebDriverException</code>异常,基本所有api都会抛出这个异常,这个时候需要捕获并进行特殊处理。</p>
<p>我的处理方式是记录页面的一些基本信息,比如url,cookie等,并定期写入到文件中,如果检测到该异常,则重启浏览器并且加载url和cookie等数据。</p>
<h3 id="selenium抓取页面请求数据">selenium抓取页面请求数据</h3>
<p>网上有通过<code>driver.requests</code>或者通过解析日志来获取页面请求的方式,但是我感觉都不是很好使。最后使用<code>mitmproxy</code>代理进行抓包处理,然后启动<code>selenium</code>时填入代理来实现。</p>
<p><code>proxy.py</code>为在<code>mitmproxy</code>基础上封装的自定义代理请求处理,其代码如下:</p>
<pre><code class="language-python">import os
import gzip
from mitmproxy.options import Options
from mitmproxy.proxy.config import ProxyConfig
from mitmproxy.proxy.server import ProxyServer
from mitmproxy.tools.dump import DumpMaster
from mitmproxy.http import HTTPFlow
from mitmproxy.websocket import WebSocketFlow


class ProxyMaster(DumpMaster):
    def __init__(self, *args, **kwargs):
      super().__init__(*args, **kwargs)

    def run(self, func=None):
      try:
            DumpMaster.run(self, func)
      except KeyboardInterrupt:
            self.shutdown()


def process(url: str, request_body: str, response_content: str):
    # 抓包请求处理,可以在这儿转存和解析数据
    pass


class Addon(object):
    def websocket_message(self, flow: WebSocketFlow):
      # 监听websockt请求
      pass

    def response(self, flow: HTTPFlow):
      # 避免一直保存flow流,导致内存占用飙升
      # flow.request.headers["Connection"] = "close"
      # 监听http请求响应,并获取请求体和响应内容
      url = flow.request.url
      request_body = flow.request
      response_content = flow.response

      # 如果返回值是压缩的内容需要进行解压缩
      if response_content.data.content.startswith(b'\x1f\x8b\x08'):
            response_content = gzip.decompress(response_content.data.content).decode('utf-8')
      Addon.EXECUTOR.submit(process, url, request_body, response_content)


def run_proxy_server():
    options = Options(listen_host='0.0.0.0', listen_port=16666)
    config = ProxyConfig(options)
    master = ProxyMaster(options, with_termlog=False, with_dumper=False)
    master.server = ProxyServer(config)
    master.addons.add(Addon())
    master.run()


if __name__ == '__main__':
    with open('proxy.pid', mode='w') as fin:
      fin.write(os.getpid().__str__())
    run_proxy_server()
</code></pre>
<p>在使用<code>mitmproxy</code>过程中,随着时间推移<code>proxy.py</code>会出现占用内存飙升的问题,在github的issue区有人也遇到过,有说是因为http连接<code>keep-alive=true</code>请求会一直保存不会释放,导致请求越多越占用内存,然后通过添加<code>flow.request.headers["Connection"] = "close"</code>来手动关闭连接,我加了以后有一定缓解,但还是不能从根本上解决。</p>
<p>最后通过写入<code>proxy.pid</code>记录代理程序进程,然后用另外一个程序定时重启<code>proxy.py</code>来解决内存泄漏的问题。</p><br><br>
来源:https://www.cnblogs.com/youyoui/p/16199944.html
頁: [1]
查看完整版本: python自动化测试工具selenium使用指南