Compare commits

...

10 Commits

Author SHA1 Message Date
da51f38e3c 불필요한 파일 삭제 2024-10-04 01:36:45 +09:00
6b71884047 1차 완료 2024-10-04 01:04:32 +09:00
82b132f519 docker test setting 2024-10-04 00:28:36 +09:00
7f03ff861b 데모 코드 작성 완료 2024-10-04 00:02:07 +09:00
019a1419d1 환경변수 호출 및 db 조회 리팩토링 2024-10-03 21:26:06 +09:00
4d1fd7a5be 코드 리팩토링 2024-10-03 08:32:03 +09:00
hm-desk
2b0d3ebb6c git 경고 수정 2024-10-02 23:53:12 +09:00
hm-desk
34bf998113 샘플 환경파일 추가 2024-10-02 23:51:49 +09:00
hm-desk
6ff1eb043e html변환, wp api push 테스트 완료 2024-10-02 23:50:09 +09:00
hm-desk
39cf244de9 개발 순서 업데이트 2024-10-02 08:44:10 +09:00
16 changed files with 372 additions and 133 deletions

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
FROM python:3.10-slim-buster
WORKDIR /usr/src/app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
COPY . /usr/src/app
# install python dependencies
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
# 6. Python main.py 파일을 실행합니다.
CMD ["python", "main.py"]

View File

@ -1,6 +1,8 @@
# wp-post-automation
워드프레스 포스팅 자동화 프로젝트.
make.com을 이용해서 만든 AutoMation Flow를 Python을 이용하여 변환.
2024.10.04 - 테스트 완료. 프로젝트 1차 종료.
* 워드프레스 포스팅 자동화 프로젝트.
* make.com을 이용해서 만든 AutoMation Flow를 Python을 이용하여 변환.
## 기존 Flow
* MariaDB에 저장된 최신 참고 url정보를 얻어온다.
* HTTP모듈을 이용하여 참고 자료를 가져온다.
@ -18,12 +20,24 @@ make.com을 이용해서 만든 AutoMation Flow를 Python을 이용하여 변환
* kubectl create -f file.yaml을 이용하여 1회성 동작 하도록 구현한다.
### Python 개발 순서
* DB에서 url을 가져오는 코드작성.
* url을 이용해서 파싱하고 텍스트만 추출하는 기능 구현.
* OpenAI이용 코드 작성.
* HTML문서 변환 코드 작성.
* 워드프레스 등록 플로우 코드 작성.
* 코드 리팩토링.
* DB에서 url을 가져오는 코드작성(완료).
* url을 이용해서 파싱하고 텍스트만 추출하는 기능 구현(완료).
* OpenAI이용 코드 작성(완료)-비용 절감을 위하여 제목, 이미지 생성 제외.
* HTML문서 변환 코드 작성(완료).
* 워드프레스 등록 플로우 코드 작성(완료).
### 코드 리팩토링.
* 전체 리팩토링(완료).
* 모듈화, 패키지화(완료).
## Docker Image Build
2024.10.04 업데이트
* Dockerfile 추가(완료).
## Kubernetes manifests
2024.10.04 업데이트
* 샘플 템플릿 작성(완료)
* 쿠버네티스 환경 테스트(완료).
## 코드 이슈
### 네이버 블로그 크롤링

View File

@ -1,45 +0,0 @@
import mysql.connector
from dotenv import load_dotenv
import os
# .env.demo 파일 로드
load_dotenv(r'./.env.dev')
# 환경 변수 가져오기
host = os.getenv('DB_HOST')
user = os.getenv('DB_USER')
password = os.getenv('DB_PASSWORD')
database = os.getenv('DB_NAME')
# MariaDB에 연결하는 함수
def fetch_data_from_mariadb():
try:
# 데이터베이스 연결
connection = mysql.connector.connect(
host=host,
user=user,
password=password,
database=database
)
# 커서 생성
cursor = connection.cursor(dictionary=True)
# 쿼리 실행
query = "SELECT * FROM healty_url_source ORDER BY idx DESC LIMIT 1;"
cursor.execute(query)
# 결과 가져오기
result = cursor.fetchone()
return result
except mysql.connector.Error as err:
print(f"Error: {err}")
finally:
if connection.is_connected():
cursor.close()
connection.close()
if __name__ == "__main__":
# 결과 확인
data = fetch_data_from_mariadb()
print(data['url'])

16
k8s-manifests/env.yaml Normal file
View File

@ -0,0 +1,16 @@
apiVersion: v1
kind: Secret
metadata:
name: wp-secret
namespace: default
type: Opaque
data:
DB_HOST:
DB_USER:
DB_PASSWORD:
DB_NAME:
OPENAI_API_KEY:
WP_URL:
WP_USER:
WP_API_KEY:
WP_POST_STYLE: ""

61
k8s-manifests/pod.yaml Normal file
View File

@ -0,0 +1,61 @@
apiVersion: v1
kind: Pod
metadata:
name: wp-auto-pod
namespace: default
annotations:
sidecar.istio.io/inject: "false"
spec:
containers:
- name: wp-auto-container
image: harbor.icurfer.com/py_prj/wp-auto:0.0.1
imagePullPolicy: IfNotPresent
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: wp-secret
key: DB_HOST
- name: DB_USER
valueFrom:
secretKeyRef:
name: wp-secret
key: DB_USER
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: wp-secret
key: DB_PASSWORD
- name: DB_NAME
valueFrom:
secretKeyRef:
name: wp-secret
key: DB_NAME
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: wp-secret
key: OPENAI_API_KEY
- name: WP_URL
valueFrom:
secretKeyRef:
name: wp-secret
key: WP_URL
- name: WP_USER
valueFrom:
secretKeyRef:
name: wp-secret
key: WP_USER
- name: WP_API_KEY
valueFrom:
secretKeyRef:
name: wp-secret
key: WP_API_KEY
- name: WP_POST_STYLE
valueFrom:
secretKeyRef:
name: wp-secret
key: WP_POST_STYLE
restartPolicy: Never
imagePullSecrets:
- name: harbor-icurfer-private

43
main.py Normal file
View File

@ -0,0 +1,43 @@
import package as pkg
from package import GetConfig, MariaDB, ChangeTextToPost, WordPress
import markdown
# 2024-10-03 환경 변수 호출
print('### Get values From .env')
config = GetConfig()
dict_data = config.get_config_as_dict()
# 2024-10-03 db에서 url정보 호출
print('### Get URL From DB')
db = MariaDB(dict_data)
url = db.fetch_data_from_mariadb()['url']
# 2024-10-03 url을 이용해서 text추출
print('### Get content From URL')
origin_content = pkg.getContents(url)
# 2024-10-03 openAI를 이용하여 게시글 스타일 변경
print('### Convert to Post - openAI')
openai_key = dict_data['openai_api_key']
wp_reference_style = dict_data['wp_post_style']
open_ai = ChangeTextToPost(openai_key)
post_article = open_ai.generate_blog_post(origin_content, wp_reference_style)
print('### Convert to HTML - markdown to html')
# 2024-10-03 Markdown을 HTML로 변환
html = markdown.markdown(post_article)
# 2024-10-03 워드프레스 포스팅 임시등록
print('### Create post')
wp = WordPress(dict_data)
rs = wp.create_post(2,html)
if __name__ == "__main__":
# print(post_article)
print("추가 확인을 위한 출력")
if rs.ok:
print(f"### 성공 code:{rs.status_code}")
else:
print(f"### 실패 code:{rs.status_code} reason:{rs.reason} msg:{rs.text}")

View File

@ -1,50 +0,0 @@
import os
from openai import OpenAI
from dotenv import load_dotenv
import translate_article as ta
# .env 파일에서 API 키 로드
load_dotenv()
client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
)
def generate_blog_post(article_text, style_reference):
# ChatCompletion API를 사용하여 텍스트 변환
prompt = (
"""
너는 대한민국에 거주하는 블로그 전문가이다.
네가 작성한 블로그 글은 지난 3년간 높은 주목성, 관여도, 전환율을 만들었다.
전문성을 이용해서 제공받는 기사를 블로그 형태로 변형하여 작성해야만 한다.
"""
f"\n블로그 스타일은 아래 문서를 모방해줘. 적절한 사례들이 들어가도 좋겠어.\n---\n{style_reference}\n"
f"제공된 기사 내용:\n{article_text}"
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "너는 대한민국에 거주하는 블로그 전문가이다."},
{"role": "user", "content": prompt}
],
max_tokens=2000,
temperature=0.7, # 창의성을 위한 적절한 값 조정
)
# 응답에서 텍스트 추출
blog_post = response.choices[0].message.content
return blog_post
# 예시 기사 텍스트 (text 파싱 결과로 제공된 텍스트를 사용할 수 있습니다)
article_text = ta.getContents()
# 블로그 스타일 참고 텍스트
style_reference = os.getenv("reference_style")
# 블로그 포스트 생성
blog_post = generate_blog_post(article_text, style_reference)
# 결과 출력
print(">>>>\n" * 3)
print(blog_post)

46
package/GetConfig.py Normal file
View File

@ -0,0 +1,46 @@
import os
from dotenv import load_dotenv
# 우선순위: .env.prd > .env.dev > .env
if os.path.exists('.env.prd'):
load_dotenv('.env.prd')
elif os.path.exists('.env.dev'):
load_dotenv('.env.dev')
else:
load_dotenv('.env') # 기본 .env 파일
class GetConfig:
def __init__(self):
self.db_host = os.getenv('DB_HOST')
self.db_user = os.getenv('DB_USER')
self.db_pw = os.getenv('DB_PASSWORD')
self.db_database = os.getenv('DB_NAME')
self.openai_api_key = os.getenv('OPENAI_API_KEY')
self.wp_url = os.getenv('WP_URL')
self.wp_user = os.getenv('WP_USER')
self.wp_api_key = os.getenv('WP_API_KEY')
self.wp_post_style = os.getenv('WP_POST_STYLE')
def show_config(self):
for key, value in self.__dict__.items():
print(f"{key.upper()}: {value}")
def get_config_as_dict(self):
# 인스턴스 속성을 딕셔너리로 반환
return self.__dict__
if __name__ == "__main__":
# 결과 확인
config = GetConfig()
config.show_config()
# 오랜만에 보다보니 헷갈려서 참고용으로 작성
# class GetConfig:
# def __init__(self, name=None):
# self.name = name if name is not None else "default_name"
# self.host = os.getenv('DB_HOST')
# self.user = os.getenv('DB_USER')
# self.password = os.getenv('DB_PASSWORD')
# self.database = os.getenv('DB_NAME')

56
package/MariaDB.py Normal file
View File

@ -0,0 +1,56 @@
import mysql.connector
class MariaDB:
def __init__(self, dict):
self.db_host = dict['db_host']
self.db_user = dict['db_user']
self.db_pw = dict['db_pw']
self.db_database = dict['db_database']
def show_config(self):
for key, value in self.__dict__.items():
print(f"{key.upper()}: {value}")
def fetch_data_from_mariadb(self):
try:
# 데이터베이스 연결
connection = mysql.connector.connect(
host=self.db_host,
user=self.db_user,
password=self.db_pw,
database=self.db_database
)
# 커서 생성
cursor = connection.cursor(dictionary=True)
# 쿼리 실행
query = "SELECT * FROM healty_url_source ORDER BY idx DESC LIMIT 1;"
cursor.execute(query)
# 결과 가져오기
result = cursor.fetchone()
return result
except mysql.connector.Error as err:
print(f"Error: {err}")
finally:
if connection.is_connected():
cursor.close()
connection.close()
if __name__ == "__main__":
import GetConfig
config = GetConfig.GetConfig()
# config 잘 가져오는지 확인
config_dict = config.get_config_as_dict()
# MariaDB 테스트
dbg = MariaDB(config_dict)
# dbg.show_config()
url = dbg.fetch_data_from_mariadb()
print(url['url'])

37
package/OpenAI.py Normal file
View File

@ -0,0 +1,37 @@
from openai import OpenAI
class ChangeTextToPost:
def __init__(self, key):
self.client = OpenAI(
api_key=key,
)
def generate_blog_post(self, origin_content, wp_post_style):
# ChatCompletion API를 사용하여 텍스트 변환
prompt = (
"""
너는 대한민국에 거주하는 블로그 전문가이다.
네가 작성한 블로그 글은 지난 3년간 높은 주목성, 관여도, 전환율을 만들었다.
전문성을 이용해서 제공받는 기사를 블로그 형태로 변형하여 작성해야만 한다.
---
글을 작성하고 제목을 만들어서 마지막 줄에 추가해줘.
"""
f"\n블로그 스타일은 아래 문서를 모방해줘. markdown형태로 작성되어야한다. 적절한 사례들이 들어가도 좋겠어.\n---\n{wp_post_style}\n"
f"제공된 기사 내용:\n{origin_content}"
)
response = self.client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "너는 대한민국에 거주하는 블로그 전문가이다."},
{"role": "user", "content": prompt}
],
max_tokens=2000,
temperature=0.7, # 창의성을 위한 적절한 값 조정
)
# 응답에서 텍스트 추출
blog_post = response.choices[0].message.content
return blog_post

61
package/Utility.py Normal file
View File

@ -0,0 +1,61 @@
import requests, json
from urllib.parse import urljoin
from bs4 import BeautifulSoup
from datetime import datetime
def getContents(url):
# HTTP GET 요청으로 페이지 가져오기
response = requests.get(url)
# 응답 상태 확인
if response.status_code == 200:
# HTML 파싱
soup = BeautifulSoup(response.text, 'html.parser')
# HTML 태그를 제거 후 페이지의 모든 텍스트 가져오기 (전체 내용)
page_content = soup.get_text()
# 빈 줄을 제거하고 텍스트만 출력 (줄바꿈 문자를 기준으로 필터링)
lines = [line.strip() for line in page_content.splitlines() if line.strip()]
# 결과 출력
contents = "\n".join(lines)
return contents
else:
print(f"Failed to fetch the URL. Status code: {response.status_code}")
class WordPress():
def __init__(self, dict):
self.wp_url = dict['wp_url']
self.wp_user = dict['wp_user']
self.wp_user = dict['wp_user']
self.wp_api_key = dict['wp_api_key']
def create_post(self, category_id, content, media_id = None, status = "draft", title="파이썬 자동 포스팅"):
payload = {
"status": status, # publish / draft
"title": title,
"content": content,
"date": datetime.now().isoformat(), # YYYY-MM-DDTHH:MM:SS
"categories": category_id
}
if media_id is not None:
payload['featured_media'] = media_id
return requests.post(urljoin(self.wp_url, "wp-json/wp/v2/posts"),
data=json.dumps(payload),
headers={'Content-type': "application/json"},
auth=(self.wp_user, self.wp_api_key))
# if result.ok:
# print(f"성공 code:{result.status_code}")
# else:
# print(f"실패 code:{result.status_code} reason:{result.reason} msg:{result.text}")
if __name__ == "__main__":
# url = 'example_url'
# tmp = getContents(url)
# print(tmp)
pass

5
package/__init__.py Normal file
View File

@ -0,0 +1,5 @@
from .Utility import getContents
from .Utility import WordPress
from .GetConfig import GetConfig
from .MariaDB import MariaDB
from .OpenAI import ChangeTextToPost

Binary file not shown.

9
sample.env.dev Normal file
View File

@ -0,0 +1,9 @@
DB_HOST=192.168.0.1
DB_USER=demo
DB_PASSWORD=demo
DB_NAME=demo
OPENAI_API_KEY=demo
WP_URL='https://www.example.com'
WP_USER='demo'
WP_API_KEY='demo'
WP_POST_STYLE="문장"

View File

@ -1,30 +0,0 @@
import requests
from bs4 import BeautifulSoup
import get_url
url = get_url.fetch_data_from_mariadb()['url']
def getContents():
# HTTP GET 요청으로 페이지 가져오기
response = requests.get(url)
# 응답 상태 확인
if response.status_code == 200:
# HTML 파싱
soup = BeautifulSoup(response.text, 'html.parser')
# HTML 태그를 제거 후 페이지의 모든 텍스트 가져오기 (전체 내용)
page_content = soup.get_text()
# 빈 줄을 제거하고 텍스트만 출력 (줄바꿈 문자를 기준으로 필터링)
lines = [line.strip() for line in page_content.splitlines() if line.strip()]
# 결과 출력
contents = "\n".join(lines)
return contents
else:
print(f"Failed to fetch the URL. Status code: {response.status_code}")
tmp = getContents()
print(tmp)

1
version Normal file
View File

@ -0,0 +1 @@
0.1.0