728x90
반응형
728x90
반응형
728x90
반응형

로그를 별도로 관리,  logback에서 propreties 값 사용, logback으로 exception 기록

 

 

이번에 작업했던 내용은 변화가 많고 수시로 수정해야 하는 내용이 있는 기능 이었다.

그래서 매번 로컬에서 테스트 한다는 것은 현실성이 없었기 때문에 로컬환경과 같이 이클립스의 console창에 표시되는 정보를 운영서버에서도 바로바로 얻어 확인할 필요성이 있었다.

이런 상황에서 적당한 것이 logback 이다.

 

 

먼저 관리 페이지에 테스터를 만들었다.

그런데 console 창 로그를 가져오고 싶은데 어떻게 해야 할까?

서버에 기본으로 남기는 로그를 가져와야 하나?

기본으로 남기는 로그는 자칫 사막에서 바늘을 찾아야 하는 상황이 될 가능성이 크다.

필터링 해서 찾는 것도 뭔가 쓸데 없는 짓으로 느껴졌다.

 

혹시나 싶어 logback에 대해 알아보기로 했다.

기존에 나는 logback에서 로그를 어떤 것을 남길 것이냐, 어느 레벨로 남기냐 정도로 사용했다.

그런데 이번에 logback을 보면서 이외에 많은 부가적인 기능이 있음을 알게 되었다.

먼저 spring logback에 대한 자세한 정보는 구글에서 검색하자.

이미 많은 현자들께서 자세히 설명해 두셨다.

 

아래는 이번에 설정한 내용이다.

 

1. logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    
    <!-- 서버 환경별 properties를 읽기 위한 방법 -->
    <property resource="application.properties" />
    <property resource="application-${spring.profiles.active}.properties" />
    
    <!-- logging 패턴 정의 -->
    <appender name="INFO_LOG_OVER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${my.log.write.path}/${my.tester.log.file.name}.log</file>    <!-- 파일을 저장할 경로를 정한다 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch> <!-- 해당 레벨만 기록한다. -->
            <onMismatch>ACCEPT</onMismatch> <!-- DENY 다른 수준의 레벨은 기록하지 않는다.(상위 레벨도 기록 안함), ACCEPT : 상위 수준의 레벨에 대한 기록된다. -->
        </filter>     <!-- 레벨별 필터링이 필요없을 경우 filter class 관련된 부분을 삭제하면 됨-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- rollover daily -->
            <fileNamePattern>${my.log.write.path}/${my.tester.log.file.name}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>    <!-- 해당 패턴 네이밍으로 이전 파일이 기록됨 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>     <!-- 한 파일의 최대 용량 -->
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>2</maxHistory> <!-- 한 파일의 최대 저장 기한 -->
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyyMMdd HH:mm:ss.SSS} [%thread] %-3level %logger{5} - %msg %n</pattern>  <!-- 해당 패턴 네이밍으로 현재 로그가 기록됨 -->
        </encoder>
    </appender>
    
    <!-- name : 콘솔에 출력된 패키지 파일에서 남긴것 에 대해서 아래 작업을 실행한다. additivity : false는 로그 파일로만, true는 콘솔도-->
    <logger name="packagename.utils.DataUtil" additivity="true">
        <!-- INFO 레벨 이상에서만 실행한다. -->
        <level value="INFO"/>
        <appender-ref ref="INFO_LOG_OVER" />
    </logger>
    <logger name="packagename.admin.TesterService" additivity="true">
        <level value="INFO"/>
        <appender-ref ref="INFO_LOG_OVER" />
    </logger>
 
    <!-- 중략 -->
 
</configuration>
cs

4~6번줄 : 환경변수를 사용하기 위해 properties을 읽는다.

기본은 logback.properties 또는 application.properties에 logging.file.path, logging.file.name를 설정해서 사용하면 된다.

다만 나의 경우 로컬과 테스트, 운영 서버의 환경이 달라 패스가 달라지는데

이에 맞게 설정하려고 하니 logging.file.path, logging.file.name를 사용할 수 없었다.

그래서 4~6번줄과 같이 서버 환경에 따른 properties 파일의 값을 가져오는 방법을 사용했다.

(6번 라인의 spring.profiles.active를 모른다면 구글 검색)

 

환경별 properties 파일에 아래와 같이 설정값을 추가해 준다.

1
2
3
## logback 설정값
my.log.write.path=C:/logs
my.tester.log.file.name=tester
cs

logback.xml 내에서는 읽어온 변수값은 10번, 18번 줄과 같이 ${변수명}으로 사용하면 된다.

(주의! properties 파일에서 패스에서 역슬러시(\)는 사용할 수 없다)

 

9~27번줄 : 로그패턴을 정의한다.

10번줄 : 로그파일 패스와 파일명

18번줄 : 백업 파일 패스와 파일명

20번줄 : 1개 로그 파일의 최대 용량

22번줄 : 로그 보유기간(일)

 

30~38번줄 : 로그패턴을 어디서 어떻게 사용할 것인지 설정한다.

30번줄 : name은 로그패턴을 사용할 메소드의 패키지 위치, additivity는 false인 경우 지정한 로그파일로만 기록, true는 console(메인로그)도 기록

 

이제 지정된 패키지에서 남기는 로그가 별도로 기록되고 된다.

 

2. exception이 기록되지 않는다?

그런데 확인해 보니 1번 소스 30~38번줄에서 excetion이 일어났지만 로그파일에는 기록되지 않고 있었다.

logback의 설정이 필요한가? 검색해 봤는데 원인은 catch 문 안에서 로그를 표시하는 방법에 문제가 있었다.

1
2
3
4
5
6
try {
    ......
catch(Exception e) {
    // e.printStackTrace(); // logback으로 관리되지 않음
    logger.error("", e);
}
cs

 

이제 내가 계획한 대로 원하는 메소드에서 남기는 로그가 별도로 기록되게 되었다.

이렇게 남겨진 로그를 메소드 호출시간 부터 검색해서 찾아 테스터 화면에 뿌려주면 원하는 기능이 완성된다.

 

* 참고한 글

 

Logback 으로 쉽고 편리하게 로그 관리를 해볼까요? ⚙️

Spring Boot…

tecoble.techcourse.co.kr

 

Spring-boot Logback을 활용한 로그저장

📝Spring-boot의 로그를 파일로 저장해보자!!📌

velog.io

 

Dveamer

현실에서 살고 있지만 이상에 대한 꿈을 버리지 못한 몽상가의 홈페이지 입니다. 개인적인 기록을 주 목적으로 하며 일상과 프로그래밍 관련 글을 포스팅합니다.

dveamer.github.io

 

728x90
반응형
728x90
반응형

크롤링으로 본문을 수집할 때 순수한 본문 내용만을 수집하고자 하지만

포스팅되는 많은 글들은 내부 링크, 좋아요 또는 댓글, 광고 등 많은 것들이 붙어 있어

이것들을 제거해야할 필요가 있었다.

 

사실 이 글의 내용은 기존에 포스팅 한 Selenium의 연장선에서 작업한 내용이다.

기존 Selenium 포스팅은 아래 글 참조.

 

[java, selenium] web crawling (웹 크롤링)

최근 좀 길게 웹 크롤링에 대해 작업을 했다. 넓은 범위로 크롤링 중인지라 길어지게 되었고, 어느정도 정리가 되어가고 있어 내용을 정리해 보고자 한다. 참고로 나는 docker에 ubuntu + tomcat + seleni

deonggi.tistory.com

 

Selenium의 연장선이라면서 Jsoup을 쓰고 있는 이유가 있다.

1차 결과물을 수집해 보았더니 원하지 않는 내용들도 딸려 들어오고 있었다.

이런 것들을 제거하는 기능이 필요했으나 작업하던 당시

Selenium에서는 이런 레퍼런스를 찾을 수 없었기 때문에 Jsoup을 섞어서 쓰게 되었다.

 

이러한 결과로 만들어진 결과를 기록해 본다.

 

1. html에서 필요하지 않는 텍스트를 태그 단위로 제거한다.

내가 얻고자 했던 것은 본문 내용 부분이다.

하지만 본문 영역의 html에 대한 텍스트를 모두 가져온다면 필요하지 않은 텍스트들이 의외로 많이 가지고 있다.

그래서 나는 이런 것들의 제거가 필요하다고 생각했고 태그 단위로 제거하길 원했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * jsoup을 이용하여 element 제거
 * @param strHtml
* @param removeSelectorList 제거할 셀렉터 리스트
 * @return
 */
public static String removeElementByJsoup(String strHtml, List<String> removeSelectorList) {
    if (removeSelectorList != null && removeSelectorList.size() > 0) {
        // String 형태의 html 문서를 html로 변경한다.
        org.jsoup.nodes.Document html = org.jsoup.Jsoup.parseBodyFragment(strHtml);
        for (String selector : removeSelectorList) {
            // html에서 제거할 태그를 찾아 제거한다.
            org.jsoup.select.Elements els = html.select(selector);
            if (els != null && els.size() > 0) {
               for (org.jsoup.nodes.Element el : els) {
                   el.remove();
               }
           }
        }
        // 제거된 html을 스트링으로 리턴한다.
        return html.toString().trim();
    }        
    return strHtml.trim();
}
cs

위 소스에서 removeSelectorList에는 이러한 제거할 태그에 대한 셀렉터다.

(ex : 내부 글 링크, 구글 광고와 같은 것, SNS 공유 버튼 텍스트, 좋아요 댓글 버튼 텍스트 등)

이 메소드의 리턴 값은 html 태그를 가지고 있다.

그러므로 이제 html 태그를 제거하고 텍스트만 가져오면 된다.

 

2. html에서 태그를 제거하고 텍스트만 남긴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * jsoup을 이용하여 태그를 제거한 text만 수집
 * @param strHtml
 * @return
 */
public static String getElementTextByJsoup(String strHtml) {
    if (!StringUtils.isBlank(strHtml)) {
        String REGEX_NEW_LINE = "(\r\n|\r|\n)";
        String NEW_LINE = System.getProperty("line.separator");
        /**
         * 1. p, br 태그와 텍스트를 남긴다. (이외 개행상태 유지)
         * 2. br 태그를 개행한다.
         * 3. 남은 태그를 제거한다.
         */
       String cleanText = org.jsoup.Jsoup.clean(
                   org.jsoup.Jsoup.clean(
                           strHtml.replace("&nbsp;"" ")            // 공백 태그 변경
                           , "", org.jsoup.safety.Whitelist.none().addTags("br""p")
                           , new org.jsoup.nodes.Document.OutputSettings().prettyPrint(true))            // 1. 
                   .replace("<br>", NEW_LINE)                                                        // 2.
                   , "", org.jsoup.safety.Whitelist.none(), new org.jsoup.nodes.Document.OutputSettings().prettyPrint(false));    // 3.
           
       // 개행문자 통일
        cleanText = cleanText.replaceAll(REGEX_NEW_LINE, NEW_LINE);
        return StringEscapeUtils.unescapeHtml4(cleanText.trim());        // html 특수문자 제거
    }
    return null;
}
cs

위 소스에서는 몇가지 포인트가 있다.

1) 개행문자가 os에 따라 다르므로 한가지로 변경하면 차후 작업에 유리하다.

2) br, p에 대한 태그는 개행으로 유지한다. 

3) html 특수문자는 &nbsp;는 공백으로 변경하고, 나머지는 제거한다.

 

 

자세한 설명은 부족하겠으나 많은 테스트 후 나온 결과물이다.

제거할 대상 태그들에 대한 셀렉터들은 다소 사전 조사가 필요하다.

눈에 보이는 것들도 있겠으나 눈에 보이지 않는 요소들도 상당히 많다.

그러므로 2번째 소스를 먼저 사용해서 정보를 수집하고 1번 소스를 사용하면 될 것 같다.

 

 

 

728x90
반응형
728x90
반응형

몇일만에 서버 스케줄 작동 결과를 확인 했는데 빈 스케줄이 발견 되었다.

원인은 트랜잭션에 잡혀 있는 테이블들을 수정하려고 했던것.

 

Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

 

 

응급조치로 문제가 되는 스케줄을 정지시켰다.

다만 이게 완벽한 해결책이 될수 없는 것이 문제.....

너무 촘촘히 서버가 돌아가고 있는게 문제인 건데... 어떻게 해야하지? =ㅅ=;

그리고 이게 문제가 된다면 준비 중인 것도 문제가 되겠다. 고민 좀 해보자.

728x90
반응형
728x90
반응형

문제 없이 작동하던 메소드에서 에러가 난다.

원인은 파일명 또는 패스에 공백 또는 사용할 수없는 문자 포함하는 경우.

 

java.io.IOException: Invalid file path while creating and writing in a File

I am trying to create a file inside a directory but when the code executes it brings back the error 'java.io.IOException: Invalid file path'. And the code does create the directory called 'ServerU...

stackoverflow.com

 

아래는 에러 전문

java.io.FileNotFoundException: Invalid file path

at java.base/java.io.FileOutputStream.(FileOutputStream.java:229)

at java.base/java.io.FileOutputStream.(FileOutputStream.java:184)

at packagename.core.common.utils.Utils.writeFile(Utils.java:496)

at packagename.core.common.utils.Utils.getWEList(Utils.java:416)

at packagename.core.common.utils.Utils.run(Utils.java:236)

at packagename.core.schedule.service.ScheduleService.commonUWorking(ScheduleService.java:648)

at packagename.core.schedule.service.ScheduleService.startTUWorking(ScheduleService.java:589)

at packagename.core.schedule.service.ScheduleService$$FastClassBySpringCGLIB$$5a06c0f7.invoke()

at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

at packagename.core.schedule.service.ScheduleService$$EnhancerBySpringCGLIB$$397471ba.startTUWorking()

at packagename.web.BaseController.getTest(BaseController.java:115)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.base/java.lang.reflect.Method.invoke(Method.java:568)

at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)

at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)

at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)

at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)

at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)

at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)

at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)

at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)

at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)

at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)

at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)

at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)

at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346)

at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)

at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)

at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887)

at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684)

at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)

at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)

at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

at java.base/java.lang.Thread.run(Thread.java:833)

 

 

728x90
반응형
728x90
반응형

이 에러는 왜 나오는지 모르겠다.

아래 소스의 19번 라인에서 발생된 에러이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    @Transactional(rollbackOn = {Exception.class})
    public void workingProcess() {
        // 스케줄 로그를 남기기 위한 DTO 선언
        SchdScheduleLogReqDTO logReqDTO = new SchdScheduleLogReqDTO(Thread.currentThread().getStackTrace()[1].getMethodName());
        Integer instCnt = 0;
        Integer updCnt = 0;
        Integer delCnt = 0;
        try {
            List<String> pathList = mapperClass.getPathList();
            if (pathList != null && pathList.size() > 0) {
                // row insert, update 작업 진행.
                   // logReqDTO = callMethod(pathList, logReqDTO);
                   if (logReqDTO != null && logReqDTO.getInstCnt() != null && logReqDTO.getInstCnt() > 0) { // break point instCnt에 11로 수정
                       SearchSchdUReqDTO reqDTO = new SearchSchdUReqDTO();
                       reqDTO.setUseYn("Y");
                       reqDTO.setNotNullWrtUpdDt(true);
                       reqDTO.setLastUpdHours(1);
                       reqDTO.setDList(pathList);
                       List<SchdUResDTO> newUList = mapperClass.getUList(reqDTO);    // exception 발생
 
                    /* .... 중간 작업 생략 .... */
 
                   }
               }
            logReqDTO.setInstCnt(logReqDTO.getInstCnt() + instCnt);
            logReqDTO.setUpdCnt(logReqDTO.getUpdCnt() + updCnt);
            logReqDTO.setDelCnt(logReqDTO.getDelCnt() + delCnt);
            mapperClass.insertScheduleLog(logReqDTO);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
cs

에러를 재현하면 아래와 같다.

1. 테스트를 위해 12번 라인은 주석 처리 했다.

2. 14번 부터 실행하기 위해 13번 라인에서 break point를 잡고 logReqDTO의 instCnt를 11로 변경했다.

3. 19번에서 해당 에러가 발생한다.

 

해당 부분의 Array 형태라고는  18번 라인의 reqDTO.setDList(pathList); 부분 뿐이다.

또한, 동일한 값으로 해당 mybatis를 실행하기 위해서 별도의 메소드에서 따로 실행했을때는 문제가 없었다.

마지막으로 java.lang.ArrayIndexOutOfBoundsException: Index 11 out of bounds for length 1 에서 11은 

내가 임의로 넣어준 instCnt의 값과 동일하게 변하면서 에러가 난다.

 

당황해서 1시간 이상 삽질을 했는데.. 결론은 사용하지도 않는 값이 index로 값으로 사용한다는 것이다. (왜?)

해당 값을 19번 라인에서 사용되는 reqDTO에서는 쓰지도 않는데... 왜일까?

에러가 발생한 상황은 테스트를 위한 것으로 정상 실행시는 문제가 없다.

그래서 현재로서는 이 문제는 미궁에 빠진체 알 수 없게 되었다.

 

참고로 java.lang.ArrayIndexOutOfBoundsException: Index 11 out of bounds for length 1

이와 같은 에러는 일반적으로 Array에서 없는 index를 사용하려고 할 발생한다.

 

 

* 에러 전문 ===================================================================================

java.lang.ArrayIndexOutOfBoundsException: Index 11 out of bounds for length 1

at org.apache.ibatis.reflection.ParamNameResolver.getNamedParams(ParamNameResolver.java:115)

at org.apache.ibatis.binding.MapperMethod$MethodSignature.convertArgsToSqlCommandParam(MapperMethod.java:309)

at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:142)

at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)

at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:144)

at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)

at jdk.proxy2/jdk.proxy2.$Proxy136.getUrlList(Unknown Source)

at packagename.core.schedule.service.ScheduleService.workingProcess(ScheduleService.java:107)

at packagename.core.schedule.service.ScheduleService$$FastClassBySpringCGLIB$$efabd1f.invoke()

at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

at packagename.core.schedule.service.ScheduleService$$EnhancerBySpringCGLIB$$20ec79ba.getPostInfoFromTistoryBlog()

at packagename.web.BaseController.testPost9(BaseController.java:121)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.base/java.lang.reflect.Method.invoke(Method.java:568)

at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)

at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)

at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)

at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)

at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)

at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)

at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)

at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)

at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)

at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)

at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)

at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)

at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346)

at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)

at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)

at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887)

at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684)

at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)

at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)

at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

at java.base/java.lang.Thread.run(Thread.java:833)

 

 

728x90
반응형
728x90
반응형

필요한 기능이 있어서 추가한 코드에서 갑작스런 에러가 났다.

원인은 간단하다. select 구문의 결과를 받을 resultType 또는 resultMap이 누락된거다.

 

1. SELECT COUNT(*) FROM MEMBER

WHERE MEM_TP = 'USER'

AND DEL_YN = 'N'

{executed in 8 msec}

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'packagename.core.schedule.mapper.ScheduleMapper.getMemberTypeCount'. It's likely that neither a Result Type nor a Result Map was specified.

at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92)

at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)

at com.sun.proxy.$Proxy104.selectOne(Unknown Source)

at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:159)

at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:87)

at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:144)

at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)

at com.sun.proxy.$Proxy127.getMemberTypeCount(Unknown Source)

at packagename.core.schedule.service.ScheduleService.common(ScheduleService.java:966)

at packagename.core.schedule.service.ScheduleService.start(ScheduleService.java:793)

at packagename.core.schedule.service.ScheduleService$$FastClassBySpringCGLIB$$5a06c0f7.invoke()

at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

at packagename.core.schedule.service.ScheduleService$$EnhancerBySpringCGLIB$$370f28bf.startTistoryUrlCrawling()

at packagename.web.BaseController.getTest(BaseController.java:101)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)

at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)

at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)

at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)

at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)

at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)

at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)

at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)

at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)

at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)

at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)

at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)

at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346)

at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)

at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)

at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887)

at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684)

at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)

at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

at java.lang.Thread.run(Unknown Source)

Caused by: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'packagename.core.schedule.mapper.ScheduleMapper.getMemberTypeCount'. It's likely that neither a Result Type nor a Result Map was specified.

at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.validateResultMapsCount(DefaultResultSetHandler.java:289)

at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:191)

at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65)

at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)

at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)

at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)

at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)

at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)

at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:108)

at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)

at com.sun.proxy.$Proxy193.query(Unknown Source)

at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)

at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)

at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426)

... 71 more

 

 

 

 

언제나 느끼는 오늘의 교훈 ㅎ

수정하면 테스트 해보자. 에러 좀 자세히 봐라.

728x90
반응형
728x90
반응형

최근 좀 길게 웹 크롤링에 대해 작업을 했다.
넓은 범위로 크롤링 중인지라 길어지게 되었고, 어느정도 정리가 되어가고 있어 내용을 정리해 보고자 한다.

참고로 나는 docker에 ubuntu + tomcat + selenium을 사용중이다.
그러므로 docker 환경에 대한 내용이 필요하다면 이전 글을 참고하시길 바란다.
* docker에 대한 이전 글
1. docker 컨테이너로 tomcat 실행하기 : https://deonggi.tistory.com/159
2. docker 컨테이너의 환경설정 : https://deonggi.tistory.com/160

* 환경정보
java 1.8
spring boot 2.4.3
기본 OS : ubuntu 20.04.4 LTS
docker 내 세팅: ubuntu focal, tomcat 8.5.77, selenium-java 3.141.59

1. selenium을 사용하기로 했다.
처음에는 Jsoup을 이용하려고 했다.
기본적인 테스트도 됐지만 조금 더 검색해보니 selenium에 대한 언급이 많았고,
동적으로 작동하는 사이트에 알맞다는 내용들이 있어서 바꾸기로 했다.

 

[크롤링] Selenium을 이용한 JAVA 크롤러 (2) - Jsoup과 비교 (With. Twitter)

2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (1) - HTML 파싱 2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (2) - 파일 다운로드 2020/02/27 - [Back-end/JAVA]..

heodolf.tistory.com


2. 브라우저를 선택한다.
브라우저에 랜더링 되는 내용을 긁어오기 때문에 서버환경에 브라우저와 함께
해당 브라우저의 webdriver가 설치되어야 한다.
당연히 나는 Chrome을 사용하기로 했다. 국내에서도 많이 사용하고 나도 익숙하니 Chrome이 좋다고 생각했다

그러나 이 결정은 서버에 배포하게 되면서 바꿔야만 했다. 내 서버는 Chrome을 설치할 수 없었기 때문이다.
그러므로 나와 같이 당황하고 싶지 않다면
먼저 당신의 서버에 사용하고자 하는 브라우저를 설치할 수 있는지 확인을 해봐야 한다.

나의 서버는 ubuntu를 사용하고 있다. (로컬환경은 windows 10. 나는 역시 한국사람 ㅎ)
Chrome을 사용하고 싶다면 서버가 36비트인지 64비트인지 확인해라.
64비트라면 설치가 가능할 것이다. (36비트여도 설치가 가능하다고 하는데 우선 나는 안 됐으니 알수 없고...)
내 경우 아키텍쳐가 aarch64이다.
ubuntu aarch64에 Chrome 설치를 검색해 봤더니 나오는 건 그냥 Chromium을 설치하라는 것이다.

 

Install Chrome on ubuntu/debian with arm64

I'm trying to install chrome using the commands below: wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - sh -c 'echo "deb [arch=$(dpkg --print-architecture)] h...

askubuntu.com

(그래도 집착을 버리지 못하고 Chrome을 설치하기 위해 많은 시도를 했지만 안 됐다.
만약 aarch64 아키텍쳐라면 고생하지 마시고 갈아 타시길...)
하지만 익숙하지 않은 Chromium이 어떨지 판단이 안되서 ubuntu 서버에 설치되어 있던 firefox를 사용하기로 했다.

3. 이제 코딩을 해보자.
firefox를 사용하기로 했으니 당연히 firefox 브라우저를 설치해야 한다.

1) 그리고 https://github.com/mozilla/geckodriver/releases 에서 브라우저 버전에 맞는 webdriver를 받아준다.
현재 내 PC에 설치된 firefox의 버전은 98.0.2 이다. 그리고 다운로드한 webdriver는 0.30.0 버전이다.


2) 다운로드한 webdriver의 압출을 해제하고 PC에 적당한 위치에 위치시켜 준다.


3) 크롤링 소스는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.TimeUnit;
 
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
 
public class Selenium {
 
    public static void main(String[] args) {
        try {
            String URL = "https://www.tistory.com/category/life";
            runSelenium(URL);
        } catch ( Exception e ) {
            e.printStackTrace();
        }
    }
    
    private static void runSelenium(String URL) throws Exception {
        System.out.println("#### START ####");
        
        // 1. WebDriver 경로 설정
        Path path = Paths.get("C:\\java/driver/geckodriver.exe");
        System.setProperty("webdriver.gecko.driver", path.toString());
        
        // 2. WebDriver 옵션 설정
        FirefoxOptions options = new FirefoxOptions();
        options.addArguments("--start-maximized");          // 최대크기로
        options.addArguments("--headless");                 // Browser를 띄우지 않음
        options.addArguments("--disable-gpu");              // GPU를 사용하지 않음, Linux에서 headless를 사용하는 경우 필요함.
        options.addArguments("--no-sandbox");               // Sandbox 프로세스를 사용하지 않음, Linux에서 headless를 사용하는 경우 필요함.
        options.addArguments("--disable-popup-blocking");    // 팝업 무시
        options.addArguments("--disable-default-apps");     // 기본앱 사용안함
        
        // 3. WebDriver 객체 생성
        WebDriver driver = new FirefoxDriver( options );
                
        try {
            
            // 4. 웹페이지 요청
            driver.get(URL);
            
            // 5. 페이지 로딩을 위한 최대 5초 대기
            driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
            
            // 6. 조회, 로드될 때까지 최대 10초 대기
            WebDriverWait wait = new WebDriverWait(driver, 10);
            String byFunKey = "CSSSELECTOR";
            String selectString = "div#mArticle";
//            String byFunKey = "XPATH";
//            String selectString = "//*[@id=\"mArticle\"]/div[2]/ul/li[3]/a";
            WebElement parent = wait.until(ExpectedConditions.presenceOfElementLocated( 
                    byFunKey.equals("XPATH") ? By.xpath(selectString) : By.cssSelector(selectString) ));
//            System.out.println("#### innerHTML : \n" + parent.getAttribute("innerHTML"));
            
            // 7. 콘텐츠 조회
            List<WebElement> bestContests = parent.findElements(By.cssSelector("div.section_best > ul > li > a"));
            System.out.println"best 콘텐츠 수 : " + bestContests.size() );
            if (bestContests.size() > 0) {
                for (WebElement best : bestContests) {
                    String title = best.findElement(By.cssSelector("div.wrap_cont > strong > span")).getText();
                    String name = best.findElement(By.cssSelector("div.info_g > span.txt_id")).getText();
                    System.out.println("Best title / blog name : " + title + " / " + name);
                    System.out.println("Best url : " + best.getAttribute("href"));
                    System.out.println();
                }
            }
            
            System.out.println("########################################");
            
            List<WebElement> contents = parent.findElements(By.cssSelector("div.section_list > ul > li > a"));
            System.out.println"조회된 콘텐츠 수 : " + contents.size() );
            if( contents.size() > 0 ) {
                for(WebElement content : contents ) {
                    String title = content.findElement(By.cssSelector("div.wrap_cont > strong > span")).getText();
                    String name = content.findElement(By.cssSelector("div.info_g > span.txt_id")).getText();
                    System.out.println("콘텐츠 title / blog name : " + title + " / " + name);
                    System.out.println("콘텐츠 url : " + content.getAttribute("href"));
                    System.out.println();
                }
            }
            
        } catch ( TimeoutException e ) {
            e.printStackTrace();
            System.out.println(e.toString());            
        } catch ( Exception e ) {
            e.printStackTrace();
            System.out.println(e.toString());         
        }
        
        // 8. WebDriver 종료
        driver.quit();
        
        System.out.println("#### END ####");
    }
}
 
cs

이 위 소스 TimeoutException 이 일어 날 수 있는 곳은 49, 60번 라인이니 참고하시길.

(1) 2)에서 위치시킨 webdriver 파일을 31라인에 기록하고 32번에서 해당 webdriver의 키를 적어준다.

(2) 60번 라인에 3항 연산자를 사용중인데, 당신이 태그를 어떻게 선택할 지에 따라 결정하여 사용하면 된다.
이외에 태그명, 클래스명 등 여러 선택방법이 있지만, 위 2가지 방법이 좀더 범용성이 있다.
그래서 나는 상황에 따라 내가 이해하고 사용하기 편한 것을 선택해서 사용 중이다.

 

(3) 100번 라인의 driver.quit()는 꼭 해줘야 한다. 만약 누락하는 경우 아래와 같이 서버가 다운되는 상황을 맞이할 수 있다.

 

만약 selectString에 해당되는 값을 찾는 것이 어렵다면 아래와 같은 방법으로 얻을 수 있다.
① Chrome 브라우저에서 개발자 모드를 연다. (F12)
② Elements에서 커서 모양을 선택(단축키 Ctrl+Shift+C) 하고 화면에서 원하는 태그를 선택한다.
③ Elements에서 선택되어진 태그에서 마우스 우클릭 -> Copy -> 원하는 항목 선택한다.


(3) 크롤링에서 얻고자 하는 것이 태그는 아닐 것이다.
태그에는 여러 정보가 있으므로, 원하는 것이 텍스트인지 링크인지 등을 판단하고 추출해야 한다.
(69번, 72번 라인 소스를 참조)

(4) 그래서 해당 코드의 전체 구조를 간략히 설명하면 아래와 같다.
① 드라이버와 옵션을 선택하고 URL의 페이지를 로딩한다.
② 페이지에서 크롤링 대상 영역의 큰 부분을 먼저 크롤링 한다. (60번 라인)
③ 세부 콘텐츠를 크롤링 한다. (65 ~ 89번 라인)

(4) 실행결과 (크롤링 자체 또는 브라우저 로그는 제거함)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#### START ####
best 콘텐츠 수 : 3
Best title / blog name : 뭐 어쩌라고!? 감자가 감자한 오늘. / 만두감자 랜선집사 in 일본
Best url : https://dumplingj.tistory.com/1048
 
Best title / blog name : 순대볶음 레시피, 들깻가루 이젠 더 이상 필요없어요 / 미자하우스
Best url : https://mija0515.tistory.com/52
 
Best title / blog name : 강아지 중성화 수술 시기, 장단점 모두 알아보고 수술여부 결정하자! / 곤냥마마
Best url : https://gonnyangmama.tistory.com/829
 
########################################
조회된 콘텐츠 수 : 9
콘텐츠 title / blog name : 뿔논병아리 육아 일기 / 행복한 사진 이야기
콘텐츠 url : https://dslrclub.tistory.com/254
 
콘텐츠 title / blog name : 2022년 기본형 공익직불금 신청하기 / 연서의여행
콘텐츠 url : https://lover6126.tistory.com/46
 
콘텐츠 title / blog name : [집밥] 초간단 해장용 된장라면밥 만들기 / 밥집(Bapzip)
콘텐츠 url : https://babzip.tistory.com/1571
 
콘텐츠 title / blog name : [걷기]두루누비 '2022 코리아둘레길 원정대' 및 걷기여행작가 모집해요. / 라온PD와 함께 디지털 노마드
콘텐츠 url : https://raonpd.tistory.com/56
 
콘텐츠 title / blog name : 송도 해수욕장 / 제이제로 다정한 일상
콘텐츠 url : https://wpdlwpfh.tistory.com/330
 
콘텐츠 title / blog name : 떼아와 함께한 커피 타임과 오페라 케이크 / 밀리멜리
콘텐츠 url : https://milymely.tistory.com/570
 
콘텐츠 title / blog name : 딸기쏙우유 찹쌀떡 리뷰 / 현토리의 일상
콘텐츠 url : https://hyuntori83.tistory.com/29
 
콘텐츠 title / blog name : 고창 / stealthily
콘텐츠 url : https://waterpolo.tistory.com/43
 
콘텐츠 title / blog name : 앱테크 추천 개이득과 유사한 출석체크형 앱테크 앱 모음 & 총정리 (1인 개발자 mrtest) / 생활의꿀팁tv
콘텐츠 url : https://livehoneytv.tistory.com/121
#### END ####
cs


(5) 부록 - 스크롤 다운 (수정)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Selenium {
 
    /* 생략 */
    
    private static final Integer WAIT_SEC = 1;
    private static final Integer SCROLL_COUNT = 2;
    
    private static void scrolldonw(WebDriver driver) throws InterruptedException {
//        driver.manage().timeouts().implicitlyWait(WAIT_SEC, TimeUnit.SECONDS);
        Thread.sleep(WAIT_SEC*1000);
        if (SCROLL_COUNT > 0) {            
            for (int i=0; i<SCROLL_COUNT; i++) {
                JavascriptExecutor JavascriptExecutor = ((JavascriptExecutor)driver);
                Long beforeHeight = (Long) JavascriptExecutor.executeScript("return document.body.scrollHeight;");
                System.out.println("브라우저 높이 (before) : " + beforeHeight);
                // 스크롤 다운 후가 전보다 커질때까지 스크롤 다운 반복
                Long afterHeight = 0L;
                while(beforeHeight >= afterHeight) {
//                    driver.manage().timeouts().implicitlyWait(WAIT_SEC, TimeUnit.SECONDS);
                    Thread.sleep(WAIT_SEC*1000);
                    JavascriptExecutor.executeScript("window.scrollTo(0, document.body.scrollHeight);");
                    afterHeight = (Long)JavascriptExecutor.executeScript("return document.body.scrollHeight;");
                    System.out.println("브라우저 높이 (after) : " + afterHeight);
                }
            }
        }
    }
}
 
cs

스크롤 다운 후 로딩되는 항목을 크롤링 해야할 때 유용하다. (5, 6번 라인은 상황에 맞게 결정)
첫번째 크롤링 소스의 52번 다음 라인에서 해당 함수의 호출을 추가하면 된다.

(1) 13, 14번 라인은 브라우저 높이를 구하기 위해 자바스크립트를 실행하는 코드다.
(2) 18~24번 라인은 스크롤 전후 높이 값을 비교하여 후 값이 큰 경우 정상 스크롤 된 것으로 판단하고 스크롤 된 카운트를 증가 시킨다.

(3) 추가 - 9, 19번 라인의 implicitlyWait는 페이지 로딩에 사용하는 것이라 적당하지 않아 Thread.sleep으로 변경했다.

* 참고한 글

 

[Java] 크롤링 crawling, 셀레니움 Selenium

[Java] 크롤링 crawling, 셀레니움 Selenium 웹 크롤링의 정식 명칭은 Web Scraping이며, 웹 사이트에서 원하는 정보를 추출하는 것을 의미한다. 보통 웹 사이트는 HTML기반이기 때문에 정보를 추출할 페이

heekng.tistory.com

 

[크롤링] Selenium을 이용한 JAVA 크롤러 (1) - HTML 파싱

2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (1) - HTML 파싱 2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (2) - 파일 다운로드 0. 서론  이전 포스트에서 Js..

heodolf.tistory.com

 

[Python] Selenium 웹페이지 스크롤하기 scrollTo, Scroll down

Python 의 selenium 을 이용해서 스크롤 하기 크롤링 할 때 웹페이지를 스크롤 다운해야하는 경우가 있죠. 스크롤다운해서 끝까지 가야 그 다음 데이터를 조회하는 경우가 있고 그 외에도 필요한 경

hello-bryan.tistory.com


4. 크롤링 소스 서버 배포 에러
처음 배포할때 멋도 모르고 로컬 windows에서 처럼 사이트에서 webdriver 파일을 받아 원하는 위치에 넣고
그 폴더의 파일을 지정해 주면 되는 줄 알았다. 이 착각이 내 큰 고난의 이유 중 하나다.

먼저 난 windows에서 처럼 webdriver 파일을 받아 원하는 위치에 넣었다.
서버는 돌아가는데 크롤링을 하면 아래와 같은 에러가 나온다.
* 참고 : /drivers/firefox/geckodriver 는 내가 webdriver 파일을 배치한 위치

1) gradle selenium-java 4.1.2 version 사용시

1
2
3
4
5
6
7
/drivers/firefox/geckodriver: 1: ELF: not found
/drivers/firefox/geckodriver: 2: Syntax error: ")" unexpected
20220317 17:47:13.083 [http-nio-8091-exec-6] ERROR c.d.b.c.a.ExceptionControllerAdvice - 
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: org/openqa/selenium/internal/Require
.
.
.
cs

2) gradle selenium-java 3.141.59 version 사용시

1
2
3
4
5
6
7
8
9
10
/drivers/firefox/geckodriver: 1: ELF: not found
/drivers/firefox/geckodriver: 2: Syntax error: ")" unexpected
20220317 17:59:14.320 [http-nio-8091-exec-6] ERROR c.d.b.c.a.ExceptionControllerAdvice - 
org.openqa.selenium.WebDriverException: java.net.ConnectException: Failed to connect to localhost/127.0.0.1:3165
Build info: version: '3.141.59', revision: 'e82be7d358', time: '2018-11-14T08:17:03'
System info: host: 'server02', ip: '127.0.1.1', os.name: 'Linux', os.arch: 'aarch64', os.version: '5.13.0-1021-oracle', java.version: '11.0.14'
Driver info: driver.version: FirefoxDriver
.
.
.
cs


구글을 검색해도 나오는 것이라곤 버전을 내려라. 버전을 브라우저에 맞춰라. 등등인데...
나의 경우는 근본적으로 버전 이전에 ubuntu에 firefox에 대한 webdriver를 설치하지 않았다는게 문제였다.
아주 우연히 거의 포기 직전에 찾은 정보인데 webdriver를 설치하고 위치를 지정하라는 것이다.

1
2
3
4
# firefox webdriver 설치
apt-get install firefox-geckodriver
 
# 설치된 webdriver 위치 : /usr/bin/geckodriver
cs

* 출처 : https://ubuntu.pkgs.org/20.04/ubuntu-updates-universe-arm64/firefox-geckodriver_99.0+build2-0ubuntu0.20.04.2_arm64.deb.html

그리고 배포하니 위 1)번의 nested exception is java.lang.NoClassDefFoundError: org/openqa/selenium/internal/Require 에러가 나시 나타났고, (1,2번 라인 에러 없이)
그동안의 검색으로 얻은 정보를 이용해 gradle selenium-java 3.141.59 버전으로 변경하니 크롤링이 실행 되었다.


이상으로 selenium을 사용하며 내가 겪었던 일들이다.
나도 전문가는 아니기에 여기 저기서 조각조각 모아 결국 반영한 내용이다.
부디 나 이후에 하실 분들은 나 같은 삽질을 안 하시길... 빌어본다. '') (먼산)

 

 

* 관련 글 : [java, Jsoup] 크롤링으로 본문 가져오기

 

 

728x90
반응형
728x90
반응형

예전에 써봤지만 기억나지 않아 살짝 삽질한...

내가 기억하기 위해 남기는 메모.

.....

1. java코드 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* #### ReqDTO.java (DTO class) ####*/
public class ReqDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer dmnSeq;
    private String expt;
    private String prscYn;
    private String delYn;
}
 
/* #### Service.java #### */
List<ReqDTO> insertList = new ArrayList<>();
for () {
    // insert할 DTO를 작성함
}
// mapper 호출
Integer cnt = mapperClass.insertTable(insertList);
 
/* #### Mapper.java (interface) #### */
public Integer insertTable(List<ReqDTO> reqDTO) throws Exception;
 
cs

 

2. mybatis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<insert id="insertTable" parameterType="com.packagename.dto.ReqDTO">
    INSERT INTO TABLE_NAME
        (
        DMN_SEQ
        , EXPT
        , PRSC_YN
        , DEL_YN
        )
    VALUES
        <foreach collection="list" item="item" index="index" open="" separator=", " close="">
           (
           #{item.dmnSeq}
           , #{item.expt}
           , #{item.prscYn}
           , #{item.delYn}
           )
        </foreach>
</insert>
cs

 

728x90
반응형

+ Recent posts