大家好!过去两年,我一直在使用Python,但从未学习过正确的面向对象编程和设计模式。今年,我决定通过阅读一些书来弥补这一差距,并将这些知识应用到一个现实世界的问题上。我期待着从所有的建议中学到很多:)
为了开始我的学习,我决定每周自动完成一次重复的任务,填充位于Microsoft团队中的时间表,使用机器人为我完成繁重的工作。bot应执行以下步骤:
目前,机器人几乎完成了所有步骤,除了最后两个步骤,我还没有实现这两个步骤。
代码很简单。我非常依赖selenium来执行所有操作,因此我希望创建一个chrome实例,代理将在其中执行其操作。
自然,我首先导入将要使用的库:
import os
import time
import random
from selenium import webdriver
from dataclasses import dataclass
from abc import ABC, abstractmethod
from webdriver_manager.chrome import ChromeDriverManager接下来,我定义了immutable类,其唯一目的是包含静态的信息,这样就可以避免代码重复。
@dataclass(frozen=True)
class XPathsContainer:
teams_login_button: str = '//*[@id="mectrl_main_trigger"]/div/div[1]'
teams_login_user_button: str = '//*[@id="i0116"]'
teams_login_next_button: str = '//*[@id="idSIButton9"]'
teams_login_pwd_button: str = '//*[@id="i0118"]'
teams_sign_in_button: str = '//*[@id="idSIButton9"]'
teams_sign_in_keep_logged_in: str = '//*[@id="KmsiCheckboxField"]'
@dataclass(frozen=True)
class UrlsContainer:
teams_login_page: str = 'https://www.microsoft.com/en-in/microsoft-365/microsoft-teams/group-chat-software'现在,我尝试实现一个名为Driver的基类。This类包含chrome对象的初始化,并设置继承的其他代理的基础。每个Agent子类可能(在将来)有不同的操作,但它们必须有一个睡眠方法(为了避免在使用机器人方面的限制),它们必须能够单击、写入信息和导航到页面。
class Driver(ABC):
def __init__(self, action, instruction, driver=None):
if driver:
self.driver = driver
else:
self.driver = webdriver.Chrome(ChromeDriverManager().install())
self.actions = {
'navigate': self.navigate,
'click': self.click,
'write': self.write
}
self.parameters = {
'action': None,
'instruction': None
}
@abstractmethod
def sleep(self, current_tick=1):
pass
@abstractmethod
def navigate(self, *args):
pass
@abstractmethod
def click(self, *args):
pass
@abstractmethod
def write(self, **kwargs):
pass
@abstractmethod
def main(self, **kwargs):
pass现在,我实现了一个基本的Agent child类,它实现了基类 Driver所需函数的逻辑。
class Agent(Driver):
def __init__(self, action, instruction, driver):
super().__init__(action, instruction, driver)
self.action = action
self.instruction = instruction
def sleep(self, current_tick=1):
seconds = random.randint(3, 7)
timeout = time.time() + seconds
while time.time() <= timeout:
time.sleep(1)
print(f"Sleeping to replicate user.... tick {current_tick}/{seconds}")
current_tick += 1
def navigate(self, url):
print(f"Agent navigating to {url}...")
return self.driver.get(url)
def click(self, xpath):
print(f"Agent clicking in '{xpath}'...")
return self.driver.find_element_by_xpath(xpath).click()
def write(self, args):
xpath = args[0]
phrase = args[1]
print(f"Agent writing in '{xpath}' the phrase '{phrase}'...")
return self.driver.find_element_by_xpath(xpath).send_keys(phrase)
def main(self, **kwargs):
self.action = kwargs.get('action', self.action)
self.instruction = kwargs.get('instruction', self.instruction)
self.actions[self.action](self.instruction)
self.sleep()最后,我创建了一个函数,每当有一组操作和指令需要在同一个铬驱动程序下执行时,就更新类的参数。我创建了一个函数,它接受一个动作脚本并执行它们。
def update_driver_parameters(driver, values):
params = driver.parameters
params['action'] = values[0]
params['instruction'] = values[1]
return params
def run_script(script):
for script_line, script_values in SCRIPT.items():
chrome = Agent(None, None, None)
for instructions in script_values:
params = update_driver_parameters(chrome, instructions)
chrome.main(**params)
chrome.sleep()
USER = os.environ["USERNAME"]
SECRET = os.environ["SECRET"]
SCRIPT = {
'login': [
('navigate', UrlsContainer.teams_login_page),
('click', XPathsContainer.teams_login_button),
('write', (XPathsContainer.teams_login_user_button, USER)),
('click', XPathsContainer.teams_login_next_button),
('write', (XPathsContainer.teams_login_pwd_button, SECRET)),
('click', XPathsContainer.teams_sign_in_button),
('click', XPathsContainer.teams_sign_in_keep_logged_in),
('click', XPathsContainer.teams_sign_in_button),
]
}
run_script(SCRIPT)现在,我认为代码有几个主要问题,主要与缺乏设计模式的经验有关:
dataclass来做这个;kwargs参数可以以不同的方式实现,我从来不确定在不使用kwargs.get的情况下解析它们的正确方法;发布于 2021-02-23 00:00:32
Bug:在run_script的第一行中,SCRIPT.items()应该是script.items()。正如所写的,它执行全局脚本,而不是函数的参数。
似乎代理不应该继承驱动程序
如果您研究Selenium最佳实践,您将发现一些对您的用例有意义的实践(大多数是针对测试的)。其中两个是Page对象和首选选择器顺序。
Page对象背后的想法是为web应用程序的每个页面创建一个类(或者至少是您正在使用的页面)。类封装了与该页面交互所需的数据和方法。然后,自动化脚本调用Page对象上的方法来自动化任务。例如,登录页面的类可能具有获取登录页面、输入用户名、输入密码、单击“记住我”复选框和单击登录按钮的方法。然后,登录方法按正确的顺序调用这些方法来进行登录。
这使您可以在一个地方隔离页面细节。例如,当前的设计似乎表明,如果您自动化另一个任务,则需要复制SCRIPT的D4部分。然后,如果登录过程更改,每个脚本需要通过更新。使用Page对象,只需要更改登录页类。
实际上,选择元素的最可靠和最健壮的方法是ID,然后是名称、css选择器,最后是Xpath是最不健壮的。看起来大多数目标都有It,所以请使用它。
构造项目如下所示:
project
pages
__init__.py # can be empty
base.py # one for each page
home.py
login.py
time.py
...etc... # add whatever other pages you use
entertime.py # the script然后
base.py
class BasePage:
URL = None
def __init__(self, driver=None):
if driver is None:
driver = webdriver.Chrome(ChromeDriverManager().install())
self.driver = driver
def click(self, locator, mu=1.5, sigma=0.3):
"""simulate human speed and click a page element."""
self.dally(mu, sigma)
self.driver.find_element(*locator).click()
return self
def dally(self, mu=1, sigma=0.2):
pause = random.gauss(mu, sigma)
while pause > 0:
delta = min(1, pause)
pause -= delta
time.spleep(delta)
return self
def navigate(self):
if self.URL:
self.driver.get(self.URL)
return self
raise ValueError("No where to go. No URL")
def send_keys(self, locator, keys):
self.driver.find_element(*locator).send_keys(keys)
return selflogin.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from .base import BasePage
class LoginPage(BasePage):
URL = 'https://www.microsoft.com/en-in/microsoft-365/microsoft-teams/group-chat-software'
#locators for elements of the page
LOGIN_BUTTON = (By.XPATH, '//*[@id="mectrl_main_trigger"]/div/div[1]')
USERNAME_FIELD = (By.ID, "i0116")
NEXT_BUTTON = (By.ID, "idSIButton9")
PASSWORD_FIELD = (By.ID, "i0118")
STAY_LOGGED_IN = (By.ID, "KmsiCheckboxField")
def click_next(self):
self.click(*self.NEXT_BUTTON)
return self
def start_login(self):
self.click(*self.LOGIN_BUTTON)
return self
def enter_username(self, username):
self.send_keys(*self.USERNAME_FIELD, username)
self.click_next()
return self
def enter_password(self, password):
self.send_keys(*self.PASSWORD_FIELD, password)
self.click_next()
return self
def toggle_stay_logged_in(self):
self.driver.find_element(*self.STAY_LOGGED_IN).click()
return self
def login(self, username, password):
self.navigate()
self.start_login()
self.enter_username(username)
self.enter_password(password)
self.toggle_stay_logged_in()
self.click_next()
return HomePage(driver) # or whatever page comes after a loginentertime.py
import os
from pages import LoginPage, HomePage # what ever pages you need for the script
from selenium import webdriver
from selenium.webdriver.common.by import By
USER = os.environ["USERNAME"]
SECRET = os.environ["SECRET"]
homepage = LoginPage().login(USER, SECRET)
timepage = homepage.navigate_to_time_entry() # <== whatever method you define
timepage.entertime() # <== whatever method you define我没有MS团队来测试这个,所以这个还没有被测试过。这仅仅是一个建议,如何组织你的项目,使它更容易更新,扩展等。
https://codereview.stackexchange.com/questions/256135
复制相似问题