본문으로 바로가기

우선 프로그램을 실행할 환경의 '크롬 브라우저 / 크롬 드라이버 / selenium' 3가지의 버전이 서로 호환되도록 세팅해야 한다.

 

 

selenium을 사용한 크롤링은 자바 보다는 파이썬으로 더 많이 구현하는 것 같다.

코드에서 주요한 일부분만 정리하였다.

import java.util.*;
import java.time.*;
import java.nio.file.*;
import java.io.File;
import java.io.InputStream;
import java.net.URL;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.*;
import org.openqa.selenium.JavascriptExecutor;

public class getInstagram {
	
	public static final String WEB_DRIVER_ID = "webdriver.chrome.driver";
	public static final String WEB_DRIVER_PATH = "C:\\chromedriver.exe";
    
	...(생략)...
    
	List<String> users = Files.readAllLines( Paths.get("D:\\insta_users.txt") );
	users = users.stream().map(String::trim).collect(Collectors.toList());
    users.removeAll(Arrays.asList("", null));
    
	public static void main(String[] args) throws Exception {
    
		try {
			System.setProperty(WEB_DRIVER_ID, WEB_DRIVER_PATH);
		} catch (Exception e) {
			e.printStackTrace();
		}
        
		ChromeOptions options = new ChromeOptions();
		options.addArguments("headless"); // GUI 없이 실행
		WebDriver driver = new ChromeDriver(options);

드라이버 옵션을 설정하고 로드한다. 중간중간 테스트를 위해서는 headless 옵션을 제거하는게 좋다.

수집 대상 계정명(id)을 텍스트 파일에 라인별로 미리 입력해 두었다.

혹시 공백이 있을지 모르므로 List 전체에서 제거.

 

		String url = "https://www.instagram.com/accounts/login/";
		
		driver.get(url);
		try {Thread.sleep(2000);} catch (InterruptedException e) {}
        
		driver.findElement(By.name("username")).sendKeys(USER_ID);
		driver.findElement(By.name("password")).sendKeys(USER_PASS);
		driver.findElement(By.xpath("//div[text()='로그인']")).click();
        
		try {Thread.sleep(4000);} catch (InterruptedException e) {}
        
		if ( driver.findElements(By.xpath("//button[text()='나중에 하기']")).size() > 0 ) {
			driver.findElement(By.xpath("//button[text()='나중에 하기']")).click();
		}

sendKeys 메소드는 keyDownkeyUp을 한번에 처리하는 것이라고 설명되어 있다.

문자열 외에 열거형인 Keys의 상수를 사용하여(Keys.SHIFT, Keys.ARROW_UP 등) 키를 입력할 수 있다.

키보드 조작이 여러 단계를 거쳐야 하는 경우 Actions 클래스를 사용하여 연결할 수 있다.

new Actions( driver ).sendKeys( driver.findElement(By.name("password")), USER_ID).sendKeys(Keys.ENTER).perform();

 

	for (int i=0; i<users.size(); i++ ) {
			
			url = "https://www.instagram.com/" + users.get(i) + "/";
			driver.get(url);
			System.out.println( driver.getCurrentUrl() );
			try {Thread.sleep(2000);} catch (InterruptedException e) {}
		
			// 해당 계정의 메인 페이지가 로딩되기까지 기다린다
			new WebDriverWait(driver, Duration.ofSeconds(10)).until( ExpectedConditions.presenceOfElementLocated(By.xpath("//div[@role='tablist']")) );

WebDriverWait 클래스는 지정한 시간만큼 기다리면서 조건을 만족하는지 체크하고, 만족 시 바로 넘어간다.

ExpectedConditions 클래스에서 사용할만한 다른 메서드는 urlToBe, numberOfElementsToBeMoreThan 등이 있다.

 

			JavascriptExecutor js = (JavascriptExecutor)driver;
			
			String href = null;
			ArrayList<String> articles = new ArrayList<String>();
			
			int cntScroll = 0;
			
			// 스크롤을 끝까지 내리면서 게시물 URL을 수집한다. 마지막에 도달하면 loading div가 사라진다.		
			while ( !driver.findElements(By.className("_aanh")).isEmpty() ) {
				js.executeScript("window.scrollTo(0, document.body.scrollHeight);");
				try {Thread.sleep(1000);} catch (InterruptedException e) {}
				
				List<WebElement> tagA = driver.findElements(By.tagName("a"));

JavascriptExecutor는 자바스크립트를 실행할 수 있는 인터페이스.

계정별 페이지 접속 시 24개의 게시물을 보여주고, 맨 아래까지 스크롤했는데도 게시물이 더 있는 경우 로딩 gif가 표시되면서 12개씩 더 불러오므로, 최초 게시물에 도달하려면 스크롤을 여러 번 해야 한다.

 

			for (int j=0; j<list.size(); j++) {
				
				System.out.println( "downloading...." );
				
				URL urlImage = new URL( list.get(j) );
				String fileName = Paths.get(urlImage.getPath()).getFileName().toString();
				
				String USER = users.get(i).replace(".", "_");
				
				File dir = new File("D:/download/" + USER);
				if ( !dir.exists() ) {
					dir.mkdir();
				}
				
				try {Thread.sleep(1000);} catch (InterruptedException e) {}
				
				downloadFile(urlImage, "D:/download/" + USER + "/" + USER + "_" + fileName);
				
			}

계정별로 폴더가 없으면 생성하고 다운로드한다.