在 python 中将包装上下文管理器创建到装饰器中的正确方法?

Proper way to create a wrap context manager into a decorator in python?

提问人:lollerskates 提问时间:11/17/2023 最后编辑:lollerskates 更新时间:11/17/2023 访问量:28

问:

我有几个网页,我想使用硒抓取。我想自动化它并在远程机器上运行它。由于每个网站都不同,因此脚本需要不同的功能才能完成工作。我没有让每个脚本都具有相同的代码来启动虚拟显示和 webdriver,而是粗略地想到使用可以启动虚拟显示和 webdriver 的装饰器,如下所示:

    def open_headless_browser(func: Callable) -> Callable:
        disp = Display(visible=False, size=(100, 100))
        options = webdriver.ChromeOptions()
        options.add_argument("--headless=new")
        options.add_argument("--dns-prefetch-disable")
        def start(): -> None
            with disp as display:
                with webdriver.Chrome(options=self.options) as wd:
                    func()
        return start

然后我可能会有我的脚本(实际执行抓取的脚本),如下所示:

@open_headless_browser
def scrape_abc(url_abc: str) -> None:
    driver.get(url_abc)
    driver.find_elements_by_xpath('abc')

@open_headless_browser
def scrape_xyz(url_xyz: str) -> None:
    driver.get(url_xyz)
    driver.find_elements_by_css('xyz')

但是,有几件事与我有关:

  • 我的代码和函数中的代码有点尴尬,因为它不知道是什么(因为它是在装饰器中定义的)。scrape_abcscrape_xzydriver
  • 这甚至行得通吗?我是把事情搞得太复杂了,还是我只是错误地处理了这个想法?
  • 这是pythonic吗

我在python3.10 selenium4.15 pyvirtualdisplay3.0上

编辑:经过一番思考,这种方法终究行不通。修饰的函数将无权访问修饰器中定义的 webdriver 对象

python-3.x selenium-webdriver 装饰器 contextmanager pyvirtualdisplay

评论


答:

0赞 RoadieRich 11/17/2023 #1

编辑:经过一番思考,这种方法终究行不通。修饰的函数将无权访问修饰器中定义的 webdriver 对象

当然可以,你只需要将参数作为参数传递给函数,如下所示:wd

def open_headless_browser(func: Callable) -> Callable:
    disp = Display(visible=False, size=(100, 100))
    options = webdriver.ChromeOptions()
    options.add_argument("--headless=new")
    options.add_argument("--dns-prefetch-disable")
    def start(): -> None
        with disp as display:
            with webdriver.Chrome(options=options) as wd:
                func(wd)
    return start

然后,您的函数将如下所示:

@open_headless_browser
def scrape_abc(driver: webdriver.Chrome) -> None:
    driver.get(url_abc)
    driver.find_elements_by_xpath('abc')

@open_headless_browser
def scrape_abc(driver: webdriver.Chrome) -> None:
    driver.get(url_xyz)
    driver.find_elements_by_xpath('xyz')

如果希望能够传入 URL,还需要在包装函数中定义参数:

def open_headless_browser(func: Callable) -> Callable:
    disp = Display(visible=False, size=(100, 100))
    options = webdriver.ChromeOptions()
    options.add_argument("--headless=new")
    options.add_argument("--dns-prefetch-disable")
    def start(url: str): -> None
        with disp as display:
            with webdriver.Chrome(options=options) as wd:
                func(wd, url)
    return start

@open_headless_browser
def scrape_abc(driver: webdriver.Chrome, url: str) -> None:
    driver.get(url)
    driver.find_elements_by_xpath('abc')

然后,请记住,尽管您将函数定义为具有两个参数,但您只使用一个参数调用它。

评论

0赞 lollerskates 11/17/2023
感谢您的回复。就最佳实践而言,将显示初始化和 chrome 选项移入或保留在原处更好?在功能方面,我认为两者都做同样的事情start
0赞 RoadieRich 11/17/2023
这实际上取决于您如何使用每个功能。如果您只使用每个装饰功能一次,则没关系。如果要将多个 URL 传递给函数,则需要考虑是否希望每个调用都有自己的 Web 驱动程序,或者是否对同一函数的所有调用都可以安全地共享。