NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# C# .NET 매크로 프로그램 만들기. (웹 자동화 실행 및 중지)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 엔지엠 매크로에는 웹업무 자동화와 관련된 기능들을 제공합니다. 총 3종류인데요. Selenium 기반의 웹 API 액션들과 웹소켓 그리고, 크로미움이 있습니다. 총 3가지의 인터페이스를 사용해서 웹업무를 자동화 할 수 있습니다. 오늘은 가장 많이 사용하는 웹 API(Selenium)를 이용해서 브라우저를 실행하고, 사이트를 이동하는 방법에 대해 알아볼께요. 그리고, 연결을 끊고 리소스를 제거하는 액션도 만들어 볼께요.

     

    우선, Connection과 Disconnection 액션을 각각 만들어 줍니다. 참고로, 엔지엠 매크로 6은 Single Macro 디자인 아키텍쳐라서 멀티 실행으로 처리하는 부분에 문제가 많았습니다. 그래서, 엔지엠 7 버전에서는 기본적으로 Multiple Macro 디자인 아키텍쳐를 사용했습니다. 처음 설계 단계부터 여러개의 인스턴스를 동시에 제어할 수 있도록 만들었기 때문에 다중 클라 작업에이 쉬워졌습니다.

     

    아래와 같이 클래스 2개를 추가하세요.

    W4NTyfO.png

     

     

    웹 연결 액션에서 사용자에게 제공할 결과 데이터는 3개입니다. 추가적으로 더 필요할지도 모르겠지만, 일단은 3가지만 처리하는걸로 할께요.

    [LocalizedCategory("Data")]
    [LocalizedDisplayName("WindowTitle")]
    [LocalizedDescription("WindowTitle")]
    [Browsable(true)]
    [DefaultValue(null)]
    [Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
    public string? Title { get; set; }
    
    [LocalizedCategory("Data")]
    [LocalizedDisplayName("CurrentWindowHandle")]
    [LocalizedDescription("CurrentWindowHandle")]
    [Browsable(true)]
    [DefaultValue(null)]
    [Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
    public string? CurrentWindowHandle { get; set; }
    
    [LocalizedCategory("Data")]
    [LocalizedDisplayName("WindowHandles")]
    [LocalizedDescription("WindowHandles")]
    [Browsable(true)]
    [DefaultValue(null)]
    public string[]? WindowHandles { get; set; }

     

    웹 업무 자동화에서 셀레니움으로 연결을 시도하면 몇가지 문제가 발생합니다. 웹 브라우저는 서버로부터 응답을 받았을 때 다음 프로세스로 넘어갑니다. 하지만, 우리가 처리하는 데이터는 웹 기준으로 onload 이벤트가 발생해야 합니다. 이 문제를 처리하기 위한 다양한 방법들이 존재하는데요. 이건 나중에 자세히 알아보기로 하고, 일단은 간단하게라도 구동되는지 먼저 체크 해볼께요.

     

    기본 설정 옵션입니다.

    [LocalizedCategory("Setting")]
    [LocalizedDisplayName("WebBrowser")]
    [LocalizedDescription("WebBrowser")]
    [Browsable(true)]
    [DefaultValue(typeof(Ai.Definition.WebBrowserType), "Chrome")]
    public Ai.Definition.WebBrowserType WebBrowser { get; set; } = Ai.Definition.WebBrowserType.Chrome;

     

    지원하는 웹브라우저는 아래와 같이 3가지입니다. FireFox는 크로미움이 아니라서 공통적으로 처리하는데 문제가 있고, 몇몇 설정들은 아예 지원하지 않습니다. 그래서, 제외시킬지 고민중인데요. 엔지엠 6에서 제공하던 기능이라 약간의 제약이 있더라도 추가하는게 좋을거 같아요. 일단, 처리 로직은 그대로 두었습니다.

    public enum WebBrowserType
    {
        Chrome = 0,
        Edge = 1,
        FireFox = 2
    }

     

    크롬과 엣지의 옵션과 확장을 배열로 제공합니다. 그리고, 작업의 연속성을 유지하기 위해 이름을 설정하도록 해줍니다. 웹브라우저의 이름은 연결 정보를 내부에 저장하는 유니크한 키 값입니다. 웹브라우저에 연결된 드라이버를 통해서 마우스 클릭이나 키보드 텍스트 입력등등... 다양한 명령을 수행할 수 있습니다. 연결 정보가 사라지면 더이상 웹브라우저를 콘트롤할 수 없게 됩니다.

    [LocalizedCategory("Setting")]
    [LocalizedDisplayName("WebArguments")]
    [LocalizedDescription("WebArguments")]
    [Browsable(true)]
    [DefaultValue(null)]
    public string[]? Arguments { get; set; }
    
    [LocalizedCategory("Setting")]
    [LocalizedDisplayName("WebAdditionals")]
    [LocalizedDescription("WebAdditionals")]
    [Browsable(true)]
    [DefaultValue(null)]
    public List<Data.KeyValueItem> Additionals { get; set; } = new List<Data.KeyValueItem>();
    
    [LocalizedCategory("Action")]
    [LocalizedDisplayName("WebName")]
    [LocalizedDescription("WebName")]
    [Browsable(true)]
    [DefaultValue(null)]
    [Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
    public string? WebName { get; set; }

     

    홈페이지, 웹사이트에서 이동할 주소입니다.

    [LocalizedCategory("Action")]
    [LocalizedDisplayName("WebUrl")]
    [LocalizedDescription("WebUrl")]
    [Browsable(true)]
    [DefaultValue(null)]
    [Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
    public string? Url { get; set; }

     

    대기 시간을 설정합니다. UsePageLoaded는 지속되는 설정은 아닙니다. 연결 액션에서 페이지 이동시 완료 상태를 확인하기 위한 1회성 설정입니다. 나머지 Wait 옵션들은 페이지 응답과 요소 엘리먼트를 찾기 까지 대기 시간입니다. 이 둘의 설정은 암묵적인 지연 시간이므로 특수한 상황에서는 무시될수도 있습니다. 따라서, 이를 보완하기 위한 여러가지 로직을 추가로 구성해야 할수도 있습니다.

    [LocalizedCategory("Action")]
    [LocalizedDisplayName("UsePageLoaded")]
    [LocalizedDescription("UsePageLoaded")]
    [Browsable(true)]
    [DefaultValue(false)]
    public bool UsePageLoaded { get; set; }
    
    [LocalizedCategory("Action")]
    [LocalizedDisplayName("PageLoadCompleteWait")]
    [LocalizedDescription("PageLoadCompleteWait")]
    [Browsable(true)]
    [DefaultValue(0)]
    public int PageLoadCompleteWait { get; set; }
    
    [LocalizedCategory("Action")]
    [LocalizedDisplayName("ElementLoadCompleteWait")]
    [LocalizedDescription("ElementLoadCompleteWait")]
    [Browsable(true)]
    [DefaultValue(0)]
    public int ElementLoadCompleteWait { get; set; }

     

    User Agent 정보를 설정하고 싶으면 아래 내용들을 추가해야 합니다. nuget에 RandomUA 페키지가 있으니 이걸 사용하면 쉽게 처리할 수 있습니다.

    [LocalizedCategory("UserAgent")]
    [LocalizedDisplayName("UserAgent")]
    [LocalizedDescription("UserAgent")]
    [Browsable(true)]
    [DefaultValue(null)]
    public string? UserAgent { get; set; }
    
    [LocalizedCategory("UserAgent")]
    [LocalizedDisplayName("UseRandomUserAgent")]
    [LocalizedDescription("UseRandomUserAgent")]
    [Browsable(true)]
    [DefaultValue(false)]
    public bool UseRandomUserAgent { get; set; }
    
    [LocalizedCategory("UserAgent")]
    [LocalizedDisplayName("RandomUserAgentOption")]
    [LocalizedDescription("RandomUserAgentOption")]
    [Browsable(true)]
    [DefaultValue(typeof(UserAgentType), "All")]

     

    아래 2개의 속성은 사용자 프로필을 사용해서 웹브라우저를 실행할 수 있도록 해줍니다. 이렇게하면 여러가지 문제가 될만한 것들을 우회할 수 있고, 테스트에도 용이합니다.

    [LocalizedCategory("UserProfile")]
    [LocalizedDisplayName("UserProfileDirectory")]
    [LocalizedDescription("UserProfileDirectory")]
    [Browsable(true)]
    [DefaultValue(null)]
    [Editor(typeof(TypeEditor.FolderSelectorEditor), typeof(UITypeEditor))]
    public string? UserProfileDirectory { get; set; }
    
    [LocalizedCategory("UserProfile")]
    [LocalizedDisplayName("UserProfile")]
    [LocalizedDescription("UserProfile")]
    [Browsable(true)]
    [DefaultValue(null)]
    public string? UserProfile { get; set; }

     

    웹 이름을 입력하지 않으면 액션이 실행되지 않도록 예외 처리를 해줍니다.

    if (!Ai.Common.Helper.NullCheckAndWriteLine(player.Manager.Client, nameof(WebName), WebName))
        return id;

     

    User Agent를 설정하는 방법은 아래와 같은데요. 패키지를 사용해서 처리하면 간단합니다. 또는 직접 랜덤 목록을 만들어서 텍스트 읽기로 처리해도 됩니다.

    if (UseRandomUserAgent)
    {
        switch (RandomUserAgentOption)
        {
            case UserAgentType.All:
                userAgent = new Random().Next(0, 2) == 0 ? MobileRandomUserAgent.RandomUserAgent : RandomUserAgent.RandomUa.RandomUserAgent;
                break;
            case UserAgentType.PC:
                userAgent = RandomUserAgent.RandomUa.RandomUserAgent;
                break;
            case UserAgentType.Mobile:
                userAgent = MobileRandomUserAgent.RandomUserAgent;
                break;
        }
    }

     

    문제를 피하기 위해서는 remote-debugging-port를 설정해서 사용하는게 좋습니다. 이 부분은 테스트를 통해서 알아볼께요.

    OpenQA.Selenium.IWebDriver? driver = null;
    
    switch (WebBrowser)
    {
        case Definition.WebBrowserType.Chrome:
            var gService = ChromeDriverService.CreateDefaultService();
            gService.HideCommandPromptWindow = true;
    
            var gOption = new OpenQA.Selenium.Chrome.ChromeOptions();
    
            if (Arguments != null && Arguments.Length > 0)
            {
                foreach (var argument in Arguments)
                {
                    if (argument.Contains("remote-debugging-port"))
                    {
                        string[] kv = argument.Split('=');
                        gOption.DebuggerAddress = $"127.0.0.1:{kv[1]}";
                    }
                    else
                        gOption.AddArgument($"{argument}");
                }
            }

     

    마지막으로 드라이버에 주소를 입력하면 웹브라우저가 실행되고, 사이트로 이동합니다. 이 후 처리 부분은 랜더링 후 처리되는 지연 설정과 실행 후 사용자에게 보여줄 결과 데이터 정보들입니다. 웹브라우저에 연결된 페이지가 여러개일수도 있기 때문에 목록을 보여주고, 현재 컨트롤하는 브라우저의 핸들 정보도 표시하도록 했습니다.

    driver.Url = Url;
    
    if (PageLoadCompleteWait > 0)
        driver.Manage().Timeouts().PageLoad = TimeSpan.FromMilliseconds(PageLoadCompleteWait);
    
    if (ElementLoadCompleteWait > 0)
        driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromMilliseconds(ElementLoadCompleteWait);
    
    CurrentWindowHandle = driver.CurrentWindowHandle;
    WindowHandles = driver.WindowHandles.ToArray();
    
    if (UsePageLoaded)
        while (string.IsNullOrEmpty(driver.Title))
    
    Title = driver.Title;
    
    if (player.Manager.WebDrivers.ContainsKey(WebName))
        player.Manager.Output.WriteLine($"[{WebName}] {player.Manager.Client.ResxMessage.GetString("ExistsWebConnection")}", log4net.Core.Level.Warn);
    else
        player.Manager.WebDrivers.Add(WebName, driver);

     

    아래 동영상처럼 간단하게 테스트를 해볼께요.

     

     

    유튜브 추천, 구독, 좋아요 꼭 눌러주세요!

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

     

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.