웹 크롤링

웹 페이지에 있는 필요한 정보들을 사용할 수 있습니다.

jsoup

jsoup은 java에서 사용할 수 있는 html parser 패키지입니다.

설치 및 라이브러리 추가

jsoup 사이트에 접속해서 최신 jar 파일을 다운받습니다. 다운받은 jar 파일을 라이브러리에 추가하는 방법은 프로젝트 페이지의 외부 라이브러리 이용을 참조합니다.

간단한 예제

jsoup이 잘 작동하는지 알아보기위해 다음 코드를 실행해봅니다.

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;

/**
 * A simple example, used on the jsoup website.
 */
public class Wikipedia {
    public static void main(String[] args) throws IOException {
        Document doc = Jsoup.connect("http://en.wikipedia.org/").get();
        log(doc.title());

        Elements newsHeadlines = doc.select("#mp-itn b a");
        for (Element headline : newsHeadlines) {
            log("%s\n\t%s", headline.attr("title"), headline.absUrl("href"));
        }
    }

    private static void log(String msg, String... vals) {
        System.out.println(String.format(msg, vals));
    }
}
  • org.jsoup.Jsoup 객체는 jsoup을 편리하게 시작할 수 있는 여러 가지 정적 메소드들을 제공합니다.

  • Jsoup.connect(‘url’) 메소드를 이용해서 지정된 url에 접속을 위한 Connection 객체를 반환받습니다.

  • Connection.get() 메소드를 이용하여 웹페이지를 가져와 파싱을 한 후 Document 객체를 반환합니다.

  • Document 객체의 select 메소드의 인자로 CSS 선택자를 이용해 지정한 성분들 newsHeadlines을 찾습니다.

  • 성분들을 순회하며 각 성분의 titlehref 속성값을 출력합니다.

jsoup 소개

jsoup은 html 파일을 파싱하여 html 성분들을 jsoup 성분(Element) 객체로 만듭니다. 즉, HTML 페이지를 웹브라우저가 사용하는 DOM(Document Object Model) 형식으로 변경합니다.

jsoup은 다음과 같은 기능들을 갖추고 있습니다.

  • html을 파일, url, 문자열로부터 파싱합니다.

  • CSS 선택자 또는 DOM 순회를 이용하여 원하는 자료를 찾거나 추출합니다.

  • HTML 성분, 속성 및 텍스트를 조작합니다.

  • HTML을 보기 편하게 출력합니다.

문서 가져오기

파싱할 문서는 url 또는 파일, 문자열을 이용해서 가져올 수 있습니다. 자세한 것은 Jsoup 클래스 메소드를 참조하시기 바랍니다.

url 이용

url 주소를 지정하여 웹사이트에 접속해서 원하는 문서를 가져올 수 있습니다. Jsoup.connect('url').get() 메소드를 이용해서 웹페이지를 파싱하여 Document 객체를 반환합니다.

Document doc = Jsoup.connect("http://example.com/").get();
String title = doc.title();

진행중에 오류가 발생하면 예외 IOException을 발생시키기 때문에 예외처리를 해주어야 합니다.

파일 이용

파일로 저장되어 있는 HTML 파일을 읽어서 파싱을 합니다. Jsoup.parse(File in, String encoding, String baseUri)를 이용하면 encoding 인코딩 파일 객체 in으로부터 파싱을 할 수 있습니다. baseUri는 HTML 문서 중 상대적인 URL을 처리할 때 baseUri를 앞에 붙여서 처리를 하기 위한 것입니다.

File input = new File("/tmp/input.html");
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");

예외 IOException이 발생할 경우를 위해 예외처리를 해야합니다.

parse(File in, String encoding)를 이용하면 baseUri는 자동으로 파일의 위치가 됩니다.

문자열 이용

문자열을 파싱할 수 있습니다.

String html = "<html><head><title>First parse</title></head>" + "<body><p>Parsed HTML into a doc.</p></body></html>";
Document doc = Jsoup.parse(html);

Note

숙제

  • 네이버 홈페이지에서 실시간 검색어를 출력하시오.

셔틀 페이지

http://sejong.korea.ac.kr/campuslife/facilities/shuttle_bus 페이지 테이블을 출력합니다.

클래스 만들기

Timetable 클래스 만들기 ArrayList<TimetableItem>을 상속받도록 합니다. ArrayList를 참고합니다.

TimetableItemstation, schoolno 필드를 넣습니다.

한국 프로야구 순위

https://www.koreabaseball.com/TeamRank/TeamRank.aspx 사이트에서 한국 프로야구 팀순위를 출력해봅니다.

테이블을 for문을 이용해서 출력하는 연습을 합니다.

  1. tableselectFirst 메소드를 이용해서 선택합니다.

  2. theadtable.selectFirst("thead") 메소드를 이용해서 선택한 후 th를 선택하여 for문을 이용해서 출력합니다.

  3. 마찬가지로 tbodytable.selectFirst("tbody") 메소드를 이용해서 선택한 후 tr을 선택합니다.

  4. 선택된 각각의 tr에 대해서 td를 선택하여 출력합니다. 여기서는 이중 for 문이 필요합니다.

셀레늄(selenium)

셀레늄은 프로그래밍으로 웹브라우저를 대신할 수 있는 라이브러리입니다. 셀레늄은 기능에 따라 웹드라이버(WebDriver), IDE, 그리드(Grid) 등으로 나눌 수 있습니다.

  • 웹드라이버는 웹브라우저를 제어할 수 있는 드라이버를 이용해서 브라우저의 기능을 사용하는 것입니다.

  • IDE는 파이어폭스와 크롬 플러그인으로 사용할 수 있는 통합개발환경을 제공합니다.

  • Grid는 원격 웹 브라우저와 여러 대의 서버를 이용할 수 있는 플락시 서버입니다.

여기서는 웹드라이버를 사용하는 방법에 대해서만 설명합니다.

설치 및 라이브러리 추가

자바 프로젝트

셀레늄 사이트에서 jar 파일을 다운받아 jar 파일을 라이브러리에 추가합니다. 추가 방법은 프로젝트 페이지의 외부 라이브러리 이용을 참조합니다.

그레이들 프로젝트

build.gradle 파일에 다음을 추가하고 그레이들 프로젝트를 새로고침 합니다. 프로젝트 이름을 선택하여 오른쪽 클릭을 하고 Gradle > Refresh Gradle Project를 선택하면 됩니다. 아래 셀레늄 버전은 메이븐 사이트에서 최신 버전을 사용하시면 됩니다.

// https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java
compile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.141.59'

웹브라우저 설정

셀레늄 웹드라이버를 사용하기 위해서는 웹브라우저가 설치되어 있어야 합니다. 크롬 또는 파이어폭스를 설치하시면 됩니다. 또한 각 브라우저가 제공하는 드라이버를 다운받아 시스템 경로에 설정을 해야 합니다. 크롬을 예로 들어보겠습니다.

크롬 드라이버를 웹페이지에서 다운받아 압축을 풉니다. 파이어폭스 드라이버는 여기에서 다운받으시면 됩니다. 다운받은 파일을 프로젝트 디렉토리 안에 drivers라는 폴더를 만들고 저장합니다. 셀레늄을 사용하기 위해서는 크롬 드라이버를 먼저 경로에 지정하시고 사용하시면 됩니다. 다음과 같이 사용하시면 됩니다.

System.setProperty("webdriver.chrome.driver", "drivers\\chromedriver.exe");
WebDriver driver = new ChromeDriver();

driver는 웹브라우저 역할을 한다고 생각하시면 됩니다.

웹드라이버 API 사용법

웹페이지 접속

driver.get("http://www.google.com");

HTML 성분 선택(WebElement)

findElement 또는 findElements 메소드를 이용하여 원하는 돔(DOM) 성분(element)을 선택할 수 있습니다. 두 메소드의 반환값은 WebElement 또는 List<WebElement> 입니다. WebElement는 웹브라우저의 돔 성분(a, input, div 등)에 대응되는 클래스입니다. findElement의 매개변수로 By 클래스를 받습니다. 다음에 나오는 각각의 메소드들을 이용하여 다양한 성분들을 편리하게 선택할 수 있습니다.

아이디(ID) 이용 선택
WebElement element = driver.findElement(By.id("coolestWidgetEvah"));
클래스 이름 이용 선택
List<WebElement> cheeses = driver.findElements(By.className("cheese"));
태그(Tag) 이름 이용 선택
WebElement frame = driver.findElement(By.tagName("iframe"));
이름(Name) 이용 선택
WebElement cheese = driver.findElement(By.name("cheese"));
부분 링크 텍스트 이용 선택
WebElement cheese = driver.findElement(By.partialLinkText("cheese"));
CSS 이용 선택
WebElement cheese = driver.findElement(By.cssSelector("#food span.dairy.aged"));
XPath 이용 선택
List<WebElement> inputs = driver.findElements(By.xpath("//input"));

액션 취하기

키보드

sendKeys 메소드를 이용하여 텍스트를 입력할 수 있습니다.

String name = "Charles";
driver.findElement(By.name("name")).sendKeys(name);
드래그 앤 드랍(Drag and Drop)
WebElement element = driver.findElement(By.name("source"));
WebElement target = driver.findElement(By.name("target"));

(new Actions(driver)).dragAndDrop(element, target).perform();
클릭 이용
driver.findElement(By.id("submit")).click(); // 또는
driver.findElement(By.cssSelector("input[type='submit']")).click();

자바스크립트 이용

WebElement element = (WebElement) ((JavascriptExecutor)driver).executeScript("return $('.cheese')[0]");

텍스트 값 얻기

WebElement element = driver.findElement(By.id("elementID"));
element.getText();

폼 입력

select 이용
WebElement select = driver.findElement(By.tagName("select"));
List<WebElement> allOptions = select.findElements(By.tagName("option"));
for (WebElement option : allOptions) {
    System.out.println(String.format("Value is: %s", option.getAttribute("value")));
    option.click();
}
Select select = new Select(driver.findElement(By.tagName("select")));
select.deselectAll();
select.selectByVisibleText("Edam");
클릭 이용
driver.findElement(By.id("submit")).click();
제출(submit) 이용
element.submit();

페이지 이동

지정된 url로 이동합니다. driver.get 메소드와 동일합니다.

driver.navigate().to("http://www.example.com");

현재 페이지의 url을 반환합니다.

driver.getCurrentUrl();

현재 페이지 제목을 반환합니다.

driver.getTitle();

현재 페이지 앞/뒤로 이동합니다.

driver.navigate().forward();
driver.navigate().back();

현재 페이지를 새로 고칩니다.

driver.navigate().refresh();

페이지 이동시 주의해야 할 것은 페이지 이동이 발생하면 리로딩이 일어나서 이전의 값들을 잃어버리게 됩니다. 웹 엘리먼트들에 대해서 반복문을 사용하여 왔다 갔다할 때는 다시 한번 웹 엘리먼트들을 선택해 줘야 합니다.

다음은 반복문에서 back 메소드를 사용했을 때 다시 성분을 선택하는 예제입니다.

driver.get(url);

List<WebElement> newsList = driver.findElements(By.cssSelector("#container > div.mCont div.news_area > div > dl > dd:nth-child(2) > a"));

for (int i = 0; i < newsList.size(); i++) {
  try {
    newsList = driver.findElements(By.cssSelector("#container > div.mCont div.news_area > div > dl > dd:nth-child(2) > a"));
    newsList.get(i).click();
    System.out.println(driver.getCurrentUrl());
    driver.navigate().back();
  } catch (Exception e) {
    e.printStackTrace();
}

창/탭과 프레임 이동

웹드라이버는 창과 탭을 구분하지 않고 같이 취급합니다.

driver.switchTo().window("windowName");
for (String handle : driver.getWindowHandles()) {
  driver.switchTo().window(handle);
}
창과 탭 닫기
//Close the tab or window
driver.close();

//Switch back to the old tab or window
driver.switchTo().window(originalWindow);
프레임 이동
//Store the web element
WebElement iframe = driver.findElement(By.cssSelector("#modal>iframe"));

//Switch to the frame
driver.switchTo().frame(iframe);

//Now we can click the button
driver.findElement(By.tagName("button")).click();
팝업 창
Alert alert = driver.switchTo().alert();

쿠기

// Go to the correct domain
driver.get("http://www.example.com");

// Now set the cookie. This one's valid for the entire domain
Cookie cookie = new Cookie("key", "value");
driver.manage().addCookie(cookie);

// And now output all the available cookies for the current URL
Set<Cookie> allCookies = driver.manage().getCookies();
for (Cookie loadedCookie : allCookies) {
    System.out.println(String.format("%s -> %s", loadedCookie.getName(),  loadedCookie.getValue()));
}

// You can delete cookies in 3 ways
// By name
driver.manage().deleteCookieNamed("CookieName");
// By Cookie
driver.manage().deleteCookie(loadedCookie);
// Or all of them
driver.manage().deleteAllCookies();

사용자 에이전트 변경

FirefoxProfile profile = new FirefoxProfile();
profile.addAdditionalPreference("general.useragent.override", "some UA string");
WebDriver driver = new FirefoxDriver(profile);

대기 시간

웹 요소를 찾는 동안 드라이버가 다음으로 진행하지 않고 대기하는 시간을 설정할 수 있습니다.

암시적 대기

WebDriver.Timeouts에 정의되어 있는 implicitlyWait() 메소드를 이용하여 대기 시간을 설정합니다. 대기 시간이 지나면 NoSuchElementException이 발생합니다. 세션 중에 있는 어떤 성분에 대해서도 적용이 됩니다. 여러 요소를 검색할 때는 그 중에 하나라도 찾으면 대기를 멈춥니다.

WebDriver driver = new FirefoxDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = driver.findElement(By.id("myDynamicElement"));
명시적 대기

어떤 조건을 만족할 때까지 다음으로 넘어가지 않고 대기하는 것을 명시적 대기(explicit wait)이라고 합니다. WebDriverWaituntil 메소드를 이용해서 대기 시간을 설정합니다. until 인자로 넘겨진 조건이 만족이 될 때까지 주어진 시간을 기다립니다. 주어진 조건을 만족하면 그 조건에 맞는 요소를 반환하고 다음으로 넘어갑니다.

WebDriver driver = new ChromeDriver();
driver.get("https://google.com/ncr");
driver.findElement(By.name("q")).sendKeys("cheese" + Keys.ENTER);
// Initialize and wait till element(link) became clickable - timeout in 10 seconds
WebElement firstResult = new WebDriverWait(driver, 10)
        .until(ExpectedConditions.elementToBeClickable(By.xpath("//a/h3")));
// Print the first result
System.out.println(firstResult.getText());

presenceOfElementLocated(WebElement elmt) 메소드는 요소 elmt가 나타나면 조건이 만족되어 다음 라인을 실행합니다.

WebDriver driver = new FirefoxDriver(); // 크롬 사용시 new ChromeDriver()로 변경.
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = new WebDriverWait(driver, 10) // 10 초
  .until(ExpectedConditions.presenceOfElementLocated(By.id("myDynamicElement")));

elementToBeSelected(WebElement elmt) 메소드는 elmt 요소가 선택될 때까지 기다리다가 선택이 되면 true를 반환합니다. 다음은 select 요소의 특정한 option을 선택했을 때 반응하는 코드입니다.

WebElement y2010 = driver.findElement(By.cssSelector("#yyyy > option[value='2010']"));
Boolean option = new WebDriverWait(driver, 10)
  .until(ExpectedConditions.elementToBeSelected(y2010));

if (option) {
  System.out.println("선택된 태그이름: " + y2010.getTagName() + ", 이름:" + y2010.getText());
}

대기 조건은 ExpectedConditions을 참조하세요.

사용자 에이전트

User-Agent 변경을 할 수 있습니다.

ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--user-agent=Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Mobile Safari/537.36");

WebDriver driver = new ChromeDriver(chromeOptions);

자바스크립트

자바스크립트를 실행할 수 있습니다.

JavascriptExecutor js = (JavascriptExecutor) driver;

String js_string = "Object.defineProperty(navigator, 'webdriver', {" +
      "      get: () => false," +
      "    });";

js.executeScript(js_string);