using System; using System.Threading.Tasks; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Support.UI; using System.Text.RegularExpressions; using WebDriverManager; using WebDriverManager.DriverConfigs.Impl; using System.IO; using System.Runtime.InteropServices; namespace BokBonCheck { public class NamguLibrarySearcher_Munhwa : NamguLibrarySearcher { public NamguLibrarySearcher_Munhwa() { SiteName = "남구통합도서관(문화정보도서관)"; // 문화관 검색기 } protected override void SelectLibrary(WebDriverWait wait) { IWebElement searchBox = null; try { var selector = "#clickAll"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == true) { searchBox.Click(); // 전체체크박스가 체크되어 있으면 클릭해서 해제 } else return; selector = "#libMA"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == false) { searchBox.Click(); // 문화관 체크박스 선택 } } catch { } } } public class NamguLibrarySearcher_Purungil : NamguLibrarySearcher { public NamguLibrarySearcher_Purungil() { SiteName = "남구통합도서관(푸른길도서관)"; } protected override void SelectLibrary(WebDriverWait wait) { IWebElement searchBox = null; try { var selector = "#clickAll"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == true) { searchBox.Click(); // 전체체크박스가 체크되어 있으면 클릭해서 해제 } else return; selector = "#libMB"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == false) { searchBox.Click(); // 체크박스 선택 } } catch { } } } public class NamguLibrarySearcher_Children : NamguLibrarySearcher { public NamguLibrarySearcher_Children() { SiteName = "남구통합도서관(청소년도서관)"; } protected override void SelectLibrary(WebDriverWait wait) { IWebElement searchBox = null; try { var selector = "#clickAll"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == true) { searchBox.Click(); // 전체체크박스가 체크되어 있으면 클릭해서 해제 } else return; selector = "#libMC"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == false) { searchBox.Click(); // 체크박스 선택 } } catch { } } } public class NamguLibrarySearcher_Hyocheon : NamguLibrarySearcher { public NamguLibrarySearcher_Hyocheon() { SiteName = "남구통합도서관(효천어울림도서관)"; } protected override void SelectLibrary(WebDriverWait wait) { IWebElement searchBox = null; try { var selector = "#clickAll"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == true) { searchBox.Click(); // 전체체크박스가 체크되어 있으면 클릭해서 해제 } else return; selector = "#libSW"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == false) { searchBox.Click(); // 체크박스 선택 } } catch { } } } public class NamguLibrarySearcher_Smart : NamguLibrarySearcher { public NamguLibrarySearcher_Smart() { SiteName = "남구통합도서관(스마트도서관)"; } protected override void SelectLibrary(WebDriverWait wait) { IWebElement searchBox = null; try { var selector = "#clickAll"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == true) { searchBox.Click(); // 전체체크박스가 체크되어 있으면 클릭해서 해제 } else return; selector = "#libSQ"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == false) { searchBox.Click(); // 체크박스 선택 } } catch { } } } 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; try { var selector = "#clickAll"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox != null && searchBox.Selected == false) { searchBox.Click(); // 전체체크박스가 해제되어 있으면 클릭해서 선택 } else return; } catch { } } public async Task SearchAsync(string searchTerm) { var result = new BookSearchResult { SiteName = SiteName, SearchTerm = searchTerm, SearchTime = DateTime.Now }; try { if (_driver == null) throw new InvalidOperationException("드라이버가 시작되지 않았습니다. StartDriver()를 먼저 호출하세요."); _driver.Navigate().GoToUrl(SiteUrl); // 페이지 로딩 대기 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); // 검색창 찾기 (남구통합도서관 사이트의 특정 선택자 사용) IWebElement searchBox = null; try { // 여러 가능한 선택자 시도 var selectors = new[] { "input[name='query']", "input[id='query']", "input[type='text']", }; foreach (var selector in selectors) { try { searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); break; } catch { continue; } } if (searchBox == null) { throw new Exception("검색창을 찾을 수 없습니다."); } } catch (Exception ex) { throw new Exception($"검색창 찾기 실패: {ex.Message}"); } // 검색어 입력 searchBox.Clear(); searchBox.SendKeys(searchTerm); // 검색 버튼 클릭 IWebElement searchButton = null; try { var buttonSelectors = new[] { "button[type='submit']", "input[type='submit']", ".search-btn", ".btn-search", "button:contains('검색')", "input[value*='검색']", "button[class*='search']", "input[class*='search']" }; foreach (var selector in buttonSelectors) { try { searchButton = _driver.FindElement(By.CssSelector(selector)); break; } catch { continue; } } if (searchButton == null) { // Enter 키로 검색 시도 searchBox.SendKeys(Keys.Enter); } else { searchButton.Click(); } } catch (Exception ex) { // Enter 키로 검색 시도 searchBox.SendKeys(Keys.Enter); } // 페이지 변경을 감지하는 메서드 await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); // 검색 결과 수 추출 var resultCount = ExtractBookCount(_driver, searchTerm,out string ermsg); if(resultCount == -1) { result.IsSuccess = false; result.ErrorMessage = ermsg; } else { result.BookCount = resultCount; result.IsSuccess = true; result.ErrorMessage = ermsg; } } catch (Exception ex) { result.IsSuccess = false; result.ErrorMessage = ex.Message; result.BookCount = 0; } return result; } private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage) { errmessage = string.Empty; try { // div.search-result 내부의 span에서 '전체 N' 텍스트 추출 var resultDiv = driver.FindElement(By.CssSelector("div.search-result")); var bodytext = resultDiv.Text; if(bodytext.Contains("검색결과가 없습니다")) { errmessage = "검색결과없음"; return 0; } var searchkey = resultDiv.FindElement(By.XPath("//*[@id=\"sub\"]/section[3]/div/div/div/div/div[2]/div[1]/p/b")); var searchtitle = searchkey.Text; if (searchTerm.Contains(searchtitle) == false) { errmessage = $"검색어 불일치({searchtitle}/{searchTerm})"; return -1; } var span = resultDiv.FindElement(By.XPath(".//span[contains(text(),'전체')]")); string text = span.Text; // 예: "전체 5 " var match = System.Text.RegularExpressions.Regex.Match(text, @"전체\s*(\d+)"); if (match.Success) { return int.Parse(match.Groups[1].Value); } else { errmessage = "수량항목없음"; return -1; } } catch (Exception ex) { errmessage = ex.Message; return -1; } } // 페이지 변경을 감지하는 메서드 public async Task WaitForPageChange(WebDriverWait wait) { try { await Task.Delay(500); // 방법 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.ClassName("search-result")).Text; if (pageText.Contains("검색결과가 없습니다")) return true; return pageText.Contains("에 대하여") && pageText.Contains("검색되었습니다"); }); } catch (Exception ex) { // 모든 감지 방법이 실패하면 최소한의 대기 시간 적용 await Task.Delay(2000); throw new Exception($"페이지 변경 감지 실패: {ex.Message}"); } } } }