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; namespace BokBonCheck { public class NamguLibrarySearcher : ILibrarySearcher { public string SiteName => "남구통합도서관"; public string SiteUrl => "https://lib.namgu.gwangju.kr/main/bookSearch"; public async Task SearchAsync(string searchTerm) { var result = new BookSearchResult { SiteName = SiteName, SearchTerm = searchTerm, 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(); } // 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); // 페이지 로딩 대기 var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15)); // 검색창 찾기 (남구통합도서관 사이트의 특정 선택자 사용) 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 Task.Delay(3000); // 검색 결과 수 추출 var resultCount = ExtractBookCount(driver); result.BookCount = resultCount; result.IsSuccess = true; } catch (Exception ex) { result.IsSuccess = false; result.ErrorMessage = ex.Message; result.BookCount = 0; } finally { driver?.Quit(); driver?.Dispose(); service?.Dispose(); } return result; } private int ExtractBookCount(IWebDriver driver) { try { // div.search-result 내부의 span에서 '전체 N' 텍스트 추출 var resultDiv = driver.FindElement(By.CssSelector("div.search-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 interface ILibrarySearcher { string SiteName { get; } string SiteUrl { get; } Task SearchAsync(string searchTerm); } }