diff --git a/BokBonCheck/.vscode/settings.json b/BokBonCheck/.vscode/settings.json new file mode 100644 index 0000000..013007b --- /dev/null +++ b/BokBonCheck/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.preferCSharpExtension": true +} \ No newline at end of file diff --git a/BokBonCheck/Form1.cs b/BokBonCheck/Form1.cs index 1fdd45c..4eebd8f 100644 --- a/BokBonCheck/Form1.cs +++ b/BokBonCheck/Form1.cs @@ -22,6 +22,18 @@ namespace BokBonCheck _searchService = new BookSearchService(); InitializeUI(); InitializeChromeDriver(); + + //_searchService.AddSearcher(new KwangjuCityLibrarySearcher_central()); + //_searchService.AddSearcher(new KwangjuCityLibrarySearcher_choisangjun()); + //_searchService.AddSearcher(new KwangjuCityLibrarySearcher_dagachi()); + //_searchService.AddSearcher(new KwangjuCityLibrarySearcher_Indi()); + //_searchService.AddSearcher(new KwangjuCityLibrarySearcher_Student()); + //_searchService.AddSearcher(new KwangjuCityLibrarySearcher_Kumho()); + //_searchService.AddSearcher(new NamguLibrarySearcher_Children()); + //_searchService.AddSearcher(new NamguLibrarySearcher_Hyocheon()); + //_searchService.AddSearcher(new NamguLibrarySearcher_Munhwa()); + _searchService.AddSearcher(new NamguLibrarySearcher_Purungil()); + //_searchService.AddSearcher(new NamguLibrarySearcher_Smart()); } private async void InitializeChromeDriver() diff --git a/BokBonCheck/SearchModel/BookSearchService.cs b/BokBonCheck/SearchModel/BookSearchService.cs index 61a078d..dfd91c4 100644 --- a/BokBonCheck/SearchModel/BookSearchService.cs +++ b/BokBonCheck/SearchModel/BookSearchService.cs @@ -23,18 +23,6 @@ namespace BokBonCheck { _searchers = new List { - new KwangjuCityLibrarySearcher_central(), - new KwangjuCityLibrarySearcher_choisangjun(), - new KwangjuCityLibrarySearcher_dagachi(), - new KwangjuCityLibrarySearcher_Indi(), - new KwangjuCityLibrarySearcher_Student(), - new KwangjuCityLibrarySearcher_Kumho(), - - new NamguLibrarySearcher_Children(), - new NamguLibrarySearcher_Hyocheon(), - new NamguLibrarySearcher_Munhwa(), - new NamguLibrarySearcher_Purungil(), - new NamguLibrarySearcher_Smart(), }; } @@ -44,8 +32,10 @@ namespace BokBonCheck foreach (var searcher in _searchers) { + searcher.StartDriver(); var result = await searcher.SearchAsync(searchTerm); results.Add(result); + //searcher.StopDriver(); } return results; diff --git a/BokBonCheck/SearchModel/ILibrarySearcher.cs b/BokBonCheck/SearchModel/ILibrarySearcher.cs index cbaf145..8bf853c 100644 --- a/BokBonCheck/SearchModel/ILibrarySearcher.cs +++ b/BokBonCheck/SearchModel/ILibrarySearcher.cs @@ -1,3 +1,4 @@ +using OpenQA.Selenium.Support.UI; using System.Threading.Tasks; namespace BokBonCheck @@ -7,5 +8,9 @@ namespace BokBonCheck string SiteName { get; } string SiteUrl { get; } Task SearchAsync(string searchTerm); + void StartDriver(); + void StopDriver(); + + Task WaitForPageChange(WebDriverWait wait); } } \ No newline at end of file diff --git a/BokBonCheck/SearchModel/KwangjuCityLibrarySearcher.cs b/BokBonCheck/SearchModel/KwangjuCityLibrarySearcher.cs index 4b78f9a..1498a66 100644 --- a/BokBonCheck/SearchModel/KwangjuCityLibrarySearcher.cs +++ b/BokBonCheck/SearchModel/KwangjuCityLibrarySearcher.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using WebDriverManager; using WebDriverManager.DriverConfigs.Impl; using System.IO; +using System.Runtime.InteropServices; namespace BokBonCheck { @@ -65,6 +66,54 @@ namespace BokBonCheck protected string SelectorValue = ""; + private ChromeDriver _driver; + private ChromeDriverService _service; + + [DllImport("user32.dll")] + private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + [DllImport("user32.dll")] + private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + private const int SW_MINIMIZE = 6; + + public void StartDriver() + { + if (_driver == null) + { + var driverPath = ChromeDriverManager.GetDriverPath(); + if (string.IsNullOrEmpty(driverPath) || !File.Exists(driverPath)) + { + driverPath = ChromeDriverManager.SetupChromeDriverAsync().GetAwaiter().GetResult(); + } + _service = ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath), Path.GetFileName(driverPath)); + _service.HideCommandPromptWindow = true; + var options = new ChromeOptions(); + options.AddArgument("--no-sandbox"); + options.AddArgument("--disable-dev-shm-usage"); + options.AddArgument("--disable-gpu"); + options.AddArgument("--window-size=1920,1080"); + options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"); + options.AddArgument("--disable-blink-features=AutomationControlled"); + options.AddExcludedArgument("enable-automation"); + options.AddAdditionalOption("useAutomationExtension", false); + _driver = new ChromeDriver(_service, options); + ((IJavaScriptExecutor)_driver).ExecuteScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"); + } + } + + public void StopDriver() + { + if (_driver != null) + { + _driver.Quit(); + _driver.Dispose(); + _driver = null; + } + if (_service != null) + { + _service.Dispose(); + _service = null; + } + } virtual protected void SelectLibrary(WebDriverWait wait) { @@ -92,44 +141,22 @@ namespace BokBonCheck SearchTime = DateTime.Now }; - IWebDriver driver = null; - ChromeDriverService service = null; - try { - // ChromeDriverManager에서 설정한 드라이버 경로 사용 - var driverPath = ChromeDriverManager.GetDriverPath(); - if (string.IsNullOrEmpty(driverPath) || !File.Exists(driverPath)) - { - // 드라이버가 없으면 다시 설정 - driverPath = await ChromeDriverManager.SetupChromeDriverAsync(); - } + if (_driver == null) + throw new InvalidOperationException("드라이버가 시작되지 않았습니다. StartDriver()를 먼저 호출하세요."); - // ChromeDriver 서비스 설정 - service = ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath), Path.GetFileName(driverPath)); - service.HideCommandPromptWindow = true; - - var options = new ChromeOptions(); - options.AddArgument("--headless"); - options.AddArgument("--no-sandbox"); - options.AddArgument("--disable-dev-shm-usage"); - options.AddArgument("--disable-gpu"); - options.AddArgument("--window-size=1920,1080"); - options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"); - options.AddArgument("--disable-blink-features=AutomationControlled"); - options.AddExcludedArgument("enable-automation"); - options.AddAdditionalOption("useAutomationExtension", false); - - // 명시적으로 서비스와 옵션을 사용하여 드라이버 생성 - driver = new ChromeDriver(service, options); - - // 자동화 감지 방지 - ((IJavaScriptExecutor)driver).ExecuteScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"); - - driver.Navigate().GoToUrl(SiteUrl); + _driver.Navigate().GoToUrl(SiteUrl); // 페이지 로딩 대기 - var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15)); + var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); + + // 모든 감지 방법이 끝나면 크롬창 최소화 + IntPtr chromeWindow = FindWindow("Chrome_WidgetWin_1", null); + if (chromeWindow != IntPtr.Zero) + { + ShowWindow(chromeWindow, SW_MINIMIZE); + } //대상도서관 선택 SelectLibrary(wait); @@ -173,7 +200,7 @@ namespace BokBonCheck } // 혹시 readonly라면 속성 제거 - ((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].removeAttribute('readonly')", searchBox); + ((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].removeAttribute('readonly')", searchBox); // 입력 전 clear searchBox.Clear(); @@ -187,12 +214,11 @@ namespace BokBonCheck { "input[type='submit']", }; - //#frm_sch > fieldset > div > div.search_style > div > input foreach (var selector in buttonSelectors) { try { - searchButton = driver.FindElement(By.CssSelector(selector)); + searchButton = _driver.FindElement(By.CssSelector(selector)); break; } catch @@ -217,11 +243,11 @@ namespace BokBonCheck searchBox.SendKeys(Keys.Enter); } - // 검색 결과 로딩 대기 - await Task.Delay(3000); + // 페이지 변경을 감지하는 메서드 + await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); // 검색 결과 수 추출 - var resultCount = ExtractBookCount(driver); + var resultCount = ExtractBookCount(_driver); result.BookCount = resultCount; result.IsSuccess = true; @@ -232,12 +258,6 @@ namespace BokBonCheck result.ErrorMessage = ex.Message; result.BookCount = 0; } - finally - { - driver?.Quit(); - driver?.Dispose(); - service?.Dispose(); - } return result; } @@ -262,6 +282,34 @@ namespace BokBonCheck return 0; } } - } + // 페이지 변경을 감지하는 메서드 + public async Task WaitForPageChange(WebDriverWait wait) + { + try + { + // 방법 4: 페이지 로딩 상태 확인 + wait.Until(d => + { + var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState"); + return readyState.Equals("complete"); + }); + + // 방법 5: 특정 텍스트가 페이지에 나타날 때까지 대기 + wait.Until(d => + { + var pageText = d.FindElement(By.TagName("body")).Text; + return pageText.Contains("전체") || pageText.Contains("건") || pageText.Contains("검색결과"); + }); + + + } + catch (Exception ex) + { + // 모든 감지 방법이 실패하면 최소한의 대기 시간 적용 + await Task.Delay(2000); + throw new Exception($"페이지 변경 감지 실패: {ex.Message}"); + } + } + } } \ No newline at end of file diff --git a/BokBonCheck/SearchModel/NamguLibrarySearcher.cs b/BokBonCheck/SearchModel/NamguLibrarySearcher.cs index 261aeb7..eea9a07 100644 --- a/BokBonCheck/SearchModel/NamguLibrarySearcher.cs +++ b/BokBonCheck/SearchModel/NamguLibrarySearcher.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using WebDriverManager; using WebDriverManager.DriverConfigs.Impl; using System.IO; +using System.Runtime.InteropServices; namespace BokBonCheck { @@ -24,7 +25,7 @@ namespace BokBonCheck { var selector = "#clickAll"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); - if (searchBox != null && searchBox.Selected==true) + if (searchBox != null && searchBox.Selected == true) { searchBox.Click(); // 전체체크박스가 체크되어 있으면 클릭해서 해제 } @@ -32,7 +33,7 @@ namespace BokBonCheck selector = "#libMA"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); - if (searchBox != null && searchBox.Selected==false) + if (searchBox != null && searchBox.Selected == false) { searchBox.Click(); // 문화관 체크박스 선택 } @@ -47,7 +48,7 @@ namespace BokBonCheck { public NamguLibrarySearcher_Purungil() { - SiteName = "남구통합도서관(푸른길도서관)"; + SiteName = "남구통합도서관(푸른길도서관)"; } protected override void SelectLibrary(WebDriverWait wait) @@ -76,12 +77,11 @@ namespace BokBonCheck } } } - public class NamguLibrarySearcher_Children : NamguLibrarySearcher { public NamguLibrarySearcher_Children() { - SiteName = "남구통합도서관(청소년도서관)"; + SiteName = "남구통합도서관(청소년도서관)"; } protected override void SelectLibrary(WebDriverWait wait) @@ -110,7 +110,6 @@ namespace BokBonCheck } } } - public class NamguLibrarySearcher_Hyocheon : NamguLibrarySearcher { public NamguLibrarySearcher_Hyocheon() @@ -148,7 +147,7 @@ namespace BokBonCheck { public NamguLibrarySearcher_Smart() { - SiteName = "남구통합도서관(스마트도서관)"; + SiteName = "남구통합도서관(스마트도서관)"; } protected override void SelectLibrary(WebDriverWait wait) @@ -177,12 +176,61 @@ namespace BokBonCheck } } } - public class NamguLibrarySearcher : ILibrarySearcher { public string SiteName { get; protected set; } = "남구통합도서관(전체)"; public string SiteUrl => "https://lib.namgu.gwangju.kr/main/bookSearch"; + private ChromeDriver _driver; + private ChromeDriverService _service; + + [DllImport("user32.dll")] + private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + [DllImport("user32.dll")] + private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + private const int SW_MINIMIZE = 6; + + public void StartDriver() + { + if (_driver == null) + { + var driverPath = ChromeDriverManager.GetDriverPath(); + if (string.IsNullOrEmpty(driverPath) || !File.Exists(driverPath)) + { + driverPath = ChromeDriverManager.SetupChromeDriverAsync().GetAwaiter().GetResult(); + } + _service = ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath), Path.GetFileName(driverPath)); + _service.HideCommandPromptWindow = true; + var options = new ChromeOptions(); + options.AddArgument("--no-sandbox"); + options.AddArgument("--disable-dev-shm-usage"); + options.AddArgument("--disable-gpu"); + options.AddArgument("--window-size=1920,1080"); + options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"); + options.AddArgument("--disable-blink-features=AutomationControlled"); + options.AddExcludedArgument("enable-automation"); + options.AddAdditionalOption("useAutomationExtension", false); + _driver = new ChromeDriver(_service, options); + ((IJavaScriptExecutor)_driver).ExecuteScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"); + } + } + + public void StopDriver() + { + if (_driver != null) + { + _driver.Quit(); + _driver.Dispose(); + _driver = null; + } + if (_service != null) + { + _service.Dispose(); + _service = null; + } + } + + virtual protected void SelectLibrary(WebDriverWait wait) { IWebElement searchBox = null; @@ -211,44 +259,22 @@ namespace BokBonCheck SearchTime = DateTime.Now }; - IWebDriver driver = null; - ChromeDriverService service = null; - try { - // ChromeDriverManager에서 설정한 드라이버 경로 사용 - var driverPath = ChromeDriverManager.GetDriverPath(); - if (string.IsNullOrEmpty(driverPath) || !File.Exists(driverPath)) - { - // 드라이버가 없으면 다시 설정 - driverPath = await ChromeDriverManager.SetupChromeDriverAsync(); - } + if (_driver == null) + throw new InvalidOperationException("드라이버가 시작되지 않았습니다. StartDriver()를 먼저 호출하세요."); - // ChromeDriver 서비스 설정 - service = ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath), Path.GetFileName(driverPath)); - service.HideCommandPromptWindow = true; - - var options = new ChromeOptions(); - options.AddArgument("--headless"); - options.AddArgument("--no-sandbox"); - options.AddArgument("--disable-dev-shm-usage"); - options.AddArgument("--disable-gpu"); - options.AddArgument("--window-size=1920,1080"); - options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"); - options.AddArgument("--disable-blink-features=AutomationControlled"); - options.AddExcludedArgument("enable-automation"); - options.AddAdditionalOption("useAutomationExtension", false); - - // 명시적으로 서비스와 옵션을 사용하여 드라이버 생성 - driver = new ChromeDriver(service, options); - - // 자동화 감지 방지 - ((IJavaScriptExecutor)driver).ExecuteScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"); - - driver.Navigate().GoToUrl(SiteUrl); + _driver.Navigate().GoToUrl(SiteUrl); // 페이지 로딩 대기 - var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15)); + var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); + + // 모든 감지 방법이 끝나면 크롬창 최소화 + IntPtr chromeWindow = FindWindow("Chrome_WidgetWin_1", null); + if (chromeWindow != IntPtr.Zero) + { + ShowWindow(chromeWindow, SW_MINIMIZE); + } //대상도서관 선택 SelectLibrary(wait); @@ -312,7 +338,7 @@ namespace BokBonCheck { try { - searchButton = driver.FindElement(By.CssSelector(selector)); + searchButton = _driver.FindElement(By.CssSelector(selector)); break; } catch @@ -337,11 +363,11 @@ namespace BokBonCheck searchBox.SendKeys(Keys.Enter); } - // 검색 결과 로딩 대기 - await Task.Delay(3000); + // 페이지 변경을 감지하는 메서드 + await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); // 검색 결과 수 추출 - var resultCount = ExtractBookCount(driver); + var resultCount = ExtractBookCount(_driver); result.BookCount = resultCount; result.IsSuccess = true; @@ -352,13 +378,7 @@ namespace BokBonCheck result.ErrorMessage = ex.Message; result.BookCount = 0; } - finally - { - driver?.Quit(); - driver?.Dispose(); - service?.Dispose(); - } - + return result; } @@ -382,5 +402,33 @@ namespace BokBonCheck return 0; } } + + // 페이지 변경을 감지하는 메서드 + public async Task WaitForPageChange(WebDriverWait wait) + { + try + { + // 방법 4: 페이지 로딩 상태 확인 + wait.Until(d => + { + var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState"); + return readyState.Equals("complete"); + }); + + // 방법 5: 특정 텍스트가 페이지에 나타날 때까지 대기 + wait.Until(d => + { + var pageText = d.FindElement(By.TagName("body")).Text; + return pageText.Contains("전체") || pageText.Contains("건") || pageText.Contains("검색결과"); + }); + + } + catch (Exception ex) + { + // 모든 감지 방법이 실패하면 최소한의 대기 시간 적용 + await Task.Delay(2000); + throw new Exception($"페이지 변경 감지 실패: {ex.Message}"); + } + } } } \ No newline at end of file