본문 바로가기
text/Python

ㅇㅎ 게시물 수집하기 (fastapi, APScheduler, MySql)

by hoonzii 2022. 5. 5.
반응형

개요

나는 커뮤니티를 자주 본다. 커뮤니티를 보다보면 게시물들 사이로 간간히 “ㅇㅎ” 라는 키워드가 붙은 게시물을 마주하게 되는데 “약한 후방주의” 라는 말의 줄임말이다.

 

후방 주의란?

남자들이 주로 접속하는 사이트들에는 하루에 적어도 하나씩 꼭 올라온다.

간간히 보이는 게시물은 보일때마다 무지성으로 클릭하게 되는데, 어느날 이런생각이 들었다.

하나로 모아서 보면 안될까?

 

 

좋은 생각이 떠오르면 그건 이미 누가 했다고 하던가... 이미 그런 사이트가 있었다.

모두의 후방

 

모두의 후방

모두의 후방

data.pureugong.com

하지만 나도 하나쯤은 만들어 보고 싶었다. 그래서 해당 사이트의 About 탭을 들어가 tech stack을 살펴본다.

오호 저런 기술로 구현하셨군... 하고 위로 좀만 올라가니 데이터는 하루에 한번 반영된다고 써있다.

그렇다면 나는 최신 게시물도 반영되게끔 구현하면 차별점을 가질 수 있겠다 싶어 구현을 해보려고 한다.
( 하지만 다 만들고 확인해보니 해당 사이트의 게시물을 보니 최신 게시물도 가져오는 걸로 보아 하루에 한번 반영은 아닌거 같다 ;;;)

 

ㅇㅎ 게시물 수집기 (using python)

우선 ㅇㅎ라는 키워드가 붙은 커뮤니티를 조사한다.

내 경우엔 아래 사이트 들에서 정보를 가져왔다.

  • 와이고수
  • 에펨코리아
  • 개드립
  • 이토렌트
  • dcinside 몸매갤러리
  • 오늘의유머
  • 보배드림

fmkorea를 예로 들면 검색을 해서 나오는 문서들 목록을 1페이지만 가져오는 로직을 구성했다.

ㅇㅎ 게시물 특성상 이미지가 많은 편인데, 저장할때 이미지는 크기가 크므로 웹에서 새창 열기를 통해 해당 사이트로 이동할 수 있게 링크만 저장한다.

# fmkorea ㅇㅎ return
def fmkorean_url_return():
    type_ = "fmkorea" #나중에 어딘가 저장될때를 대비해서 해당 데이터의 타입을 지정
    url = """https://www.fmkorea.com/index.php?mid=home&act=IS&where=document&search_target=title&is_keyword=%E3%85%87%E3%85%8E&page=1"""
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    ul = soup.find('ul',{'class':'searchResult'})

    title_url_list = []
    for li in ul.find_all("li"): # 제목과 url 정보를 추출
        a = li.find('a')
        title = a.text
        title = re.sub('\[[^\]]*\]','', title)
        title = title.strip()
        
        url = "https://www.fmkorea.com"+a.get('href') # 해당 ㅇㅎ 게시물의 링크
        title_url_list.append([title, url, type_]) # 제목, 링크, 해당사이트이름을 리스트에 저장
    
    return title_url_list

코드를 실행시켜보면 이러한 결과를 볼 수 있다.

주의 할 점은 1page의 결과만 가져온다는 점이다.

대신 저 코드를 주기가 짧게 (5분?) 정도로 만들어서 1page에서 나오는 것들을 계속 가져오게 하면

하루지났을때 전날 올라온 ㅇㅎ게시물은 전부 가져올 수 있지 않을까 해서 그렇게 만들었다.

(물론 5분 동안 1page의 게시물보다 많은 양의 게시물이 올라온다면 다 가져올 수 없다.)

 

 

위에 적은 사이트 들에 대해서도 함수를 하나씩 구성해준다음 클래스로 만들어준다.

(해당 클래스 인스턴스를 통해 함수 호출로 동작하게끔 하기 위해!)

#fileName : yackhu.py

import requests
from bs4 import BeautifulSoup
import re

class crawler:

    keywords = []

    def __init__(self) -> None:
        self.keywords = ['ㅇㅎ','후방','약후','ㅎㅂ']

    # ygosu ㅇㅎ return
    def ygosu_url_return(self):
        type_ = 'ygosu'
        title_url_list = []
		# do the thing!              
        return title_url_list

    # fmkorea ㅇㅎ return
    def fmkorean_url_return(self):
        type_ = "fmkorea"

        url_set = set()
        title_url_list = []
        for keyword in self.keywords:
            url = "https://www.fmkorea.com/index.php?mid=home&act=IS&where=document&search_target=title&is_keyword={}&page=1".format(keyword)
            response = requests.get(url)
            soup = BeautifulSoup(response.text, 'html.parser')
            ul = soup.find('ul',{'class':'searchResult'})

            for li in ul.find_all("li"):
                a = li.find('a')
                title = a.text
                title = re.sub('\[[^\]]*\]','', title)
                title = title.strip()
                
                url = "https://www.fmkorea.com"+a.get('href')
                if url not in url_set:
                    title_url_list.append([title, url, type_])
                    url_set.add(url)
        
        return title_url_list

    #dogdrip ㅇㅎ return
    def dogdrip_url_return(self):
        type_ = 'dogdrip'

        title_url_list = []
        # do the thing!  
        return title_url_list

    # etorent ㅇㅎ return
    def etorent_url_return(self):
        type_ = "etoland"

        title_url_list = []
        # do the thing!  
        return title_url_list

    # dc ㅇㅎ return
    def dc_mom_url_return(self):
        type_ = "dc_mom"
        title_url_list = []
        # do the thing!  
        return title_url_list

    # td ㅇㅎ return
    def today_humor_url_return(self):
        type_ = "today_humor"

        title_url_list = []
        # do the thing!  
        return title_url_list

    def bobadream_url_return(self):
        type_ = "bobadream"
        title_url_list = []
        # do the thing!  
        return title_url_list

 

각 사이트 별로 [’제목’,’url’,’사이트이름(타입)’] 을 리턴하게끔 함수를 구성했다.

(코드가 넘 길기때문에 다른 함수들은 생략! 자세한 코드는 깃허브주소)

사이트 별로 함수를 나눠놓고, 클래스 파일로 구성한 이유는

  1. 사이트 구성이나 배치가 바꼈을때 해당 함수만 고치기에 용이
  2. 특정사이트를 제거/추가 시 함수를 붙이거나, 떼기에 용이
  3. 각 사이트별로 동작해 결과를 한꺼번에 보기 위해!

마지막 3번째 이유 때문에 APScheculer 라는 파이썬 모듈을 사용한다.

일정시간마다 각 사이트를 방문해 키워드를 검색하고 조건에 맞는 게시물을 가져오려면

한번 실행되는게 아닌 정해진 시간에 맞춰 동작해야하고,

사이트별로 동작시 서로에게 영향을 주지 않아야 한다.

 

 

리눅스 기반의 운영체제에는 crontab 을 이용해 주기적 실행이 가능하지만

내 데스크탑은 윈도우 이기 때문에 테스트 역시 윈도우에서 가능 해야 한다.

APScheduler 에 대한 자세한 사용은 document

 

User guide — APScheduler 3.9.0.post1.post1 documentation

Sometimes the scheduler may be unable to execute a scheduled job at the time it was scheduled to run. The most common case is when a job is scheduled in a persistent job store and the scheduler is shut down and restarted after the job was supposed to execu

apscheduler.readthedocs.io

먼저 해당 모듈에 대한 간단한 테스트를 진행해보자

# pip install apscheduler 
# fileName : back_test.py

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.base import JobLookupError
import time

def job(): # 수행할 일
    print("hello") 

sched = BackgroundScheduler(timezone='Asia/Seoul') #백그라운드로 실행하기 위해 선언
sched.start() 

sched.add_job(job, 'interval', seconds=1, id="hello") # 수행할 함수를 job에 등록
#1초 마다 한번씩 함수를 수행한다.

count = 0
while True:
    time.sleep(3)
    print("count :",count)
    count += 1
    if count > 10:
        break

실행결과

메인 스레드는 3초에 한번씩 sleep 에서 일어나 count 를 프린트하고, 그것과 상관없이 사용자가

job에 등록한 함수의 경우, 정해진 주기대로 실행되는 것을 볼 수 있다.

 

위 테스트를 통해 앞서 만들어 놓은 사이트 게시물 수집 함수를 job으로 등록해 놓는다면 정해진 주기에 따라 실행될 것이라 볼수 있다.

 

mysql 세팅

주기마다 실행되는 사이트 게시물 함수는 어딘가 저장되어야 한다. 쉽게 사용가능한 mysql 을 사용한다.

컴퓨터에 다운로드해 세팅하는건 구글에 쳐보면 수두룩 하므로,

전부 세팅 되어있다는 가정하에

데이터 베이스, 테이블을 먼저 만들어 준다.

CREATE TABLE `데이터베이스이름`.`urldata` (
  `seq` BIGINT NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(1024) NULL,
  `url` VARCHAR(2048) NULL,
  `type` VARCHAR(45) NULL,
  `reg_date`  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`seq`)
  )
ENGINE = InnoDB
COMMENT = 'ㅇㅎurl저장';

mysql 워크벤치를 통해 확인

이제 넣을 공간을 갖추었다. db insert 함수를 구성해 사이트 수집 함수에 달아준다면

background 함수로 등록해 특정 시간마다 동작해 db에 데이터를 넣는 작업플로우를 만들어 줄 수 있다.

여기까지 만들고 생각이 바뀌었었다.

각 함수가 디비에 직접 insert ( sql 을 만들고 해당 sql 에 데이터를 채워서 디비에 직접 넣기) 를 구성하려다가

‘나중에 웹으로 보여줄때 해당 데이터를 주고 받을 api 구성을 해야 할텐데 insert도 api 로 만들면 어떨까?’ 라고 생각이 들었다.

 

fastapi 로 로컬 서버 구축

파이썬 프레임 워크가 여러개 있는데 나는 그중에 fastapi라는 프레임워크를 이용해볼 예정이다.

(커뮤니티 돌아다니다가 읽었는데 되게 금방 쉽게 구성이 가능하다.)

자세한 document는 여기

 

FastAPI

FastAPI FastAPI 프레임워크, 고성능, 간편한 학습, 빠른 코드 작성, 준비된 프로덕션 문서: https://fastapi.tiangolo.com 소스 코드: https://github.com/tiangolo/fastapi FastAPI는 현대적이고, 빠르며(고성능), 파이썬

fastapi.tiangolo.com

pip install fastapi
pip install uvicorn

이렇게 설치한 후에 main.py 를 구성해준다.

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}

아니면 아래 코드와 같이 구성후 python main.py 라는 명령으로 python 파일 실행!

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}

if __name__ == "__main__":
    uvicorn.run(app,
    host="0.0.0.0",
    port=8000)

 

main.py 가 웹서버 역할로 떠있게 되고 주소를 통해 받은

api 명령을 수행하는 함수를 짜주면 원하는 api 구성을 완료할 수 있겠다.

먼저 아까 만든 APSchduler 백그라운드 수집기 결과를 디비에 넣을 수 있게 api를 하나 만들어 준다.

#fileName : main.py

def insert_Data(data_url_list):
    password = "PASSWORD"
    yackhu_db = pymysql.connect(
        user='root', 
        passwd=password, 
        host='127.0.0.1', 
        db='hoonzidata', 
        charset='utf8'
    )
    cursor = yackhu_db.cursor(pymysql.cursors.DictCursor)
    
    select_sql = """
    SELECT url FROM `urldata` WHERE url IN (%s)
    """

    insert_sql = """
    INSERT INTO `urldata` (title, url, type)
    VALUE (%s,%s,%s)
    """

    url_list = tuple([data_url[1] for data_url in data_url_list]) #url 리스트를 구성

    format_strings = ",".join(['%s'] * len(data_url_list)) 
    cursor.execute(select_sql % format_strings, url_list)

    result = cursor.fetchall() #중복되는 url은 해당 결과로 나오게 됌
    result_url_list = [ info['url'] for info in result]

    for data_url in data_url_list:
        title = data_url[0]
        url = data_url[1]
        type_ = data_url[2]

        if url not in result_url_list: # 만약 넣으려는 데이터가 중복이 아니라면
            cursor.execute(insert_sql, (title, url, type_))

    yackhu_db.commit()
    cursor.close()

insert 하는 함수를 만들어준다. 중간에 insert sql과 select sql이 있는데

이는 넣는 데이터가 이전에 있었던 url 이라면 db에 새로 넣지 않기 위해 (중복방지)

만들어 놓았다.

 

#fileName : main.py

@app.post("/items/")
async def insert_item(items: List[Item]):

    data_url_list = []
    for item in items:
        data_url_list.append([item.title, item.url,item.type])
    insert_Data(data_url_list)
    
    return items

만들어준 insert 함수를 api 형식으로 감싼다.

api 는 POST 요청으로 데이터를 받아서 해당 데이터를 디비에 넣는다.

이제 api 로 디비에 insert 할수 있게 되었으니 아까 만들어준 수집기 코드를 수정해준다.

 

#fileName : yackhu.py
#예시로 fmkorea 수집 함수만 표현, 실제 코드에서는 모든 수집함수에 insert함수를 달아준다.
import requests
from bs4 import BeautifulSoup
import re

class crawler:

    keywords = []
		
    def __init__(self) -> None:
        self.keywords = ['ㅇㅎ','후방','약후','ㅎㅂ']
		
		def db_insert_query(self,data_url_list): # API를 통해 db에 insert
        post_data = []
        for data_info in data_url_list:
            temp_dict = {}
            temp_dict['title'] = data_info[0]
            temp_dict['url'] = data_info[1]
            temp_dict['type'] = data_info[2]    
            post_data.append(temp_dict)

        api_url = "http://localhost:8000/items/"
        response = requests.post(api_url, json=post_data)

    # ygosu ㅇㅎ return
    def ygosu_url_return(self):
        type_ = 'ygosu'
        
        url_set = set()
        title_url_list = []
        for keyword in self.keywords:
            url = "https://ygosu.com/all_search/?type=&add_search_log=Y&keyword={}".format(keyword)
            response = requests.get(url)
            soup = BeautifulSoup(response.text, 'html.parser')
            
            ul = soup.find('ul', {'id': "board_search_result"})
            for sub_ul in ul.find_all('ul'):
                if sub_ul.find("li",{'class':'thumbnail_li'}):
                    a = sub_ul.find('a',{'class':"subject"})

                    title = a.text
                    url = a.get('href')
                    if url not in url_set:
                        title_url_list.append([title, url, type_])
                        url_set.add(url)
        self.db_insert_query(data_url_list)  
        return title_url_list

이로써 ㅇㅎ게시물 수집기 코드를 구성할 수 있었다.

 

 

ㅇㅎ 게시물 select API 구성하기

insert는 해결 했지만 중요한건 저장한 걸 보여주기 위한 select 이다.

fastapi 도 역시 다른 프레임워크 처럼 데이터를 서빙할때 model 객체에 담아서 해당 결과를 옮긴다.

그러므로 코드에 원하는 model 클래스를 선언해준다.

필요한 정보는 제목, url, 어떤 타입(어디서 나온 게시물인지) 이므로

#fileName : main.py

import uvicorn

from typing import Optional, List
from pydantic import BaseModel

class Item(BaseModel): #model 클래스 선언
    title : str
    url : str
    type : str

class ItemList(BaseModel): # 여러개 반환시 List로 감싸게끔
    items : List[Item]

이렇게 선언해준다. 그리고 db에 select sql 을 날려서 결과를 반환하는 함수와 api로 접근 가능하게끔 코드를 짜준다.

 

def select_url_page(limit=10, offset=0):
    password = "PASSWORD"
    yackhu_db = pymysql.connect(
        user='root', 
        passwd=password, 
        host='127.0.0.1', 
        db='hoonzidata', 
        charset='utf8'
    )

    cursor = yackhu_db.cursor(pymysql.cursors.DictCursor)
    
    select_sql = """
    SELECT title, url, type FROM `urldata` ORDER BY reg_date DESC LIMIT %s OFFSET %s
    """

    cursor.execute(select_sql, (limit, offset*limit))
    result = cursor.fetchall()
    cursor.close()
    return result

@app.get("/items/{offset}", response_class=HTMLResponse)
async def return_urls(request: Request, offset : int):
    if offset < 1:
        offset = 1
    num_list = []
    if offset < 3:
        num_list = [1,2,3,4,5]
    else:
        for n in range(offset-2, offset+3):
            num_list.append(n)

    url_infos = select_url_page(limit=10, offset=offset)
    ItemList = url_infos

    return {"request": request,
    "url_infos": ItemList, 
    "num_list" : num_list, 
    "offset" : offset}

select sql 과 select API도 역시 이렇게 구성할수 있었다.

중요한건 이걸 내가 접근해서 편하게 볼 수 있어야 한다는 점이다.

web으로 볼수있게 “/item/1” 경로로 접근시 db에 저장된 정보가 표시되어야 한다.

내가 참고한 건 여기

 

Templates - FastAPI

Templates You can use any template engine you want with FastAPI. A common choice is Jinja2, the same one used by Flask and other tools. There are utilities to configure it easily that you can use directly in your FastAPI application (provided by Starlette)

fastapi.tiangolo.com

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")


templates = Jinja2Templates(directory="templates")


@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
    return templates.TemplateResponse("item.html", {"request": request, "id": id})

예제 코드를 가져와서 보자.

main.py와 동일한 경로에 /static 폴더 , /templates 폴더를 만들어 준다.

/static 폴더의 경우, css 를 넣는 용도

/templates 폴더의 경우, 웹으로 보여줄 html 을 넣는 용도로 쓰인다.

 

#fileName : main.py

@app.get("/items/{offset}", response_class=HTMLResponse)
async def return_urls(request: Request, offset : int):
    if offset < 1:
        offset = 1
    num_list = []
    if offset < 3:
        num_list = [1,2,3,4,5]
    else:
        for n in range(offset-2, offset+3):
            num_list.append(n)

    url_infos = select_url_page(limit=10, offset=offset)
    ItemList = url_infos
		
#url 데이터를 보여줄 html과 정보를 지정해준다.
    return templates.TemplateResponse("urls.html",  
    {"request": request,
    "url_infos": ItemList, 
    "num_list" : num_list, 
    "offset" : offset})

templates/urls.html 파일

<!--fileName : urls.html -->
<html>
<head>
    <title>ㅇㅎLinks</title>
    <!-- <link href="{{ url_for('static', path='/styles.css') }}" rel="stylesheet"> -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
    <style>
        .pagination {
            /* display: inline-block; */
            margin-left: auto;
            margin-right: auto;
            width: 300px;
        }
        
        .pagination a {
            color: black;
            float: left;
            padding: 8px 16px;
            text-decoration: none;
        }
        
        .pagination a.active {
            background-color: #4CAF50;
            color: white;
            border-radius: 5px;
        }
        
        .pagination a:hover:not(.active) {
            background-color: #ddd;
            border-radius: 5px;
        }
        ul {
        /* width: 300px; */
        margin-left: auto;
        margin-right: auto;
        }
    </style>
</head>
<body>
    <!-- <ul class="collection with-header">
        {#loop through galaxies and render them in list#}
        {% for url_info in url_infos %}
            <li class="collection-item">
                <p><a href="{{url_info['url']}}" target="_blank">{{url_info['title']}}</a> {{ url_info['type'] }}</p>
            </li>
        {% endfor %}
    </ul> -->
    <div class="container">
        <div class="row">
          <div class="col-12">
            <h1>Links</h1>
            <ul class="list-group">
            {% for url_info in url_infos %}
                    <li class="list-group-item">
                        <p><a href="{{url_info['url']}}" target="_blank">{{url_info['title']}}</a><span class="badge badge-success  float-right">{{ url_info['type'] }}</span></p>
                    </li>
            {% endfor %}
            </ul>
            <div class="pagination">
                <!-- <a href="">&laquo;</a>
                <a href="#">&raquo;</a> -->
        
                {% for num in num_list %}
                    {% if num == offset %}
                        <a href="/items/{{num}}" class="active">{{num}}</a>
                    {% else %}
                        <a href="/items/{{num}}">{{num}}</a>
                    {% endif %}
                {% endfor %}
            </div>
          </div>
        </div>
      </div>
</body>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
</html>

jinja2 (?) 문법으로 {{}} 을 이용해 for와 if 구문을 이용해 db에서 가져온 값을 보여준다.

결과를 봐보자

잘 나온걸 확인할 수 있다.

select API를 구성했으니 아까 못한 APScheduler 코드를 마저 구성해보자.

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.base import JobLookupError
import random
from yackhu import crawler
import time

crawler = crawler()

def job1():
    try:
        crawler.ygosu_url_return()
    except Exception as e:
        print("ygosu error",e)

def job2():
    try:
        crawler.today_humor_url_return()
    except Exception as e:
        print("today_humor error",e)

def job3():
    try:
        crawler.fmkorean_url_return()
    except Exception as e:
        print("fmkorean error",e)

def job4():
    try:
        crawler.etorent_url_return()
    except Exception as e:
        print("etorent error",e)

def job5():
    try:
        crawler.dogdrip_url_return()
    except Exception as e:
        print("dogdrip error",e)

def job6():
    try:
        crawler.dc_mom_url_return()
    except Exception as e:
        print("dc_mom error",e)

def job7():
    try:
        crawler.bobadream_url_return()
    except Exception as e:
        print("bobadream error",e)

sched = BackgroundScheduler(timezone='Asia/Seoul')
sched.start()

#
second = 15 # random.randint(300,600)
sched.add_job(job1, 'interval', seconds=second, id="ygosu_url_return")
sched.add_job(job2, 'interval', seconds=second, id="today_humor_url_return")
sched.add_job(job3, 'interval', seconds=second, id="fmkorean_url_return")
sched.add_job(job4, 'interval', seconds=second, id="etorent_url_return")
sched.add_job(job5, 'interval', seconds=second, id="dogdrip_url_return")
sched.add_job(job6, 'interval', seconds=second, id="dc_mom_url_return")
sched.add_job(job7, 'interval', seconds=second, id="bobadream_return")


while True:
    print("crawling start...")
    time.sleep(3600)

 

 

aws 서버 세팅

로컬에 세팅하면 내 데스크탑이 꺼졌을땐 해당 사이트에 접근할 수 없다. (당연하다)

물론 외부에서 이 사이트를 접근해 게시물을 보기엔 어렵지만 (이름대로 후방주의 이기때문에)

‘할 수 있다’랑 ‘할 수 없다’는 천지 차이 이기 때문에 서버를 구성해보자

 

가장 먼저 python 을 설치해준다.

내 경우에 python 설치하고 필요한 모듈 설치하고 이제 실행! enter! 하는데 python 버전이 안맞아 동작하지 않았었다.

그러므로 필히 python version을 잘 확인하고 설치 해야 한다.

그래서 내경우엔 가상환경을 구성해 그 위에서 설치하고 동작 시켰다...

python 세팅에 관해서는 여기 블로그

 

Ubuntu에 python 설치하기

설치 환경 : VMware 16 / Ubuntu 20.04 LTS 우분투 20.04 LTS 버전은 파이썬이 설치되어있었습다. (굉장히 좋네요! 트렌디해~) 그렇다면 우리는 gcc 컴파일러나 파이썬을 설치하지 않고 사용할 수 있다는 거

nyangnyangworld.tistory.com

두번째로 mysql 을 설치해준다.

참고했던 블로그는 여기

 

[AWS] AWS EC2에 mysql 설치하기 (ubuntu)

1. ubuntu 패키지 정보 업데이트 sudo apt update 2. mysql 설치 apt를 이용하여 mysql 을 설치합니다. 설치 과정에서 y/n를 묻는 문구가 나오면, y 를 입력하여 설치합니다. sudo apt install mysql-server 3...

mirae-kim.tistory.com

설치 한뒤 로컬과 동일하게 데이터베이스, 테이블을 구성해준다.

주의점은 fastapi서버를 실행하는 main.py 와 수집기.py 의 디비 주소를 수정해줘야 한다는 점이다.

 

서버에 설치한 mysql은 로컬에서 접속이 가능한지 sql 날려도 이상없이 날라오는지 확인해본다.

세번째

로컬에 구성한 fastapi 및 코드를 서버로 옮겨준다.

내 경우엔 윈도우라서 Xshel을 통해 편하게 옮겼는데, 깃허브에 코드를 옮겨 해당 깃을 통해 옮겨도 될듯 하다.

내 fastapi 폴더 구성

디비는 api 서버를 통해서 insert 하기 때문에 main.py를 먼저 동작 시켜준다.

nohup python main.py > api_output.log &

#nohup 명령어로 백그라운드 실행 명령
#파일 실행시 나오는 결과는 api_output.log 에 쓰게끔 명시

nohup python backSchedule.py > parser_output.log &

#위에서 구성한 수집기 백그라운드 실행 설정

이렇게 구성해 주기마다 사이트에 접근해 ㅇㅎ게시물을 가져올수 있게 구성한다.

실행 확인을 해보자! (디자인은 소인의 일이 아니오...)

 

서버 주소 = http://{서버IP}:8000/items/1

동작을 확인해보자!

 

 

잘되는 걸 확인할 수 있다.

fastapi의 경우, 또 하나의 좋은 점이 있는데

api 명세서를 알아서 만들어준다는 점이다. (메인 페이지에 /docs 를 붙혀주면 짠)

document 페이지를 알아서 구성해주기 때문에 토이프로젝트를 위한 api 구성시 fastapi를 사용하는 것도 괜찮아 보인다.

 

전체 코드는 깃허브 주소

 

GitHub - hoonzinope/hobby_coding: 취미로 하는 코딩

취미로 하는 코딩. Contribute to hoonzinope/hobby_coding development by creating an account on GitHub.

github.com

 

반응형

댓글