블루호스팅을 사용하는 경우 로컬에서 원하는 편집기로 파일을 수정하고 파일이 변경됨과 동시에 해당파일을 자동으로 업로드해서 실행결과를 서버에서 볼수 있도록 자동으로 파일을 감시하고 업로드하는 파이썬 스크립트를 만들어 보겠습니다.
일단 필요한 패키지는 watchdog이라는 파이썬 패키지 인데요 아래와 같이 설치해줍니다.
pip install watchdog
코딩에 앞서 필요한 정보를 모아주세요. 일단 변경할 폴더위치와 변경시 업로드할 FTP서버정보를 알고 있어야겠죠?
# ===== FTP 설정 =====
FTP_HOST = "ftp.mydomain.com"
FTP_USER = "your_username"
FTP_PASS = "your_password"
FTP_UPLOAD_DIR = "/remote/path/" # FTP 서버에서 업로드할 경로
# ===== 감시할 폴더 경로 =====
WATCH_FOLDER = "/path/to/watch" # 감시할 로컬 디렉토리
혹시 Mac에서 ftp명령이 없다고 나오면 ftp를 설치해주세요.
brew install inetutils
그리고 해당 FTP정보가 올바른지 확인합니다.
gftp ftp.mydomain.com
이제 Watchdog을 이용해서 특정 폴더를 감시하는 프로그램을 만들어볼게요.
가장 먼저 스크립트를 만들 폴더에 .env파일을 생성하고 각 환경변수를 아래와 같이 저장하도록 합니다.
FTP_HOST=ftp.mydomain.com
FTP_USER=your_username
FTP_PASS=your_password
FTP_UPLOAD_DIR=/remote/path/
WATCH_FOLDER=/path/to/watch
그리고 스크립트는 watch_and_upload.py라는 이름으로 생성할게요. 그리고 dotenv패키지를 이용해서 환경변수를 읽어옵니다.
import os
from dotenv import load_dotenv
# .env 파일 로드
load_dotenv()
# 환경 변수 가져오기
FTP_HOST = os.getenv("FTP_HOST")
FTP_USER = os.getenv("FTP_USER")
FTP_PASS = os.getenv("FTP_PASS")
FTP_UPLOAD_DIR = os.getenv("FTP_UPLOAD_DIR")
WATCH_FOLDER = os.getenv("WATCH_FOLDER")
우선 특정파일을 FTP로 업로드하는 함수를 정의하도록 하겠습니다. ftplib패키지에서 FTP를 import하고, ensure_remote_path함수와 upload_to_ftp함수 두개를 선언합니다. ensure_remote_path함수는 FTP로 파일을 업로드 하려고 할때 매칭되는 파일의 경로가 존재하지 않을 경우 폴더를 생성해주는 역할을 합니다. 그리고 upload_to_ftp함수는 FTP로 서버에 접근하여 로컬의 경로와 서버의 경로를 매칭하여 올바른 폴더에 해당 파일을 업로드하는 기능을 합니다.
from ftplib import FTP
def ensure_remote_path(ftp, remote_dir):
"""FTP 서버에 디렉토리 경로가 없으면 생성한다."""
parts = remote_dir.strip("/").split("/")
current_path = ""
for part in parts:
current_path += "/" + part
try:
ftp.cwd(current_path)
except:
try:
ftp.mkd(current_path)
ftp.cwd(current_path)
except Exception as e:
print(f"❌ Failed to create {current_path}: {e}")
return False
return True
def upload_to_ftp(file_path, relative_path):
try:
with FTP(FTP_HOST) as ftp:
ftp.login(FTP_USER, FTP_PASS)
# 경로 구성
remote_path = os.path.join(FTP_UPLOAD_DIR, relative_path).replace("\\", "/")
remote_dir = os.path.dirname(remote_path)
filename = os.path.basename(file_path)
if ensure_remote_path(ftp, remote_dir):
with open(file_path, 'rb') as f:
ftp.storbinary(f"STOR {filename}", f)
print(f"✅ Uploaded: {remote_path}")
else:
print(f"❌ Failed to ensure remote path: {remote_dir}")
except Exception as e:
print(f"❌ FTP Upload Error: {e}")
이제 파일을 감시하는 코드를 넣을게요. 우선 watchdog 패키지에서 파일을 감시하는 이벤트핸들러, FileSystemEventHandler를 import합니다. 그리고 해당 이벤트 핸들러에 on_modified이벤트가 발생했을때 취해야할 액션을 아래와 같이 재정의 합니다. on_modified는 watchdog을 실행했을때 파일이나 폴더에 변경이 있을 경우 실행되는 함수입니다. 폴더는 스킵하고 파일이 변경된 경우에만 FTP업로드를 하는 것으로 할게요. 바로 여기에서 변경된 파일의 풀경로와 상대경로를 획득해서 FTP에 업로드하도록 upload_to_ftp함수를 호출합니다.
from watchdog.events import FileSystemEventHandler
class ChangeHandler(FileSystemEventHandler):
def on_modified(self, event):
if not event.is_directory:
full_path = event.src_path
relative_path = os.path.relpath(full_path, WATCH_FOLDER)
upload_to_ftp(full_path, relative_path)
그럼 이제 스크립트가 실행되면 핸들러를 작동시키도록 코드를 추가하겠습니다. 감시에 필요한 클래스 Observer를 import합니다. 그리고 Observer를 observer로 객체화 합니다. 방금 위에서재정의한 ChangeHandler클래스도 마찬가지로 event_handler에 객체생성을 하고, 해당 이벤트 핸들러를 적용하여 observer의 schedule을 start()시킵니다. 그리고 매초마다 Ctrl+C가 입력이 되었는지 확인하여 Ctrl+C를 누른경우 observer를 종료하도록 합니다.
from watchdog.observers import Observer
if __name__ == "__main__":
print(f"👀 Watching folder (and subfolders): {WATCH_FOLDER}")
observer = Observer()
event_handler = ChangeHandler()
observer.schedule(event_handler, path=WATCH_FOLDER, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
전체 코드는 다음과 같습니다.
import os
from dotenv import load_dotenv
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from ftplib import FTP
import time
# .env 파일 로드
load_dotenv()
# 환경 변수 가져오기
FTP_HOST = os.getenv("FTP_HOST")
FTP_USER = os.getenv("FTP_USER")
FTP_PASS = os.getenv("FTP_PASS")
FTP_UPLOAD_DIR = os.getenv("FTP_UPLOAD_DIR")
WATCH_FOLDER = os.getenv("WATCH_FOLDER")
# ===== FTP 전송 함수 =====
def ensure_remote_path(ftp, remote_dir):
"""FTP 서버에 디렉토리 경로가 없으면 생성한다."""
parts = remote_dir.strip("/").split("/")
current_path = ""
for part in parts:
current_path += "/" + part
try:
ftp.cwd(current_path)
except:
try:
ftp.mkd(current_path)
ftp.cwd(current_path)
except Exception as e:
print(f"❌ Failed to create {current_path}: {e}")
return False
return True
def upload_to_ftp(file_path, relative_path):
try:
with FTP(FTP_HOST) as ftp:
ftp.login(FTP_USER, FTP_PASS)
# 경로 구성
remote_path = os.path.join(FTP_UPLOAD_DIR, relative_path).replace("\\", "/")
remote_dir = os.path.dirname(remote_path)
filename = os.path.basename(file_path)
if ensure_remote_path(ftp, remote_dir):
with open(file_path, 'rb') as f:
ftp.storbinary(f"STOR {filename}", f)
print(f"✅ Uploaded: {remote_path}")
else:
print(f"❌ Failed to ensure remote path: {remote_dir}")
except Exception as e:
print(f"❌ FTP Upload Error: {e}")
# ===== 변경 감지 핸들러 클래스 =====
class ChangeHandler(FileSystemEventHandler):
def on_modified(self, event):
if not event.is_directory:
full_path = event.src_path
relative_path = os.path.relpath(full_path, WATCH_FOLDER)
print(f"📂 Modified: {relative_path}")
upload_to_ftp(full_path, relative_path)
# ===== 감시 시작 =====
if __name__ == "__main__":
print(f"👀 Watching folder (and subfolders): {WATCH_FOLDER}")
event_handler = ChangeHandler()
observer = Observer()
observer.schedule(event_handler, path=WATCH_FOLDER, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
실행을 해보면 아래와 같이 필요한 경로에 잘 들어가네요.

시청해주셔서 감사합니다.