자바 셀레니움으로 웹업무 자동화하기(feat 교통안전 공익제보단)

개발개발·2023년 5월 5일
0

코로나 기간동안 배달수요의 증가로 도로에 이륜차들이 많아졌다. 신속, 안정, 정확 이라는 배달의 3요소를 마음에 새기고 운전하시는 기사님도 많지만 신속만 지키시는 기사님들이 간혹 보였고, 이런 분들이 교통안전을 위협하는일들이 종종 생긴는 듯 하다.

이런 소수의 운전자들의 위협적인 운전을 줄이고자 시작된 교통안전 공익제보단이라는 것을 알게되었다.도로교통공단 모집 상세 교통법규 위반자를 신고하고 신고결과를 캡쳐해서 제출하면 포상금을 주고있다. 매달 제출해야 하는게 번거로워서 제작하게 되었다.

소스는 깃허브에 올려두었다.

처음 로그인 화면에 들어가면 아래와 같이 키보드 보안 프로그램을 설치하라 alert가 나온다.

설치하지 않으면 키패드가 나온다. 페이지를 수정해서 가상키패드를 사용하지 않을 수 있긴하다.

키패드 이용


로그인하면 개인정보가 있으므로 가상키패드를 이용해서 로그인하는게 당연한 방법이다. 키패드를 개발자도구로 확인하니 'click'를 이용해서 pw를 만들고 있었다.

해당 web element를 모두 읽어서 map으로 만들고 String으로 맵핑하기로 했다.

	private void keypadExtract(Map<String, String> keyMap) {
		String handle = "";
		String key = "";
		String val = "";

		for (int i = 0; i < 3; i++) { // shift를 누른 경우, 한글인 경우, 아무런 변환이 없는 경우
			if (i == 0) {

			} else if (i == 1) {
				handle = "VPad.ClickHangul();";
			} else {
				handle = "VPad.ClickShift();";
			}
			js.executeScript(handle);
			for (int j = 0; j < 47; j++) {
				webElement = driver.findElement(By.id("m_charText" + j + ""));
				key = webElement.getText();
				if (keyMap.get(key) == null) {
					val = handle + webElement.getAttribute("onclick") + ";" + handle;
					keyMap.put(key, val);
				}
			}
			js.executeScript(handle);
		}
	}

키패드를 추출했으므로 id와 pw가 있으면 로그인을 할 수 있다.

// JavascriptExecutor js;
		String pw = propertiesMap.get("password");
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < pw.length(); i++) {
			String temp = keyMap.get(String.valueOf(pw.charAt(i)));
			sb.append(temp);
			sb.append("\n");
		}
		sb.append("VPad.ClickEnter('char');");
		pw = sb.toString();
		js.executeScript(pw);

로그인을 완료하면 아래와 같은 화면이 나온다.


닫기 버튼을 눌러도 되지만 이미 세션에 나의 로그인 정보가 저장됐는지 '나의 제보조회'페이지로 바동 이동해도 문제가 없다.

나의 제보조회에 가니 현재일을 기준으로 3달치의 제보가 나온다. 여기서 제보상태가 처리 완료인 것을 찾아 캡쳐해서 제출해야 한다. 임의로 제보의 상세페이지를 들어간 다음 url을 살펴보니 아래와 같았다
url : /mypage/myGiveInfoView.do
param : {
gvnfSrcSe='개별 정보'
&gvnfSn='개별정보'

&gvnfCategory=
&searchWord=
&pageNo=1
&periodType=reportDt
&mFromDate=2023-02-04
&mToDate=2023-05-05
&searchInputTextTitle=001
}
볼드처리안 파라미터만 url에 입력하면 화면이 바뀌는 것을 확인했고 목록페이지의 제보 row에서 해당 정보를 확인할 수 있었다.

				webElement = driver.findElement(
						By.cssSelector("#container > div.content > div.table_list > table > tbody > tr:nth-child(" + Nth
								+ ") > td:nth-child(2) > a"));
				// 페이지 이동을 위해 parameter 추출
				String tempstr = webElement.getAttribute("href").split(":")[1];
				int a = tempstr.indexOf('\'');
				int b = tempstr.indexOf('\'', a + 1);
				String gvnfSn= tempstr.substring(a + 1, b);
				int c = tempstr.indexOf('\'', b + 1);
				int d = tempstr.indexOf('\'', c + 1);
				String gvnfSrcSe= tempstr.substring(c + 1, d);
				
				webElement = driver.findElement(
						By.cssSelector("#container > div.content > div.table_list > table > tbody > tr:nth-child(" + Nth
								+ ") > td:nth-child(1)"));
				String imageIndex = webElement.getText();
				// 새창에서 열기
				js.executeScript(
						"window.open('http://onetouch.police.go.kr/mypage/myGiveInfoView.do?gvnfSrcSe="+gvnfSrcSe+"&gvnfSn="
								+ gvnfSn + "&title=" + imageIndex + "');");
				

새창을 캡쳐해서 저장하기만 하면 자동화는 끝나는데 이 부분이 유독 재밌었다.


캡쳐해야 하는 화면의 크기는 1980인데 반해 노트북의 화면은 880(15.6인치 노트북)이었다. 전체 화면캡쳐를 위해서 몇가지 방법을 시도했다.

  1. chrome drive의 화면크기를 2000으로 늘림
    =>실패 : 사용자 화면의 최대크기 이상으로는 캡쳐가 되지 않음

  2. js의 scrollTo기능을 이용해서 화면을 조금씩 내리면서 캡쳐 그 다음 이미지 합치기
    => 2-1 실패 : fileoutputstream에 캡쳐한 이미지를 연달아 덮어쓰면 자연히 새로로 길어질거라고 생각했지만 아니었다. 처음 캡쳐된 화면만 나오고 다음에 저장되는 화면은 나오지 않고 용량만 커진 상태였다.
    이 방법을 사용한다면 내가 원하는 이미지를 숨기고 원복할 수 있다! 의도치 않게 흥미로운 내용을 찾았고 다음 글에 작성해볼 생각이다.

=> 2-2 성공 : ImageIO를 이용해서 화면을 붙이니 원하는 대로 화면이 나왔다. 완벽하게 간격을 조정하지 못해서 화면겹침이 조금은 나오지만 제보에는 문제없이 사용할 수 있었다.

// image 캡쳐하기
	private void imageSave(String mainWindow, String windowName) {
		driver.switchTo().window(windowName);
		webElement = driver.findElement(By.xpath("//*[@id=\"container\"]/div[2]"));
		int height = webElement.getSize().getHeight();

		int n = (int) height / 400 + 1;
		File[] scrFile = new File[n];
		for (int k = 0; k < n; k++) {
			scrFile[k] = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
			js.executeScript("window.scrollTo(0," + 400 * (k + 1) + ")");
			sleep(10);
		}
		mergeImage(scrFile, driver.getCurrentUrl().split("title")[1].substring(1) + "."+ driver
				.findElement(By.xpath("//*[@id=\"container\"]/div[2]/div[1]/table/tbody/tr[4]/td[1]")).getText(),
				height);
		driver.close();
	}
// 캡쳐된 이미지 하나로 합치기
	private void mergeImage(File[] images, String fileName, int h) {
		try {
			BufferedImage[] is = new BufferedImage[images.length];

			int width = 0;
			int height = 0;
			int minus = 0;
			for (int i = 0; i < is.length; i++) {
				is[i] = ImageIO.read(images[i]);
				width = Math.max(width, is[i].getWidth());
				height += is[i].getHeight();
				minus = is[i].getHeight();
			}
			// 파일 높이 조정
			height -= ((minus / 2 - 30) * (is.length - 1));

			BufferedImage mergedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
			Graphics2D graphics = (Graphics2D) mergedImage.getGraphics();
			graphics.setBackground(Color.WHITE);
			int tempHeight = 0;
			for (int i = 0; i < is.length; i++) {
				graphics.drawImage(is[i], 0, tempHeight - (i * (400 - 20)), null);
				tempHeight += is[i].getHeight();
			}
			Arrays.stream(images).forEach(t -> t.delete());
			ImageIO.write(mergedImage, "png", new File(saveDir+ fileName.replaceAll("[0-9]", "") + ".png"));
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}

위와 같은 방법을 이용해서 스마트국민제보에 로그인하고 제보된 내용중 답변완료된 제보를 자동을 캡쳐해서 파일로 저장할 수 있도록 만들었다. 혹시 공익제보단 중에서 사용하시고 싶으신 분들은 git에서 소스를 받아서 사용해보시면 도움되지 않을까 싶다.

profile
청포도루이보스민트티

0개의 댓글