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

Windows10에서 C 드라이브 디스크의 활성시간이 100% (지금도 이해 안 되는데 시간인데 왜 %야?)이 일어났다.

그래서 나는 지난주 토요일 새벽에 SSD가 너무 노후하여 일어난 것이라고 결론을 내렸다.

(이전글 : https://deonggi.tistory.com/167 )

 

사실 내가 이번에 PC를 바꾸게 된 결정적인 이유는 사용하던 PC를 더 이상 사용하기에 무리가 있었기 때문이다.

하지만 지난 몇년간 여름마다 계속해서 노트북을 살까? 라는 병이 주기적으로 도졌다.

*********************************** 여기부터는 PC를 바꾸고 싶은 변명 ***********************************

너무 더운 와중에 쿨러가 열심히 열기를 뿜지만, 오래되서 성능 떨어지는 데스트탑 보다는

노트북을 쓰고 싶었던 거다. (생각해보면 이 논리 좀 이상한거 같다. 노트북은 덜 뜨겁냐?)

그리고 데스크탑이 창문 근처에 있는데 정말 더울때는 방법이 없다. 그냥 꺼야 한다.

게다가 쓰던 데스크탑은 성능이 떨어져서 못쓰는 최신 프로그램들이 있었다.

************************************************** 변명 끗 ****************************************************

 

이성을 챙기고...

SSD가 원인이라면 SSD 교체에 10만원 정도만 쓰면 해결될 것이다.

하지만 이 방법은 결정적인 문제가 있는데, 이 전제가 근본적으로 맞냐는 것이다.

그리고 이 데스크탑은 이미 구형이라 조만간 바꿔야 할 가능성이 크다.

설사 SSD를 교체해서 당장의 문제가 해결된다고 해도

잠제적으로 PC 전체를 교체해야 하는 상황을 1,2년 정도 미루는 결과가 될 가능성이 농후하다.

 

게다가 SSD가 요즘 많이 바뀌었다.

나도 잘은 모르나 예전의 2.5인치 사이즈의 옛날 HDD 디스크 모양이 아니라 메모리 처럼 생긴것을 많이 사용하고 있다.

 

행복쇼핑의 시작 ! 다나와 (가격비교) - Danawa.com

 

prod.danawa.com

 

행복쇼핑의 시작 ! 다나와 (가격비교) - Danawa.com

 

prod.danawa.com

뭘 사야 하는지 모르겠다 =_=;;;

 

친구의 경우 SSD를 바꿨더니 파워를 바꿔야 했고, 파워를 바꾸니 메인보드를 바꿔야 하고.....

그래서 결국 전체를 바꿔야 하는 상황에 도래했다는 무서운 경험담이 있다.

나라고 해서 과연 다를 수 있을까...?

그래서 그 친구의 조언은 '그 스트레스를 모두 겪느니 그냥 PC를 바꿔라' 였다.

 

그래서 PC를 새로 사기로 하고 생각한 나의 구상은 이렇다.

1. 전체 예산은 200만원 이하로 한다.

2. 가성비 게이밍 노트북으로 최대 170만원 가량의 노트북을 선택한다.

성능면에서 게이밍 노트북을 원했던 건 간단한 이유다.

게임을 많이 하는건 아니지만 할 가능성이 있고, 대단하지 않지만 영상편집을 하는데

기존 데스크탑은 성능이 떨어져서 흔히 사용하는 툴을 실행할 수 도 없었다.

최상이 아니더라도 흔히 사용하는 툴을 설치해서 사용하고 싶었다.

 

나는 이런 하드웨어 쪽으로는 아는 정보도 부족하고 해서 다나와에서 가격대로 검색해 봤다.

그리고 텍스트 리뷰들과 동영상 리뷰를 보고 마음에 드는 녀석으로 선택했다.

 

선택한 노트북의 단점은 무게!!! (두둥)

 

그래.. 너무 많은걸 바라지 말자.

어자피 데스크탑도 들고 다닌거 아닌데 왜 노트북은 들고 다니려고 하나.

예전에 2키로 노트북도 허리 아프다고 징징 댔으면서...

노트북은 들고 다니는게 아니다. 짱 박아 놓은거다.

응...?

 

3. (미실행 계획) 메모리를 업그레이드 한다.

예전의 계획으로는 메모리를 업그레이드 하려고 했다. 다다익램이라고 하지 않았던가!!

근데 요즘은 또 꼭 다다익램이 아니라고 한다.

* 다다익램이 아니라는 어느 유튜버의 영상 : https://youtu.be/qVrCeAYiQ5Y

내가 사용하는 환경에서 검토해보고 메모리가 꼭 필요한 가를 보고 판단하면 된다고..

원래라면 32G로 업그레이드 하라고 했으나 꼭 노트북을 살때부터 업그레이드가 필요한 것도 아니고

필요하다면 나중에 하면되니 그냥 패스하기로 했다.

 

별도로 덧붙이자면 난 디스크 용량에 욕심이 없다. 

쓰던 데스크탑 C의 SSD도 128GB로 잘 썼다. (쓰다가 안 되겠다 싶으면 안 쓰는거 지우면 되는거지...)

 

4. (미실행 계획) 살아있는 디스크들은 그래도 계속 사용해야 하므로 DAS를 구매한다.

멀쩡한 HDD 2TB가 하나 있다. 이걸 2년전에 백업용으로 구매해서 사용하던 녀석이다.

이건 다른곳으로 백업도 못한다. 동영상이 너무 많다! (당신이 상상하는 스포츠 영상 아니다!! 오해하지 마시라!)

그리고 이런 디스크를 로컬처럼 쓰고 싶은데 노트북에 연결하는 방법이 필요했다.

그래서 DAS라는게 있다는걸 알고는 사려고 했다.

 

그런데 생각을 해보니 DAS를 굳이 사야하나...??

웹서핑이 힘들어서 그렇지 기존의 데스크탑이 잘 돌아가고 있다.

그래서 데스크탑을 우선 살려서 서브컴으로 쓰고, 이녀석을 네트워크 드라이브와 터미널로 연결해서 사용할 수 있다.

이녀석의 SSD가 사망하면 그때가서 생각해 보면 될 문제다.

 

* 구글링 하다 찾은 저장장치에 대한 좋은 글(4부까지 있음) : https://blog.naver.com/dummy_98/222295756269

 

HDD 오해와 진실 그리고 선택 - 1부(불량율)

많은 사람들이 하드웨어 포럼에 와서 "어느 HDD가 좋냐"는 질문을 수없이 던진다. 그때 마다 ...

blog.naver.com

 

5. 노트북을 편하게 쓰기 위해 거치대가 필요하다.

현대인 모두의 병 거북목, 허리 디스크.... ㅎㅎ 나도 있다. 그래서 거치대가 필요했다.

 

아래는 노트북 거치대에 대한 리뷰다. 나는 개인적으로 도움이 됐다.

* 거치대를 많이 써본게 아닌데 거치대를 구매할 생각이라면 한번 보는걸 추천드림.

 

그래서 다나와에서 노트북 거치대를 검색 해보았고 마음에 든 거치대가 이었는데

노트북이 일요일에 오는데 이녀석이 수요일에 온다는 거다.

그래서 어.. 이거.. 왠지 짜증나.... (이 모든 스트레스 상황을 한큐에 끝내고 싶은데 안 된다고?)

하지만 이게 어떤면에서는 합리적인 고민을 하게 해 주었다.

 

1) 그래서 구매한 거치대는?

 - 나의 구매가격 : 가격 27,800 + 배송비 3,000 = 총 30,800원 (현재는 5만원대로 검색됨)

 

darkFlash DLT21 알루미늄 접이식 노트북 스탠드 : 다나와 가격비교

컴퓨터/노트북/조립PC>노트북>노트북 주변기기, 요약정보 : 받침대/쿨러 / 받침대 / 호환 크기: 43.18cm(17인치대) / 높이,각도 조절 가능 / 크기 : 240 x 251 x 41mm / 무게 : 588g

prod.danawa.com

 

2) 이 거치대를 선택하게 된 이유

내 노트북의 사이즈와 하판 디자인은 이러하다.

 

그래서 내가 이런 정보를 종합하여 원한 조건은 아래와 같다.

 

(1) 사이즈

위 사진의 노란색 점선박스는 흡입구로 쿨링에 영향을 준다.

그러므로 쿨링을 생각한다면 저 부분을 최대한 활용할 구조여야 했다.

개인적으로는 안정적으로 사용하기 위해 가로가 넓으면 좋겠다고 생각했지만 내가 원하는 형태로는 찾을 수 없었다.

그래서 생각한 노트북이 거치되는 부분의 이상적인 사이즈는 227*240mm 이상이어야 하고,

흡입구 부분이 최대한 뚤려 있는 형태이길 바랬다.

 

(2) 노트북이 안정적으로 고정되도록 필요한 위치에 패드

노트북을 올려 두었을때 안정적으로 올려둘 수 있도록 고무 같은 패드가 적당한 위치에 필요했다.

당연히 거치대가 노트북에 직접적으로 닫는 모든 부위에 있어야 한다.

(왠만하면 이건 거의 다 되어 있는듯.)

 

(3) 디자인과 내구성, 마감이 중요했다.

디자인은 위에 언급한 노트북을 올려두는 부분 외에 2단 형태로 밑에 스탠드 부분이 납작한 형태를 원했다.

2단 이어야 하는 이유는 적당한 높이 때문이고, 스탠드 부분은 책상의 공간 활용 때문이다.

거치대를 놓더라도 책상 공간은 가능한 넓게 사용하길 원했기 때문이다.

 

그리고 내구성은 당연히도 노트북이 올라가는데 흔들흔들하다면 불안하지 않은가!

흔들리지 않고 적당히 고정해 줄수 있어야 했다.

구매한 거치대는 생각보다 짱짱한 내구도다.

처음 설치하려고 피는데 '엇! 이거 뭐 풀어줘야 하나?' 싶을 정도로 딴딴하다.

현재로서는 각도를 조절해야할 생각이 전혀 없으니, 아마도 10년이 지나도 저 딴딴함은 계속 될거라고 추측해 본다.

 

깔끔한 마감은 책상위에 올라가는 물건인 지라 손이 왔다갔다 할 구역이기 때문이다.

여기저기 물건 찾거나 만지다가 거치대에 손을 긁히고 싶지 않았다.

게다가 잘못해서 노트북에 상처가 남기는 불상사를 원치 않았기 때문이다.

물건을 받을때까지 이걸 좀 걱정 했는데 생각보다 아주 깔금하다.

 

(4) 추가로 이건 생각하지 못 했던 것인데 노트북을 거치했을때 흘러내리지 않도록 걸쳐 주는 부분의 높이다.

(음.. 정보에 안 써있네. 내가 구매한 제품은 대략 10mm 쯤 된다.)

다행히도 구매하고 나서 사용하다보니 만족한 부분인데...

노트북 하부 두께 보다 살짝 작은 정도면 적당한 것 같다.

이게 하부 두께 보다 크면 노트북을 닫았을때 잘못하면 모니터 부분이 닿아서 망가질 수 있기 때문이다.

 

음? 이거 리뷰가 되어 버렸네.

돈 받은거 없음. 내돈내산.

(5) 단점 : 아래 스탠드 부분이 넓지 않아서 두손으로 조심스럽게 노트북을 열어야 한다.

전체적으로 무게감이 있어서 사용하고 있는 중에는 전혀 영향이 없는 것 같다.

다만 스탠드 부분이 노트북 보다 좁고 높게 올려졌다면 노트북을 열때 조심해 줘야한다.

 

그래서 저의 의견이요? 전체적으로 만족중입니다.

 

6. (희망사항) 들고 다닐때 허리가 아프지 않게 가벼운 거였으면 좋겠다.

이미 게이밍 노트북 + 가성비에서 불가능한 조건이다. ㅎㅎㅎㅎㅎㅎㅎㅎㅎ

그럼에도 희망사항이며 언젠가 이뤄지길 간절히 바래본다.

 

여기까지가 PC를 교체하기 위한 나의 노력의 기록이다. 끗 ~

 

## 모든 폭풍이 지나고 난 현재의 후기 ##

 

1. 이번에 노트북을 구매하면서 PC사용 패턴을 바꿔야만 했다.

나는 개인 PC로서는 이번이 첫 노트북이다.

데스크탑을 오래 사용해온 사람이라면 잘 알거다.

C는 언제나 날아갈 수 있다. 너의 데이터를 지키고 싶다면 파티션을 나눠야 한다! (어머! 나 정말 올드한 사람인가봐....;;)

근데 요즘은 또 그렇지 않다고 하니...;;; 살짝 불안한 마음을 가졌는데....

새로 SSD를 구매할 생각이 없으니 생각을 고쳐 먹었다.

디스크는 언제든지 사망할 수 있다는 사실은 어떤 것 이어도 변함은 없다.

(하드웨어 적인 고장이라면 파티션 나누기만으로 자료 보존을 장담할 수 없다.)

만약 사망할때를 대비해 다른 백업방법을 생각해 두어야 할 것 같다.

 

2. 아 역시 돈질은 이길 수가 없다. 

돈이면 이런 쾌적한 상황이 가능하구나...

성능에 감격

 

3. 1주일 가까이 PC 문제로 에너지를 썼더니 너무 피곤하다.

이제 슬슬 적응해서 일상으로 돌아가야지....

이 글을 끝으로 당분간 좀 즐겨보자..

728x90
반응형
728x90
반응형

지난 10일 전쯤 갑자기 PC가 미친듯 블루스크린을 띄웠다.

어쩌다 한번 뜨는 경우는 있었어도 연속으로 5번 정도 일어나니... '아.. 뭔가 큰일이로군'이라는 생각을 하게 되었다.

 

이건 이번에 겪은 블루스크린 들이다.

이런 정겨운 블루스크린....

어쩜.. 동일한 에러가 하나도 없냐?

디스크 검사, 복원 등을 해서 5일 간은 어떻게 돌아는 갔다. 도저히 못 써먹을 정도는 아니었으니까..

그런데 지난주 목요일 부터 갑자기 브라우저를 열어 어떤 페이지를 방문하면

C 드라이브(SSD) 디스크 활성 시간이 100%를 찍고는 잠시 먹통이 된다. (활성율 인줄 알았다. 근데 왜 %로 표시하지??)

 

아니 요즘 같은 세상에

브라우저 안 되는 PC를 어따 쓰라고!

포맷해야 하나??

시른데.. 귀찮은데..

 

그래서 나는 목금 2일간 미친듯한 PC 살리기 심폐소생술을 시도했다.

 

1. Windowns10 설정 변경 또는 최적화

윈도우 재설치(삽질을 포함해 총 3번쯤?), 알림 끄기, 빠른 실행 끄기, 서비스 끄기, 디스크 관리 어떤 기능 끄기.. 등등

구글에 검색해보면 여러가지 나온다. 검색해서 나오는 모든 방법을 다 해본 것 같다.

나는 처음 겪는 문제였고 어떻게 해결하면 된다는 확실한 솔루션 따위는 없었다.

Windows10으로 넘어오면서 발견된 문제라고 하는데... 어떤 설정으로도 내 문제가 해결되지 않았다.

이것들을 모두 시도해 보는데 거의 10시간 이상을 쓴 것 같다.

2. 브라우저 최적화

내게 발생된 문제의 특징이 브라우저로 어떤 페이지를 방문하면 발생했다.

(특정 페이지들 이었는데 언제나는 아니고, 그 페이지에서 빈도가 높음. 예를들면 오라클 클라우드 페이지.)

그래서 처음에는 크롬이 문제인가 싶어 크롬을 최적화 하는 방법들을

닥치는데로 검색해서 다 적용해 봤지만 해결되지 않았다.

게다가 엣지에서도 발생하므로 브라우저를 바꾼다고 해결될 상황이 아니었다.

이것도 한 5시간쯤 이상은 해봤나?

 

3. 그럼 이제 SSD가 문제인건가?

뭔가 SSD에 불량이 생겼나? 사망각인가? 싶어 흔히들 많이 사용하는 CrystalDiskInfo로 확인 해봤는데 정상이다.

상태 수치가 좀 낮았지만 분명 정상이다.

근데 스샷을 보면 알겠지만 이 녀석 그만 장례를 치뤄줘야 할 만큼 연로하신 녀석이다.

 

 

삼성전자 840 PRO Series (128GB) : 다나와 가격비교

컴퓨터/노트북/조립PC>PC주요부품>SSD, 요약정보 : 내장형SSD / 6.4cm(2.5형) / SATA3(6Gb/s) / 삼성 MLC(토글) / DDR2 256MB / 컨트롤러: 삼성 MDX(트리플코어) / [성능] / 순차읽기: 530MB/s / 순차쓰기: 최대 390MB/sMB/s

prod.danawa.com

나온지도 벌써 10년 되어가는 녀석으로 툴로 확인되는 사용시간이 어마어마 하다.

대충 하루 8시간 매일 사용으로 계산해도 8년 나온다.

그러므로 이젠 이녀석을 그만 학대 해야할까보다..

 

덧1. SSD도 펌웨어라는게 있다. 아시는가? 나는 몰랐다.

BIOS 펌웨어가 있는건 알았지만 SSD도 있더라... (제조사의 홈페이지를 확인하시도록~)

혹시나 해서 이것도 해봤지만 역시 효과는 없었다.

 

덧2. SSD 공장 초기화 해보라는 내용도 있었다.

이걸 해볼려고 시도하다 내용을 제대로 안 읽어서 Windows10 설치 USB를 날려 먹었다.

 

끄아~악

내가 서브컴이 있는것도 아니라서 당시 완전히 멘붕...

어찌어지 Windows를 다시 설치 했으나 역시나 해결되지는 않았다.

 

덧3. 안드로이드로 Windows10 설치 USB를 만들 수 있는 방법이 있다고 한다.

내 폰은 SD카드를 꼿을 수도 없고, 연결할 방법이 없어서 시도해 보진 않았다.

다만 하는 방법이 있다고 하니 나와 같이 USB를 날려 먹었다면 참고 하시길....

(해본적 없으므로 책임질 수 없다 ㅎㅎㅎ)

 

4. HDD에 windows10설치

이젠 확실히 SSD가 의심스럽다.....

그럼 SSD가 문제라고 확인 사살하기 위해 확인할 방법은 남은 디스크에 Windows를 설치해 보시는 건데...

난 HDD밖에 없다........ 어우 다시 생각해도 숨막혀......

인고의 시간을 지나 HDD에 Windows10을 설치 봤는데... 역시 범인은 SSD인 가보다.. ㅠㅠ

 

이 결론에 오기까지 장장 2일의 시간을 날렸다.

그래서 결국 나는 새로운 SSD인가 아니면 새로운 PC를 구입하는가에 대한 기로에 놓이게 되었고

친구의 조언에 따라 나는 새로운 PC구입으로 결정했다.

 

 

다음글에 이어서 : https://deonggi.tistory.com/168

 

728x90
반응형
728x90
반응형

문제 발생일 : 2022-06-10 13시 경

 

결론 부터 말하자면 ubuntu update시 문제로 보여진다.

 

나의 경우 정확히는 host 우분투에서 발생한 것은 아니다.

docker 컨테이너로 ubuntu를 실행 중 필요해서 서버를 리부팅하고 다시 컨테이너를 실행하려고 했더니 에러가 나는 상황.

한번 이미지를 만들어 놓으면 별다른 업데이트를 하지 않기 때문에 내가 업데이트를 진행한 케이스는 아니다.

내가 모르는 어떤 설정에 의해 컨테이너의 재시작 시 ubuntu가 자동으로 업데이트가 된 것으로 보인다.

(이미지에 update가 들어가면 재시작시 자동으로 update를 하나? ㅡㅡㅋ )

 

# 에러 메시지 =================================================================

Message from syslogd@server at Jun 10 13:32:35 ...
 kernel:[  129.502235] Internal error: Oops - BUG: 0 [#1] SMP

Message from syslogd@server at Jun 10 13:32:35 ...
 kernel:[  129.654261] Code: 91059283 52800020 1400016f 17ffffa0 (d4210000)
...

============================================================================

 

* 참고정보 1

 

서버포럼 - Oracle Cloud A1 서버 apt upgrade 실행 하지마세요 - 추가) 커널 업그레이드????

++ 추가 ++ 부트볼륨 백업을 적용 했다가 계속 이상해서 새로운 마음으로 다시 설치 중입니다. 아래 메시지가 뜨면서 커널 업그레이드 하네요. 기존 버전에서 매끄럽게 업그레이드가 안되는거 같

svrforum.com

 

* 참고정보 2 - 오라클 포럼 게시글 : https://community.oracle.com/customerconnect/discussion/634302/kernel-panic

포럼에 로그인을 안 하면 내용이 다 보이지 않는다.

요약하면 최근에 ubuntu를 업데이트 한 경우에 발생된 것으로 보여진다.

 

위 글들을 읽고 한참 후에야  '아~ docker의 ubuntu가 문제로 구나!' 라고 생각하고

다른 ubuntu 이미지를 받아 컨테이너로 실행해보니 다 동일한 에러가 나타나고 있다.

 

두개의 글 모두 최근 3일내이고, 구글 검색으로 확인되는 정보도 모두 3일내 정보다.

또한 docker hub의 ubuntu 최종 업데이트 일이 3일 전인 걸로 봐선.........

 

 

다행히도 당장 살려놔야 할 정도의 서버는 아닌지라 급하진 않은데...

이런 상태가 지속된다면 다른 OS로 갈아타는걸 생각해야 겠다.

 

 

220704 덧,

해결방법이 있다는 글이 있어 확인해 보려고 했으나 실수로 시도하지 않고 그냥 docker를 다시 살렸는데 잘 된다.

해결 방법이라는 글은 아래 글. (난 적용 안함.)

 

kernel:[ 15.642879] Code: 91059283 52800020 1400016f 17ffffa0 (d4210000) - Error when booting (Oracle cloud instance)

When I boot on my oracle cloud instance (running on the lastest ubuntu), since I installed the last update, my machine show me the error : Message from syslogd@ampere at Jun 9 18:45:43 ... kernel...

stackoverflow.com

그래서 내가 왜 해결된 건지, 뭔가 다른 패치가 된건지 잘 모르겠다.

그래도 혹시 몰라서 host는 업데이트를 안 하고 있다.

나의 경우 서슴없이 테스트 할 수 있었던 것도 어디까지나 docker 환경이기 때문이므로 아직은 쉽게 추천할 수 없다.

 

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
반응형

Integer인 각각의 A, B의 값이 같은지 비교하여, 다른 경우 C의 값을 2로 변경하는 코드가 있다.

그런데 A, B 모두 200 이었는데 C 값이 변경이 2로 변경된게 발견 되었다.

동일한 Integer이고, 둘다 200인데 왜 내 의도와 다른 결과가 나왔을까?

 

디버깅을 해봐도 분명 200으로 같은 값인데 뭐가 문제인가 싶어 검색을 해보니

두개의 Integer의 비교 시 == 또는 != 사용시 변수의 주소값을 비교하기 때문 이었다.

 

아래는 이를 보여주는 단순한 예시코드다

* 예시코드

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 TestCode {
    
    public static void main(String[] args) {
        
        Integer integerA = 1;
        Integer integerB = 1;
        Integer integerC = new Integer(1);
        int intA = 1;
        int intB = 1;
        
        System.out.println("integerA : " + integerA);
        System.out.println("integerB : " + integerB);
        System.out.println("integerC : " + integerC);
        System.out.println("intA : " + intA);
        System.out.println("intB : " + intB);
        System.out.println("#################");
        System.out.println(integerA == 1);
        System.out.println(intA == 1);
        System.out.println(integerA == integerB);
        System.out.println(integerA == integerC);
        System.out.println(intA == intB);
        System.out.println(integerA == intA);
        System.out.println("#################");
        System.out.println(integerA.equals(1));
        System.out.println(integerA.equals(integerB));
        System.out.println(integerA.equals(integerC));
        System.out.println(integerA.equals(intA));
    }
}
cs

* 출력된 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
integerA : 1
integerB : 1
integerC : 1
intA : 1
intB : 1
#################
true
true
true
false
true
true
#################
true
true
true
true
cs

분명 같은 1을 갖고 있음에도 소스코드 20번 라인의 실행결과는 false로 나오고 있다.

그러므로 Integer 값에 대해 동일여부를 확인할때 == 또는 !=를 사용하고 있다면, 

내 의도와 다른 결과가 나올 수 있으므로 equals을 사용하자.

(더 곤란한건 exception도 없이 소스는 잘 돌아간다.)

 

 

추가로,

사실 이 이슈는 단순히 주소값 비교로 설명하면 안 된다.

Primitive Type, boxed Primitive Type, wrapper class 라는 개념에 관련된 내용이다.

이 내용을 설명하기에는 너무 길어지니 자세한 내용은 구글 검색을 추천!

728x90
반응형
728x90
반응형

시작은 단순하다.

실행중인 tomcat 서버의 로그는 계속해서 쌓여만 갔다.

특히 문제는 catalina.out 파일이 용량이 계속 늘어가고 있다는 것 이었다.

이렇게 방치하고 무한정 둔다면 언젠가 서버가 사망하게 되리라...

 

목표는 2가지다.

1. catalina.out 파일을 날짜별로 나눈다.

2. 일정기간 이상 오래된 파일은 삭제한다.

 

이걸 위해 검색해 본 결과 tomcat설치폴더/bin/catalina.sh 파일을 수정하라는 것 이었다.

근데 왠지 나는 실행하면 에러가 나니 검색된 내용을 반영할 수가 없었다.

또한 rotatelogs를 이용하라는데 나는 설치되어 있지 않다.

 

그래서 선택한 방법은 터미널 명령어와 cron이다.

 

1. 파일 관리를 위한 delete_log.sh 파일을 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# tomcat log folder location
TOMCAT_LOG=/tomcat/logs
 
# get today date
DATE="$(date '+%Y-%m-%d')"
 
# back up 'catalina.out' files by date
cp "$TOMCAT_LOG/catalina.out" "$TOMCAT_LOG/catalina.out.$DATE"
 
# catalina.out file reset
cat /dev/null > "$TOMCAT_LOG/catalina.out"
 
# delete file that is 30 days old
find "$TOMCAT_LOG" -mtime +30 -type f -exec rm -f {} \;
cs

내가 참고한 글에서는 5번 라인에 DATE='date +%Y_%m_%d' 라고 되어 있었다.

그러나 실행시 cp: target '+%Y-%m-%d' is not a directory 라는 에러가 났고, 위와 같이 수정 했더니 해결 되었다.

 

(1) 2번 라인은 당신이 tomcat을 설치한 위치로 변경해야 한다.

(2) 1~11번 라인까지는 catalina.out 파일을 오늘 날짜로 백업하고 기존 파일은 초기화 한다.

(3) 14번 라인은 30일 이상된 log 파일을 삭제한다

 

2. delete_log.sh 파일을 배치 시킨다.

이 파일을 원하는 위치에 배치한다. 예를들면 /home/ubuntu 폴더 아래에 둔다.

그리고 실행할 수 있도록 권한을 준다.

1
chmod 744 /home/ubuntu/delete_log.sh
cs

권한을 준 후에 'bash /home/ubuntu/delete_log.sh'를 실행하면

위에 작성한 delete_log.sh의 내용이 현재 시간 기준으로 진행되는 것을 확인 할 수 있다.

 

3. cron을 사용해서 delete_log.sh를 실행할 스케줄을 만들어 준다.

(1) 먼저 해야할 일은 cron을 설치하고 활성화 하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
# cron 설치
apt install -y cron
 
# cron 시작
service cron start
 
# cron systemctl 활성화
systemctl enable cron.service
 
# cron systemctl 등록 확인
systemctl list-unit-files | grep cron
cs

(만약 systemctl 명령어를 찾을 수 없다는 에러가 난다면 'apt install -y systemctl' 를 통해 설치)

 

(2) crontab 편집

1
crontab -e
cs

위 명령어를 입력해서 편집 화면으로 들어간다.

그리고 아래 내용을 가장 하단에 넣고 저장하여 종료한다.

1
2
3
#매일 23시 55분에 터미널 명령어 파일 실행
#분   시   일   월   주   명령어
55    23   *    *    *    /home/ubuntu/delete_log.sh
cs

(3) cron 재시작

1
service cron restart
cs

 

4. 부록 - cron 실행시간 이상

4-1. docker에서

위에 crontab 편집으로 입력한 정보에 따르면 매일밤 23:55에 delete_log.sh 파일이 실행 되어야 한다.

나의 경우 원래 머신의 시간은 런던시간이고, 최초에 서버를 세팅할때 타임존을 한국시간으로 변경하여 사용중이다.

또한 다른 이유로 인해 docker의 시간 역시 한국의 타임존으로 설정해 사용하고 있다.

그런데 cron 실행시간이 런던시간으로 실행되는 것을 확인했다.

검색해 나온 정보들을 적용해 보려했지만 난 잘되지 않았다.

어떻게 해야하나 고민을 했는데 사실 생각해보면 단순하게 해결이 가능하다.

특별한 관리상의 이유가 있는게 아니라면 crontab 편집에서 23시라고 입력한 것을 14시로 입력하면 되는거다.

4-2. docker 없이 ubuntu에서

모종의 이유로 인해 다시 docker의 사용을 멈추고, ubuntu에 tomcat을 사용하고 있으며,

위 설정대로 적용하고, 23:55에 실행되도록 적용했다.

그런데 놀랍게도 이건 또 내가 원하는 한국시간으로 적용되고 있다.

 

* tomcat 관련글

[tomcat] memory leak 에러 : https://deonggi.tistory.com/26
[Tomcat] 실행에러 : https://deonggi.tistory.com/76
[Tomcat] java.lang.IllegalArgumentException... : https://deonggi.tistory.com/77
[tomcat] war 배포하기, 가상 디렉토리 : https://deonggi.tistory.com/126
docker 컨테이너로 tomcat 실행하기 : https://deonggi.tistory.com/159

 

* 참고한 글

 

[Tomcat] catalina.out 파일 날짜 별 백업 및 오래된 로그파일 삭제 (ShellScript, Crontab)

1. Shell Script 파일 생성 [root@hostname ~]# cd /home [root@hostname home]# vi delete_log.sh 2. delete_log.sh에 아래 내용 입력(위치는 예시임) #톰캣 로그 디렉토리 TOMCAT_LOG=/home/tomcat/logs #오늘..

page-view.tistory.com

 

Crontab 설치 및 사용법

Crontab installation and Usage Crontab 설치 - CentOS # cron 설치 sudo yum update -y sudo yum install -y cronie # cron 시작 sudo systemctl start crond # cron systemctl 활성화 sudo systemctl enable cr..

blog.secuof.net

 

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
반응형

이전글에서는 docker의 컨테이너 설치부터 삭제까지 간단히 다뤘다.

이번엔 내가 docker를 사용하면서 필요했던 설정에 대해서 적어보고자 한다.

 

이 글의 주요 내용은 아래와 같다.

1. 여러 컨테이너의 서버자원 사용을 관리하고 싶다.

2. 컨테이너는 host에 설치된 DB에 연결하고 싶다.

3. 컨테이너에서 업로드 또는 다운로드 되는 파일은 host 영역에 배치하고 싶다.

번외1. docker를 이용하며 발생한 이슈 : 서버시간, 한글

번외2. log파일을 관리해야 겠다.

번외3. docker file을 만든다.

 

1. 여러 컨테이너의 서버자원 사용을 관리하고 싶다.

내 운영서버는 1대로 dev, prod, 컨테이너 관리를 위한 portainer를 함께 사용한다.

이 말은 각 컨테이너의 자원을 할당해야 한다는 의미다.

아무래도 prod가 더 많은 일을 할 것이고, 문제없이 서비스 되어야 하므로 다른 컨테이너들이 자원을 사용하는 것을 제한해야 한다고 생각했다.

docker stats 명령어를 통해 현재 실행중인 docker 컨테이너의 자원 사용상황을 확인할 수 있다.

자원 할당전

 

나는 그래서 prod외 컨테이너들의 cpu, memory 각 10%로 제한하기로 했다.

(아래 방법은 실시간으로 변경하는 방법이다.)

docker update --memory 용량 --memory-swap 용량 컨테이너명 : 컨테이너의 메모리 사용은 2.4g로 제한했다.

docker update --cpus=갯수(또는 비율) 컨테이너명 : --cpus에 정수인 경우 CPU의 코어 갯수이고, 1 미만의 실수를 입력하면 비율로 정해진다.

 

이후 자원 사용현황을 확인하면 아래와 같다.

자원 할당후 (cpu 할당 변화는 어떻게 보는걸까?)

 

2. 컨테이너는 host에 설치된 DB에 연결하고 싶다.

docker 컨테이너의 개념에서 실제 운영중인 DB를 컨테이너로 관리한다는 것은 무리가 있다고 생각했다.

그래서 host의 설치된 DB를 각 컨테이너가 연결하는 것이 맞다고 생각하고, 연결하기로 했다.

그럼 내부에서의 IP만 알면 되는 것인데 이건 어떻게 확인할 것인가?

ifconfig를 입력하면 확인되는 docker0의 ip로 접근하면 된다.

 

만약 ifconfig를 사용할 수 없다면 아래 명령어로 패키지를 설치하도록.

sudo apt-get update

sudo apt install net-tools 

 

자세한 docker 네트워크에 대해서는 아래 글 참고

 

Docker 네트워크 이해 - Voyager of Linux

Table of Contents1 Docker 네크워크 상태2 Docker0 브릿지 네트워크3 Docker 네트워크 특징4 Docker Network Namespace5 docker0 와 eth0 의 연결: iptables -t nat6 Inter-Container Communication7 docker0 브릿지 네트워크 ip 변경 Docke

linux.systemv.pe.kr

 

3. 컨테이너에서 업로드 또는 다운로드 되는 파일은 host 영역에 배치하고 싶다.

나는 host에 존재하는 폴더를 컨테이너에서 읽기, 쓰기, 삭제의 모든 권한이 필요하다.

그래서 내가 사용한 것은 bind mount다. 

현재 권장되는 것은 아니나(docker에서는 volume mount를 권장),

난 내가 지정한 위치에 폴더를 배치하고 싶어서 사용하기로 했다.

 

아래 순서로 진행하면 컨테이너에서 host의 폴더를 mount되는 것을 확인할 수 있다.

1) host에 mount 할 폴더를 만들고 테스트로 txt파일을 하나 배치한다.

 

2) 컨테이너를 만들면서 bind mount 한다.

bind mount와 함께 컨테이너 생성

docker run -d -it --name 컨테이너명 -v host의폴더명:컨테이너의폴더명 -p 이미지명

 

3) 컨테이너로 접속해서 파일 확인

컨테이너에 접속해 보면 mount된 폴더가 확인된다. 그리고 파일 삭제도 가능하다.

이렇게 하면 컨테이너에서 host의 폴더를 내 폴더인 처럼 바로 사용이 가능하다.

 

docker의 데이터 파일 mount에 관련해서 자세한 내용은 아래 글 참조

 

Docker 컨테이너에 데이터 저장 (볼륨/바인드 마운트)

Engineering Blog by Dale Seo

www.daleseo.com

 

번외1. docker를 이용하며 발생한 이슈 : 서버시간, 한글

이후 운영서버에서 서비스 오픈하고 상황을 모니터링하는 과정에서 몇가지 이슈가 발견 되었다.

 

1) 서버시간이다.

내 서버는 특정 시간마다 진행되는 스케줄이 있다.

나의 운영서버는 세팅한지 1년이 넘어가는 서버로 이미 세팅할때 서버시간을 한국시간으로 적용하고 있었다.

그래서 당연히 정해진 시간에 정해진 스케줄이 돌아가리라 생각하고 있었다.

그런데 실행되는 스케줄이 내가 정해 놓은 시간과 다르다.

이러한 이유는 컨테이너의 시간이 운영서버의 시간과 다를 수 있기 때문이다.

(사실 이 결론에 도달하는데 여러 삽질이 있긴 했지만 그건 그냥 넘어가기로 하고....)

 

컨테이너로 접속하고 아래 순서로 진행해 보자.

apt-get update && apt-get -y upgrade : tzdata을 사용할 수 없는 경우
apt-get -y install tzdata : 타임존 설정
6(Asia) -> 69(Seoul) 선택

 

그리고 터미널에 date를 입력하면 컨테이너가 적용되고 있는 시간이 변경된 것을 확인할 수 있다.

 

시간 변경에 대해서는 아래글 참조

 

docker container 컨테이너 시간 변경 방법

docker container 컨테이너 시간 변경 방법 docker에 Tomcat을 올려 사용하던 중 로그 시간이 안맞아 보기 불편하여 정리. docker 컨테이너 시간바꾸기 -컨테이너 접속 후 # dpkg-reconfigure tzdata - 한국 서울..

tptrmfwk.tistory.com

 

[solved] ubuntu 도커 이미지 빌드 시 timezone 설정 방법

최근 Docker를 이용한 작업이 많아지면서 여러 이슈들이 있었는데, 그 중에서 많은 삽질을 했던 도커 컨테이너 시간대 설정 방법을 공유한다.

stynxh.github.io

 

2) 한글이 안 나온다.

가끔 서버에서 log 메시지를 기록할 때가 있다. 근데 문제는 이게 깨져 나온다는 거다.

컨테이너 내부에서 변경하는 방법도 있긴하나 나는 컨테이너 생성시 지정하는 것을 선호한다.

docker run -d -it -e LC_ALL=C.UTF-8 --name 컨테이너명 이미지명

 

언어변경에 대해서는 아래 글 참조

 

도커(Docker) 컨테이너 로케일 설정: 데비안(Debian), 우분투(Ubuntu) 이미지에서 한글 입력 문제

도커 우분투, 데비안 이미지에서는 기본적으로 한글 입력을 지원하지 않습니다. 한글을 입력하기 위해서는 로케일 설정을 UTF-8로 지정할 필요가 있습니다. 이 글에서는 로케일 개념과 함께 도커

www.44bits.io

 

번외2. log파일을 관리해야 겠다.

내 운영서버는 무료 클라우드 서버다. 그래서 디스크 잔여 용량에 대해서 신경 쓰고 있다.

그래서 무한대로 늘어날 가능성이 있지만 매번 관리하기 어려운 log는 관리 대상인 셈이다.

 

이 내용은 단순하지 않아서 자세한 글을 링크해 둔다. 이 글을 참고해서 적용해 보길 바란다.

 

Logrotate와 Crontab을 활용한 Docker Log 관리

💡늘어나는 Docker log는 어떻게 관리할까??🤷‍♀️

velog.io

+ 덧

이걸로 완전히 해결되지는 않는다. 쓰다보면 점점 docker에 대한 파일 용량이 늘어난다.

그리고 뭔가 관리로 해결할 방법을 찾아봤는데 쉽지가 않다.

다행이라면 docker는 쉽게 설치, 삭제를 가능하게 하는 것이니 완전히 삭제하는 방법이 그나마 내가 찾은 차선책 이었다.

 

 

Cloud VM에 docker를 적용 후 sda 용량 100% 문제

Cloud VM에 docker를 적용 후 sda 용량 100% 문제 오늘 테스트를 하면서 해결한 듯 하면서도 찜찜하다. 이유는 시험한 내용을 설명 후 쓰도록 하겠다. 전반적인 내용을 확인하고자 하시는 분은 htt

shyash.tistory.com

 

번외3. docker file을 만든다.

docker file은 나만의 여러 환경설정이 지정되어 있는 docker 이미지를 만드는데 사용된다.

그러므로 동일한 환경을 자주 구성해야 한다면, 그리고 그 환경이 여러가지를 설정을 포함하고 있다면 docker file을 만드는 것을 추천한다.

 

역시 이 내용도 자세한 글을 링크해 두니 참고하시길.

 

emunhi

Programming > Docker [Tomcat] Dockerfile 로 설치하기 1) Dockerfile 만들기 ※  $ vi Dockerfile 설치할려는 tomcat버전은 바뀔수가 있으므로 tomcat 홈페이지에서 검색해서 사용하면 된다. ※ 내역 /usr/local/tomcat/webap

emunhi.com

 

 

 

728x90
반응형

+ Recent posts