[Python] 파이썬 자동화 기초 : 파일시스템 및 폴더 제어, 로그 쓰기 명령어 모음

[Python] 파이썬 자동화 기초 : 파일시스템 및 폴더 제어, 로그 쓰기 명령어 모음

이 포스트는 파이썬에서 파일이나 폴더를 제어하는(생성, 조회, 변경, 삭제하는) 자동화 명령어를 정리했다.

작업 경로 설정하기

현재 작업 경로 출력

현재 작업 경로를 줄여서 CWD(Current Working Directory) 라고 부르는 듯.

기본값은 프로그램이 실행되는 현재 경로임.

import os # 현재 작업경로 출력 (Current Working Directory) print(os.getcwd())

현재 작업 경로 변경

상대경로 변경도 되고 절대경로 변경도 된다.

# 1. 현재 작업 경로 변경 (상대경로) # 상위 폴더로 이동 os.chdir(“..”) print(os.getcwd()) # 상위 폴더의 상위 폴더로 이동 os.chdir(“../..”) print(os.getcwd()) # 2. 현재 작업 경로 변경 (절대경로) # 경로 앞에 r을 붙이는 이유는 오류 방지 (역슬래시가 이스케이프 문자로 적용되는 일을 방지) os.chdir(r”C:\test”) print(os.getcwd())

파일 경로 만들기 (폴더경로와 파일명 결합하기)

# 파일 경로 # 파일 경로 만들기 절대경로를 얻는다 file_path= os.path.join(os.getcwd(), “filename.txt”) print(file_path)

폴더 경로 가져오기 (전체 경로에서 파일명만 떼어내기)

dir_path = os.path.dirname(r”C:\coding\python-workspace\pythonProject\filename.txt”) print(dir_path)

파일 정보 가져오기

파일 만든 날짜

ctime = os.path.getctime(r”C:\test\test.txt”) print(ctime) # => 1643003377.320888 ctime2 = datetime.datetime.fromtimestamp(ctime) print(ctime2) # => 2022-01-24 14:49:37.320888 ctime3 = datetime.datetime.fromtimestamp(ctime).strftime(“%Y-%m-%d %H:%M:%S”) print(ctime3) # => 2022-01-24 14:49:37

파일 수정한 날짜

mtime = os.path.getmtime(r”C:\test\test.txt”) mtime2 = datetime.datetime.fromtimestamp(mtime).strftime(“%Y-%m-%d %H:%M:%S”) print(mtime2) # => 2019-11-22 01:04:38

파일 엑세스한 날짜

atime = os.path.getatime(r”C:\test\test.txt”) atime2 = datetime.datetime.fromtimestamp(atime).strftime(“%Y-%m-%d %H:%M:%S”) print(atime2) # => 2022-01-24 14:49:37

파일 용량 가져오기 (byte 단위)

file_size = os.path.getsize(r”C:\test\test.txt”) print(file_size) # => 136580

파일 목록 가져오기

모든 폴더 및 파일 가져오기

# (현재 작업경로 기준으로) 모든 폴더 및 파일 가져오기 print(os.listdir()) # (특정 폴더 기준으로) 모든 폴더 및 파일 가져오기 print(os.listdir(r”C:\test”)) # 리스트 형태로 리턴됨 => [‘file1.txt’, ‘file2.txt’, ‘folder1’, ‘folder2’, ‘test.txt’]

n단계 하위 폴더 모두 포함해서, 모든 폴더 및 파일 가져오기

# 파일 목록 제너레이터 타입으로 가져오기 (n단계 하위 폴더 모두 포함) result = os.walk(r”C:\test”) for root, dirs, files in result: print(“root :”, root) print(“dirs :”, dirs) print(“files :”, files) print(” “) ##### # root : C:\test # dirs : [‘folder1’, ‘folder2’] # files : [‘file1.txt’, ‘file2.txt’, ‘test.py’, ‘test.txt’] # # root : C:\test\folder1 # dirs : [‘child_folder1’, ‘child_folder2’] # files : [] # # root : C:\test\folder1\child_folder1 # dirs : [] # files : [‘child_file1.txt’] # # root : C:\test\folder1\child_folder2 # dirs : [] # files : [] # # root : C:\test\folder2 # dirs : [] # files : []

n단계 하위 폴더 모두 포함해서, 특정 파일명 찾기

# 특정 파일명 찾기 target_name = “test.txt” result = [] for root, dirs, files in os.walk(r”C:\test”): if target_name in files: result.append(os.path.join(root, target_name)) print(result) # => [‘C:\\test\\test.txt’]

n단계 하위 폴더 모두 포함해서, 특정 패턴으로 파일명 찾기

# 특정 패턴으로 파일명 찾기 import fnmatch patten = “*.py” result = [] for root, dirs, files in os.walk(r”C:\test”): for file_name in files: if fnmatch.fnmatch(file_name, patten): result.append(os.path.join(root, file_name)) print(result) # => [‘C:\\test\\test.py’]

파일 제어하기

주어진 경로가 파일인지 폴더인지 확인

# 주어진 경로가 파일인지 확인 print(os.path.isfile(r”C:\test\test.txt”)) # True print(os.path.isfile(r”C:\test”)) # False # 주어진 경로가 폴더인지 확인 print(os.path.isdir(r”C:\test\test.txt”)) # False print(os.path.isdir(r”C:\test”)) # True

주어진 경로가 존재하는지 확인

print(os.path.exists(r”C:\test”))

새 파일 생성 (내용이 비어있는 파일 생성)

# 새 파일 생성 (내용이 비어있는 파일 생성) open(“file_name.txt”, “a”).close()

파일명 변경

# 파일명 변경하기 os.rename(“file_name.txt”, “file_rename.txt”)

파일 삭제하기

# 파일 삭제하기 os.remove(“file_rename.txt”)

파일 복사하기

copy 또는 copyfile 함수는 메타정보 복사하지 않음

# 파일 복사하기 # 원본 파일경로, 결과 폴더경로 또는 결과 파일경로 shutil.copy(“file1.txt”, “folder1”) # 폴더경로 쓰면 해당 폴더 안에 카피됨 shutil.copy(“file1.txt”, “folder1/copied_file.txt”) # 파일경로 쓰면 해당 파일명으로 카피됨 # 원본 파일경로, 결과 파일경로 shutil.copyfile(“file1.txt”, “copied_file2.txt”)

파일 메타정보까지 복사하기

copy2 함수는 메타정보(만든 날짜, 수정한 날짜, 엑세스한 날짜)까지 복사함

# 원본 파일경로, 결과 폴더경로 또는 결과 파일경로 shutil.copy2(“file1.txt”, “folder2”) # 폴더경로 쓰면 해당 폴더 안에 카피됨 shutil.copy2(“file1.txt”, “folder2/copied_file.txt”) # 파일경로 쓰면 해당 파일명으로 카피됨

폴더 제어하기

폴더 생성하기

os.mkdir(“folder_name”)

하위 폴더까지 생성하기

os.makedirs(“folder_name/f1/f2/f3”)

폴더 삭제하기

# 폴더 삭제하기 (폴더 안이 비었을 때만 삭제 가능) # (폴더 안이 비어있지 않으면, OSError: [WinError 145] 디렉터리가 비어 있지 않습니다: ‘폴더명’ 오류 발생함) os.rmdir(“folder_name”)

폴더 하위까지 강제로 삭제하기 (주의 : 경로를 잘못 적었다가는 모든 파일이 삭제될 수 있으므로 조심)

# shell utilities import shutil shutil.rmtree(“folder_name”)

폴더 복사하기

# 원본 폴더경로, 결과 폴더경로 shutil.copytree(“folder_name”, “copied_folder”)

폴더 이동하기 (또는 폴더명 바꾸기)

# 이동할 폴더 경로가 존재하는 경우, 해당 폴더 내부로 이동됨 shutil.move(“folder_name”, “folder1”) # 이동할 폴더경로가 존재하지 않는 경우, 폴더명이 변경되는 효과 shutil.move(“folder_name”, “new_folder_name”)

로그 쓰기

콘솔(터미널)에 로그 쓰기

import logging # 로그 포맷 설정 logging.basicConfig(level=logging.DEBUG, format=”%(asctime)s [%(levelname)s] %(message)s”) logging.debug(“디버그 메시지를 출력합니다.”) logging.info(“정보 메시지를 출력합니다.”) logging.warning(“경고 메시지를 출력합니다.”) logging.error(“오류 메시지를 출력합니다.”) logging.critical(“치명적인 오류 메시지를 출력합니다.”) # level=logging.DEBUG 인 경우 debug, info, warning, error, critical 출력 가능 # level=logging.INFO 인 경우 info, warning, error, critical 출력 가능 # level=logging.WARNING 인 경우 warning, error, critical 출력 가능 # level=logging.ERROR 인 경우 error, critical 출력 가능 # level=logging.CRITICAL 인 경우 critical 출력 가능

파일시스템에 로그 쓰기 (로그파일에 쓰기)

import logging from datetime import datetime # 콘솔(터미널) 및 파일시스템에 로그 쓰기 logger = logging.getLogger() logger.setLevel(logging.DEBUG) streamHandler = logging.StreamHandler() # “시간 [로그레벨] 메시지” 형태로 로그를 작성 logFormatter = logging.Formatter(“%(asctime)s [%(levelname)s] %(message)s”) streamHandler.setFormatter(logFormatter) logger.addHandler(streamHandler) # 파일명 설정 # filename = datetime.now().strftime(“test_%Y%m%d_%H%M%S.log”) filename = datetime.now().strftime(“test_%Y%m%d.log”) fileHandler = logging.FileHandler(filename, encoding=”utf-8″) fileHandler.setFormatter(logFormatter) logger.addHandler(fileHandler) logging.debug(“로그 시작”) logging.debug(“디버그 메시지를 출력합니다.”) logging.info(“정보 메시지를 출력합니다.”) logging.warning(“경고 메시지를 출력합니다.”) logging.error(“오류 메시지를 출력합니다.”) logging.critical(“치명적인 오류 메시지를 출력합니다.”)

참고사이트 1 : pyautogui 라이브러리 공식문서 https://pyautogui.readthedocs.io/

참고사이트 2 : 나도코딩 https://www.youtube.com/watch?v=exgO1LFl9x8&t=4285s

[Python] 파이썬 자동화 기초 : pyautogui 오토마우스, 오토키보드 자동화 명령어 모음

[Python] 파이썬 자동화 기초 : pyautogui 오토마우스, 오토키보드 자동화 명령어 모음

엑셀VBA에서도 오토마우스, 오토키보드가 가능했다.

파이썬에서는 훨씬 더 쉽고 간단하게 마우스 이동, 클릭, 키입력 등을 자동화할 수 있다.

pyautogui 설치

pip install pyautogui

단어 gui 는 알다시피 Graphic User Interface 의 약자이다.

대기하기

n초 대기하기

import pyautogui # 3초 대기 pyautogui.sleep(3)

카운트다운 출력하며 대기하기

# 3초 동안 카운트 다운 # 3 2 1 출력됨 pyautogui.countdown(3)

모든 명령어에 대기 적용하기

# 모든 동작에 1초씩 sleep을 적용 pyautogui.PAUSE = 1 # 테스트 코드 # for문임에도 불구하고 1초마다 마우스 100, 100 씩 이동하게 됨 for i in range(10): pyautogui.move(100, 100)

마우스 자동화 (오토마우스)

절대 좌표로 마우스 이동하기

import pyautogui # 절대 좌표로 마우스 이동하기 pyautogui.moveTo(100, 100) # 0.25초 동안 절대좌표로 마우스 이동 pyautogui.moveTo(100, 100, duration=0.25)

상대좌표로 마우스 이동 (현재 커서 위치 기준으로 이동)

# 상대좌표로 마우스 이동 (현재 커서 위치 기준으로 이동) pyautogui.move(100, 100) # 0.25초 동안 상대좌표로 마우스 이동 (현재 커서 위치 기준으로 이동) pyautogui.move(100, 100, duration=0.25)

마우스 위치 출력하기

# 마우스 위치 출력하기 # 예 : Point(x=700, y=600) print(pyautogui.position()) # 마우스 위치 x, y 각각 출력하기 p = pyautogui.position() # 예 : 700 600 print(p[0], p[1]) print(p.x, p.y)

마우스 클릭하기

# 현재 위치 마우스 클릭 pyautogui.click() # 절대 좌표의 마우스 클릭 pyautogui.click(700, 600) # 1초 동안 절대 좌표로 이동 후에, 마우스 클릭 (기능상 moveTo + mouseDown + mouseUp 를 합친 것과 같음) pyautogui.click(700, 600, duration=1)

마우스 더블클릭

# 마우스 더블클릭 pyautogui.doubleClick() # 마우스 연속 500번 클릭 pyautogui.click(clicks=500)

마우스 우클릭

# 마우스 우클릭 pyautogui.rightClick()

마우스 드래그

# 현재 위치부터 상대좌표 (100, 150) 까지 드래그 pyautogui.drag(100, 150, duration=0.25) # 현재 위치부터 절대좌표 (100, 150) 까지 드래그 pyautogui.dragTo(100, 150, duration=0.25) # 드래그 직접구현 : (200, 200)부터 (300, 350)까지 드래그 pyautogui.moveTo(200, 200) pyautogui.mouseDown() pyautogui.moveTo(300, 350) pyautogui.mouseUp()

마우스 스크롤

# 마우스 스크롤 : 양수이면 위 방향으로, 음수이면 아래 방향으로 스크롤 # 위 방향으로 300만큼 스크롤 pyautogui.scroll(300) # 아래 방향으로 300만큼 스크롤 pyautogui.scroll(-300)

현재 마우스 정보 표시하기 (MouseInfo 프로그램 실행)

import pyautogui pyautogui.mouseInfo()

<MouseInfo 프로그램 사용 방법>

마우스를 원하는 특정 위치로 옮겨놓고, F1키(Copy All)를 누르면, xy좌표나 픽셀 RGB 값 등이 클립보드에 복사된다. 이어서 Ctrl + V키로 메모장 같은 곳에 붙여넣기하면 F1키를 누른 시점의 마우스 정보가 나온다.

마우스 제어 중단하는 방법

pyautogui 로 마우스 제어 시, 사용자가 화면 네 귀퉁이에 마우스를 갖다놓으면 프로그램이 중단된다.

# 사용자가 화면 네 귀퉁이에 마우스를 갖다놓으면 프로그램이 중단됨

마우스 제어 중단하지 않도록 처리

만약 pyautogui.FAILSAFE 값을 False 로 대입하면, 화면 네 귀퉁이에 마우스를 갖다놓아도 제어를 계속한다.

# 사용자가 화면 네 귀퉁이에 마우스를 갖다놓으면 프로그램이 중단됨 # 아래 변수를 False 처리하면 화면 네 귀퉁이에 마우스를 갖다놓아도 제어 계속함 pyautogui.FAILSAFE = False

키보드 자동화 (오토키보드)

숫자 또는 영어 문장 입력하기 (한글은 불가)

pyautogui.write(“12345”) pyautogui.write(“Hello World”, interval=1)

차례대로 키입력

# world 입력 후, 좌측화살표 5번 누르고, hello 띄어쓰기 입력 후, 엔터키 입력하기 # left는 좌측 화살표, right는 우측 화살표, enter는 엔터키를 의미함 pyautogui.write([“w”, “o”, “r”, “l”, “d”, “left”, “left”, “left”, “left”, “left”, “h”, “e”, “l”, “l”, “o”, ” “, “enter”])

문자열

의미

‘a’, ‘b’, ‘c’, ‘A’, ‘B’, ‘C’, ‘1’, ‘2’, ‘3’, ‘!’, ‘@’, ‘#’ 등등

해당하는 단일문자

‘enter’ (또는 ‘return’ 또는 ‘\n’)

ENTER 키

‘esc’

ESC 키

‘shiftleft’, ‘shiftright’

왼쪽과 오른쪽 SHIFT 키

‘altleft’, ‘altright’

왼쪽과 오른쪽 ALT 키

‘ctrlleft’, ‘ctrlright’

왼쪽과 오른쪽 CTRL 키

‘tab’ (또는 ‘\t’)

TAB 키

‘backspace’, ‘delete’

BACKSPACE 키, DELETE 키

‘pageup’, ‘pagedown’

PAGE UP 키, PAGE DOWN 키

‘home’, ‘end’

HOME 키, END 키

‘up’, ‘down’, ‘left’, ‘right’

상, 하, 좌, 우 화살표 키

‘f1’, ‘f2’, ‘f3’ 등등

F1 ~ F12 키

‘volumemute’, ‘volumedown’, ‘volumeup’

음소거, 볼륨 감소 키, 볼륨 증가 키

(일부 키보드에는 이러한 키가 없지만 운영 체제에서는 시뮬레이션된 키입력을 이해할 수 있음)

‘pause’

PAUSE 키

‘capslock’, ‘numlock’, ‘scrolllock’

CAPS LOCK 키, NUM LOCK 키, SCROLL LOCK 키

‘insert’

INS 또는 INSERT 키

‘printscreen’

PRTSC 또는 PRINT SCREEN 키

‘winleft’, ‘winright’

왼쪽과 오른쪽 윈도우(WIN) 키 (Windows OS 전용)

‘command’

Command 키 (mac OS 전용)

‘option’

OPTION 키 (mac OS 전용)

조합키 입력하기 (ex : shift + 4, ctrl + a)

# 조합키 간편하게 입력 # 달러 기호($)에 해당하는 shift + 4 입력하기 pyautogui.hotkey(“shift”, “4”) # 전체선택 단축키인 ctrl + a 입력하기 pyautogui.hotkey(“ctrl”, “a”) # 조합키 직접 구현하기 (1) pyautogui.keyDown(“ctrl”) pyautogui.keyDown(“a”) pyautogui.keyUp(“a”) pyautogui.keyUp(“ctrl”) # 조합키 직접 구현하기 (2) pyautogui.keyDown(“ctrl”) pyautogui.press(“a”) pyautogui.keyUp(“ctrl”)

한글 문자열 입력하기 (복사 붙여넣기 이용)

import pyperclip pyperclip.copy(“복사할 문자열”) # 문자열 붙여넣기 pyautogui.hotkey(“ctrl”, “v”) # 문자열 출력하기 print(pyperclip.paste())

키보드 제어 중단하는 방법

# 윈도우 OS 는 ctrl + alt + del 키입력 시 키보드 제어 중단함 # 맥 OS 는 cmd + shift + option + q 키입력 시 키보드 제어 중단함

스크린 정보 가져오기 (화면 정보 가져오기)

화면 사이즈 가져오기

import pyautogui # 현재 화면의 스크린 사이즈 가져오기 size = pyautogui.size() print(size) # size[0] == width # size[1] == height

현재화면 스크린샷 찍기

import pyautogui # 스크린샷 찍기 img = pyautogui.screenshot() # 파일 저장 img.save(“screenshot.png”)

화면 특정좌표의 픽셀값 가져오기

pixel = pyautogui.pixel(0, 0) # RGB값 출력 ex) (60, 64, 67) print(pixel)

화면 특정좌표의 픽셀값 색상 일치하는지 검사

# if pyautogui.pixelMatchesColor(x좌표, y좌표, (R, G, B)): if pyautogui.pixelMatchesColor(0, 0, (60, 64, 67)): print(“색상 일치”) else: print(“색상 불일치”)

이미지 찾기

화면에 이미지 존재하는지 확인 (locateOnScreen)

import pyautogui target_img= pyautogui.locateOnScreen(“target_img.png”) print(target_img) # 이미지를 찾으면 결과는 다음과 같이 나온다. ex) Box(left=900, top=82, width=31, height=23) # 이미지를 찾지 못하면 결과는 None 이라고 나온다

이미지 찾아서 클릭하기

# 이미지 클릭 target_img= pyautogui.locateOnScreen(“target_img.png”) pyautogui.click(target_img)

이미지 찾아서 마우스 이동하기

# 이미지 찾아서 마우스 이동하기 target_img= pyautogui.locateOnScreen(“target_img.png”) pyautogui.moveTo(file_menu)

복수의 이미지(n개 이미지) 가져오기 (locateAllOnScreen)

# 이미지 1개 가져오기 (locateOnScreen) # 이미지가 n개인 경우 첫번째 이미지 객체를 가져온다. target_img= pyautogui.locateOnScreen(“target_img.png”) # 이미지 n개 가져오기 (locateAllOnScreen) # 이미지가 n개 인 경우 제너레이터를 리턴하므로, for문 처리하면 된다. # 예를 들어 n개 이미지를 0.25 초 주기로 차례대로 클릭하기 for i in pyautogui.locateAllOnScreen(“target_img.png”): pyautogui.click(i, duration=0.25)

이미지 찾기 속도 개선하기

# 1. GrayScale 로 찾기 # 흑백으로 전환해서 찾기. 정확도가 떨어지지만 30% 정도 속도가 개선됨 target_img= pyautogui.locateOnScreen(“target_img.png”, grayscale=True) print(target_img) # 2. 범위 지정해서 찾기 # 메뉴같은 경우 위쪽에만 있는 상황에서 사용 # target_img= pyautogui.locateOnScreen(“target_img.png”, region=(x, y, width, height)) target_img= pyautogui.locateOnScreen(“target_img.png”, region=(0, 0, 1024, 300)) print(target_img) # 3. 정확도를 낮춰서 찾기 # 분명히 똑같은 이미지로 자동화했는데 사소한 차이로 클릭이 안되는 상황에서 사용 # 주의) pip install opencv-python 명령어로 패키지 설치해야 한다. # confidence=0.999 값이 기본값이다(=99.9%). # 아래 코드에서는 80% 정확도로 해본다. target_img= pyautogui.locateOnScreen(“target_img.png”, confidence=0.8) print(target_img)

이미지가 나타날 때까지 기다려야 하는 경우 : 찾을 때까지 무한정 반복하는 코드

print(“이미지 찾기 시작”) target_img = None while target_img is None: target_img = pyautogui.locateOnScreen(“target_img.png”) pyautogui.sleep(1) print(“이미지 찾기 성공”)

이미지가 나타날 때까지 기다려야 하는 경우 : 1초 간격으로 100번 찾아보는 코드

print(“이미지 찾기 시작”) target_img = None for i in range(0, 100): target_img = pyautogui.locateOnScreen(“target_img.png”) pyautogui.sleep(1) if target_img is None: print(“이미지 찾기 실패”) else: print(“이미지 찾기 성공”)

윈도우 정보 가져오기 (창 정보 가져오기)

현재 활성화된 윈도우(창) 객체 가져오기 (getActiveWindow)

import pyautogui # 현재 활성화된 윈도우(창) 객체 win = pyautogui.getActiveWindow() print(win) # 창 제목 print(win.title) # 창 크기 print(win.size) print(win.left, win.top, win.right, win.bottom)

모든 윈도우(창) 객체 가져오기 (getAllWindows)

# 모든 윈도우 객체 가져오기 for win in pyautogui.getAllWindows(): print(win)

특정 문자열을 제목에 포함하는 윈도우 객체 가져오기 (getWindowsWithTitle)

# 특정 문자열을 제목에 포함하는 윈도우 객체 n개 가져오기 for win in pyautogui.getWindowsWithTitle(“메모장”): print(win) # 특정 문자열을 제목에 포함하는 윈도우 객체 1개 가져오기 win = pyautogui.getWindowsWithTitle(“메모장”)[0] print(win)

특정 윈도우(창) 활성화 시키기

win = pyautogui.getWindowsWithTitle(“메모장”)[0] # 최소화 되어있다면 원복 if win.isMinimized == True: win.restore() # 윈도우(창) 활성화 win.activate()

기타 윈도우(창) 정보

# 최대화 여부 : 최대화는 화면 전체만큼 큰 사이즈 상태임. restore 를 사용하면 원복 가능 print(win.isMaximized) # 최소화 여부 : 최소화는 작업 표시줄로 숨어있는 상태임. restore 를 사용하면 원복 가능 print(win.isMinimized) # 활성화 여부 print(win.isActive)

메시지 박스

메시지 박스 표시 (alert)

import pyautogui pyautogui.alert(“메시지 내용입니다”, “경고”)

확인 박스 표시 (confirm)

result = pyautogui.confirm(“확인하시겠습니까?”, “확인”) print(result) # 결과는 “OK” 또는 “Cancel”

문자열 입력받기 (prompt)

result = pyautogui.prompt(“파일명을 무엇으로 하시겠습니까?”, “입력”) print(result) # 취소하면 None 값 나옴 # 빈값 입력 후 확인하면 “” 값 나옴

참고사이트 1 : pyautogui 라이브러리 공식문서 https://pyautogui.readthedocs.io/

참고사이트 2 : 파이썬 프로그래밍으로 지루한 작업 자동화하기 https://automatetheboringstuff.com/2e/chapter20/

참고사이트 3 : 나도코딩 https://www.youtube.com/watch?v=exgO1LFl9x8&t=4285s

읽을거리 : 개발자 부족 현상은 계속 될겁니다.

읽을거리 : 개발자 부족 현상은 계속 될겁니다.

제가 쓴 글은 아니지만 아래 글 읽어볼만 합니다.

* 한줄요약 : 비대면과 DT(Digital Transformation. 회사의 기반이 IT기반으로 변화하는 현상) 가속화로 인해 개발자 품귀현상이 가속화될 것이다.

https://www.clien.net/service/board/park/16790569?fbclid=IwAR3WDCP0ZajLvYj0A4OjK9J04fMTk0B07gEacwqmmmWLBd-QlR5YHedRLnI

[VBA] 한글 문자열을 UTF-8 로 변환 (VBA convert string to UTF-8)

[VBA] 한글 문자열을 UTF-8 로 변환 (VBA convert string to UTF-8)

UTF8 이라는 함수를 정의해서 사용하면 된다.


■ 함수 정의

Private Declare PtrSafe Function WideCharToMultiByte Lib “kernel32” ( _
    ByVal CodePage As Long, _
    ByVal dwFlags As Long, _
    ByVal lpWideCharStr As LongPtr, _
    ByVal cchWideChar As Long, _
    ByVal lpMultiByteStr As LongPtr, _
    ByVal cchMultiByte As Long, _
    ByVal lpDefaultChar As Long, _
    ByVal lpUsedDefaultChar As Long _
As Long

Private Const CP_UTF8 As Long = 65001

‘ 한글 -> UTF-8 인코딩 처리
Public Function UTF8(ByRef T) As String
On Error GoTo ErrLbl
    Dim str As String
    str = T
    ‘ str = Replace$(str, “<“, “<“)
    ‘ str = Replace$(str, “>”, “>”)
    Dim BufSize As Long, MultiArr() As Byte, Buf As String, i As Long
    Dim UniArr() As Byte
    
    UniArr = str
    BufSize = WideCharToMultiByte(CP_UTF8, 0&, VarPtr(UniArr(0)), (UBound(UniArr) + 1) / 2, 0&, 0&, 0&, 0&)
    
    If BufSize > 0 Then
        ReDim MultiArr(BufSize – 1&)
        WideCharToMultiByte CP_UTF8, 0&, VarPtr(UniArr(0)), (UBound(UniArr) + 1) / 2, VarPtr(MultiArr(0)), BufSize, 0&, 0&
    End If
    
    For i = 0 To UBound(MultiArr)
        If MultiArr(i) = 63 Then
            Buf = Buf & Chr(MultiArr(i)) ‘// ?
        Else
            Select Case Chr(MultiArr(i))
                Case Space(1): Buf = Buf & “+”
                Case vbNewLine, vbCrLf, vbLf, vbCr: Buf = Buf & “%0A”
                Case “~”, “!”, “*”, “(“, “)”, “_”, “-“, “‘”, “.”
                Buf = Buf & Chr(MultiArr(i)) ‘// 특문
                Case 0 To 9
                Buf = Buf & Chr(MultiArr(i)) ‘// 숫자
                Case “A” To “Z”
                Buf = Buf & Chr(MultiArr(i)) ‘// 영대
                Case “a” To “z”
                Buf = Buf & Chr(MultiArr(i)) ‘// 영소
                Case Else
                Buf = Buf & “%” & IIf(Len(Hex$(MultiArr(i))) Mod 2, 0, “”) & Hex$(MultiArr(i))
            End Select
        End If
    Next i
UTF8 = Buf
ErrLbl:
End Function

 사용 예

Sub 매크로1()

‘ 매크로1 매크로
‘ 바로 가기 키: Ctrl+k

    Dim a
    Dim b
    
    a = “한글”
    b = UTF8(“한글”)
    
    MsgBox (a)
    MsgBox (b)
End Sub

 결과

결과는 문자열 “한글”이 “%ED%95%9C%EA%B8%80” 으로 변환되어 표시된다.


 

참고사이트 : https://blog.naver.com/program114/220426949984

[Python] 파이썬 자동화 기초 : 오픈파이엑셀(openpyxl) 엑셀 차트 그리기, 셀 스타일, 수식 넣기, 이미지 추가

[Python] 파이썬 자동화 기초 : 오픈파이엑셀(openpyxl) 엑셀 차트 그리기, 셀 스타일, 수식 넣기, 이미지 추가

이전글에 이어서 오픈파이엑셀(openpyxl) 라이브러리를 이용한 파이썬 자동화 명령어를 정리했다.

파이썬에서 엑셀 차트 그리는 방법, 엑셀 셀 스타일 지정하는 방법, 수식 넣는 방법, 이미지 추가하는 방법이다.

이전글 : [Python] 파이썬 자동화 기초 : 오픈파이엑셀(openpyxl) 엑셀 자동화 명령어 모음

1. 파이썬에서 엑셀 차트 그리는 방법

엑셀 차트의 형태와 종류가 너무나 다양하므로 여기서는 두 가지 차트(바 차트, 라인 차트)만 그려본다.

1-1. BarChart 그리기

from openpyxl import Workbook wb = Workbook() ws = wb.active # 셀 값 대입하기 ws[“A1”] = “번호” ws[“B1”] = “국어” ws[“C1”] = “수학” from random import * for x in range(2, 12): for y in range(1, 4): if y == 1: ws.cell(row=x, column=y, value=x – 1) else: ws.cell(row=x, column=y, value=randint(0, 100)) # 바 차트 그리기 from openpyxl.chart import BarChart, Reference bar_value = Reference(ws, min_row=1, max_row=11, min_col=2, max_col=3) bar_chart = BarChart() bar_chart.add_data(bar_value, titles_from_data=True) ws.add_chart(bar_chart, “E1”) wb.save(“filename.xlsx”) wb.close()

결과

1-2. LineChart 그리기

from openpyxl import Workbook wb = Workbook() ws = wb.active # 셀 값 대입하기 ws[“A1”] = “번호” ws[“B1”] = “국어” ws[“C1”] = “수학” from random import * for x in range(2, 12): for y in range(1, 4): if y == 1: ws.cell(row=x, column=y, value=x – 1) else: ws.cell(row=x, column=y, value=randint(0, 100)) # 라인 차트 그리기 from openpyxl.chart import LineChart, Reference line_value = Reference(ws, min_row=1, max_row=11, min_col=2, max_col=3) line_chart = LineChart() line_chart.add_data(line_value, titles_from_data=True) line_chart.title = “성적표” # 미리 정의된 스타일을 적용, 사용자가 개별 지정도 가능 line_chart.style = 10 line_chart.y_axis.title = “점수” line_chart.x_axis.title = “번호” ws.add_chart(line_chart, “E1”) wb.save(“filename.xlsx”) wb.close()

결과

이외에도 많은 차트 종류와 형태가 있으며, 자세한 내용은 라이브러리 문서의 charts 항목을 확인해본다.

https://openpyxl.readthedocs.io/en/stable/charts/introduction.html

2. 파이썬에서 엑셀 셀 스타일 변경하는 방법

2-1. 로우 길이, 컬럼 길이 변경

(1) 열 너비(column width) 변경하기

# A열 너비를 5로 지정 ws.column_dimensions[“A”].width = 5

(2) 행 높이(row height) 변경하기

# 첫번째 행의 높이를 50으로 지정 ws.row_dimensions[1].height = 50

2-2. 셀 스타일 변경

(1) 폰트 변경

# 폰트 변경 from openpyxl.styles import Font cell = ws[“A1″] cell.font = Font(color=”FF0000″, name=”Arial”, size=20, italic=True, bold=True, strike=True, underline=”double”) # color 는 RGB 값을 넣으면 글자색상 적용됨 (FF0000은 빨간색) # name 은 폰트명 # size 는 폰트크기 # italic 은 기울임 여부 # bold 는 굵게표시 여부 # strike 는 취소선 여부 # underline 은 밑줄을 의미함 # underline 값은 다음값 중에서 선택 가능함 : “single”, “double”, “singleAccounting”, “doubleAccounting” # 차례대로 실선, 이중실선, 실선(회계용), 이중실선(회계용) 을 의미함

(2) 셀 테두리 적용

# 셀 테두리 적용 from openpyxl.styles import Border, Side cell = ws[“A1″] thin_border = Border(left=Side(style=”thin”), right=Side(style=”thin”), top=Side(style=”thin”), bottom=Side(style=”thin”)) cell.border = thin_border # style 값이 “thin”이므로 얇은 선이 적용됨 # style 값은 다음값 중에서 선택 가능함 : “dashDot”, “dashed”, “dashDotDot”, “dotted”, “thin”, “mediumDashDot”, “mediumDashDotDot”, “hair”, “medium”, “double”, “thick”, “mediumDashed”, “slantDashDot”

(3) 셀 배경색 적용

from openpyxl.styles import PatternFill cell = ws[“A1″] # 셀 값이 정수형(int)이고 90 초과하는 경우 if isinstance(cell.value, int) and cell.value > 90: # 배경을 초록색으로 설정 cell.fill = PatternFill(fgColor=”00FF00″, fill_type=”solid”)

(4) 가운데 정렬 (수평 가운데 정렬, 수직 가운데 정렬 각각 적용가능)

cell = ws[“A1″] cell.alignment = Alignment(horizontal=”center”, vertical=”center”)

(5) 틀 고정

# B2 기준으로 틀 고정 ws.freeze_panes = “B2”

3. 파이썬에서 엑셀 수식 넣는 방법

3-1. 셀에 엑셀 수식 넣기

import datetime from openpyxl import Workbook wb = Workbook() ws = wb.active # 오늘 날짜 대입 ws[“A1”] = datetime.datetime.today() ws[“A2”] = “=SUM(1, 2, 3)” ws[“A3”] = “=AVERAGE(1, 2, 3)” ws[“A4”] = 10 ws[“A5”] = 20 ws[“A6”] = “=SUM(A4:A5)” wb.save(“filename.xlsx”) wb.close()

3-2. (수식이 그대로 나오도록) 엑셀파일 불러오기

from openpyxl import load_workbook wb = load_workbook(“filename.xlsx”) ws = wb.active # 수식 그대로 가져온다. for row in ws.values: for cell in row: print(cell) wb.close()

결과는 아래와 같다.

2022-01-19 05:12:05.534153 =SUM(1, 2, 3) =AVERAGE(1, 2, 3) 10 20 =SUM(A4:A5)

3-3. (수식이 아닌 계산 값이 나오도록) 엑셀파일 불러오기

from openpyxl import load_workbook wb = load_workbook(“filename.xlsx”, data_only=True) ws = wb.active # 수식이 아닌 실제데이터를 가지고 옴 # evaluate 되지 않은 상태의 데이터는 None 이라고 나옴 for row in ws.values: for cell in row: print(cell) wb.close()

만약 결과가 아래처럼 “None” 값이 포함되어 나온다면, 오픈파이엑셀(openpyxl) 라이브러리로 엑셀파일을 생성한 경우다.

2022-01-19 05:12:05.534153 None None 10 20 None

오픈파이엑셀(openpyxl) 라이브러리를 이용해서 엑셀파일을 생성한 경우, 해당 엑셀파일의 수식 부분이 None으로 표시된다. 오픈파이엑셀(openpyxl)로 만든 파일은, 한 번 사람이 직접 엑셀파일을 열어서 저장해야만 수식의 계산결과 값이 저장된다.

이러한 계산을 영어로 evaluate[이밸류에이트]라고 표현하며, evaluate 되지 않은 상태의 데이터는 None 이라고 표시된다.

엑셀파일을 열어서 저장하고, 다시 코드를 실행해보면 아래와 같이 출력결과가 나온다.

2022-01-19 05:12:05.534153 6 2 10 20 30

4. 파이썬에서 엑셀파일에 이미지 추가하는 방법

from openpyxl import Workbook from openpyxl.drawing.image import Image wb = Workbook() ws = wb.active img = Image(“bb.png”) # C3 위치에 bb.png 파일의 이미지를 삽입 ws.add_image(img, “C3”) wb.save(“image.xlsx”) wb.close() # 만약 ImportError : You must install Pillow to fetch image… # 위와 같은 에러가 발생할 경우, pip install Pillow 명령어로 패키지를 설치하고 다시 실행하자.

결과

참고사이트 1 : openpyxl 라이브러리 공식문서 https://openpyxl.readthedocs.io/

참고사이트 2 : 나도코딩 https://www.youtube.com/watch?v=exgO1LFl9x8&t=4285s

[Python] 파이썬 자동화 기초 : 오픈파이엑셀(openpyxl) 엑셀 자동화 명령어 모음

[Python] 파이썬 자동화 기초 : 오픈파이엑셀(openpyxl) 엑셀 자동화 명령어 모음

오픈파이엑셀(openpyxl)는 파이썬에서 엑셀 파일을 조작할 수 있도록 돕는 라이브러리다.

이 포스트는 오픈파이엑셀(openpyxl)을 이용한 파이썬 자동화 명령어를 정리했다.

오픈파이엑셀(openpyxl) 설치

pip install openpyxl

1. 엑셀파일 다루기

1-1. 새 엑셀파일 생성하기 & 엑셀파일 저장하기

from openpyxl import Workbook # 새 엑셀파일 생성 wb = Workbook() ##### 이 위치에 엑셀파일 다루는 로직작성하면 됨 # ex) ws = wb.active # 엑셀파일 저장 wb.save(“filename.xlsx”) wb.close()

1-2. 기존 엑셀파일 불러오기 & 엑셀파일 저장하기

from openpyxl import load_workbook # 기존 엑셀파일 불러오기 wb = load_workbook(“filename.xlsx”) ##### 이 위치에 엑셀파일 다루는 로직작성하면 됨 # ex) ws = wb.active # 엑셀파일 저장 wb.save(“filename.xlsx”) wb.close()

2. 워크시트 다루기

2-1. 새 워크시트 생성하기

(1) 새로운 시트 생성 : 기본이름으로 생성

# 새로운 시트 생성 : 기본이름으로 생성 ws = wb.create_sheet()

(2) 새로운 시트 생성 : 지정한 이름으로 생성

# 새로운 시트 생성 : 지정한 이름으로 생성 ws = wb.create_sheet(“새로운 시트명”)

(3) 새로운 시트 생성 : 시트위치 인덱스 지정

# 새로운 시트 생성 : 시트위치 인덱스 지정 ws = wb.create_sheet(“새로운 시트명”, 0)

2-2. 기존 워크시트 가져오기

(1) 현재 활성화된 시트 가져오기

# 현재 활성화된 시트 가져오기 ws = wb.active

(2) 시트명으로 시트 가져오기

# 워크북에서 시트명으로 접근 가능 # 딕셔너리(Dict) 형태로 시트에 접근 가능 ws = wb[“새로운 시트명”]

cf) 모든 시트이름 확인하기

# 모든 시트이름 확인 print(wb.sheetnames)

2-3. 워크시트 속성 변경

* 시트명 변경

# 시트명 변경 ws.title = “새로운 시트명”

* 시트명 탭 색상지정

# 시트명 탭 색상지정 (RGB 값 입력) ws.sheet_properties.tabColor = “0000FF”

2-4. 시트 복사하기

target = wb.copy_worksheet(ws) target.title = “복사된 시트”

3. 셀 다루기

3-1. 셀 객체 가져오기

# 셀 객체 가져오기 (문자열 주소를 이용) ws[“A1”] ws[“B1”] # 셀 객체 가져오기 (로우 값과 컬럼 값을 이용) ws.cell(row=1, column=1) ws.cell(row=1, column=2)

3-2. 셀 값 가져오기

# 셀 값 가져오기 (문자열 주소를 이용) ws[“A1”].value ws[“B1”].value # 셀 값 가져오기 (로우 값과 컬럼 값을 이용) ws.cell(row=1, column=1).value ws.cell(row=1, column=2).value # 만약 값이 없을 때는 None을 가져오게 됨 ws[“A10”].value

cf) 반복문을 활용한 셀 값 가져오기

# 셀 값 출력하기 (지정된 범위) # 10개 row 곱하기 10개 column 에 대해서 값을 출력하기 for x in range(1, 11): for y in range(1, 11): print(ws.cell(row=x, column=y).value, end=” “) print() # 셀 값 출력하기 (전체 row 와 column 범위) for x in range(1, ws.max_row + 1): for y in range(1, ws.max_column + 1): print(ws.cell(row=x, column=y).value, end=” “) print()

3-3. 셀 값 변경하기

# 셀 값 변경하기 (문자열 주소를 이용) ws[“A1”].value = 10 # 셀 값 변경하기 (로우 값과 컬럼 값을 이용) ws.cell(row=1, column=1, value=10)

cf) 랜덤값을 활용한 셀 값 변경하기

# 셀 값을 랜덤값으로 변경하기 첫번째 # A1 셀에 0 에서 100 사이의 숫자 대입 from random import * ws.cell(row=1, column=1, value=randint(0, 100)) # 셀 값을 랜덤값으로 변경하기 두번째 # 10개 row 곱하기 10개 column 에 대해서 값으로 0 에서 100 사이의 숫자 대입 from random import * for x in range(1, 11): for y in range(1, 11): ws.cell(row=1, column=1, value=randint(0, 100))

3-4. 셀 객체의 주소 가져오기 (cell.coordinate)

from openpyxl.utils.cell import coordinate_from_string # A2 셀 객체 cell = ws[“A2”] # A2 셀 값 print(cell.value) # A2 print(cell.coordinate) # (‘A’, 2) cell_tuple = coordinate_from_string(cell.coordinate) # A print(cell_tuple[0]) # 2 print(cell_tuple[1])

3-5. 셀 병합하기 / 셀 병합해제

(1) 셀 병합하기

# 셀 병합하기 ws.merge_cells(“B2:D2”) # B2부터 D2까지 합치기 ws[“B2”].value = “병합한 셀 값”

(2) 셀 병합 해제하기

# B2:D2 병합되어 있던 셀을 병합해제 ws.unmerge_cells(“B2:D2”)

4. 로우(row), 컬럼(column) 다루기

4-1. 로우, 컬럼 추가하기

(1) 새 로우 추가하기

# 8번째 줄에 새 로우를 추가하기 ws.insert_rows(8)

# 8번째 줄에 5줄을 추가하기 ws.insert_rows(8, 5)

(2) 새 컬럼 추가하기

# B열에 새 컬럼을 추가하기 ws.insert_cols(2)

# B열에 새 컬럼을 5열 추가하기 ws.insert_cols(2, 5)

4-2. 로우, 컬럼 삭제하기

(1) 기존 로우 삭제하기

# 8번째 줄 삭제하기 ws.delete_rows(8)

# 8번째 줄부터 5줄 삭제하기 ws.delete_rows(8, 5)

(2) 기존 컬럼 삭제하기

# B열 삭제하기 ws.delete_cols(2)

# B열부터 3개 열 삭제하기 ws.delete_cols(2, 3)

4-3. 로우, 컬럼 이동하기

# C1:C11 범위의 내용을 오른쪽으로 1열 이동하기 ws.move_range(“C1:C11”, rows=0, cols=1) # 이동하려는 범위를 먼저 정의하고, 현재 셀 기준에서 이동시킬 위치 지정 # rows 값이 음수일 경우 위로 이동, rows 값이 양수일 경우 아래로 이동 # cols 값이 음수일 경우 왼쪽으로 이동, cols 값이 양수일 경우 오른쪽으로 이동

4-4. 한 줄씩 데이터 넣기 (1 row 씩 데이터 추가)

# 한 줄씩 데이터 넣기 ws.append([“컬럼A”, “컬럼B”, “컬럼C”])

cf) 한 줄씩 데이터 넣기를 10번 반복

# 한 줄씩 데이터 넣기를 10번 반복 from random import * for i in range(1, 11): ws.append([i, randint(0, 100), randint(0, 100)])

4-5. 특정한 1개 row, column 가져오기

(1) 1개 로우 가져오기 (튜플 형태로 리턴)

# 1번째 row만 가져오기. 튜플 형태로 여러 cell 들을 가져옴 => (셀, 셀, 셀…) row = ws[1] # 셀 값 출력 for cell in row: print(cell.value, end=” “)

(2) 1개 컬럼 가져오기 (튜플 형태로 리턴)

# B 컬럼만 가져오기. 튜플 형태로 여러 cell 들을 가져옴 => (셀, 셀, 셀…) col = ws[“B”] # 셀 값 출력 for cell in col: print(cell.value, end=” “)

4-6. 여러개 row, column 가져오기

(1) 여러개 로우 가져오기 (이중튜플 형태로 리턴)

# 2번째부터 6번째 row까지 가져오기. 이중튜플 형태임 => ((셀, 셀, 셀…), (셀, 셀, 셀…)) row_range = ws[2:6] # 아래 row 변수가 튜플 형태임 => (셀, 셀, 셀…) for row in row_range: for cell in row: print(cell.value, end=” “) print()

참고로 2번째 줄부터 마지막 줄까지 가져오기

# 2번째 줄부터 마지막 줄까지 데이터 가져오기 row_range = ws[2:ws.max_row]

(2) 여러개 컬럼 가져오기 (이중튜플 형태로 리턴)

# B 컬럼부터 C 컬럼 가져오기. 이중튜플 형태임 => ((셀, 셀, 셀…), (셀, 셀, 셀…)) col_range = ws[“B:C”] # 아래 col 변수가 튜플 형태임 => (셀, 셀, 셀…) for col in col_range: for cell in col: print(cell.value, end=” “) print()

4-7. 전체 row, column 가져오기

(1) 전체 로우 가져오기 (이중튜플 형태로 리턴)

# 전체 rows print(ws.rows) # => 알 수 없는 정보가 출력되므로 튜플로 감싸주자 # 이중튜플 형태임 => ((셀, 셀, 셀…), (셀, 셀, 셀…)) print(tuple(ws.rows)) # 각 로우 튜플의 첫번째 값만 출력. 즉 A1, A2, A3… 출력 for row in tuple(ws.rows): print(row[0].value)

(2) 전체 컬럼 가져오기 (이중튜플 형태로 리턴)

# 전체 columns print(ws.columns) # => 알 수 없는 정보가 출력되므로 튜플로 감싸주자 # 이중튜플 형태임 => ((셀, 셀, 셀…), (셀, 셀, 셀…)) print(tuple(ws.columns)) # 각 컬럼 튜플의 첫번째 값만 출력. 즉 A1, B1, C1… 출력 for col in tuple(ws.columns): print(col[0].value)

4-8. 이터레이터 활용해서 row, column 가져오기

# 이터레이터를 활용해서 전체 row 가져오기 for row in ws.iter_rows(): print(row) # 이터레이터를 활용해서 전체 col 가져오기 for column in ws.iter_cols(): print(column)

# min_row, min_col, max_row, max_col 이 존재함 # 일부를 잘라서 출력 (1번째 줄부터 5번째 줄까지 출력) # iter_rows 이므로 반복문은 5번 돌게됨 (1 ~ 5) for row in ws.iter_rows(min_row=1, max_row=5): print(row) # 일부를 잘라서 출력 (1번째 줄부터 5번째 줄까지 출력, 2번째 열부터 3번째 열까지) # iter_rows 이므로 반복문은 5번 돌게됨 (1 ~ 5) for row in ws.iter_rows(min_row=1, max_row=5, min_col=2, max_col=3): print(row) # 일부를 잘라서 출력 (1번째 줄부터 5번째 줄까지 출력, 2번째 열부터 3번째 열까지) # iter_cols 이므로 반복문은 2번 돌게됨 (2 ~ 3) for col in ws.iter_cols(min_row=1, max_row=5, min_col=2, max_col=3): print(col)

참고사이트 1 : openpyxl 라이브러리 공식문서 https://openpyxl.readthedocs.io/

참고사이트 2 : 나도코딩 https://www.youtube.com/watch?v=exgO1LFl9x8&t=4285s

robots.txt 파일

robots.txt 파일

robots.txt 파일은 웹사이트에 로봇이 접근하는 것을 방지하기 위한 규약이다.

웹사이트를 크롤링하기 전에 robots.txt 파일 내용을 읽어보고 크롤링해도 되는지 판단하도록 한다.

모두 허용

User-agent: *

Allow: /

모두 차단

User-agent: *

Disallow: /

기타 다양한 조합이 가능하다.

# googlebot 로봇만 적용하며, private 디렉토리 접근을 차단한다.

User-agent: googlebot 

Disallow: /private/

# googlebot-news 로봇만 적용하며, 모든 접근을 차단한다.

User-agent: googlebot-news

Disallow: /

# something 디렉토리 접근을 차단한다.

User-agent: *

Disallow: /something/

아래는 각종 커뮤니티 사이트들의 robots.txt 파일 내용이며 참고하면 된다.

https://www.ppomppu.co.kr/robots.txt

User-agent: *

Allow: /zboard/

Disallow: /include/

Disallow: /zboard/view.php?id=market

Disallow: /zboard/view.php?id=market_phone

Disallow: /zboard/view.php?id=market_social

Disallow: /zboard/view.php?id=cmarket

Disallow: /zboard/view.php?id=onmarket

Disallow: /zboard/view.php?id=market_story

Disallow: /zboard/view.php?id=gonggu

Disallow: /zboard/view.php?id=my

Disallow: /zboard/view.php?id=ppomppu2

Disallow: /zboard/view.php?id=ppomppu7

Disallow: /zboard/view.php?id=ppomppu6

Disallow: /zboard/view.php?id=pmarket2

Disallow: /zboard/view.php?id=pmarket3

Disallow: /zboard/view.php?id=card_market

Disallow: /zboard/view.php?id=pmarket7

Disallow: /search_bbs.php

Disallow: /zboard/view_info2.php

Disallow: /bookmark/

Disallow: /openapi/

Sitemap: https://www.ppomppu.co.kr/sitemap.txt

https://www.clien.net/robots.txt

User-agent: *

Allow:/service/board/

Disallow:/service/group/

Disallow:/service/board/sold/

Disallow:/service/board/hongbo/

Disallow:/service/mypage/

Disallow:/service/message/

Disallow:/service/popup/

Disallow:/service/search/

Disallow:/service/cs/

User-agent: Daumoa

Allow:/service/board/sold/

User-agent: Daum

Allow:/service/board/sold/

User-agent: grapeshot

Disallow:

User-agent: Mediapartners-Google*

Disallow: 

User-Agent:*

Disallow:/ 

https://www.dcinside.com/robots.txt

User-agent: *

Disallow: /

# Ads

User-agent: grapeshot

Allow: /  

# Search

User-agent: Googlebot

Allow : / 

Crawl-delay: 60

User-agent: grapeshot

Allow: /

참고사이트 : https://youtu.be/udRyLx1W9v8

[Python] 파이썬 웹 크롤링 기초 2-2 : Scrapy

[Python] 파이썬 웹 크롤링 기초 2-2 : Scrapy

지난글에 이어서 파이썬 크롤링 라이브러리 스크래피(Scrapy) 설명을 보충한다.

지난글

https://blog.naver.com/bb_/222619390736

1. 스크래피 셀렉터(selector)

html 문서의 어떤 요소를 가져오기 위해서는 selector를 사용해야 한다.

스크래피는 xpath 셀렉터와 css 셀렉터 두 종류를 지원한다.

html 문서 예

<html>

  <head>

    <base href=’http://example.com/‘ />

    <title>Example website</title>

  </head>

  <body>

    <div id=’images’>

      <a href=’image1.html’>Name: My image 1 <br /><img src=’image1_thumb.jpg’ /></a>

      <a href=’image2.html’>Name: My image 2 <br /><img src=’image2_thumb.jpg’ /></a>

      <a href=’image3.html’>Name: My image 3 <br /><img src=’image3_thumb.jpg’ /></a>

      <a href=’image4.html’>Name: My image 4 <br /><img src=’image4_thumb.jpg’ /></a>

      <a href=’image5.html’>Name: My image 5 <br /><img src=’image5_thumb.jpg’ /></a>

    </div>

  </body>

</html>

xpath 셀렉터와 css 셀렉터 중 편한 방법을 사용하면 된다.

아래는 동일한 요소를 가져오는 세트이다.

response.xpath(‘//title/text()’)

response.css(‘title::text’)

response.xpath(‘//base/@href’).extract()

response.css(‘base::attr(href)’).extract()

response.xpath(‘//a[contains(@href, “image”)]/@href’).extract()

response.css(‘a[href*=image]::attr(href)’).extract()

response.xpath(‘//a[contains(@href, “image”)]/img/@src’).extract()

response.css(‘a[href*=image] img::attr(src)’).extract()

2. 스크래피 프로젝트 구조

먼저 스크래피 프로젝트를 생성한다.

# 스크래피 프로젝트 생성

scrapy startproject [프로젝트명]

ex) scrapy startproject community

이어서 파이썬 IDE인 파이참(pycharm)으로 해당 프로젝트 열기한다.

좌측 트리를 보면 스크래피 구조가 보인다.



(1) spiders 폴더 : 이 폴더 안에 실질적으로 크롤링 하는 로직 파일이 들어가게 된다.

예륻 들어 community_spider.py 라는 파일을 생성했다면, html 의 특정한 데이터들을 선택(select)해서 아이템으로 만드는 로직을 해당 파일에 구현하면 된다.

(2) items.py : 크롤링 대상을 아이템이라는 단위로 클래스화 가능하다.

(3) pipelines.py : 크롤링 시 파이프라인에서 데이터를 DB에 반영하거나, 데이터를 필터링할 수 있다.

데이터의 유효성 검사, 데이터 중복 체크, 데이터베이스에 아이템 저장, 필터링 등을 여기서 처리하면 된다.

(4) settings.py : 전체 프로젝트에 대한 설정 가능함.

파이프라인 순서를 결정할 수 있고, 로그 파일 지정하고 로그 파일의 레벨을 지정 가능하다.

아래와 같이 파이프라인을 지정할 수 있고, 여러개 써넣을 수도 있다.

실행순서를 숫자를 통해 지정가능한데 낮은 숫자가 먼저 실행된다.

ITEM_PIPELINES = {

    ‘community.pipelines.CommunityPipeline’: 300,

}

로그 파일명과 로그 레벨을 설정할 수 있다.

로그레벨은 5개가 있다. CRITICAL, ERROR, WARNING, INFO, DEBUG.

아래처럼 내용 추가하면 로그가 터미널에 쌓이지 않고 해당 파일에 떨어진다.

LOG_FILE = ‘scrapy.log’

LOG_LEVEL = logging.DEBUG

(5) scrapy.cfg : 전체 프로젝트를 배포할 때의 설정이다.

3. 스크래피 프로젝트 예제

스크래피 프로젝트를 만들었으면, 먼저 아이템을 정의한다. (items.py 파일 수정)

spiders 폴더 안에 새 파일을 추가해서 파싱 로직을 넣으면 된다. (ex : community_spider.py)

대상 url을 지정하는 방법은 크게 두 가지가 있다.

첫번째로 start_urls 이라는 변수명으로 스트링 리스트를 생성하는 방법이 있고,

두번째로 start_requests 함수를 정의해서 특정한 url에 대해서 콜백함수를 지정하는 방법이 있다. 각 url에 대해서 콜백함수를 지정해야 하는데, 보통 parse라는 이름으로 콜백함수를 만든다.

아래 예제에서는 두번째 방법을 사용한다.

(1) community_spider.py (spiders 폴더 하위에 새 파일 작성)

import scrapy

from community.items import CommunityItem

# scrapy.Spider 를 상속하는 CommunitySpider 클래스 생성

class CommunitySpider(scrapy.Spider):

    name = “communityCrawler”

    def start_requests(self):

        # 1페이지 ~ 2페이지 크롤링

        for i in range(1, 3, 1):

            # 1페이지 호출 시와 그렇지 않은 경우 url 체계가 달라서 분기처리함

            if i == 1:

                yield scrapy.Request(“http://it-archives.com/“, self.parse_wordpress)

            else:

                yield scrapy.Request(“http://it-archives.com/page/%d/” % i, self.parse_wordpress)

            yield scrapy.Request(“https://gall.dcinside.com/board/lists/?id=dcbest&page=%d” % i, self.parse_dcinside)

    def parse_wordpress(self, response):

        for sel in response.xpath(‘//article’):

            item = CommunityItem()

            item[‘source’] = ‘흑곰의 유익한 블로그 2호점’

            item[‘category’] = ‘wordpress’

            item[‘title’] = sel.xpath(‘header/h3[@class=”entry-title”]/a/text()’).extract()[0]

            item[‘url’] = sel.xpath(‘header/h3[@class=”entry-title”]/a/@href’).extract()[0]

            # yield : 아이템을 한 개씩 차곡차곡 쌓기

            yield item

    def parse_dcinside(self, response):

        for sel in response.xpath(‘//tr[@class=”ub-content us-post”]’):

            item = CommunityItem()

            item[‘source’] = ‘디시인사이드 실시간베스트’

            item[‘category’] = ‘dcinside’

            item[‘title’] = sel.xpath(‘td/a/text()’).extract()[0]

            item[‘url’] = ‘https://gall.dcinside.com/‘ + sel.xpath(‘td/a/@href’).extract()[0]

            # yield : 아이템을 한 개씩 차곡차곡 쌓기

            yield item

for문이 한 번 돌때마다 scrapy.Request 를 두 번 수행하도록 작성되었다.

첫번째 요청은 흑곰의 유익한 블로그 2호점, 두번째 요청은 디시인사이드 실시간베스트 게시판을 파싱한다.

각각의 콜백함수는 parse_wordpress 함수와 parse_dcinside 함수를 만들어서 구현했다.

(2) items.py

import scrapy

class CommunityItem(scrapy.Item):

    source = scrapy.Field()

    category = scrapy.Field()

    title = scrapy.Field()

    url = scrapy.Field()

    pass

아이템의 필드를 정의한다.

(3) pipelines.py

from scrapy.exceptions import DropItem

class CommunityPipeline:

    words_to_filter = [‘아이폰’, ‘안드로이드’]

    def process_item(self, item, spider):

        for word in self.words_to_filter:

            if word in item[‘title’]:

                raise DropItem(“Contains forbidden word: %s” % word)

            else:

                return item

아이폰, 안드로이드라는 텍스트가 포함되는 경우 아이템 추가하지 않도록 DropItem 이벤트를 발생시킨다.

이렇게 하면 나중에 “scrapy crawl communityCrawler -o output.json” 과 같은 명령어로 실행했을 때, 해당하는 아이템은 결과파일인 output.json 에서 제외된다.

(4) settings.py

# Configure item pipelines

# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html

ITEM_PIPELINES = {

    ‘community.pipelines.CommunityPipeline’: 300,

}

파일 내 ITEM_PIPELINES 부분을 검색해서 위와 같이 주석해제 및 수정한다.

scrapy crawl [크롤러명] 명령어로 크롤링을 실행해본다. (ex : scrapy crawl communityCrawler)


 

참고사이트 2 : https://youtu.be/LQS6QfDQ9RA

[Python] 파이썬 웹 크롤링 기초 2-1 : Scrapy

[Python] 파이썬 웹 크롤링 기초 2-1 : Scrapy

웹 크롤링이란 간단히 설명하면, 웹 페이지 내용을 긁어오는 행위를 뜻한다.


파이썬 웹 크롤링 라이브러리 Scrapy 는 (잘 알려진 다른 라이브러리인) Beautiful Soup 보다 다양하고 많은 기능을 제공하며 프레임워크 형태로 사용할 수 있다.


스크래피(Scrap + y) 라는 라이브러리 이름에서 알 수 있듯이 웹페이지에서 내용을 스크랩하는 기능을 제공한다.


1. 설치

(1) 리눅스(ex : 우분투) OS 사용하는 경우

sudo apt-get install libffi-dev libssl-dev

(파이썬 가상환경 사용하는 경우, “workon [가상환경명]” 입력)

pip install scrapy

(2) 윈도우 OS 사용하는 경우

(파이썬 가상환경 사용하는 경우, “workon [가상환경명]” 입력)

pip install scrapy

2. 특징

프레임워크 형태로 Beautiful Soup 보다 다양하고 많은 기능을 제공한다.

파이프라인 : 어떤 데이터를 가져올 때 가공/필터링할 수 있는 기능을 제공한다.

로깅 : 데이터 처리 시 관련 로그를 쓸 수 있다.

이메일 : 어떤 데이터가 들어왔을 때 이메일 전송이 가능하다.

3. 라이브러리 문서

https://doc.scrapy.org/en/0.24/intro/tutorial.html

4. 예제
4-1. 스크래피 프로젝트 예제
스크래피는 일종의 프레임워크 성격을 띄고 있어서, 프로젝트를 생성해서 사용한다.
# 스크래피 프로젝트 생성
scrapy startproject [프로젝트명]
ex) scrapy startproject testproject

 

cmd 터미널에서 명령어를 실행하면 스크래피 프로젝트 경로가 출력된다.

ex) C:\Users\gendev\testproject

파이썬 IDE 프로그램인 파이참(PyCharm) 에서 해당 폴더를 열어보자.

참고로 파이참 커뮤니티 버전은 무료다.

 

좌측의 폴더트리를 보면 이미 구조가 갖춰져있는 프레임워크 형태임을 알 수 있다.

아이템 파일(items.py)은 데이터를 가져올 때 해당 데이터를 클래스 형태로 만들 수 있도록 한다.

파이프라인 파일(pipelines.py)은 데이터를 후처리/가공하거나 필터링하는 기능을 담당한다. 예를 들면 데이터를 데이터베이스에 반영하는 코드를 추가할 수 있다.

세팅 파일(settings.py)은 스크래피 스파이더라고 하는 메인 프로그램에 대한 설정을 담당한다. 대표적으로 파이프라인의 순서를 지정하거나, 봇 이름을 변경할 수 있다.

스파이더 폴더(spiders) 안에 실질적으로 스크랩할 내용을 프로그래밍하면 된다.

(1) 웹페이지 html 전체내용 가져오기

spiders 폴더 하위에 bb_spider.py 라는 파일을 새로 생성하자.

bb_spider.py

import scrapy

class BBSpider(scrapy.Spider) :

    name = “bb”

    allowed_domains = [“it-archives.com”]

    start_urls = [

        “http://it-archives.com/page/8/“,

        “http://it-archives.com/page/9/

    ]

    def parse(self, response):

        filename = response.url.split(“/”)[-2] + “.html”

        with open(filename, ‘wb’) as f:

            f.write(response.body)

 

스파이더 이름은 bb로 정했기 때문에(name = “bb”), 터미널에서 명령어 “scrapy crawl bb”라고 입력하면 된다.

명령어 실행 후 좌측 파일트리를 닫았다 열면(새로고침하면) 8.html 과 9.html 파일이 생성되었을 것이다.

열어보면 요청한 주소에 해당하는 웹페이지 html 전체 내용이 다운로드되어 있을 것이다.

(2) 웹페이지 xpath 로 원하는 값 가져오기 (xpath 로 파싱하기)

대상 웹페이지 (http://it-archives.com/page/8/) 를 크롬이나 엣지 브라우저에서 개발자 도구(단축키 F12)를 열어 확인해보면, 아래와 같은 html 구조를 갖고 있다.



html의 각 엘리먼트는 xpath라고 불리우는 구조적 경로를 갖고 있는데, 이 xpath 를 알면 웹페이지의 내용을 파싱할 수 있다.

spiders 폴더 하위의 bb_spider.py 파일 내용을 수정하자.

bb_spider.py

import scrapy

class BBSpider(scrapy.Spider) :

    name = “bb”

    allowed_domains = [“it-archives.com”]

    start_urls = [

        “http://it-archives.com/page/8/“,

        “http://it-archives.com/page/9/

    ]

    def parse(self, response):

        for sel in response.xpath(‘//article’):

            title = sel.xpath(‘header/h3/a/text()’).extract()

            link = sel.xpath(‘header/h3/a/@href’).extract()

            desc = sel.xpath(‘header/div/a/time/text()’).extract()

            print (title, link, desc)

터미널에서 명령어 “scrapy crawl bb”라고 입력하면 아래와 같은 결과가 나온다.

 

 

(3) 웹페이지 파싱결과를 스크래피 아이템 객체에 담기

spiders 폴더 하위의 bb_spider.py 파일 내용을 수정하자.

bb_spider.py

import scrapy

from testproject.items import BBItem

class BBSpider(scrapy.Spider) :

    name = “bb”

    allowed_domains = [“it-archives.com”]

    start_urls = [

        “http://it-archives.com/page/8/“,

        “http://it-archives.com/page/9/

    ]

    def parse(self, response):

        for sel in response.xpath(‘//article’):

            item = BBItem()

            item[‘title’] = sel.xpath(‘header/h3/a/text()’).extract()

            item[‘link’] = sel.xpath(‘header/h3/a/@href’).extract()

            item[‘desc’] = sel.xpath(‘header/div/a/time/text()’).extract()

            yield item

이어서 items.py 파일 내용을 수정하자.

items.py

# Define here the models for your scraped items

#

# See documentation in:

# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy

class TestprojectItem(scrapy.Item):

    # define the fields for your item here like:

    # name = scrapy.Field()

    pass

# 아이템 클래스 추가. scrapy.Item 를 상속받는다.

class BBItem(scrapy.Item):

    title = scrapy.Field()

    link = scrapy.Field()

    desc = scrapy.Field()

    pass

터미널에서 명령어 “scrapy crawl bb”라고 입력하면 아래와 같은 결과가 나온다.

print 함수를 따로 사용하지 않았지만 debug 로그를 통해 아이템 내용이 출력된다.

 

(4) 스크래피 아이템 객체에 저장된 내용을 결과파일 형태로 출력하기

이렇게 파싱한 결과를 파일로 저장하고 싶다면, 코드 내용은 그대로 두고 -o 옵션을 사용하면 된다.

scrapy crawl bb -o items.json 명령어를 사용하면 아래와 같이 json 파일이 결과파일로 떨어진다.

 

결과 json 파일을 다른 원하는 곳으로 보내서 가공하는 방법도 있고, pipelines.py 파일을 수정해서 데이터를 후처리할 수도 있다. 예를 들면 원하는 데이터를 DB에 반영하는 코드를 추가할 수 있다.

4-2. 스크래피 쉘 예제

프로젝트를 만들지 않고 대화형 쉘을 사용해서 스크래피 기능을 실행할 수 있다.

먼저 cmd 터미널에서 scrapy shell “http://it-archives.com/page/8/” 명령어를 실행해본다.

 

쉘이 실행되면 response.xpath(‘//title’) 이라고 입력하면 타이틀 태그를 가져온다.

=> [<Selector xpath=’//title’ data='<title>흑곰의 유익한 블로그 2호점 – 페이지 8 – 당신의 …’>]

이때 “//”는 모든 요소를 다 가져오기 한다는 의미이다.

결과는 list 형태로 반환된다.

# a 태그를 다 가져오기

response.xpath(‘//a’)

=> [<Selector xpath=’//a’ data='<a class=”skip-link screen-reader-tex…’>, <Selector xpath=’//a’ data='<a href=”http://it-archives.com/” rel…’>, <Selector xpath=’//a’ data='<a href=”#content” class=”menu-scroll…’>, <Selector xpath=’//a’ data='<a href=”http://it-archives.com/22163…‘>, <Selector xpath=’//a’ data='<a href=”http://it-archives.com/22163…‘>, <Selector xpath=’//a’ data='<a href=”http://it-archives.com/22163…‘>, <Selector xpath=’//a’ data='<a href=”http://it-archives.com/22163…‘>, (중략)

# 값을 가져올 때는 extract() 함수 사용

response.xpath(‘//title’).extract()

=> [‘<title>흑곰의 유익한 블로그 2호점 – 페이지 8 – 당신의 업무력 향상과 칼퇴를 돕는 블로그</title>’]

response.xpath(‘//title/text()’)

=> [<Selector xpath=’//title/text()’ data=’흑곰의 유익한 블로그 2호점 – 페이지 8 – 당신의 업무력 향상과…’>]

response.xpath(‘//title/text()’).extract()

=> [‘흑곰의 유익한 블로그 2호점 – 페이지 8 – 당신의 업무력 향상과 칼퇴를 돕는 블로그’]

# 정규표현식 사용하려면 re 함수 사용

# 정규표현식으로 단어별로 가져오기

response.xpath(‘//title/text()’).re(‘(\w+)’)

=> [‘흑곰의’, ‘유익한’, ‘블로그’, ‘2호점’, ‘페이지’, ‘8’, ‘당신의’, ‘업무력’, ‘향상과’, ‘칼퇴를’, ‘돕는’, ‘블로그’]

5. 활용

크롤러/크롤링 프로그램은 주기적으로 실행하고 싶은 경우.

리눅스(ex : 우분투) OS를 사용한다면 “crontab” 을 활용하면 되고, 윈도우 OS를 사용한다면 “작업 스케줄러” 를 활용하면 된다.

자세한 내용은 검색을 통해 해결하자.

다음글

https://blog.naver.com/bb_/222620363771

관련글

https://blog.naver.com/bb_/222619333734

[Python] 파이썬 웹 크롤링 기초 1 : Beautiful Soup

[Python] 파이썬 웹 크롤링 기초 1 : Beautiful Soup

자바에 jsoup 라이브러리가 있듯이, 파이썬에는 Beautiful Soup 이라는 웹 크롤링 라이브러리가 있다.

웹 크롤링이란 간단히 설명하면, 웹 페이지 내용을 긁어오는 행위를 뜻한다.

Beautiful Soup 은 html이나 xml 형태의 문서에서 원하는 내용을 가져올 수 있도록 돕는다.

2022년 1월 현재 Beautiful Soup 의 버전은 4.9.0 버전이며 예제는 파이썬 2.7 과 Python 3.2 에서 똑같이 작동한다.

1. 설치

(1) 리눅스(ex : 우분투) OS 사용하는 경우

sudo apt-get install libxml2-dev libxslt-dev python-dev zliblg-dev

sudo apt-get install python-lxml

(파이썬 가상환경 사용하는 경우, “workon [가상환경명]” 입력)

pip install lxml

pip install beautifulsoup4

(2) 윈도우 OS 사용하는 경우

(파이썬 가상환경 사용하는 경우, “workon [가상환경명]” 입력)

pip install lxml

pip install beautifulsoup4

2. 특징

파이썬 Beautiful Soup 은 lxml, html5lib 파서를 이용하는 라이브러리로, html 문서 내용을 가져와서 정보를 파싱(분석)해주는 파서(Parser, 분석기) 역할을 한다.

웹페이지 내용을 가져올 때 인코딩을 유니코드로 변환해서 UTF-8로 출력해준다.

3. 라이브러리 문서

https://www.crummy.com/software/BeautifulSoup/bs4/doc/

위 사이트의 내용을 토대로 개발하면 된다.
태그 객체 가져오기(find_all), 자식노드 가져오기(.children), 형제노드 가져오기(.next_sibling 와 .previous_sibling) 등 예제 코드가 자세히 나와있다.
4. 예제
test.py 파일을 작성하고 cmd 에서 python test.py 명령어로 실행해본다.
test.py

html_doc = “””<html><head><title>The Dormouse’s story</title></head>

<body>

<p class=”title”><b>The Dormouse’s story</b></p>

<p class=”story”>Once upon a time there were three little sisters; and their names were

<a href=”http://example.com/elsie” class=”sister” id=”link1″>Elsie</a>,

<a href=”http://example.com/lacie” class=”sister” id=”link2″>Lacie</a> and

<a href=”http://example.com/tillie” class=”sister” id=”link3″>Tillie</a>;

and they lived at the bottom of a well.</p>

<p class=”story”>…</p>

“””

from bs4 import BeautifulSoup

soup = BeautifulSoup(html_doc, ‘html.parser’)

# html 내용 출력하기

print(soup.prettify())

 

결과

<html>

 <head>

  <title>

   The Dormouse’s story

  </title>

 </head>

 <body>

  <p class=”title”>

   <b>

    The Dormouse’s story

   </b>

  </p>

  <p class=”story”>

   Once upon a time there were three little sisters; and their names were

   <a class=”sister” href=”http://example.com/elsie” id=”link1″>

    Elsie

   </a>

   ,

   <a class=”sister” href=”http://example.com/lacie” id=”link2″>

    Lacie

   </a>

   and

   <a class=”sister” href=”http://example.com/tillie” id=”link3″>

    Tillie

   </a>

   ;

and they lived at the bottom of a well.

  </p>

  <p class=”story”>

   …

  </p>

 </body>

</html>

만약 특정 웹페이지의 내용을 가져오고 싶다면 requests 모듈을 임포트해서 활용한다.

request 모듈을 임포트하려면 (파이썬 가상환경 등에서) pip install requests 명령어로 미리 설치해야 한다.

import requests

html = requests.get(‘https://google.com/‘)

html_doc = html.text

from bs4 import BeautifulSoup

soup = BeautifulSoup(html_doc, ‘html.parser’)

# html 내용 출력하기

print(soup.prettify())

 

5. 자주 사용하는 명령어
(1) 태그명으로 객체 가져오기
soup.태그명 으로 첫번째 객체 1개를 가져올 수 있다.
# title 태그 가져오기
soup.title
=> <title>The Dormouse’s story</title>
# 객체의 태그명 가져오려면 .name 사용
soup.title.name
=> ‘title’

# 객체의 내용 가져오려면 .string 사용
soup.title.string
=> “The Dormouse’s story”

 

# p 태그 가져오기
soup.p
=> <p class=”title”><b>The Dormouse’s story</b></p>
# 객체의 클래스 가져오려면 [‘class’] 사용
soup.p[‘class’]
=> [‘title’]
# a 태그 가져오기
soup.a
=> <a class=”sister” href=”http://example.com/elsie” id=”link1″>Elsie</a>
(2) find_all
find_all 함수는 가장 많이 사용하게 되는 명령어로, 여러개 태그 객체를 리스트 형태로 반환한다.
# a 태그 가져오기
soup.find_all(‘a’)
=> [<a class=”sister” href=”http://example.com/elsie” id=”link1″>Elsie</a>, <a class=”sister” href=”http://example.com/lacie” id=”link2″>Lacie</a>, <a class=”sister” href=”http://example.com/tillie” id=”link3″>Tillie</a>]
# 정규표현식 활용해서 find_all 사용하기 첫번째
# b로 시작하는 모든 태그를 가져온다. 결과는 body 태그와 b 태그를 가져온다.
import re
soup.find_all(re.compile(“^b”))

=> [<body>
<p class=”title”><b>The Dormouse’s story</b></p>
<p class=”story”>Once upon a time there were three little sisters; and their names were
<a class=”sister” href=”http://example.com/elsie” id=”link1″>Elsie</a>,
<a class=”sister” href=”http://example.com/lacie” id=”link2″>Lacie</a> and
<a class=”sister” href=”http://example.com/tillie” id=”link3″>Tillie</a>;
and they lived at the bottom of a well.</p>
<p class=”story”>…</p>
</body>, <b>The Dormouse’s story</b>]
# 정규표현식 활용해서 find_all 사용하기 두번째
# b로 시작하는 모든 태그를 가져와서 태그명을 출력한다. 결과는 body 와 b 가 출력된다.
import re
for tag in soup.find_all(re.compile(“^b”)): print(tag.name)
=> body
b
# a 태그와 b 태그 둘 다 가져오기
soup.find_all([“a”, “b”])
=> [<b>The Dormouse’s story</b>, <a class=”sister” href=”http://example.com/elsie” id=”link1″>Elsie</a>, <a class=”sister” href=”http://example.com/lacie” id=”link2″>Lacie</a>, <a class=”sister” href=”http://example.com/tillie” id=”link3″>Tillie</a>]
# id 값으로 태그 가져오기 (ex : id 값이 “link2″인 태그)
soup.find_all(id=’link2′)
=> [<a class=”sister” href=”http://example.com/lacie” id=”link2″>Lacie</a>]
# 특정 어트리뷰트의 값으로 태그 가져오기 (ex : href 어트리뷰트 값이 “http://example.com/elsie“인 태그)
soup.find_all(attrs={“href”: “http://example.com/elsie“})
=> [<a class=”sister” href=”http://example.com/elsie” id=”link1″>Elsie</a>]
# p 태그이면서 클래스 값이 “title”인 태그 가져오기
soup.find_all(“p”, class_=”title”)
=> [<p class=”title”><b>The Dormouse’s story</b></p>]
관련글

개발/프로그래밍을 입문하는 사람들을 위한 3가지 조언

개발/프로그래밍을 입문하는 사람들을 위한 3가지 조언

 

안녕하세요, 흑곰입니다.

2022년 1월, IT·컴퓨터 부문에서 네이버 이달의 블로그로 선정되었습니다.

이 블로그에 방문해주시고, 댓글 달아주신 여러분 덕분입니다.

정말 감사드립니다.

 

작년 2021년은 특별한 해였습니다.

개인적으로 가장 큰 이벤트로는 2021년 12월 31일부로 다니던 회사를 퇴사하는 일이 있었습니다.

회사에서는 좋은 사람들을 만났고, 개발자로서 성장했고, 항상 좋은 평가만을 받아왔습니다.

나올 때도 칭찬과 격려를 받으며 나왔습니다.

회사 분들이 이 글을 읽으실지는 모르겠지만, 이 자리를 빌어 다시 한번 감사드립니다.

 

저는 만으로 6년 4개월 경력, 7년차 자바 개발자였습니다.

자바로 웹사이트를 만들어서 기업에 납품하는 솔루션 회사를 다녔습니다.

백엔드, 프론트엔드, DB, 안드로이드, 아이폰 가리지 않고 했으니 풀스택 개발자라고 볼 수도 있겠네요. (어찌보면 전문성 없는 개발자이기도 하고요.)

이 블로그에는 개발/프로그래밍을 처음 입문하시는 분들이 꽤 오시는 것으로 압니다.

주제넘지만 이달의 블로그 기념으로(?) 그분들에게 조언을 좀 드려보려고 합니다.

<개발/프로그래밍을 입문하는 사람들을 위한 3가지 조언>

첫번째 조언. 개발 목표를 생각해보기


 

목표에 따라 학습과정이 달라집니다.

(1) 목표가 일단 프로그래밍을 경험해본다거나, 입문해본다는 목적이라면 언어는 중요하지 않습니다. 간결한 언어로 꼽히는 [파이썬]을 추천드립니다.

(2) 개발을 전혀 모르는데 무언가를 만들어보는 것이 목표라면 꼭 코드가 아니어도 될겁니다. [노코드]를 검색해보시기 바랍니다. 게임 개발을 원하시면 [유니티]를 검색해보세요.

그래도 코딩을 통해 무언가를 만들어보고 싶다면 [프론트엔드], 그 중에서도 [자바스크립트]를 배워보시기 바랍니다.

(3) 직업 개발자가 되고 싶다, 개발자 커리어를 밟고 싶으신 분이라면 언어 이전에 1 회사 분류와 2 도메인을 고민해보시기 바랍니다.

두번째 조언. (만약 직업 개발자를 해보고 싶다면) 회사 분류와 도메인을 고민해보기


처음 입문하시는 분들일수록 개발 언어에 신경을 쓰시는데, 언어의 중요도는 3순위입니다.

경력자 분들은 모두 알고 계시지만, 입문자들은 잘 모르는…

저도 개발 일을 하고 몇 년이 지나서야 알게된 천기누설 내용입니다.

(1) 회사 분류(또는 직무 분류)

(2) 도메인

이 두 가지가 개발 언어보다 중요하다고 생각합니다.

이 두 가지는 처음에 정해두면 바꾸기가 쉽지 않습니다. (물론 실력만 있다면 언제든 바꿀 수 있긴 합니다.)

(1) IT회사 분류 고민해보기

IT회사 분류는 크게 SI, SM, 솔루션, 서비스 네 가지가 있습니다.

– SI (System Integration) 는 프로그램을 의뢰받아서 개발하는 형태입니다. 예로 삼성SDS, LG CNS, 한화시스템, 농심NDS 등의 대기업 SI회사들이 있고, 무수한 중소SI 회사들이 있습니다.

– SM (System Management) 은 운영하고 있는 프로그램을 수정하거나 개선하는 유지보수 업무를 뜻합니다. IT회사가 아닌 일반회사의 IT부서/전산실에서 유지보수 업무를 하는 경우가 대표적인 SM 이라고 볼 수 있습니다.

– 솔루션은 미리 만들어둔 프로그램을 판매하는 형태입니다. 국내 솔루션 회사들은 일부 커스터마이징해서 개인이나 회사에 납품하는 SI 성격을 갖고 있기도 합니다. 예로 마이크로소프트의 윈도우 OS, 한글과컴퓨터의 한컴오피스, 안랩의 V3, 더존의 ERP 솔루션, 파수의 문서DRM 등이 있습니다.

– 서비스는 개인이나 회사가 상시 사용할 수 있도록 웹/앱 프로그램을 라이브로 운영하는 형태입니다. 예로 네이버, 카카오톡, 쿠팡, 배달의 민족 등이 있으며 요새 가장 각광받는 회사 분류입니다.

만약 서비스 회사를 다니고 싶다면 [프론트엔드]를 할 것인가, [백엔드]를 할 것인가를 먼저 정해보는게 좋습니다.

(2) 도메인 고민해보기

도메인은 영역/분야라는 뜻입니다. 크게는 인공지능(AI), 빅데이터, 보안, 금융, 게임 등으로 나눌 수 있습니다.

개발 경력이 많으신 분들은 아시겠지만, 이직 시 가장 중요한 요소 중의 하나입니다.

도메인은 큰 범위도 중요하지만, 세부적인 분류가 많습니다.

입사하고 싶은 기업의 모집공고를 자세히 읽어보는 과정이 필요합니다.

세번째 조언. 조력자 구하기

 

모든 일은 사람에게 배우는 것이 가장 빠릅니다.

코치, 강사, 선배, 교수님 등 경력이 있는 누구든 좋습니다.

뭔가를 물어볼 수 있는 조력자를 구해보시기 바랍니다. 조력자를 넘어 멘토가 될 수 있다면 더할 나위 없습니다.

유튜브, 인터넷 커뮤니티, 아니면 이런 블로그에 댓글을 달아도 좋고요.

혹시 저의 조언이라도 필요하다면 언제든 조언해드릴 수 있습니다. (그렇지만 저보다 좋은 조력자를 구해보시기 바랍니다.)

참고로 저는 명문대를 나오지도 않았으며, 대기업에 다녀본 것도 아니고, 유명한 회사에 다녀보지도 않았습니다.

작은 회사에서 개발자로 6년 넘게 성실히 일했을 뿐입니다.

이 경험이 혹시나 어떤 분에게 조그만 도움이라도 될 수 있다면, 제가 아는 한도 내에서는 최대한 답변드리겠습니다.

주로 오후 1시 ~ 밤 10시 사이에 답변을 드릴 수 있을 것 같네요.

카카오톡 1:1 오픈채팅

https://open.kakao.com/o/syQsKzTd

[Python] 파이썬 가상환경 구성하기 (virtualenv, virtualenv-wrapper)

[Python] 파이썬 가상환경 구성하기 (virtualenv, virtualenv-wrapper)

파이썬 가상환경은 독립적인 공간을 구성하고 패키지를 설치해서 사용하는 방법이다.

특정 가상환경 내에서 패키지를 설치하는 형태이기 때문에, 파이썬 프로그램마다 의존하는 패키지 버전이 다른 경우 버전 충돌을 방지할 수 있다.

예를 들어 2개의 파이썬 프로그램이 동일한 A 라는 패키지를 사용하는데, 한 프로그램은 A 패키지의 1.0 버전을, 다른 프로그램은 A 패키지의 2.0 버전을 사용하는 경우, 파이썬 가상환경을 사용해서 버전 충돌을 방지할 수 있다.

1. 파이썬 설치

우선 파이썬이 설치된 상태여야 한다.

윈도우 운영체제에서 파이썬 설치하기는 다음 포스트 참고하면 된다. => https://blog.naver.com/bb_/221716543567

2. 파이썬 가상환경 패키지 설치
이어서 파이썬 가상환경을 위한 패키지인 virtualenv 와 virtualenvwrapper 를 설치하자. (우분투 등 리눅스 운영체제를 사용하는 경우)
만약 윈도우 운영체제를 사용하는 경우 virtualenv 와 virtualenvwrapper-win 를 설치해야 한다.
(1) 우분투(리눅스)의 경우

sudo pip install virtualenv

sudo pip install virtualenvwrapper

(2) 윈도우의 경우

윈도우는 sudo 명령어가 없으므로, [명령 프롬프트] (cmd) 터미널을 [관리자 권한으로 실행]해서 설치해야 한다.

pip install virtualenv

pip install virtualenvwrapper-win

터미널에서 mkvirtualenv 명령어를 입력했을 때 인식하면 설치된 것이다.

3. 환경변수 설정

WORKON_HOME 라는 이름의 환경변수를 설정한다.
이 과정을 생략하면 임의의 기본값으로 진행된다.
(1) 우분투(리눅스)의 경우

cd

vi .bashrc

vi .bashrc 명령어를 통해 배시 프로파일이 열리면, 파일 가장 하단에 아래 내용을 추가한다.

export WORKON_HOME=[원하는 디렉토리 경로]

source /usr/local/bin/virtualenvwrapper .sh

예를 들어 아래와 같이 내용을 추가하고 저장하면 된다.

export WORKON_HOME=$HOME/.virtualenvs

source /usr/local/bin/virtualenvwrapper .sh

파일 편집을 마친 이후, 환경변수 적용이 필요하다.

리눅스에서는 source .bashrc 를 입력해서 환경변수를 적용할 수 있다.

(2) 윈도우의 경우

– 환경변수 값 조회하기
WORKON_HOME 이라는 환경변수명의 값을 확인해야 한다.

set WORKON_HOME

참고로 set 이라고만 입력하면 환경변수명과 상관없이 모든 환경변수 값을 조회한다.

– 환경변수 값 설정하기

setx WORKON_HOME [원하는 디렉토리 경로]

예를 들어 아래와 같이 입력하면 된다.

setx WORKON_HOME C:\Users\gendev\Envs

명령어를 입력한 이후, 환경변수 적용이 필요하다.

set WORKON_HOME 명령어로 조회해보면 알겠지만 값이 적용되어 있지 않다.

새로 실행하는 [명령 프롬프트] (cmd) 부터 적용이 된다.

4. 가상환경 만들기

mkvirtualenv [가상환경명]

예를 들면 mkvirtualenv bb 라고 입력하면 bb라는 이름의 가상환경이 구성된다.



5. 가상환경 진입, 가상환경 빠져나오기, 가상환경 제거


가상환경에 진입하려면 workon 명령어를 사용한다. 예를 들면 workon bb 라고 입력하면 된다.

workon [가상환경명]


가상환경에서 빠져나오려면 deactivate 명령어를 사용한다. 가상환경 안에 진입한 상태에서 써야 한다.

deactivate

가상환경을 더 이상 사용하지 않도록 완전히 제거하려면 rmvirtualenv 명령어를 사용한다. 예를 들어 rmvirtualenv bb 라고 입력하면 된다.

rmvirtualenv [가상환경명]


이렇게 파이썬 가상환경 구성이 가능한 이유는, 각 가상환경 폴더 하위 Lib 폴더 안에 모든 패키지를 설치하기 때문이다.
예를 들어 WORKON_HOME 환경변수 값이 C:\Users\gendev\Envs 이고, 가상환경명이 bb 인 경우, C:\Users\gendev\Envs\bb\Lib 폴더에 접근해보면 해당 가상환경에서 사용하는 패키지들이 설치되어 있음을 확인할 수 있다.
참고사이트 : https://technerd.tistory.com/52

[Python] ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 액세스가 거부되었습니다

[Python] ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 액세스가 거부되었습니다

파이썬에서 “pip install 설치패키지명” 와 같은 명령어를 입력했는데, “ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 액세스가 거부되었습니다” 오류가 발생하는 경우.

 

윈도우 운영체제를 사용하는 경우 [시작] – [명령 프롬프트](cmd) – 마우스우클릭 해서 [관리자 권한으로 실행] 하면 된다.

우분투 등 리눅스 운영체제를 사용하는 경우 명령어 앞에 sudo 를 붙여서 재시도해본다. (ex : sudo pip install virtualenv)

참고사이트 : https://blog.naver.com/kiddwannabe/221795742358

Node.js 공부기록 (6) 모던 자바스크립트, let 과 const

Node.js 공부기록 (6) 모던 자바스크립트, let 과 const

모던 자바스크립트

https://tc39.es/

비영리 기구 Ecma International은 JavaScript를 포함한 다양한 기술 표준 정립을 목적으로 하는 단체임

그 중 TC39 위원회(committee)는 자바스크립트(ECMAScript) 표준 제정을 담당함

이 위원회에는 MS, Google, Apple 등 웹 기술과 관계가 깊은 거대 기술 벤더들이 참여함

ECMAScript == JavaScript 

(에크마스크립트는 자바스크립트의 기술적인 표준을 뜻함

상표권 문제로 자바스크립트라고 부를 수 없는 것이지, 에크마스크립트는 자바스크립트라고 보면 됨.)

대부분의 논의는 github에 공개되어 있음

https://github.com/tc39 에서 People을 클릭하면 위원회 인원들을 볼 수 있음 (자바스크립트 표준 제정 일을 하심)

https://tc39.es/process-document/

* The TC3 Process

Stage 0 (Strawperson) – 누구나 제출은 가능함. 초안에 해당됨

Stage 1 (Proposal) – 여기서부터 좀 의미가 있는데, 이끌어갈 챔피언을 선정함. 폴리필, 데모 등이 필요함

Stage 2 (Draft)

Stage 3 (Candidate)

Stage 4 (Finished) – 거의 모든 브라우저에 적용된 상태

뒤로 갈 수록 성숙한 상태라고 보면 됨 

어떤 프로포절들이 있는지 살펴보자. 아래 리파지토리 참고

https://github.com/tc39/proposals

자바스크립트 라는 언어가 어떻게 진화하고 있는지 과정을 퍼블릭하게 조회 가능함

https://node.green/ 에서 Node의 각 버전별로 지원하는 ECMAScript 기능을 확인 가능함

—–

<let, const>

let taste = ‘hot’

let food = ‘chicken’

const dish = `${taste} ${food}` // ‘hot chicken’

hoisting 규칙이 없고, block scoping을 지원함.

따라서 var보다 훨씬 에측 가능한 코드를 짤 수 있게 해줌

* const : constant(상수)에서 따온 단어

* let은 레퍼런스가 바뀔 수 있고, const는 바뀔 수 없음

let x = 1

x = 2 // OK

const y = 1

y = 2 // Uncaught TypeError: Assignment to constant variable.

* var와는 달리 let과 const는 같은 스코프 내에서 같은 변수를 두 번 이상 정의할 수 없음

var x = 1

var x = 2 // OK

let x = 1

let x = 2 // Uncaught SyntaxError: Identifier ‘x’ has already been declared

* var 와는 달리 let과 const는 변수를 정의하기 전에는 사용할 수 없음

console.log(x) // undefined

var x = 0

console.log(x) // ReferenceError: Cannot access ‘x’ before initialization

const x = 0

* block scoping

(let 과 const는 block scoping rule을 따름.

var 는 function scoping rule을 따름)

var x = 1

{

    var x = 2

console.log(x) // 2

}

console.log(x) // 2

const x = 1

{

    const x = 2

console.log(x) // 2

}

console.log(x) // 1

* let과 const의 예측 가능성과 유지보수성이 var보다 훨씬 뛰어남

* 가능하다면 const만 쓰고, 필요한 경우에 한해 let을 쓰고, var는 절대 쓰지 말것

Node.js 공부기록 (5) 프로토타입

Node.js 공부기록 (5) 프로토타입

자바스크립트에서 상속을 구현 가능한 개념인 프로토타입

함수형으로 짜거나, OOP와 관계없이 작성하면 프로토타입을 다루지 않을 수도 있음.

function Student(name) {

    this.name = name

}

현재 js에서 클래스라는 키워드를 제공하긴 하지만,

클래스라는 키워드가 존재하기 전에는, 함수를 정의하듯이 “function 클래스명” 으로 클래스를 만들어왔다.

js의 클래스는 사실상 프로토타입을 기반으로 한 function 이다.

const me = new Student(‘Jeongho’)

console.log(me)

실행하면

Student { name: ‘Jeongho’ }가 찍힌다.

me.name 을 찍을 수도 있지만,

me.toString() 을 사용할 수도 있다.

프로토타입 체인이라는 개념이 있음.

가장 상위부터 찾는다는 개념임. 상위에서 가장 가까운 것을 찾음.

me.name 은 가장 상위에 갖고 있다. toString() 은 가장 상위에 없으므로, 그 하위인 프로토타입 안에서 찾는다.

function Student(name) {

    this.name = name

}

Student.prototype.greet = function greet() {

    return `Hi, ${this.name}!`

}

const me = new Student(‘Jeongho’)

console.log(me.greet())

상속 구조를 만들어보자.

/* eslint-disable */

function Person(name) {

  this.name = name

}

Person.prototype.greet = function greet() {

  return `Hi, ${this.name}!`

}

function Student(name) {

  // 부모의 생성자 실행

  this.__proto__.constructor(name)

}

Student.prototype.study = function study() {

  return `${this.name} is studying.`

}

// Student의 프로토타입을 Person 프로토타입으로 지정 (상속)

Object.setPrototypeOf(Student.prototype, Person.prototype)

const me = new Student(‘Jeongho’)

console.log(me.greet())

console.log(me.study())

* instance of

const me = new Student(‘Jeongho’)

console.log(me instanceof Student)

console.log(me instanceof Person)

둘 다 true로 나온다.

const anotherPerson = new Person(‘Foo’)

console.log(anotherPerson instanceof Student)

console.log(anotherPerson instanceof Person)

순서대로 false, true가 나온다.

instance of 는 어레이 검사에 좋다.

[] instance of Array 는 true가 나온다.

물론 어레이는 오브젝트 이기도 하므로, [] instance of Object 도 true가 나온다.

지금까지의 설명은 ES5이다.

같은 내용을 클래스로 다시 해보자. (ES6)

/* eslint-disable */

class Person {

  constructor(name) {

    this.name = name

  }

  greet() {

    return `Hi, ${this.name}.`

  }

}

class Student extends Person {

  constructor(name) {

    super(name)

  }

  study() {

    return `${this.name} is studying.`

  }

}

const me = new Student(‘JeongHo’)

console.log(me.study())

console.log(me.greet())

클래스 역시 프로토타입 체인을 활용한 것이다. (내부적으로 앞의 것과 다를바 없다)

클래스는 신택틱 슈거일 뿐이다.

syntactic sugar

문법적 설탕, 신택틱 슈거

– 사람이 이해하기 쉽고 표현하기 쉽게 컴퓨터 언어를 디자인해 놓은 문맥

– 내부적인 동작은 기존과 동일하지만, 어떤 구현 방식에 맞추어 새로운 문법을 제공하는 경우를 가리킴

Node.js 공부기록 (4) closure

Node.js 공부기록 (4) closure

<JavaScript 기초 이론 다지기> closure

closure = function + environment

(클로져란, 함수와 환경의 합이다.)

closure는 function 이 하나 생길 때마나 하나씩 생깁니다.

environment 는 함수 자신을 둘러싼, 접근할 수 있는 모든 스코프를 뜻합니다.

Closure 예시

function and(x) {

    return function print(y) {

    return x + ‘ and ‘ + y

    }

}

const saltAnd = and(‘salt’)

console.log(saltAnd(‘pepper’)) // salt and papper

console.log(saltAnd(‘sugar’)) // salt and sugar

and 함수로 만들어진 saltAnd의 closure는:

함수: print

환경: x => “salt”

and 함수는 higher-order function 이다. (고차원 함수, 고차함수)

closure는 higher-order function을 만드는 데 유용하다.

const waterAnd = and(‘water’)

console.log(waterAnd(‘juice’)) // water and juice

saltAnd와 waterAnd는 모두 함수는 같은 print이지만, 각각 주어진 변수가 다르다.

saltAnd는 x가 “salt”, waterAnd는 x가 “water”로 바인딩 되어 있다.

즉, 둘은 서로 다른 closure를 형성하고 있다.

function foo() {

    function bar() {

}

function baz() {

}

}

foo()

이 코드의 실행과정에서 closure는 총 몇 개가 생겼을까?

정답은 3개 (foo 1개, bar 1개, baz 1개)

function foo() {

    function bar() {

}

function baz() {

}

}

foo()

foo()

이 코드의 실행과정에서 closure는 총 몇 개가 생겼을까?

정답은 5개 (foo 1개, bar 2개, baz 2개)

function getCounter() {

    var result = {

    count: count,

total: 0

}

function count() {

    result.total += 1

}

return result

}

var counter = getCounter()

counter.count()

counter.count()

console.log(counter.total)

실행결과는 2가 나온다.

function getCounter() {

    var result = {

    count: count,

total: 0

}

function count() {

    result.total += 1

}

return result

}

var counterA = getCounter()

counterA.count()

counterA.count()

var counterB = getCounter()

counterB.count()

console.log(counterA.total, counterB.total)

실행결과는 2 1 이다.

counterA : 첫 getCounter 실행 때 만들어진 total과 count로 이루어진 객체

counterB: 두번째 getCounter 실행 때 만들어진 total과 count로 이루어진 객체

var numCounters = 0

function getCounter() {

    numCounters += 1

    var result = {

    count: count,

total: 0

}

function count() {

    result.total += 1

}

return result

}

var counterA = getCounter()

counterA.count()

counterA.count()

var counterB = getCounter()

counterB.count()

console.log(counterA.total, counterB.total, numCounters)

실행결과는 2 1 2 이다.

* node 디버거 활용하기

1. vscode 실행

2. .vscode 하위에 launch.json 파일 생성

3. 우측 하단의 [구성 추가…]([Add Configurations…]) 버튼 클릭

4. 이어서 [Node.js : Launch via npm] 항목 클릭

5. 아래처럼 내용 추가됨

{

    “configurations”: [

    {

        “name”: “Launch via NPM”,

        “request”: “launch”,

        “runtimeArgs”: [

            “run-script”,

            “debug”

        ],

        “runtimeExecutable”: “npm”,

        “skipFiles”: [

            “<node_internals>/**”

        ],

        “type”: “pwa-node”

    }

    ]

}

package.json 파일로 가서, 상단에

“scripts” : {

    “debug”:”node src/main.js”

},

추가하기

ex)

{

  “scripts”: {

    “debug”:”node src/main.js”

  }

}

src 폴더 하위에 main.js 파일 추가 및 내용 수정하기

/* eslint-disable */

var numCounters = 0

function getCounter() {

  numCounters += 1

  var result = {

    count: count,

    total: 0,

  }

  function count() {

    result.total += 1

  }

  return result

}

var counterA = getCounter()

counterA.count()

counterA.count()

var counterB = getCounter()

counterB.count()

console.log(counterA.total, counterB.total, numCounters)

F5 키 누르면 디버그 시작

Node.js 공부기록 (3) 자바스크립트 기초 이론

Node.js 공부기록 (3) 자바스크립트 기초 이론

이벤트 루프, 펑션, 스코프, 호이스팅 등.

클로저, 프로토타입 등.

이벤트 루프 

자바스크립트의 동시성 모델을 잘 이해해보자. 동시성 모델은 실행 모델 이라고도 함.

자바스크립트의 동시성 모델 (실행 모델) 은 이벤트 루프, 콜 스택, 콜백 큐 개념으로 이뤄진다.

이벤트 루프 모델은 여러 스레드를 사용한다.

그 중 우리가 작성한 자바스크립트가 실행되는 스레드가 메인 스레드임.

한 node.js 프로세스에서 메인 스레드는 하나이며, 한 순간에 한 줄씩만 실행함

그러나 그 외의 일(File I/O, network)을 하는 워커 스레드는 여럿이 있을 수 있음.

[Ghostscript] 고스트스크립트 PDF 변환 시 Last OS error: Permission denied 오류

[Ghostscript] 고스트스크립트 PDF 변환 시 Last OS error: Permission denied 오류

Current allocation mode is local

Last OS error: Permission denied

GPL Ghostscript 9.54.0: Unrecoverable error, exit code 1

1. -sOUTPUTFILE 옵션을 -sOutputFile 로 변경

명령줄에 -sOUTPUTFILE 옵션이 있는 경우 -sOutputFile 로 대소문자를 변경해서 해결했다.

[AS-IS]

gswin32 -sDEVICE=jpeg -dJPEGQ=100 -dTextAlphaBits=4 -dDOINTERPOLATE -r192 -sOUTPUTFILE=”C:\test\결과파일명.jpg” -dBATCH -dNOPAUSE “C:\test\기존파일명.pdf”

[TO-BE]

gswin32 -sDEVICE=jpeg -dJPEGQ=100 -dTextAlphaBits=4 -dDOINTERPOLATE -r192 -sOutputFile=”C:\test\결과파일명.jpg” -dBATCH -dNOPAUSE “C:\test\기존파일명.pdf” 

2. -dNOSAFER 옵션을 추가

고스트스크립트 9.50 버전 이상부터 명령줄에 -dNOSAFER 옵션을 추가해서 해결했다. (cf. Need to add -dNOSAFER in the gs command. It’s a change since 9.50 version of ghostscript.)

[AS-IS]

gswin32 -sDEVICE=jpeg -dJPEGQ=100 -dTextAlphaBits=4 -dDOINTERPOLATE -r192 -sOUTPUTFILE=”C:\test\결과파일명.jpg” -dBATCH -dNOPAUSE “C:\test\기존파일명.pdf”

[TO-BE]

gswin32 -sDEVICE=jpeg -dJPEGQ=100 -dTextAlphaBits=4 -dDOINTERPOLATE -r192 -sOUTPUTFILE=”C:\test\결과파일명.jpg” -dNOSAFER -dBATCH -dNOPAUSE “C:\test\기존파일명.pdf”

정확한 버전명은 기억나지 않으나 과거버전의 경우  -dSAFER 옵션을 추가했을 때 동작하는 경우도 있었다.

정리하면, (1)  -dSAFER 옵션 사용 (2)  -dNOSAFER 옵션 사용 (3) 옵션 둘 다 제거, 이렇게 세 가지 방법을 사용해보고 동작하는 쪽으로 사용하면 된다.

참고사이트 : https://github.com/bmjcode/tkDocViewer/issues/1

Node.js 공부기록 (2)

Node.js 공부기록 (2)

5강 노드의 버전관리

nodejs.org 에서 node.js 다운로드 가능

LTS : Long Term Service 의 약자 (안정적)

nvm 도구 : 노드 버전 매니저의 약자

노드의 버전 매니지먼트 방법.

nvm은 복잡하므로, 대신 tj/n [티제이 의 엔 이라고 읽으면 됨]을 추천. (github.com/tj/n)

티제이의 엔은 노드 버전 매니지먼트 툴. 아주 괜찮은 바이너리 cli.

n[엔] 설치 방법

npm install -g n

n이 깔려있는 상태라면.

1. 터미널에 which n 입력 (깔려있는지 확인)

2. n 이라고 입력하면 설치한 노드 버전들이 쭉 나옴

3. 원하는 버전을 선택 (ex : v.12.18.3)

4. node –version 입력하면 버전이 바뀌어 있음

5. 항목 2~4를 반복하며 버전을 계속 바꿔보자

6. n latest 를 입력하면 가장 최신의 노드 설치함

단, 환경변수 설정을 잘 해줘야 함

echo $N_PREFIX 입력해보기

$N_PREFIX 에 해당하는 경로에 노드 버전들이 설치가 되므로 주의.

만약 $N_PREFIX 가 /Users/myname/n 일 경우,

/Users/myname/n/versions/node 폴더 안에 노드들이 설치되어 있다.

ex) /Users/myname/n/versions/node/12.18.3

  /Users/myname/n/versions/node/14.15.4

  /Users/myname/n/versions/node/15.14.0

 

—–

6강

개발환경에 필요한 여러가지 패키지들이 있는데,

이것들은 노드의 패키지 매니저인 NPM을 통해 설치할 것이다.

NPM 외에도 여러가지 패키지 매니저가 있다.

ex) yarn[얀], pnpm [피엔피엠]

npm 은 노드만 설치해도 같이 깔리기 때문에 편함.

성능적 문제가 아니라면 npm을 쓰자. (모든 기능 지원)

작업하는 프로젝트가 npm 이 관리하는 패키지임을 알려줘야 한다.

그러려면 package.json 이라는 파일이 필요함.

간단하게 만들려면 터미널에서 npm init -y 입력하면 package.json 만들어짐

기본내용

{

  “name”: “test2”,

  “version”: “1.0.0”,

  “description”: “”,

  “main”: “index.js”,

  “scripts”: {

    “test”: “echo \”Error: no test specified\” && exit 1″

  },

  “keywords”: [],

  “author”: “”,

  “license”: “ISC”

}

package.json 은 패키지의 메타데이터를 포함하는 파일임.

즉, 우리가 작업할 모든 내용은 노드 패키지를 위한 내용일거라고 생각한다.

우리의 개인 프로젝트라도 일단 패키지로 본다.

package.json 필드

name : 패키지의 이름. 패키지로서 밖에 배포할게 아니면 의미가 별로 없다.

name, version, description, main, keywords, author, license 등도 마찬가지다.

패키지로서 배포할게 아니므로 필요없는 것을 다 지워보자.

{

  “scripts”: {

    “test”: “echo \”Error: no test specified\” && exit 1″

  }

}

사실상 스크립츠 하나만 남게 된다.

스크립츠는 npm을 사용하면서 자주 사용하게 되는, 혹은 프로젝트를 관리하면서 자주 사용하게 되는 스크립트를 간단하게 호출할 수 있도록 만들어준 필드이다.

호출 방법은

터미널에 다음 내용 입력하기

npm run test (스크립트에 있는 키의 이름)

에러를 내면서 exit하게 되어있으므로 내용을 고쳐보자.

{

  “scripts”: {

    “test”: “echo \”Hello, world!\””

  }

}

다시 입력

npm run test

main.js 파일 생성

내용은

console.log(‘Hello, world!’)

터미널 입력

node main.js

실행이 될것이다.

* 포매팅, 린팅

Formatting 은 스페이스가 몇개인지 스페이스가 몇개 붙었는지 : 미적인것에 가까움

Linting 은 베스트 프랙티스, 잘지키면 좋은 것들, 혹여나 에러가 날 수 있는 것들을 잡아줌.

Formatting : Prettier [프리티어]

프리티어를 설치해보자.

npm install –save-dev prettier

package.json 내용이 바뀌었다.

{

  “scripts”: {

    “test”: “echo \”Hello, world!\””

  },

  “devDependencies”: {

    “prettier”: “^2.5.1”

  }

}

package.json 중요한 역할은 메타데이터를 표현하는 것도 있지만, 이 패키지/프로젝트의 의존성을 나열하는 것에도 큰 목적이 있다.

처음으로 의존성이 추가되었다.

package-lock.json 이란 파일도 생겼다.

락 제이슨 파일은 실제로 설치된 패키지들이 어떤 것인지 알려주는 파일이라고 생각하면 된다.

버전 컨트롤을 한다면(git으로 형상관리한다면) package-lock.json [패키지 락 제이슨] 파일도 같이 넣어주는 게 좋다.

package.json 에 쓰여있는 ^2.5.1 , 여기의 ^기호는 버전값이 정확하게 일치하지 않더라도 설치하게 만든다.

실제 로컬환경에 설치되는 버전이 2.5.1이 아닐 수 있다.

정확한 설치 버전을 알려면 package-lock.json 을 봐야 한다.

여러 사람과 같이 작업을 한다면 package-lock.json 을 꼭 커밋해줘야 한다.

node_modules 폴더가 생겼고, node_modules 밑에 .bin, prettier 폴더가 생겼다.

(1) node_modules 아래의 디렉토리들은 우리 프로젝트가 의존하고 있는 패키지들이라고 보면 된다.

(2) .bin 밑의 파일은 우리가 실행할 수 있는 바이너리 파일들이라고 보면 된다.

즉 프리티어라는 바이너리 파일이 하나 생겼다.

이제 프리티어로 포매팅을 해보자.

프리티어에게 필요한 설정파일 만들어야 함.

(1) .prettierrc [프리티어 알씨]라는 파일이 필요하다.

.prettierrc 파일을 새로 생성한다.

세미콜론 쓰는가, 홑따옴표 쓰는가 여부를 기입

{

    “semi”: false,

    “singleQuote”: true

}

(2) .vscode 라는 폴더 만들기.

(3) .vscode 라는 폴더 하위에 settings.json 파일 만들기.

[세팅스 닷 제이슨] 파일은 비주얼 스튜디오 코드가 바라보는 로컬 세팅을 모아두는 것.

이 프로젝트에만 적용되는 세팅.

내용은

{

    “[javascript]” : {

        “editor.formatOnSave”: true,

        “editor.defaultFormatter”: “esbenp.prettier-vscode”

    }

}

(4) 익스텐션 탭 (Ctrl + Shift + X)에서 Prettier – Code formatter 꼭 설치

(5) 이제 프리티어가 동작하기 시작.

(동작하지 않는다면 위 항목을 제대로 수행했는지, 파일명 등이 틀렸는지 확인)

main.js 파일에서 console.log(“Hello, world!”); 를 입력하고 파일 저장하면

console.log(‘Hello, world!’) 로 바뀜

—–

7강 : 든든한 개발환경 ESLint

프리티어는 포매터 역할이고, 린팅 역할의 플러그인을 깔자.

린팅의 역할에 해당되는, ESLint [이에스 린트]를 깔자.

ESLint는 패키지이자 플러그인임. vscode에서는 플러그인인 것이고.

ESLint 설치

npm install –save-dev eslint

(1) 익스텐션 탭 (Ctrl + Shift + X)에서 ESLint 꼭 설치

(2) ESLint 설정 파일을 만들어야 함.

.eslintrc.js [이에스 린트 알씨 닷 제이에스]

아래 내용 입력

module.exports = {}

이에스 린트가 사용할 룰들을 기재해줘야 하는데, 싱클 쿼트를 써라, var를 쓰지 말고 const와 let만 써라 등이 있는데

굳이 그렇게 하지 않아도 베스트 프랙티스에 맞게 나온 플러그인들이 있어서 그걸 쓰면 된다.

에어비엔비에서 ESLint 플러그인을 내놓은 것이 있다.

https://github.com/airbnb/javascript

아래 명령어로 설치

npm install –save-dev eslint-config-airbnb-base eslint-plugin-import

eslint-plugin-import 는 eslint-config-airbnb-base 가 필요로 하는 의존성 패키지이므로 같이 설치.

설치하고,

.eslintrc.js 파일 내용 수정하기

module.exports = {

  extends: [‘airbnb-base’],

}

문제가 있다면 ESLint가 세미콜론이 없다고 오류를 냄.

그런데 세미콜론을 붙이고 저장을 하면 프리티어가 세미콜론을 없애버려서 답이 없음.

이때 ESLint와 프리티어가 충돌하지 않고 공존하도록 eslint-config-prettier 라는 패키지 설치하면 됨.

npm install –save-dev eslint-config-prettier

이제 또 .eslintrc.js 파일 내용을 바꾸자.

module.exports = {

  extends: [‘airbnb-base’, ‘prettier’],

}

참고로 prettier 가 가장 마지막에 와야 eslint-config-prettier 가 잘 동작한다고 함.

이제 main.js 파일을 고쳐보자.

console.log(‘Hello, world!’) 노란줄이 떴다. ESLint 가 작동하고 있는 것이다.

마우스를 갖다 대보면 “Unexpected console statement.eslint(no-console)” 라고 나온다.

하지만 콘솔 출력이 필요하므로 잠깐 ESLint 룰을 꺼보자.

/* eslint-disable-next-line */

console.log(‘Hello, world!’)

하지만 이렇게 모든 룰을 다 꺼버리면 다른 규칙을 어겼을 때도 ESLint 오류가 나지 않는다.

아래와 같이 입력해야 특정 룰만 끌 수 있다.

/* eslint-disable-next-line no-console */

console.log(‘Hello, world!’)

모든 룰을 껐을 때 문제 : 아래 코드는 노란줄이 안뜨는 문제가 있다.

/* eslint-disable-next-line */

console.log(eval())

var x = 1 은 airbnb-base 에서 금지하고 있다.

let(값 변경가능) 이나 const(값 불변) 사용하자.

마지막으로 추가할 플러그인은 노드 전용 플러그인임.

npm install –save-dev eslint-plugin-node

eslintrc.js 파일 수정

module.exports = {

  extends: [‘airbnb-base’, ‘plugin:node/recommended’, ‘prettier’],

}

이제 아래와 같은 코드는 쓸 수 없다.

exports = 3

마우스를 대보면,

exports = 3

Unexpected assignment to ‘exports’ variable. Use ‘module.exports’ instead.eslint(node/no-exports-assign)

린터와 포매터의 필요성

(1) 코딩중 즉각적 피드백 : 올바른 코딩 습관 형성

(2) 여러 사람 코드 관리할 때 코드를 청결하게 관리. 시맨틱/신택스로 싸울 일도 줄어듬.

(3) 여기 나와있는 룰들을 살펴보기만 해도 베스트 프랙티스 학습이 됨

    airbnb-base 의 룰을 살펴보자. https://github.com/airbnb/javascript

(4) 버그방지

—–

8강 타입스크립트 설치

자바스크립트는 다이나미컬리 타입드 랭귀지 (동적으로 타입이 정의되는 언어임)

실행시간에 가서야만 어떤 변수의 타입을 알 수 있음.

자바스크립트는 자바스크립트 자체만 놓고 봐서는 컴파일이라는 과정을 거치지 않기 때문에

변수가 어떤 타입으로 활용될지를 미리 알 수 없음.

미리 에러가 나지도 않음.

그래서 타입 에러가 있는 코드를 써놓아도, 그것이 실제로 실행되는 순간에서야 알게되는 불상사가 벌어짐.

강력하게 타입체크를 하는 다른 언어들에서 보기 힘든 점임 (컴파일 언어들. 타입 불일치로 인한 에러는 거의 없음.)

타입을 체크하기 위해 페이스북에서 만든 Flow라는 도구도 있지만, 타입스크립트를 추천.

타입스크립트는 자바스크립트로 컴파일(정확히 말하면 트랜스파일)되는 언어인 것이다.

타입스크립트는 자바스크립트 위에 타입정의만 얹어놓은 것이다.

문제가 되는 코드를 짜보자.

main.js 내용 수정

const someString = ‘Hello’

const result = Math.log(someString) // 문자열을 로그 연산. 애초에 말이 안되지만 에디터는 오류를 알아차리지 못한다.

console.log(result)

node main.js 실행

NaN 이 출력된다. 낫 어 넘버, 낫 에이 넘버라는 뜻.

타입스크립트를 설치해보자.

npm install –save-dev typescript

설치 후 main.js 내용 수정

// @ts-check

const someString = ‘Hello’

const result = Math.log(someString) // 문자열을 로그 연산

console.log(result)

이제 빨간줄이 그어진다.

—–

9강 노드에서 타입스크립트 활용

타입스크립트가 노드 프로그래밍 환경에서 동작하게 하려면 타입스-노드 패키지를 설치해야 함

npm install –save-dev @types/node

이 패키지에 들어있는 정보는, 노드에서 주로 사용되는 객체들에 대한 타입정보들이 들어있다.

아직 이해하기 어렵겠지만 중요한 점은, vscode가 타입스크립트 바이너리를 이용해서 타입체킹을 도와준다는 점이다.

main.js 파일 수정

const http = require(‘http’)

const server = http.createServer((req, res) => {

  res.statusCode = 200

  res.end(‘Hello!’)

})

const PORT = 4000

server.listen(PORT, () => {

  console.log(`The server is listening at port : ${PORT}`)

})

node main.js로 실행.

The server is listening at port : 4000 라고 뜰것임.

http://localhost:4000/ 접속해보기.

여기서 만약 res.statusCode = 200 을 res.statuscode = 200 로 대소문자 오타를 냈다면?

마우스를 갖다대면 타입스크립트가 에러를 잘 캐치해주는 것을 볼 수 있다.

‘statuscode’ 속성이 ‘ServerResponse’ 형식에 없습니다. ‘statusCode’을(를) 사용하시겠습니까? ts(2551)

http.d.ts(478, 9): 여기서는 ‘statusCode’이(가) 선언됩니다.

아래 코드의 문제도 타입스크립트가 잘 감지한다.

res.statusCode = “200”

마우스를 갖다대면 아래처럼 나온다.

‘string’ 형식은 ‘number’ 형식에 할당할 수 없습니다. ts(2322)

에러를 미연에 방지 가능하다.

—–

10강 타입스크립트 설정파일

타입스크립트를 사용해서 프로젝트를 진행한다면, 타입스크립트 프로젝트 설정파일인 tsconfig.json 에 설정 내용을 넣게 되는데

우리는 자바스크립트 프로젝트를 진행하면서, 타입스크립트 타입 체킹의 도움만 받을 것이기 때문에 조금 다른 설정 파일을 쓸거다.

(1) jsconfig.json 파일 생성

{

    “compilerOptions”: {

        “strict”: true

    },

    “include”: [

        “src/**/*”

    ]

}

(2) src 폴더 생성

(3) src 폴더 안에 main.js 옮겨넣기

(4) src 폴더 안에 ts.js 새 파일 생성

src 폴더 안의 모든 파일에 대해서 strict를 적용하게 된다고 한다.

ts.js 내용 작성 (두줄)

// @ts-check

const x = null

아래 두 개 에러가 나와야 한다고 하는데 첫번째 에러만 나타난다.

strict 값이 true이면 아래 두 개 에러가 나고, false이면 첫번째 에러만 나타난다는데 이상하다.

분명 strict값은 true로 해두었고, vscode도 여러번 재기동 해보았다.

첫번째 : ‘x’ is assigned a value but never used. eslint(no-unused-vars)

두번째 : Variable ‘x’ implicitly has an ‘any’ type. ts(7005)

아무리 해도 적용이 안되어서 일단 넘어간다.

—–

11강 환경설정 종합

VSCode JavaScript Development Setup

              | Formatting  | Linting                   | Type Checking

————–+————-+—————————+————–

Package       | prettier    | eslint                    | typescript

————–+————-+—————————+————–

              |             | eslint-config-airbnb-base | @types/node

Additional    |             | eslint-config-prettier    |

dependencies  |             | eslint-plugin-import      |

              |             | eslint-plugin-node        |

————–+————-+—————————+————–

Config file   | .prettierrc | .eslintrc.js              | jsconfig.json

————–+————-+—————————+————–

VSCode        |      O      |             O             |       X

extensions    |             |                           |

————–+————-+—————————+————–

[JAVA] javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed

[JAVA] javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed

HttpURLConnection 객체 사용 시 아래와 같이 javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 오류가 발생하는 경우.

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
 at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1747)
 at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:241)
 at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:235)
 at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1209)
 at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:135)
 at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:593)
 at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:529)
 at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:943)
 at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1188)
 at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1215)
 at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1199)
 at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:434)
 at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166)
 at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1031)
 at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:230)
 (중략)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:323)
 at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:217)
 at sun.security.validator.Validator.validate(Validator.java:218)
 at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:126)
 at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:209)
 at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:249)
 at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1188)
 … 13 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:174)
 at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:238)
 at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:318)
 … 19 more

https 프로토콜의 주소에 접속하기 전에 아래 코드를 실행하기.

​import java.net.URLConnection;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

(중략)

    private void setDefaultSSLSocket() throws NullPointerException, Exception {
        TrustManager[] trustManager = new TrustManager[] {new X509TrustManager() {
            
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }
            
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }
        }};
        
        SSLContext sslContext = SSLContext.getInstance(“SSL”);
        sslContext.init(null, trustManager, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
    }

[JAVA] java.io.IOException: Server returned HTTP response code: 415 for URL

[JAVA] java.io.IOException: Server returned HTTP response code: 415 for URL

HttpURLConnection 객체 사용 시 java.io.IOException: Server returned HTTP response code: 415 for URL: https://도메인명 오류가 발생하는 경우.

만약 HTTP POST 메서드를 사용해서 JSON 데이터를 보내려고 한다면, “Content-Type: application/x-www-form-urlencoded” 가 아닌 “Content-Type: application/json” 을 헤더에 적용해야 한다.

[AS-IS]

HttpURLConnection ucon = null;

    try {
        URL url = new URL(urlString);

        ucon = (HttpURLConnection) url.openConnection();

        ucon.setRequestMethod(“POST”);

(후략)

[TO-BE]

HttpURLConnection ucon = null;

    try {
        URL url = new URL(urlString);

        ucon = (HttpURLConnection) url.openConnection();

        ucon.setRequestMethod(“POST”);

        ucon.setRequestProperty(“Content-Type”, “application/json”);

(후략)

참고사이트 : https://pythonq.com/so/java/267460

Node.js 공부기록 (1)

Node.js 공부기록 (1)

한 번에 끝내는 Node.js 웹 프로그래밍 초격차 패키지 Online

—–

1강 Node.js 소개

노드 : 자바스크립트 런타임

빠르고 스케일러블한 서버

크롬에 V8[브이 에잇] 엔진이 들어있음.

js engine

(open-source)

V8은 환경에 상관없이 작동하는 자바스크립트 엔진임

V8에게 자바스크립트를 주면 V8은 이를 실행함.

2009년 라이언 달

V8 엔진을 사용해서, 서버에서 동작하는 자바스크립트 런타임 개발

express 2010년 등장 노드 웹서버 프레임워크의 표준이 됨

react 2013년에 등장해서 프론트엔드 개발 트렌드를 바꿔놓음

2014년 바벨과 웹팩이 등장

한 언어로 풀스택 구현이 가능 => 코드의 재사용성이 높아짐

—–

2강 Node.js 소개

특징, 강점, 약점

IO needs to be done differently. (Ryan Dahl, JSConf 2009)

IO는 (지금까지와는) 다르게 되어야 한다.

자바스크립트의 성질 : 이벤트 루프에 비동기적인 성질.

자바스크립트는 이벤트 루프를 활용해서 동작하게 만들어져 있음.

라이언달은 이를 IO 바운드 프로그램에 가장 큰 문제인 요청하고 기다리기를 해결하는 열쇠로 보았음.

– 고전적인 처리 방식 : 한줄씩 실행하고 넘어가는 방식. 너무많은 클럭수를 낭비한다. (사칙연산이나 정렬 같은 분류는 괜찮으나, 네트워크를 타는 데이터베이스 접근 등의 경우)

– 자바스크립트 식 비동기 처리 방식 : 네트워크, 파일 등 IO를 기다리지 않고 다음줄로 넘어간다.

(자바스크립트는 언어 수준에서 비동기 문제가 잘 해결되어 있음)

“저수준의 오래 걸리는 일은 Node에게, 고수준의 로직은 메인 스레드에서.”

이것을 Offloading(오프로딩)이라고 한다.

노드가 빠른 속도와 매우 높은 확장성을 갖는 근본적인 이유이다.

=> IO(파일/네트워크)는 노드가 알아서 하고, 프로그래머는 비즈니스 로직에 집중.

비동기에 있어서 싱글 스레드이기 때문에 비동기로 인한 동시성 문제에 대한 걱정도 거의 없다.

(왜 아예 없는게 아니라 거의 없을까? 검색을 해도 잘 안나오는데 아마 아무리 노드라도 한 자원을 동시에 접근하는 경우 문제가 있지 않을까)

저수준 일은 잘하는 노드가 해주고, 요청이 얼마나 많든 간에 그 나머지만 메인 스레드에서 처리하기 때문에 메인 스레드의 확장성이 높다고 한다.

싱글 쓰레드의 블락킹만 일으키지 않는 다면 V8은 빠른 성능을 보장한다. (V8은 수준높은 구글 엔지니어링의 산물)

* 한계

자바스크립트 한계는 저수준 처리가 느릴 수 밖에 없다.

ex) 이미지파일 전체 버퍼를 돌면서 특정한 픽셀을 찾는다든지

오디오 파일을 쭉 돌면서 두 개 오디오 파일을 합친다든지

더 잘하는 다른 언어들이 있다.

C, Rust, go 등 : 기계어와 대응하도록 컴파일해주기 때문에 빠르다.

node에서 바인딩해서 사용할 수 있다.

대표적인 대안 : C 그리고 WebAssembly 사용

C와 WebAssembly 모듈을 바인딩해 사용하는 방법 제공

C는 node-gyp [노드 지와이피]를 통해서, WebAssembly 는 Node 12버전부터 제공

WebAssembly [웹 어셈블리]란?

바이너리 인스트럭션 포맷이다.

C나 Rust를 컴파일했을 때 바이너리 실행줄들이 쭉 떨어지는 것처럼,

브라우저를 위한 어셈블리라고 보면 된다.

자바스크립트의 경우 스크립트 언어이기때문에 결국 런타임에서는 예측할 수 없는 부분들이 생기기 마련이므로 완벽히 최적화가 안되는데,

웹 어셈블리를 사용하게 되면 이 최적화 효율을 엄청나게 끌어올릴 수 있다.

노드의 대표적인 약점인 저수준 처리 => C와 WebAssembly로 극복가능

* 방대한 오픈 소스 생태계

NPM [엔피엠]: 노드를 위한 패키지 매니저

—–

3강 Glitch 서비스

Glitch 말고 코드샌스박스라는 것도 있다.

Glitch 라는 서비스로 설정 없이 바로 Node 웹 서버 코딩해보기

1. https://glitch.com/ 가입

2. discover 메뉴 접근 (또는 https://glitch.com/create-project)

3. node.js 항목의 Hello Node 클릭

4. server.js 내용 모두 삭제 (현재 필요가 없는 코드)

5. server.js 내용 새로 작성

const http = require(‘http’)

const server = http.createServer((req, res) => {

  res.status = 200

  res.end(‘Hello!’)

})

const PORT = 3000

server.listen(PORT, () => {

  console.log(‘The server is listening at port’, PORT)

})

6. 상단의 [Show] 버튼 클릭하고 [Next To The Code] 클릭

7. 하단의 [Tools] – [Logs] 로 로그 조회 가능

8. 하단의 [Tools] – [Terminal] 클릭하면 어떤 장비에 접속한 상태임을 알 수 있다.

8-1. ls 쳐보기

8-2. cat server.js 쳐보기

(실제로는 어떤 컨테이너 안에 떠있는 것인데 에디터를 물려서 변경하고 있는 것임)

9. 상단의 [Show] 버튼 클릭하고 [In a New Window] 클릭하면 놀라운 사실

브라우저 주소를 보면, 웹상에서 실제 호스팅이 되고 있음.

심지어 https도 적용이 되어있음.

노드 서버 프로토타이핑을 해보고 싶다면 이렇게 글리치나, 코드샌스박스를 사용하는 것을 추천.

아주 간단한 노드 API 서버를 만들고 싶다면 추천.

글리치에서도 CPU, 메모리 등 제한은 두고 있겠지만 테스트에 유용함.

—–

4강

에디터 : Visual Studio Code

오픈소스 에디터임. MS가 주 오픈소스backer.

(github.com/Microsoft/vscode)

언어 구성을 보면 TypeScript 93.6% / JavaScript 3.4%

장점 : 자바스크립트 인텔리센스 (자동완성이나 타입 유추 등) 기능이 기본 지원됨.

타입스크립트도 지원이 잘됨

강력한 익스텐션이 특장점.

code-server(코드 서버)라는 것이 있어서, vscode 는 웹상에서도 동작함.

기본적으로 웹기반 기술로 만들어졌으므로 다양한 운영체제 지원

폴더 별로 프로젝트가 관리됨.

1. test.js 새 파일 만들고 작성

console.log(‘Hello!’)

Ctrl + Shift + P : 커맨드 팔레트

커맨드 팔레트에서 >Terminal: Focus Terminal 검색

터미널이 뜨면

1-1. node –version 입력

1-2. node test.js 입력

터미널에 Hello! 라고 뜰것임

1-3. git init 입력

1-4. 소스 제어 탭 선택 (Ctrl + Shift + G)

1-5. 상단 인풋박스에 “Initial Commit” 이라고 입력하고 플러스 기호 클릭 (스테이징)

1-6. 상단 V 표시 아이콘 클릭 (커밋)

1-7. 익스텐션 탭 선택 (Ctrl + Shift + X)

각종 익스텐션 얼마든지 설치 가능

1-8. Ctrl + Shift + P : 커맨드 팔레트  => Keyboard Shortcuts 검색

단축키 조회/변경 가능

1-9. Ctrl + Shift + P : 커맨드 팔레트에서 defaultSettings.json 열기

1-10. Ctrl + Shift + P : 커맨드 팔레트에서settings.json 열기

settings.json은 내용이 거의 없는 상태인데, defaultSettings.json 의 내용을 가져와서 settings.json 안에 입력하는 식으로 사용

ex) “workbench.colorTheme”: “Abyss” 입력

“workbench.colorTheme”: “Visual Studio Dark” 입력

[JAVA] 자바 이진파일 읽기 쓰기 (자바 바이너리 파일을 hex 형태로 읽기 쓰기)

[JAVA] 자바 이진파일 읽기 쓰기 (자바 바이너리 파일을 hex 형태로 읽기 쓰기)

파일은 크게 텍스트 파일과 바이너리 파일로 나눌 수 있다. PDF파일과 같은 바이너리 파일은, 텍스트 형태로 읽어들이고 다시 쓰면 십중팔구 파일이 깨져서 원본 상태로 열리지 않는다.

바이너리 파일을 텍스트 형태로 만들기 위해서는, 파일을 BASE64 로 만드는 방법도 있지만 HEX 문자열 형태로 만드는 방법이 있다. 바이트를 정수(int)로 만들고, 정수를 HEX로 만들면 깨지지 않으며 나중에 원본 그대로 되살릴 수 있다. 바이트 하나가 HEX 2자리가 되므로, 용량은 딱 2배가 된다.

※ 바이트(-128~127)가 정수(int)가 되고, 정수(int)는 HEX가 된다. 최저 -128에서 최고 127까지이므로 HEX는 2자리를 초과할 수 없다.

정수 -128 은 HEX로 80 이 되고, 정수 127 은 HEX로 7F 가 된다.

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;

public class JavaBinaryFileToHex {

    public static void main(String[] args) {
        try {
            String originFilePath = “C:\\test\\TODO.pdf”;
            String hexFilePath = “C:\\test\\TODO_hex.txt”;
            String destFilePath = “C:\\test\\TODO_2.pdf”;

            File file = new File(originFilePath);

            // 1. 바이너리 파일을 hex 형태로 읽기
            String hex = readBinaryFileToHex(file);

            // 2. hex 문자열을 텍스트 파일로 쓰기 (확인용)
            writeTextFile(hexFilePath, hex);

            // 3. hex 문자열을 binaryFile로 만들기
            writeBinaryFileFromHex(destFilePath, hex);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 바이너리 파일을 hex 형태로 읽기 (file to hex 문자열)
     *
     * @param file
     * @return
     * @throws IOException
     * @throws Exception
     */

    public static String readBinaryFileToHex(File file) throws IOException, Exception {
        if (file == null || !file.exists()) {
            return null;
        }

        StringBuffer hexBuff = new StringBuffer();

        FileInputStream fileInputStream = null;
        DataInputStream inputStreamReader = null;

        try {
            fileInputStream = new FileInputStream(file);
            inputStreamReader = new DataInputStream(fileInputStream);

            byte[] b = new byte[1024];
            int len = 0; // 실제로 읽어온 길이 (바이트 개수)

            while ((len = inputStreamReader.read(b)) > 0) {
                for (int i = 0; i < len; i++) {
                    hexBuff.append(String.format(“%02X “, b[i]).trim());
                }
            }

        } catch (IOException e) {
            throw e;

        } catch (Exception e) {
            throw e;

        } finally {
            close(inputStreamReader);
            close(fileInputStream);
        }

        return hexBuff.toString();
    }

    /**
     * hex 문자열을 binaryFile로 만들기
     *
     * @param filePath
     * @param hex
     * @return
     * @throws IOException
     * @throws Exception
     */

    public static boolean writeBinaryFileFromHex(String filePath, String hex) throws IOException, Exception {
        if (filePath == null || filePath.length() == 0) {
            return false;
        }

        if (hex == null) {
            hex = “”;
        }

        boolean isWrite = false;

        File file = new File(filePath);

        FileOutputStream fileOutputStream = null;
        DataOutputStream outputStreamWriter = null;

        try {
            boolean isAppend = false;
            fileOutputStream = new FileOutputStream(file, isAppend);
            outputStreamWriter = new DataOutputStream(fileOutputStream);

            int arrayLen = hex.length() / 2;
            int[] b = new int[arrayLen];

            // hex string to int array
            int c = 0;
            int len = hex.length();
            for (int i = 0; i < len; i = i + 2) {
                b[c] = Integer.parseInt(hex.substring(i, i + 2), 16);
                c++;
            }

            // int array to byte
            for (int i = 0; i < b.length; i++) {
                outputStreamWriter.write(b[i]);
            }

            isWrite = true;

        } catch (IOException e) {
            throw e;

        } catch (Exception e) {
            throw e;

        } finally {
            close(outputStreamWriter);
            close(fileOutputStream);
        }

        return isWrite;
    }

    /**
     * 문자열을 텍스트 파일로 쓰기 (한글 등 인코딩 고려하지 않았음)
     *
     * @param filePath
     * @param content
     * @return
     * @throws IOException
     * @throws Exception
     */

    public static boolean writeTextFile(String filePath, String content) throws IOException, Exception {
        boolean isWrite = false;

        FileWriter fileWriter = null;
        try {
            fileWriter = new FileWriter(filePath);
            fileWriter.write(content);
            isWrite = true;

        } catch (IOException e) {
            throw e;

        } catch (Exception e) {
            throw e;

        } finally {
            close(fileWriter);
        }

        return isWrite;
    }

    private static void close(FileWriter obj) {
        try {
            if (obj != null) {
                obj.close();
            }
        } catch (Exception e) {
        } finally {
            obj = null;
        }
    }

    private static void close(DataOutputStream obj) {
        try {
            if (obj != null) {
                obj.close();
            }
        } catch (Exception e) {
        } finally {
            obj = null;
        }
    }

    private static void close(DataInputStream obj) {
        try {
            if (obj != null) {
                obj.close();
            }
        } catch (Exception e) {
        } finally {
            obj = null;
        }
    }

    private static void close(FileInputStream obj) {
        try {
            if (obj != null) {
                obj.close();
            }
        } catch (Exception e) {
        } finally {
            obj = null;
        }
    }

    private static void close(FileOutputStream obj) {
        try {
            if (obj != null) {
                obj.close();
            }
        } catch (Exception e) {
            obj = null;
        }
    }
}

참고사이트 1 : https://rabbitchris.tistory.com/625

참고사이트 2 : http://mwultong.blogspot.com/2007/04/java-binary-file-write-save.html

참고사이트 3 : http://mwultong.blogspot.com/2007/04/java-read-binary-file.html

[Javascript] 아이패드 아이폰에서는 onbeforeunload 대신 onpagehide 사용

[Javascript] 아이패드 아이폰에서는 onbeforeunload 대신 onpagehide 사용

아이패드에서 특정 기능이 수행되지 않는 버그가 있었다.

의견을 입력하고 확인 버튼을 누를 수 있는 창이 있었는데(의견창), 이 의견창이 문제였다.

윈도우 PC, 맥 PC, 안드로이드 폰 모두 잘 동작하는 기능이었는데 아이패드에서만 동작하지 않았다.

창은 잘 열렸으나(window.open으로 창을 띄웠음) 확인 버튼을 누르면 아무 반응이 없고 그다음 로직을 타지 않았다.

알고보니 해당 윈도우 창의 js 콜백함수 호출이 onbeforeunload 안에 들어있었기 때문이었다.

아이패드, 아이폰의 사파리 브라우저는 onbeforeunload 가 동작하지 않고, onpagehide 라는 함수를 타는 것으로 보인다.

아이패드, 아이폰의 경우 창 닫힘 시 이벤트를 부여하려면 window.onpagehide = function() {} 또는 window.addEventListener(“pagehide”, function(event) {}); 식으로 코딩하면 된다.


실제로는 window.onbeforeunload 함수 안에 있던 콜백함수 호출 코드를 잘라내고, 확인 버튼 클릭 시의 이벤트 안으로 옮겨서 OS 및 브라우저 디펜던시 상관없이 동작하도록 수정했다.​

참고할만한 코드는 아래.

var isOnIOS = navigator.userAgent.match(/iPad/i)|| navigator.userAgent.match(/iPhone/i);
var eventName = isOnIOS ? “pagehide” : “beforeunload”;

window.addEventListener(eventName, function(event) {
    …
});

참고사이트 : http://daplus.net/javascript-window-onbeforeunload%EA%B0%80-ipad%EC%97%90%EC%84%9C-%EC%9E%91%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EC%8A%B5%EB%8B%88%EA%B9%8C/

[VB] 'DAO350.DLL' 파일을 찾을 수 없습니다.

[VB] ‘DAO350.DLL’ 파일을 찾을 수 없습니다.

DAO350.DLL 파일을 첨부한다.

비주얼베이직(Microsoft Visual Basic 6.0)을 실행했을 때 ‘DAO350.DLL’ 파일을 찾을 수 없습니다. 라는 메시지가 나오면, 비주얼베이직 실행파일(VB6.EXE)이 있는 위치(ex : C:\Program Files (x86)\Microsoft Visual Studio\VB98 폴더)에 Dao350.dll 파일을 넣고 다시 실행해보면 된다.

참고사이트 : https://qazqazqaz1.tistory.com/36

[JAVA] 자바 랜덤 숫자 구하기

[JAVA] 자바 랜덤 숫자 구하기

자바에서 min 이상 max 이하의 랜덤 숫자 구하는 코드.

public int getRandomNumber(int min, int max) {
    int number = (int)(Math.random() * (max – min +1)) + min;
    return number;
}

[Intellij] 인텔리제이 메서드 한 줄로 표시되는 문제 해결

[Intellij] 인텔리제이 메서드 한 줄로 표시되는 문제 해결

인텔리제이에서 자바 메서드 구현부가 한 줄인 경우, 메서드명을 포함해서 중괄호 시작부터 끝까지 한 줄로 표시되는 현상이 있다.

오류는 아니고 선택 기능인데 불편하면 아래 방법으로 해제할 수 있다.

1. 인텔리제이 상단메뉴 [File] – [Settings…] 클릭해서 Settings 다이얼로그 열기

2. 좌측 메뉴의 [Editor] – [General] – [Code Folding] 선택 – 우측 내용의 [One-line methods] 항목을 체크해제

 

[JAVA/JSP] EUC-KR 시스템과 UTF-8 시스템 전송 시 한글깨짐

[JAVA/JSP] EUC-KR 시스템과 UTF-8 시스템 전송 시 한글깨짐

이제 EUC-KR을 사용하는 시스템이 많이 없어졌다곤 하지만, EUC-KR을 사용하는 시스템과 UTF-8을 사용하는 시스템 간의 한글 파라미터 전달 문제는 여전히 골치가 아프다.

■ EUC-KR 로 파라미터를 잘 받는지 (또는 UTF-8로 파라미터를 잘 받는지) 확인하는 가장 좋은 방법

스프링 컨트롤러 단에서 파라미터를 받든, 자바 서블릿 단에서 파라미터를 받든, jsp 페이지 상단에서 파라미터를 받든, 파라미터를 받는 페이지가 있을 것이다.

이때, 파라미터를 특정 인코딩으로 잘 받는지 확인하고 싶을 때가 있다.

WAS 차원에서 어떤 인코딩이 적용되어 있느냐를 확인하고 싶을 수도 있고, 특정 페이지의 인코딩만 확인하고 싶을 수도 있다.

어쨌든 핵심은 보내는 쪽의 인코딩이 확실해야 한다는 점이다.

보내는 쪽의 인코딩이 불확실 내지는 부정확하다면, 테스트를 하면 할수록 뭐가 뭔지 아무것도 알 수 없게 된다.

가장 좋은 방법은 순수 html 에서 submit을 쏘는 것이다.

jsp나 php는 결국 WAS 내지는 웹서버의 영향을 받고, 백엔드 단에서 자바로 server to server로 쏘는 행위는 더더욱 인코딩을 확신할 수 없다(보통 이 server to server 인코딩이 제일 문제가 되곤 한다).

WAS나 웹서버를 통하지 않고 웹 브라우저로 순수 html 파일을 열고 submit을 날려서 테스트해보자.

■ EUC-KR로 submit을 전송하는 html 코드 (euc-kr.html)

<html>

<head>

<meta charset=”euc-kr”>

<script>

window.onload = function() {

    document.charset = “euc-kr”;

}

</script>

</head>

<form id=”form1″ name=”form1″ method=”POST” action=”http://localhost:8080/action_test.jsp” target=”_blank”>

    param1 : <input type=”text” name=”param1″ value=””>

</form>

<br>

<input type=”button” onclick=”document.form1.submit()” value=”submit”>

</html>

■ UTF-8로 submit을 전송하는 html 코드 (utf-8.html)

<html>

<head>

<meta charset=”utf-8″>

<script>

window.onload = function() {

    document.charset = “utf-8”;

}

</script>

</head>

<form id=”form1″ name=”form1″ method=”POST” action=”http://localhost:8080/action_test.jsp” target=”_blank”>

    param1 : <input type=”text” name=”param1″ value=””>

</form>

<br>

<input type=”button” onclick=”document.form1.submit()” value=”submit”>

</html>

■ UTF-8 시스템에서 UTF-8 파라미터를 받는 코드

String param1 = request.getParameter(“param1”);

■ EUC-KR 시스템에서 EUC-KR 파라미터를 받는 코드

String param1 = request.getParameter(“param1”);

■ UTF-8 시스템에서 EUC-KR 파라미터를 받는 코드

<%@page language=”java” contentType=”text/xml; charset=utf-8″ pageEncoding=”utf-8″%>

<%

    response.setContentType(“text/xml; charset=utf-8”);

    request.setCharacterEncoding(“euc-kr”);


    String param1 = request.getParameter(“param1”);

    param1 = new String(param1.getBytes(“8859_1”), “euc-kr”);

    System.out.println(“param1 : ” + param1);

%>

■ EUC-KR 시스템에서 UTF-8 파라미터를 받는 자바코드

<%@page language=”java” contentType=”text/xml; charset=euc-kr” pageEncoding=”euc-kr”%>

<%

    response.setContentType(“text/xml; charset=utf-8”);

    request.setCharacterEncoding(“utf-8”);

    String param1 = request.getParameter(“param1”);

    System.out.println(“param1 : ” + param1);

%>

■ 동일하게 “한글”이라는 파라미터를 전달하는 경우

 

EUC-KR 은 “param1=%C7%D1%B1%DB” 이라고 전송됨.

UTF-8은 “param1=%ED%95%9C%EA%B8%80” 이라고 전송됨.

[Tomcat] 톰캣 JSESSIONID 변경 / 세션아이디 쿠키값 변경

[Tomcat] 톰캣 JSESSIONID 변경 / 세션아이디 쿠키값 변경

톰캣 세션아이디 쿠키값을 변경하는 방법이다. 다시 말해 JSESSIONID 변경하는 방법인데 세션쿠키값의 기본값이 JSESSIONID 이다.

로컬 이클립스에서 톰캣을 띄울 때도 동일하게 변경 가능하다.

1. conf/web.xml 파일 수정

[AS-IS]

<session-config>

    <session-timeout>30</session-timeout>

</session-config>

[TO-BE]

<session-config>

    <session-timeout>30</session-timeout>

    <cookie-config>

        <name>MYSESSIONID</name>

    </cookie-config>

</session-config>

2. conf/server.xml 파일 수정

[AS-IS]

<Context docBase=”C:\coding\workspaces\ProjectName\Project\webapp” path=”/” reloadable=”false”/></Host>

[TO-BE]

<Context docBase=”C:\coding\workspaces\ProjectName\Project\webapp” sessionCookieName=”MYSESSIONID” path=”/” reloadable=”false”/></Host>

1번과 2번 중 어떤 것을 적용해도 무방하며 적용되었는지 확인하려면 피들러 등을 실행해서 쿠키값을 보면 된다.

1번과 2번을 동시 적용했을 때 어떤 세션아이디 명이 우선순위인지는 추후 테스트해보고 내용 추가할 생각임.

[jsonp] jsonp 적용 방법, jsonp 예제

[jsonp] jsonp 적용 방법, jsonp 예제

다른 도메인 주소로부터 json 데이터를 받기 위한 방법 중의 하나.

js 단에서 다른 도메인의 url 을 ajax로 호출하고 그 결과 데이터인 json 데이터를 콜백함수의 첫번째 인자(아규먼트)로 전달받는 방식이다.

이쪽만 작업하면 되는 일이 아니고 대상이 되는 다른 도메인에서 jsonp 형태로 API를 제공해야 한다.

 

■ 일반적인 json 데이터의 아웃풋 내용 예시

{‘key1′:’value1’, ‘key2′:’value1’, ‘key3′:’value1’}

 

■ jsonp 를 적용한 json 데이터의 아웃풋 내용 예시

jQuery22406444848349974217_1634705610458({‘key1′:’value1’, ‘key2′:’value1’, ‘key3′:’value1’})

 

■ jsonp_test.jsp

원래는 다른 도메인에서 제공하는 API 페이지라고 생각하면 된다.

테스트를 위해 WAS는 localhost 포트 8080 으로 띄웠음.

<%@page language=“java”%><%@
page contentType=“text/html; charset=UTF-8”%><%
    response.setContentType(“text/html; charset=UTF-8”);
    
    boolean useJsonp = false;
    String callback = request.getParameter(“callback”);
    if (callback != null && callback.length() > 0) {
        useJsonp = true;
    }
    
    String result = “{‘key1′:’value1’, ‘key2′:’value1’, ‘key3′:’value1’}”;
    if (useJsonp) {
        result = callback + “(“ + result + “)”;
    }
    
    // 만약 callback 파라미터가 “jQuery22406444848349974217_1634705610458” 인 경우
    // 아웃풋 내용은 jQuery22406444848349974217_1634705610458({‘key1′:’value1’, ‘key2′:’value1’, ‘key3′:’value1’}) 이다.
    // 즉 json을 첫번째 인자(아규먼트)로 삼는 jQuery22406444848349974217_1634705610458 함수를 실행하는 효과가 있다.
    out.print(result);
%>

 

■ test1.html

WAS나 웹서버 거치지 않고 크롬 브라우저로 바로 실행하면 됨.

페이지 로드하자 마자 ajax를 통해 jsonp_test.jsp 페이지를 호출.

<!DOCTYPE html>
<html lang=“ko”>
<meta charset=“UTF-8” />
<script src=https://code.jquery.com/jquery-2.2.4.min.js></script>
<script type=“text/javascript”>
window.onload = function() {
    $.ajax({
        url : http://localhost:8080/jsonp_test.jsp,
        dataType : “jsonp”,
        jsonp : “callback”,
        success : function(_data) {
            var keyArr = Object.keys(_data);
            if (keyArr != null && keyArr.length > 0) {
                var oneKey = “”;
                var keyCount = keyArr.length;
                for (var i=0; i<keyCount; i++) {
                    oneKey = keyArr[i];
                    if (oneKey == null || oneKey == “”) {
                        continue;
                    }
                    
                    $(“#div_result”).append(oneKey + ” : “ + _data[oneKey] + “<br>”);
                }
            }
        }
    });
}
</script>
<body>
    <div id=“div_result”>
    </div>
</body>
</html>

 

■ ajax 호출 내용

아래 스크린샷은 test1.html 의 js 온로드 단에서 http://localhost:8080/jsonp_test.jsp 주소를 대상으로 ajax 호출한 내용을 피들러에서 확인한 결과임.

“callback” 이라는 파라미터 값으로 “jQuery22406444848349974217_1634705610458” 문자열이 넘어감.

 

 

■ test1.html 의 실행결과

ajax 콜백함수 첫번째 인자(아규먼트)로 json 객체를 넘겨받게 됨.

정상 데이터인지 확인하기 위해 json 객체를 텍스트 형태로 만들어서 div 영역에 출력했음.

 

참고사이트 : https://stove99.tistory.com/10

[VBS/VBA] 엑셀 PDF 저장 시 [인쇄할 내용이 없습니다] 오류 메시지 방지

[VBS/VBA] 엑셀 PDF 저장 시 [인쇄할 내용이 없습니다] 오류 메시지 방지

VBS 또는 VBA 로 엑셀 PDF 저장 시 “인쇄할 내용이 없습니다” 오류 메시지가 alert 형태로 뜨는 경우가 있다.

실제로 워크시트가 비어있을 때(아무 값도 입력되어 있지 않은 워크시트일 때) 위 오류메시지가 발생했다.

해당 오류 메시지가 뜨는 것을 방지하기 위해 비어있는 시트인지 체크하는 코드를 계속 찾았는데 여러가지로 문제가 많았다.

그러다가 최고의 코드를 생각해냈다.

시트에 아무 값도 입력되어 있지 않다면 값을 입력하면 되는거다.

아래와 같이 해결했다.

‘[인쇄할 내용이 없습니다] 오류 메시지 방지
If ActiveSheet.Cells(1, 1).Value = “” Then
    ActiveSheet.Cells(1, 1).Value = ” “
End If

[VBA] 워크시트의 마지막 셀 주소 가져오기 (vba last row, vba last column, vba last cell address)

[VBA] 워크시트의 마지막 셀 주소 가져오기 (vba last row, vba last column, vba last cell address)

엑셀 VBA 마지막 셀 주소 가져오는 방법.

엑셀 VBA 마지막 행, 로우 가져오는 방법.

엑셀 VBA 마지막 열, 컬럼 가져오는 방법.

여러 코드가 있지만 시트 중간에 공백 셀이 존재하는 경우(ex : A1 셀이 공백) 공백을 문서의 끝으로 오인해서 잘못된 주소를 가져오는 경우가 많았다.

여태까지 이를 방지하기 위해서 공백이 10번 이상 연속되면 문서의 끝으로 판단하는 등 불완전한 방법을 사용해왔는데 아래 코드는 그런 문제가 없다.

Sub TestSub1()

Dim lastRow
Dim lastCol

lastRow = ActiveSheet.Cells.Find(“*”, LookIn:=xlFormulas, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
lastCol = ActiveSheet.Cells.Find(“*”, LookIn:=xlFormulas, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column

MsgBox (“lastRow : ” & lastRow) ‘lastRow : 75
MsgBox (“lastCol : ” & lastCol) ‘lastCol : 11

Dim lastCellAddr
lastCellAddr = ActiveSheet.Cells(lastRow, lastCol).Address

MsgBox (“lastCellAddr : ” & lastCellAddr) ‘$K$75

End Sub

참고사이트 : https://www.thespreadsheetguru.com/blog/2014/7/7/5-different-ways-to-find-the-last-row-or-last-column-using-vba

[JAVA] HttpURLConnection POST 호출 예제

[JAVA] HttpURLConnection POST 호출 예제

자바에서 server to server 로 POST 호출하는 코드.

일반 파라미터가 아닌 json 데이터를 실어 보내는 방식은 다음 포스트 참고 : https://blog.naver.com/bb_/222047426772

public String postHttp(String urlString, String parameters, String encoding) throws IOException, Exception {
    if (encoding == null || encoding.length() == 0) {
        encoding = “UTF-8”;
    }
    
    HttpURLConnection ucon = null;
    String result = null;
    
    OutputStream os = null;
    DataOutputStream dos = null;
    InputStream is = null;
    
    try {
        URL url = new URL(urlString);
        
        ucon = (HttpURLConnection) url.openConnection();
        
        ucon.setRequestMethod(“POST”);
        
        ucon.setDoOutput(true);
        ucon.setConnectTimeout(3 * 1000);
        ucon.setReadTimeout(3 * 1000);
        ucon.setUseCaches(false);
        
        ucon.setRequestProperty(“Accept-Language”, encoding);
        ucon.setRequestProperty(“connection”“Keep-Alive”);
        ucon.setRequestProperty(“cache-control”“no-cache”);
        ucon.setRequestMethod(“POST”);
        
        os = ucon.getOutputStream();
        
        dos = new DataOutputStream(os);
        dos.writeBytes(parameters);
        dos.flush();
        dos.close();
        
        int status = ucon.getResponseCode();
        
        if (status >= HttpURLConnection.HTTP_OK || status < HttpURLConnection.HTTP_MULT_CHOICE) {
            is = ucon.getInputStream();
            StringBuffer buf = new StringBuffer();
            
            int c = 0;
            
            while ((c = is.read()) != -1) {
                buf.append((char) c);
            }
            
            result = buf.toString();
            result = new String(result.getBytes(“iso-8859-1”));
        }
        ucon.disconnect();
        
    } catch (NullPointerException e) {
        throw e;
        
    } catch (Exception e) {
        throw e;
    
    } finally {
        try {
            if (is != null) {
                is.close();
            }
        } catch (IOException e) {
        } catch (Exception e) {
        }
        
        try {
            if (dos != null) {
                dos.close();
            }
        } catch (IOException e) {
        } catch (Exception e) {
        }
        
        try {
            if (os != null) {
                os.close();
            }
        } catch (IOException e) {
        } catch (Exception e) {
        }
        
        try {
            if (ucon != null) {
                ucon.disconnect();
            }
        } catch (NullPointerException e) {
        } catch (Exception e) {
        }
    }
    
    return result;
}

[Linux] 리눅스 쉘(sh) 파일 실행 시 한글 깨지는 경우 (linux, sh, 한글깨짐 현상)

[Linux] 리눅스 쉘(sh) 파일 실행 시 한글 깨지는 경우 (linux, sh, 한글깨짐 현상)

리눅스 쉘(sh) 파일 실행 시 한글 깨지는 경우 조치방법.

우선 리눅스에 원격으로 접속한 경우, putty 등 원격 프로그램 단에서 깨지는 것이 아닌지 확인부터 한다.

sh 파일 상단에 다음과 같이 입력하면 한글깨짐을 방지할 수 있다.

export LANG=ko_KR.utf8

또는

LANG=ko_KR.utf8

export LANG

이때 LANG 값으로는 아무값이나 넣는게 아니라 리눅스에서 사용 가능한 로케일 값을 넣어야 한다.

리눅스에서 사용 가능한 로케일 값은 locale -a 명령어로 조회할 수 있다.

locale -a 명령어 결과 예시

cs_CZ.utf8

da_DK.utf8

de_DE.utf8

en_US.utf8

es_ES.utf8

fr_FR.utf8

hu_HU.utf8

it_IT.utf8

ja_JP.utf8

ko_KR.utf8

nb_NO.utf8

nl_NL.utf8

pl_PL.utf8

POSIX

pt_BR.utf8

pt_PT.utf8

ru_RU.utf8

sv_SE.utf8

tr_TR.utf8

zh_CN.utf8

zh_TW.utf8

참고사이트 : https://www.lesstif.com/lpt/locale-87949397.html

[IE11] window.close() 멈춤 현상 (window.close(), self.close() hang problem)

[IE11] window.close() 멈춤 현상 (window.close(), self.close() hang problem)

최근(2021년 10월) 윈도우 10 업데이트 이후, IE11 에서 브라우저 닫힐 때 멈춤 현상(hang) 해결방법.

브라우저를 닫는 js 함수( window.close(), self.close() ) 실행 시 hang 걸리는 문제가 있었다.

IE11의 버전이 20H2 인 경우 현상이 발생된다고 하며, 직접적인 원인은 IEToEdge BHO 라는 추가기능 때문이라고 한다.

아래와 같이 (1) IE 브라우저 우측상단 도구 버튼(톱니바퀴 모양) – (2) [추가 기능 관리] 클릭 – (3) 좌측의 [도구 모음 및 확장 프로그램] 메뉴 클릭을 통해 IEToEdge BHO 항목을 조회할 수 있다.

 

IEToEdge BHO 를 삭제하는 방법도 있다고 하지만, Edge 브라우저 설정을 변경하는 방법도 있어서 여기서는 후자의 방법만 소개한다.

아래는 마이크로소프트 엣지(Edge) 브라우저에서 설정을 변경하는 방법이다.

 

마이크로소프트 엣지(Edge) 브라우저를 실행하고,

(1) 브라우저 우측상단 […] 버튼 클릭 ([설정 및 기타] 버튼이며 단축키는 Alt + F)

(2) [설정] 메뉴 클릭

(3) 좌측 메뉴의 [기본 브라우저] 클릭

(4) 우측 화면 [Internet Explorer 호환성] 항목의 [Internet Explorer를 사용하여 Microsoft Edge에서 사이트를 열어 보세요.] 셀렉트박스의 값을 [안 함]으로 설정 할 것.

해당 셀렉트박스 값이 [호환되지 않는 사이트만]인 경우 window.close(), self.close() 함수가 실행될 때 멈춤 현상(hang)이 발생하는 것으로 보인다.

참고사이트 1 : https://answers.microsoft.com/ko-kr/ie/forum/all/window10-20h2-ie-%EC%82%AC%EC%9A%A9-%EC%A4%91/cf83551e-f5d5-4e50-9764-c87cfa6c8f3e

참고사이트 2 : https://shine72.tistory.com/194
참고사이트 3 : https://shine72.tistory.com/197

[Java, Tomcat] java.lang.AbstractMethodError at net.sourceforge.jtds.jdbc.JtdsConnection.isValid(JtdsConnection.java:2833)

[Java, Tomcat] java.lang.AbstractMethodError at net.sourceforge.jtds.jdbc.JtdsConnection.isValid(JtdsConnection.java:2833)

마이바티스(또는 아이바티스) 사용 시 GetConnection 은 성공했는데 select 시도할 때 아래 오류가 발생하는 경우.

DEBUG : 2021-09-29 21:52:54 – jdbc.JdbcTransaction (JdbcTransaction.java:132:openConnection) – Opening JDBC Connection

29-Sep-2021 21:52:54.618 심각 [http-nio-8541-exec-14] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [jsp] in context with path [] threw exception [javax.servlet.ServletException: java.lang.AbstractMethodError] with root cause

 java.lang.AbstractMethodError

        at net.sourceforge.jtds.jdbc.JtdsConnection.isValid(JtdsConnection.java:2833)

        at org.apache.tomcat.dbcp.dbcp2.DelegatingConnection.isValid(DelegatingConnection.java:916)

        at org.apache.tomcat.dbcp.dbcp2.PoolableConnection.validate(PoolableConnection.java:282)

        at org.apache.tomcat.dbcp.dbcp2.PoolableConnectionFactory.validateConnection(PoolableConnectionFactory.java:362)

        at org.apache.tomcat.dbcp.dbcp2.BasicDataSource.validateConnectionFactory(BasicDataSource.java:2346)

        at org.apache.tomcat.dbcp.dbcp2.BasicDataSource.createPoolableConnectionFactory(BasicDataSource.java:2329)

        at org.apache.tomcat.dbcp.dbcp2.BasicDataSource.createDataSource(BasicDataSource.java:2062)

        at org.apache.tomcat.dbcp.dbcp2.BasicDataSource.getConnection(BasicDataSource.java:1532)

        at org.apache.ibatis.transaction.jdbc.JdbcTransaction.openConnection(JdbcTransaction.java:134)

        at org.apache.ibatis.transaction.jdbc.JdbcTransaction.getConnection(JdbcTransaction.java:61)

        at org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:279)

        at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:72)

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

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

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

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

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

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

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

해결책은 톰캣 폴더 하위 context.xml 파일에 아래 내용 추가


<Resource url=”DB주소”

    testOnBorrow=”true” 

    password=”패스워드”

    username=”유저네임”

    maxWait=”5000″ 

    minIdle=”2″ 

    maxActive=”4″

    driverClassName=”드라이버명” 

    type=”javax.sql.DataSource”

    auth=”Container”

    name=”jdbc/리소스명”

    validationQuery=”select 1″/>

예를 들어 아래와 같이 작성한다.

ex 1) oracle

<Resource url=”jdbc:oracle:thin:@아이피:포트:DB명”

    testOnBorrow=”true” 

    password=”패스워드”

    username=”유저네임”

    maxWait=”5000″ 

    minIdle=”2″ 

    maxActive=”4″

    driverClassName=”oracle.jdbc.OracleDriver” 

    type=”javax.sql.DataSource”

    auth=”Container”

    name=”jdbc/testdb”

    validationQuery=”select 1 from dual”/>

ex 2) mssql

<Resource url=”jdbc:jtds:sqlserver://아이피:포트/DB명”

    testOnBorrow=”true” 

    password=”패스워드”

    username=”유저네임”

    maxWait=”5000″ 

    minIdle=”2″ 

    maxActive=”4″

    driverClassName=”net.sourceforge.jtds.jdbc.Driver” 

    type=”javax.sql.DataSource”

    auth=”Container”

    name=”jdbc/testdb”

    validationQuery=”select 1″/>

참고사이트 : https://stackoverflow.com/questions/41231750/abstractmethoderror-with-jtds-jdbc-driver-on-tomcat-8/41232124

[Windows] 윈도우 tail 실행방법

[Windows] 윈도우 tail 실행방법

리눅스에서 사용하는 tail 명령어를 윈도우에서 실행하는 방법이다.

1. tail.bat 이라는 파일을 아래 내용으로 생성한다.

메모장으로 작성해서 적당한 위치에 저장하면 된다(ex : C:\test\tail.bat)

tail.bat

@echo off
PowerShell Get-Content -Path “%1” -Wait -Tail 10 -Encoding UTF8

 

2. 명령 프롬프트(cmd)를 열어 tail.bat [파일경로] 명령어로 실행한다.

예를 들면 cmd 창에 아래와 같이 입력한다.

C:\test\tail.bat C:\test\localhost.log

명령어를 실행하면 파일 내용을 아래부터 10라인 출력한다.

출력 라인수를 바꾸려면 tail.bat 파일 내용의 숫자 10을 원하는 숫자로 변경하면 된다.

[IntelliJ] 인텔리제이 롬복(lombok) 적용되지 않는 문제

[IntelliJ] 인텔리제이 롬복(lombok) 적용되지 않는 문제

@Getter, @Setter, @RequiredArgsConstructor, @AllArgsConstructor 등 롬복 어노테이션이 적용되지 않는 경우 해결방법.

인텔리제이 상에서는 빨간줄도 생기지 않고 정상적인데 프로젝트를 실행했을 때 오류가 발생하는 경우였다.

특히 아래 내용 중 2번을 적용했더니 해결됐다.

1. 롬복 플러그인 설치

윈도우 단축키 [Ctrl + Shift + A] (맥 단축키 Command + Shift + A) 로 검색창 띄우기 – [Actions] 탭 선택 – [Plugins] 검색 후 엔터 – [Plugins] 창이 뜨면 [Lombok] 검색해서 [Install] 버튼 클릭

2. build.gradle 파일의 dependencies 부분에 아래 내용 추가

dependencies {

    (중략)

    compile(‘org.projectlombok:lombok’)

    compileOnly(‘org.projectlombok:lombok’)

    annotationProcessor(‘org.projectlombok:lombok’)

    testAnnotationProcessor(‘org.projectlombok:lombok’)

    testCompile(‘org.projectlombok:lombok’)

    testImplementation(‘org.projectlombok:lombok’)

}

3. [File] – [Settings…] – [Build, Execution, Deployment] – [Compiler] – [Java Compiler] – [Use compiler] 셀렉트박스 값 [Javac] 선택

4. [File] – [Settings…] – [Build, Execution, Deployment] – [Compiler] – [Annotation Processors] – [Enable annotation processing] 체크박스 체크처리

5. [File] – [Invalidate Caches / Restart] 클릭

참고사이트 : https://jaimemin.tistory.com/1379

[Eclipse] 이클립스 실행 목록 삭제 (Run Configurations 삭제, Debug Configurations 삭제)

[Eclipse] 이클립스 실행 목록 삭제 (Run Configurations 삭제, Debug Configurations 삭제)

이클립스에서 Run Configurations 또는 Debug Configurations 목록을 삭제하는 방법.

워크스페이스\.metadata\.plugins\org.eclipse.debug.core\.launches 폴더 내의 파일을 모두 삭제하면 된다.

ex) C:\workspaces\myproject\.metadata\.plugins\org.eclipse.debug.core\.launches

오작동을 방지하기 위해 이클립스를 종료한 상태로 삭제하는 것을 권장한다.

참고사이트 : https://blog.naver.com/seban21/221145803737

[Javascript] 인풋 파일 객체 (input type="file") 용량 가져오기

[Javascript] 인풋 파일 객체(input type=”file”) 용량 가져오기

파일 업로드 전 인풋 파일 객체(input type=”file”)의 용량을 알아내야할 때가 있다.

대표적으로 대용량파일 첨부를 방지하고 싶을 때 사용한다.

<html>

    <input type=”file” id=”fileObj” />

</html>

* 파일 선택여부 확인

파일 선택하기 전에는 document.getElementById(“fileObj”).files.length == 0 이다.

var fileArr = document.getElementById(“fileObj”).files;

if (fileArr != null && fileArr.length > 0) {

    alert(“파일 선택한 상태”);

} else {

    alert(“파일 선택 전”);

}

* 파일 1개 선택한 경우 용량 (첫번째 파일 용량)

document.getElementById(“fileObj”).files[0].size

* 파일 n개 선택한 경우 용량

var fileArr = document.getElementById(“fileObj”).files;

if (fileArr != null && fileArr.length > 0) {

    var fileCount = fileArr.length;

    for (var i=0; i< fileCount; i++) {

        alert((i + 1) + “번째 파일 용량 : ” + fileArr[i].size);

    }

}

[Oracle] 오라클 인덱스 리빌드(rebuild), 오라클 힌트(hint) 적용 방법

[Oracle] 오라클 인덱스 리빌드(rebuild), 오라클 힌트(hint) 적용 방법

1. 오라클 인덱스 리빌드(rebuild)

동일한 쿼리의 실행결과가 (환경에 따라) 다른 경우, 먼저 인덱스 리빌드를 해본다.

alter index 인덱스명 rebuild;

ex) alter index table_name_idx1 rebuild;

2. 오라클 쿼리문에 힌트(hint) 적용 방법

그래도 동일한 쿼리의 실행결과가 다른 경우, 쿼리문에 힌트를 적용해서 실행해본다.

select /*+ 힌트종류(테이블명 인덱스명) */ 컬럼명, 컬럼명 …

from 테이블명

where 조건절

ex 1) select /*+ index(table_name table_name_idx1) */ col1, col2

       from table_name

       where col1 = ‘1’ and col2 = ‘2’;

ex 2) select /*+ index(t1 table_name_idx1) */ col1, col2

        from table_name t1

        where col1 = ‘1’ and col2 = ‘2’;

힌트가 잘 적용되었는지 확인하려면 DB툴에서 플랜을 떠본다.
플랜 확인 단축키는 툴마다 다르다. (ex : 오라클 SQL Developer 플랜 단축키는 F10, 티베로 tbadmin 플랜 단축키는 F7)

[JAVA] Invalid character found in method name. HTTP method names must be tokens

[JAVA] Invalid character found in method name. HTTP method names must be tokens

java.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must be tokens

at org.apache.coyote.http11.InternalInputBuffer.parseRequestLine(InternalInputBuffer.java:140)

at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1050)

at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)

at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

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

at java.lang.Thread.run(Thread.java:745)

https 프로토콜이 활성화되지 않은(http 프로토콜을 사용하는) 웹서비스에 https URL을 요청하는 경우 발생하는 오류다.

결국 오류가 발생한 원인은 둘 중 하나다.

1. 브라우저 주소창에 http 를 입력해야 하는데 실수로 https 를 입력한 경우

2. https 프로토콜로 띄워야하는 웹서비스인데 SSL 적용이 안된 경우

실수로 잘못 입력하지 않았는지 브라우저 주소창을 확인해보자.

참고사이트 : https://yeonyeon.tistory.com/84

[Javascript] IE11/Chrome 자바스크립트 인쇄

[Javascript] IE11/Chrome 자바스크립트 인쇄

자바스크립트 인쇄는 간단하게 window.print(); 명령어를 사용하면 된다.

문제는 아이프레임이 있는 페이지의 경우다.

1. 여러 개의 아이프레임 인쇄

여러 개의 아이프레임은 한 번에 인쇄할 수 없다고 본다.

여러 개의 아이프레임을 한 번에 인쇄하고 싶다면 빈 아이프레임을 하나 만들고 innerHTML 명령어를 이용해 그 안에 모든 엘리먼트를 다 집어넣어서 인쇄하는 방법이 있다.

2. 특정 아이프레임 인쇄

특정 아이프레임을 인쇄하고 싶은 경우 아이프레임 안의 해당 페이지에 print 함수를 만들어놓는 편이 좋다.

예를 들면 아래와 같은 함수를 인쇄할 대상 페이지에 넣는다.

function doPrintPage() {

    window.print();

}

그러면 아이프레임의 바깥에서 print 명령을 수행하기 쉬워진다.

예를 들어 아이프레임이 <iframe id=”targetFrame” name=”targetFrame”> 과 같다면, window.targetFrame.doPrintPage(); 명령어로 인쇄를 실행하면 된다.

3. IE11/Chrome 에서 아이프레임 인쇄

크롬(Chrome)에서는 이전 항목에서 밝혔듯 window.targetFrame.doPrintPage(); 명령어만 사용하면 인쇄가 실행된다.

반면 IE11은 아이프레임에 먼저 focus를 주고 print 명령어를 실행해야 인쇄가 정상적으로 수행된다.

크롬은 포커스와 상관없이 print 함수가 실행되는 프레임 내용이 인쇄되지만, IE11은 현재 포커스되어 있는 프레임 내용이 인쇄되기 때문이다.

하지만 굳이 코드를 분기할 필요는 없다. 결론은 IE11이든 크롬이든 아래처럼 쓰면 된다.

window.targetFrame.focus(); // for IE11

window.targetFrame.doPrintPage();

[JAVA] 자바에서 만든 zip 파일이 맥(Mac)에서 내부 파일명 한글깨짐/한글 깨지는 경우

[JAVA] 자바에서 만든 zip 파일이 맥(Mac)에서 내부 파일명 한글깨짐/한글 깨지는 경우
자바에서 만든 zip 파일을 맥(Mac)에서 압축해제 시 내부 파일명 한글깨짐/한글 깨지는 경우다.
zip 파일명 자체는 깨지지 않는데, zip 내부 파일들의 파일명이 깨지는 경우다.
처음에는 zip 파일 다운로드하는 과정에 문제가 있다고 생각했는데, zip 압축파일을 만들어내는 방식의 문제였다.

이것은 zip 파일을 압축할 때 파일명을 유니코드(UTF-8)로 저장하는 방식을 사용해서 해결할 수 있다.

그런데 윈도우의 몇몇 압축 프로그램의 경우에는 오히려 파일명을 유니코드(UTF-8)로 해서 압축했을 때 이름이 깨지는 경우도 있다고 하니까, 맥 OS를 사용하는 경우에 대해서만 처리를 해주는 것을 권장한다.

방법은 자바에서 zip 압축파일을 만들 때 ZipOutputStream 객체(import org.apache.tools.zip.ZipOutputStream;) 가 아닌, ZipArchiveOutputStream 객체(import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;) 를 사용하면 된다.

기존 코드에서 ZipOutputStream 객체를 사용하고 있다면 해당 객체만 ZipArchiveOutputStream 객체로 바꿔서 테스트해보자.

ZipArchiveOutputStream 객체를 사용하기 위해서는 Apache Common Compress 라이브러리를 임포트하면 된다.

http://commons.apache.org/compress/ 에서 구할 수 있고, https://mvnrepository.com/artifact/org.apache.commons/commons-compress/1.20 에서도 곧바로 다운로드받을 수 있다.

필자는 1.20 버전을 사용했다. (commons-compress-1.20.jar)

이때 자바 압축 코드의 핵심은 파일명을 UTF-8 인코딩 설정하는 아래 코드이다.

    zipOutputStream = new ZipArchiveOutputStream(fileOutputStream);

    // Mac OS 사용하는 경우 zip 압축파일 내부 파일명 깨지는 문제 방지
    zipOutputStream.setEncoding(“UTF-8”);
    zipOutputStream.setFallbackToUTF8(true);
    zipOutputStream.setUseLanguageEncodingFlag(true);
    zipOutputStream.setCreateUnicodeExtraFields(ZipArchiveOutputStream.UnicodeExtraFieldPolicy.ALWAYS);

전체적인 코드 수정은 아래와 같이 하면 된다.

[AS-IS]

    public static void makeZip() {
        
        File outputFile = null;
        FileOutputStream fileOutputStream = null;
        ZipOutputStream zipOutputStream = null;
        
        try {
            outputFile = new File(“C:\\test\\output.zip”);
            fileOutputStream = new FileOutputStream(outputFile);
            
            zipOutputStream = new ZipOutputStream(fileOutputStream);
            zipOutputStream.setEncoding(“KSC5601”);
    
            ZipEntry zipEntry = new ZipEntry(“input.txt”);
            zipOutputStream.putNextEntry(zipEntry);
    
            FileInputStream fileInputStream = null;
            try {
                byte[] buf = new byte[4096];
                int byteRead = 0;
                
                File inputFile = new File(“C:\\test\\테스트.txt”);
                fileInputStream = new FileInputStream(inputFile);
        
                while ((byteRead = fileInputStream.read(buf)) > 0) {
                 zipOutputStream.write(buf, 0, byteRead);
                }
                
                zipOutputStream.flush();
                zipOutputStream.closeEntry();
                
            } catch (Exception e) {
                throw e;
                
            } finally {
                try {
                    if (fileInputStream != null) {
                        fileInputStream.close();
                    }
                } catch (Exception e) {}
            }
            
        } catch (Exception e) {
            e.printStackTrace();
            
        } finally {
            try {
                if (zipOutputStream != null) {
                    zipOutputStream.close();
                }
            } catch (Exception e) {}
            
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (Exception e) {}
        }
    }

[TO-BE]

    public static void makeZip() {
        
        File outputFile = null;
        FileOutputStream fileOutputStream = null;
        ZipArchiveOutputStream zipOutputStream = null;
        
        try {
            outputFile = new File(“C:\\test\\output.zip”);
            fileOutputStream = new FileOutputStream(outputFile);
            
            zipOutputStream = new ZipArchiveOutputStream(fileOutputStream);


            // Mac OS 사용하는 경우 zip 압축파일 내부 파일명 깨지는 문제 방지
            zipOutputStream.setEncoding(“UTF-8”);
            zipOutputStream.setFallbackToUTF8(true);
            zipOutputStream.setUseLanguageEncodingFlag(true);
            zipOutputStream.setCreateUnicodeExtraFields(ZipArchiveOutputStream.UnicodeExtraFieldPolicy.ALWAYS);
    
            ZipArchiveEntry zipEntry = new ZipArchiveEntry(“input.txt”);
            zipOutputStream.putArchiveEntry(zipEntry);
            
            FileInputStream fileInputStream = null;
            try {
                byte[] buf = new byte[4096];
                int byteRead = 0;
                
                File inputFile = new File(“C:\\test\\테스트.txt”);
                fileInputStream = new FileInputStream(inputFile);
        
                while ((byteRead = fileInputStream.read(buf)) > 0) {
                 zipOutputStream.write(buf, 0, byteRead);
                }
                
                zipOutputStream.flush();
                zipOutputStream.closeArchiveEntry();
                
            } catch (Exception e) {
                throw e;
                
            } finally {
                try {
                    if (fileInputStream != null) {
                        fileInputStream.close();
                    }
                } catch (Exception e) {}
            }
            
        } catch (Exception e) {
            e.printStackTrace();
            
        } finally {
            try {
                if (zipOutputStream != null) {
                    zipOutputStream.close();
                }
            } catch (Exception e) {}
            
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (Exception e) {}
        }
    }

참고사이트 1 : https://visu4l.tistory.com/394

참고사이트 3 : https://huskdoll.tistory.com/m/189?category=526538

[JAVA] 백엔드(자바) 단에서 사용자가 맥 OS 유저인지 체크 (agent Mac OS 여부 체크)

[JAVA] 백엔드(자바) 단에서 사용자가 맥 OS 유저인지 체크 (agent Mac OS 여부 체크)

사용자의 프론트엔드 환경(운영체제, OS)를 확인하기 위해서는 HttpServletRequest (request) 객체의 User-Agent 값을 확인하면 된다.

User-Agent 값에 “Macintosh” 또는 “Mac OS”라는 문자열이 포함되어 있으면 맥킨토시 사용자인 것으로 판단했다.

    /**
     * request 객체를 통해 Mac OS 사용여부 체크하기
     *
     * @param req
     * @return
     * @throws NullPointerException
     * @throws Exception
     */

    public static boolean checkIsMacOS(HttpServletRequest req) throws NullPointerException, Exception {
        boolean isMacOS = false;
        
        String reqUserAgent = req.getHeader(“User-Agent”);
        if (reqUserAgent != null) {
            reqUserAgent = reqUserAgent.toLowerCase();
            if (reqUserAgent.contains(“macintosh”) || reqUserAgent.contains(“mac os”)) {
                isMacOS = true;
            }
        }
    
        return isMacOS;
    }

참고로 백엔드(자바) 단에서 출력한 User-Agent의 값을 남겨둔다.

(모든 맥과 윈도우 환경이 동일하다는 것은 아니고 하나의 예이다.)

1. 맥(Mac OS)에서 크롬 브라우저를 사용한 경우

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36

2. 윈도우(Windows)에서 크롬 브라우저를 사용한 경우

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36

[JAVA] 맥(Mac OS)에서 파일 업로드 시 파일명 자모음 분리되는 경우(자소분리) 처리방법

[JAVA] 맥(Mac OS)에서 파일 업로드 시 파일명 자모음 분리되는 경우(자소분리) 처리방법

운영체제 맥(Mac OS)에서 파일 업로드 시 file 타입의 input 박스(<input type=”file”>)에 넣은 파일명이 “테스트.txt”일 경우, 백엔드(자바) 단에서 “ㅌㅔㅅㅡㅌㅡ.txt” 형태로 한글 자모음이 분리되어 온다.

이는 multipart/form-data 로 전송해도 그렇고 일반적인 parameter 로 전달해도 똑같다.

만약 프론트엔드에서 UTF-8로 인코딩하고 BASE64 로 감싸서 전달하더라도 문제는 동일하다. (ex : btoa(encodeURIComponent(“테스트”)); 를 파라미터로 전달)

BASE64 로 받은 파라미터 값을 백엔드(자바) 단에서 BASE64 디코드하고 URLDecoder.decode 처리해보면 동일하게 한글 자모음이 분리되어 있다.

만약 맥 OS에서는 글자가 “테스트”라고 정상적으로 보여도, 실제로는 “ㅌㅔㅅㅡㅌㅡ” 로 구성되어 있다는 것이다.

애초에 맥 OS가 인코딩 방식으로 NFD 라고 불리는 한글 조합형 방식을 사용하기 때문으로 보인다.

맥 OS는 NFD(Normalization Form Canonical Decomposition) 방식을, 윈도우 OS는 NFC(Normalization Form Canonical Composition) 방식을 사용한다고 한다. 맥 OS는 조합형 방식(ex : ㅌㅔㅅㅡㅌㅡ), 윈도우는 완성형 방식(ex : 테스트)이다.

다행히 이 두 가지 인코딩 방식은 자바 단에서 서로 교환 가능하다.

아래와 같이 사용하면 된다.

즉, 파일 업로드 시점에 아래 메서드를 태워서 운영체제에 맞게 문자열 값을 보정해주면 된다.

    /**
     * 맥 OS의 한글 인코딩 방식을 고려한 파일명 보정
     *
     * @param fileName
     * @param isMacOS
     * @param convertToMacFileName
     * @return
     */

    public static String convertFileNameForMac(String fileName, boolean convertToMacFileName) {
        if (fileName == null) {
            fileName = “”;
        }
        
        // 맥은 NFD(조합형), 윈도우는 NFC(완성형) 방식을 사용한다.
        if (convertToMacFileName) {
            // 윈도우 파일명 => 맥 파일명
            if (Normalizer.isNormalized(fileName, Normalizer.Form.NFC)) {
                fileName = Normalizer.normalize(fileName, Normalizer.Form.NFD);
            }
        } else {
            // 맥 파일명 => 윈도우 파일명 (맥에서 파일 업로드 시 사용)
            if (Normalizer.isNormalized(fileName, Normalizer.Form.NFD)) {
                fileName = Normalizer.normalize(fileName, Normalizer.Form.NFC);
            }
        }
        
        return fileName;
    }

참고사이트 1 : https://tt.kollhong.com/79

참고사이트 2 : https://gemimi.tistory.com/43

[Ghostscript] 고스트스크립트 Can't find CID font "Batang".

[Ghostscript] 고스트스크립트 Can’t find CID font “Batang”.

pdf 파일을 jpg 이미지로 변환해주는 VBS(Visual Basic Script)를 Ghostscript로 실행시킬 때, Can’t find CID font 오류가 발생해서 변환에 실패하는 경우가 있었다.

아래와 같이 여러가지 글꼴명으로 오류가 발생한다.

Can’t find CID font “Batang”.

Can’t find CID font “Dotum”.

Can’t find CID font “GulimChe”.

Can’t find CID font “맑은고딕”.

등등

해당 오류는 pdf 파일 안에 포함되어있는 글꼴에 대한 CID 폰트가 없기 때문에 발생하는 것으로 보인다. CID 폰트란 Character ID keyed Font 의 약자로 미국 어도비시스템즈 사가 개발한 폰트 형식이라고 하는데 주로 pdf 파일에서 내장 폰트로 사용한다고 한다.

이를 해결하는 방법은 2가지가 있는데 실제로 CID 폰트를 구하는 방법과, CID 폰트 대신 일반 폰트를 매핑해서 대체하는 방법이 있다.

찾아보니 CID 폰트를 구하기는 어려워서 일반 폰트로 대체하는 방법으로 해결했다.

CID 폰트를 일반 폰트로 대체하는 방법은 고스트스크립트 폴더 하위의 doc/Use.htm 파일에 나와있다. ex) C:\Program Files (x86)\gs\gs8.71\doc\Use.htm

위 파일에서 CID fonts, CID font substitution 항목을 찾아보면 된다.



간단히 설명하면 고스트스크립트 폴더 하위의 lib/cidfmap 파일 안에 폰트 정보를 매핑해주면 된다. ex) C:\Program Files (x86)\gs\gs8.71\lib\cidfmap



cidfmap 파일내용

%!
% cidfmap generated automatically by mkcidfm.ps from fonts found in
%   C:/WINDOWS/fonts

% Substitutions
/MS-PGothic << /FileType /TrueType /SubfontID 1 /CSI [(Japan1) 3] /Path (C:/WINDOWS/fonts/msgothic.ttc) >> ;
/DotumChe << /FileType /TrueType /SubfontID 3 /CSI [(Korea1) 3] /Path (C:/WINDOWS/fonts/gulim.ttc) >> ;
/SimSun << /FileType /TrueType /SubfontID 0 /CSI [(GB1) 2] /Path (C:/WINDOWS/fonts/simsun.ttc) >> ;
/Gulim << /FileType /TrueType /SubfontID 0 /CSI [(Korea1) 3] /Path (C:/WINDOWS/fonts/gulim.ttc) >> ;
/BatangChe << /FileType /TrueType /SubfontID 1 /CSI [(Korea1) 3] /Path (C:/WINDOWS/fonts/batang.ttc) >> ;
/Dotum << /FileType /TrueType /SubfontID 2 /CSI [(Korea1) 3] /Path (C:/WINDOWS/fonts/gulim.ttc) >> ;
/Gungsuh << /FileType /TrueType /SubfontID 2 /CSI [(Korea1) 3] /Path (C:/WINDOWS/fonts/batang.ttc) >> ;
/MS-UI-Gothic << /FileType /TrueType /SubfontID 2 /CSI [(Japan1) 3] /Path (C:/WINDOWS/fonts/msgothic.ttc) >> ;
/MS-Gothic << /FileType /TrueType /SubfontID 0 /CSI [(Japan1) 3] /Path (C:/WINDOWS/fonts/msgothic.ttc) >> ;
/NSimSun << /FileType /TrueType /SubfontID 1 /CSI [(GB1) 2] /Path (C:/WINDOWS/fonts/simsun.ttc) >> ;
/Batang << /FileType /TrueType /SubfontID 0 /CSI [(Korea1) 3] /Path (C:/WINDOWS/fonts/batang.ttc) >> ;
/GulimChe << /FileType /TrueType /SubfontID 1 /CSI [(Korea1) 3] /Path (C:/WINDOWS/fonts/gulim.ttc) >> ;
/GungsuhChe << /FileType /TrueType /SubfontID 3 /CSI [(Korea1) 3] /Path (C:/WINDOWS/fonts/batang.ttc) >> ;

% Aliases
/STSong-Light /SimSun ;
/HeiseiKakuGo-W5 /MS-Gothic ;
/HYSMyeongJo-Medium /Batang ;
/GothicBBB-Medium /MS-Gothic ;
/HYRGoThic-Medium /Gulim ;
/HYGoThic-Medium /Dotum ;
/STFangsong-Light /SimSun ;

만약 고스트스크립트 폴더 하위의 lib/cidfmap 파일이 없다면 고스트스크립트를 다시 설치하는 것을 권장한다. ex) 설치파일명 gs871w32.exe

고스트스크립트를 설치할 때 “Use Windows TrueType fonts for Chinese, Japanese and Korean” 체크박스를 체크하면 된다.

만약 고스트스크립트 재설치 이후에도 오류가 재현될 경우, 폰트명을 보고 cidfmap 파일에 누락된 폰트 정보를 매핑시켜서 CID 폰트를 대체하는 방식으로 문제를 해결할 수 있다.

참고사이트 1 : https://m.blog.naver.com/naroo/221848383186

[DBeaver] DBeaver AutoCommit off (디비버 오토커밋 해제)

[DBeaver] DBeaver AutoCommit off (디비버 오토커밋 해제)

1. DBeaver 상단메뉴 [윈도우] – [설정] 클릭


2. 환경 설정 윈도우가 뜨면 좌측 트리의 [연결] – [연결 유형]

우측 화면의 [Auto-commit by default] 체크박스를 해제하고 [Apply and Close] 버튼 클릭


 

3. 이후 디비버(DBeaver)를 재기동하면 오토커밋 해제가 적용된다.

[Chrome] 크롬 브라우저 항상 시크릿 모드로 열기

[Chrome] 크롬 브라우저 항상 시크릿 모드로 열기

크롬 브라우저에서 시크릿 모드(영어로는 Incognito mode)는 인터넷 사용 기록을 남기지 않는다.

방문 기록, 로그인 아이디/패스워드 정보와 같은 쿠키값을 저장하지 않는다. 즉, 개인 프라이버시가 보장된다.

1. 크롬 브라우저 시크릿 모드로 창 열기 (시크릿 창 열기)

크롬 브라우저에서 시크릿 모드로 창을 열기 위해서는, 크롬 브라우저에서 단축키 Ctrl + Shift + N 키를 누르거나, 우측 상단 […] 버튼 클릭 – [새 시크릿 창] 을 클릭하면 된다.


2. 크롬 브라우저 항상 시크릿 모드로 열기
 

회사 PC 등에서는 크롬 브라우저를 항상 시크릿 모드로 실행하고 싶은 경우가 있다.
사용하는 크롬 브라우저 바로가기 아이콘 위에서 마우스 우클릭 – [속성(R)] 클릭한다.



속성 창이 뜨면 상단 [바로가기] 탭 – [대상(T)] 값 맨 뒤에 -incognito 라고 입력한다.

하단의 [적용] 또는 [확인] 버튼을 클릭한다.




해당 바로가기 아이콘을 이용해서 크롬 브라우저를 실행하면 항상 시크릿 모드가 적용되어 열린다.


참고사이트 : https://prolite.tistory.com/948

[Windows] 이 앱은 사용자 보호를 위해 차단되었습니다. 관리자가 이 앱을 실행할 수 없도록 차단했습니다.

[Windows] 이 앱은 사용자 보호를 위해 차단되었습니다. 관리자가 이 앱을 실행할 수 없도록 차단했습니다.

윈도우 10에서 “이 앱은 사용자 보호를 위해 차단되었습니다. 관리자가 이 앱을 실행할 수 없도록 차단했습니다. 자세한 내용은 관리자에게 문의하세요.” 오류 메시지가 발생하며 exe 파일을 실행할 수 없는 경우.

1. [시작] – [실행] 또는 Ctrl + R 단축키를 눌러 실행 창을 연다.

secpol.msc 를 입력하고 [확인] 버튼을 클릭한다.

2. 로컬 보안 정책 창이 뜨면, 좌측 트리의 [보안 설정] 하위의 [로컬 정책] – [보안 옵션] 을 클릭한다.

우측 정책 목록의 [사용자 계정 컨트롤: 관리 승인 모드에서 모든 관리자 실행] 을 찾아서 더블클릭한다.

3. 사용자 계정컨트롤: 관리 승인 모드에서 모든 관리자 실행 속성 창이 뜨면 [사용 안 함(S)] 을 선택하고 [확인] 버튼을 클릭한다.

 

4. 윈도우 재시작 후 오류가 발생했었던 exe 파일을 다시 실행해본다.

참고사이트 : https://blog.naver.com/zeta7755/221494768870