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 KwangjuCityLibrarySearcher_choisangjun : KwangjuCityLibrarySearcher { public KwangjuCityLibrarySearcher_choisangjun() { SelectorValue = "MF"; SiteName = "광주시교육청통합도서관(분관최상준도서관)"; } } public class KwangjuCityLibrarySearcher_dagachi : KwangjuCityLibrarySearcher { public KwangjuCityLibrarySearcher_dagachi() { SelectorValue = "ME"; SiteName = "광주시교육청통합도서관(송정다가치문화도서관)"; } } public class KwangjuCityLibrarySearcher_central : KwangjuCityLibrarySearcher { public KwangjuCityLibrarySearcher_central() { SelectorValue = "MD"; SiteName = "광주시교육청통합도서관(중앙도서관)"; } } public class KwangjuCityLibrarySearcher_Student : KwangjuCityLibrarySearcher { public KwangjuCityLibrarySearcher_Student() { SelectorValue = "MC"; SiteName = "광주시교육청통합도서관(학생교육문화회관)"; } } public class KwangjuCityLibrarySearcher_Kumho : KwangjuCityLibrarySearcher { public KwangjuCityLibrarySearcher_Kumho() { SelectorValue = "MB"; SiteName = "광주시교육청통합도서관(금호평생교육관)"; } } public class KwangjuCityLibrarySearcher_Indi : KwangjuCityLibrarySearcher { public KwangjuCityLibrarySearcher_Indi() { SelectorValue = "MA"; SiteName = "광주시교육청통합도서관(학생독립운동기념회관)"; } } public abstract class KwangjuCityLibrarySearcher : ILibrarySearcher { public string SiteName { get; protected set; } = "광주시교육청통합도서관"; public string SiteUrl => "https://lib.gen.go.kr/main/site/search/bookSearch.do#simple"; 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) { try { // 콤보박스(select) 요소 찾기 var selectElement = wait.Until(d => d.FindElement(By.CssSelector("#manage_code"))); var select = new OpenQA.Selenium.Support.UI.SelectElement(selectElement); // value가 "MA"인 옵션 선택 select.SelectByValue(SelectorValue); } 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[id='search_txt']", "input[type='text']", }; foreach (var selector in selectors) { try { searchBox = wait.Until(d => { var el = d.FindElement(By.CssSelector(selector)); return (el != null && el.Displayed && el.Enabled) ? el : null; }); break; } catch { continue; } } if (searchBox == null) { throw new Exception("검색창을 찾을 수 없습니다."); } } catch (Exception ex) { throw new Exception($"검색창 찾기 실패: {ex.Message}"); } // 혹시 readonly라면 속성 제거 ((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].removeAttribute('readonly')", searchBox); // 입력 전 clear searchBox.Clear(); searchBox.SendKeys(searchTerm); // 검색 버튼 클릭 IWebElement searchButton = null; try { var buttonSelectors = new[] { "input[type='submit']", }; 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); result.BookCount = resultCount; result.IsSuccess = true; } catch (Exception ex) { result.IsSuccess = false; result.ErrorMessage = ex.Message; result.BookCount = 0; } return result; } private int ExtractBookCount(IWebDriver driver) { try { // div.search-result 내부의 span에서 '전체 N' 텍스트 추출 var resultDiv = driver.FindElement(By.CssSelector("div.ndls_result")); 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); } return 0; } catch { return 0; } } // 페이지 변경을 감지하는 메서드 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.TagName("body")).Text; return pageText.Contains("전체") || pageText.Contains("건") || pageText.Contains("검색결과"); }); } catch (Exception ex) { // 모든 감지 방법이 실패하면 최소한의 대기 시간 적용 await Task.Delay(2000); throw new Exception($"페이지 변경 감지 실패: {ex.Message}"); } } } }