Crawling
Crawl의 사전적의미는 "기어가다"라는 뜻이다. 개발에서는 웹 사이트의 정보를 긁어오는 것을 의미한다.
아마 웹 사이트를 기어다니면서 정보를 수집하는 행위가 아닐까..?
Selenium
셀레늄 (소프트웨어) - 위키백과, 우리 모두의 백과사전
ko.wikipedia.org
Selenium은 코드에서 웹 접근과 제어에 대한 기능을 제공해주는 프레임워크이다. 이 프레임워크를 통해 Crawling을 수행할 수 있다.
예제
이론적인 설명은 이 정도까지만 하기로 하고 일단 해보기로 하자.
Selenium Nuget Package
.NET 환경에서 Crawling을 해보고자 한다. 먼저 Nuget Package에서 아래 항목을 추가해준다.
- Selenium.WebDriver
- Selenium.WebDriver.ChromeDriver
- Selenium.Support
- DotNetSeleniumExtras.WaitHelpers
예제에서는 Chrome을 사용할 것이기 때문에 Selenium.WebDriver.ChromeDriver를 설치했지만, IE, Gecko 등 다른 브라우저를 사용할 때에는 다른 Driver를 설치해주면 된다.
웹 사이트의 정보 가져오기
Nuget Package 설치가 완료되면 웹 사이트에 접근하여 원하는 정보를 가져올 수 있다. 네이버 스포츠 메뉴의 첫 기사 제목을 가져오는 코드를 작성해보자.
private static string GetTitle()
{
// driver 생성
var driver = new ChromeDriver();
// 탐색할 웹 사이트 URI 설정
driver.Url = "https://sports.news.naver.com/index";
// 가져올 element 탐색
var element = driver.FindElement(By.ClassName("today_item")).FindElement(By.TagName("a"));
// title attribute 수집
var title = element.GetAttribute("title");
// title 반환
return title;
}
FindElement, FindElements
Selenium에서는 WebElement를 찾기 위해서 FindElement, FindElements와 같은 함수를 제공한다. 매개변수로 찾고자하는 WebElement의 TagName, CssSelector, Id, Name, XPath등의 정보를 전달하면 금방 해당 Element를 찾아준다.
public static By ClassName(string classNameToFind);
public static By CssSelector(string cssSelectorToFind);
public static By Id(string idToFind);
public static By LinkText(string linkTextToFind);
public static By Name(string nameToFind);
public static By PartialLinkText(string partialLinkTextToFind);
public static By TagName(string tagNameToFind);
public static By XPath(string xpathToFind);
Chrome 창 숨기기
위와 같이 코드를 작성하고 수행하면 Chrome창이 실행되면서 탐색하고자하는 웹 사이트가 나타난다. 이를 숨겨서 기능을 수행하고자 할 땐, ChromeOptions를 사용하면 된다. (다른 브라우저는 검색을 해봐야 할 듯.)
var options = new ChromeOptions();
options.AddArgument("headless");
var driver = new ChromeDriver(options);
결과
아래와 같이 결과가 출력되는 것을 확인할 수 있다. (흥민이형...)
문제 발생
chromedriver.exe 위치
예제의 경우, Build시 chromedriver.exe가 자동으로 실행 파일 위치와 동일한 폴더에 복사가 된다. 그래서 해당 문제가 발생하지 않지만 Submodule로써 사용되는 경우는 실행 파일 위치에 복사되도록 설정해주어야 한다. 그렇지 않으면
DriverServiceNotFoundException이 발생한다.
(이 정도는 발생하더라도 누구나 쉽게 해결할 수는 있겠지만...)
StaleElementReferenceException
Message : stale element reference: element is not attached to the page document
이 예외가 발생했을 때, 가장 당황스러웠다. 검색해보니 Web Element에 접근할 때, DOM에 해당 Element가 존재하지 않아서 발생한다고 한다. 그럴 때마다 페이지가 모두 로드될 때까지 기다려주는 코드를 작성하면 해결된다고 하는데, 이는 페이지가 완전히 로드되지 않았을 때 발생하는 문제인 것 같다.
나의 경우는 조금 다른 경우인 것 같다. 내가 접근하려는 사이트는 일정 시간마다 Refresh를 수행하는데, 그 때마다 Driver에서 Element를 접근하지 못하는 문제인듯 하다. 그래서 정상적으로 Parsing을 진행하다가 일정 시간 이후에는 해당 Exception이 발생하는 모습을 보였다.
어떻게 이 문제를 해결할까 고민하다가, 성능은 기능이 먼저 구현된 후 개선하기로 하고 기능 구현에 초점을 맞춰서 함수를 작성했다. WebElement를 수집하고 WebElement 별로 T를 생성하는 함수다. 아래와 같이 작성하면 driver를 여러 번 생성하는 등의 효율적이지 않게 동작하지만, 기능은 정상 동작한다.
(TODO : 이 부분을 효율적으로 개선할 수 있는 방법을 알아보자.)
private static List<T> CrawlWhile<T>(string url, Func<IWebElement, T> creator)
{
var result = new List<T>();
var options = new ChromeOptions();
// element별 T 형식 개체 생성 index
var idx = 0;
var driver = default(ChromeDriver);
while(true)
{
try
{
// driver 생성
driver = new ChromeDriver(options);
// url 설정
driver.Url = url.ToString();
// 조건에 맞는 Element 수집
var elements = driver
.FindElements()
.Skip(idx)
.ToList();
foreach (var element in elements)
{
result.Add(creator(element));
idx++;
}
}
catch(Exception e)
{
if (e is StaleElementReferenceException)
{
driver?.Close();
continue;
}
else
{
return null;
}
}
break;
}
return result;
}
코드
GitHub - jaywapp/Tutorials: Tutorial Codes
Tutorial Codes. Contribute to jaywapp/Tutorials development by creating an account on GitHub.
github.com
'Software Develop > C# , .NET , WPF' 카테고리의 다른 글
private, protected method 단위 테스트 코드 작성하기 (0) | 2023.02.07 |
---|---|
ListBox 사용하기 (0) | 2023.02.07 |
Adonis UI 사용기 (0) | 2023.02.07 |
Lazy<T> (0) | 2023.01.16 |
ReactiveUI (0) | 2022.02.28 |