Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.venv
__pycache__/
.venv/
__pycache__/
.idea/
22 changes: 22 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
name: isort (python)
- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.32.0
hooks:
- id: yapf
additional_dependencies: [ toml ]
- repo: https://github.com/PyCQA/flake8
rev: 7.1.0
hooks:
- id: flake8
additional_dependencies:
- flake8-builtins
- flake8-coding
- flake8-polyfill
- flake8-quotes
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,37 @@ suya community는 학생들과 함께 만들어가는 꿈의 나라

[개발기여](CONTRIBUTING.md)

## .env는 원칙상 올리지 않는 것이 맞지만 원활한 협업을 위해 추가했습니다.

## Home
대시보드입니다.
![img_5.png](apps/info_reels/img_5.png)

## Data
data 미리보기 입니다.
학사정보는 항상 최근 10페이지만 저장되며 오래된 데이터는 데이터 생신 시 지워집니다.
자동 크롤링은 10분에서 20분 사이의 랜덤 시간을 간격으로 작동합니다(너무 일정하게 요청을 보내면 서버 쪽에서 이상하게 생각할 수도 있어서 이렇게 설정했습니다.)
![img_3.png](apps/info_reels/img_3.png)

## Info
api 명세서 부분입니다.
![img_6.png](apps/info_reels/img_6.png)

## 실행하는 법
step0 : docker-compose.yml 을 실행을 원하는 디렉터리에 위치시킵니다.

step1 : 이후 아래 명령어로 이미지를 pull 합니다.
```bash
docker pull creepereye/info_reels_2:1.0
```
step2 : 다운이 됐으면 아래 명령어로 실행시킵니다.
```bash
docker-compose up
or(안되는 경우)
docker compose up
```
step3
```bash
ipconfig (로 자신의 IP를 찾고 port는 8080을 붙여 들어가시면 됩니다.
ex) 192.168.0.12:8080
```
File renamed without changes.
33 changes: 33 additions & 0 deletions apps/info_reels/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

FROM python:3.11.0-slim


WORKDIR /opt/info_reels_docker

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH .



RUN set -xe \
&& apt-get update \
&& apt-get -y install libpq-dev gcc \
&& pip install psycopg2\
&& apt-get install -y --no-install-recommends build-essential \
&& pip install virtualenvwrapper poetry==1.4.2 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*


COPY ["poetry.lock", "pyproject.toml", "./"]
RUN poetry install --no-root


COPY ["Makefile", "./"]
COPY apps apps


EXPOSE 8080

CMD make run-server
32 changes: 32 additions & 0 deletions apps/info_reels/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.PHONY: run-server
run-server:
poetry run uvicorn src.main:app --host 0.0.0.0 --port 8080 --reload

.PHONY: build-dev
build-dev:
docker-compose -f ../info_reels/docker-compose.yml build

.PHONY: up-dev
up-dev:
docker-compose -f ../info_reels/docker-compose.yml up

.PHONY: build-up-dev
build-up-dev: build-dev up-dev

.PHONY: build-prod
build-prod:
docker-compose -f ../info_reels/docker-compose.prod.yml build

.PHONY: up-prod
up-prod:
docker-compose -f ../info_reels/docker-compose.prod.yml up

.PHONY: build-up-prod
build-up-prod: build-prod up-prod

.PHONY: up
up:
docker-compose -f ../info_reels/docker-compose.yml build
docker commit fastapi-projects-app-1 info_reels:1.0
docker tag info_reels:1.0 creepereye/info_reels_2:1.0
docker push creepereye/info_reels_2:1.0
104 changes: 104 additions & 0 deletions apps/info_reels/database.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<mxfile host="app.diagrams.net" modified="2024-06-20T07:02:27.590Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" etag="4kcCRx13r_NHvirM_ii9" version="24.5.5" type="github">
<diagram id="R2lEEEUBdFMjLlhIrx00" name="Page-1">
<mxGraphModel dx="712" dy="795" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0" extFonts="Permanent Marker^https://fonts.googleapis.com/css?family=Permanent+Marker">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="NpD_dlb7KBPNnQ73nk4W-7" value="&lt;span style=&quot;font-weight: 400; text-wrap: wrap;&quot;&gt;Info&lt;/span&gt;" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
<mxGeometry x="330" y="90" width="180" height="240" as="geometry" />
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-7">
<mxGeometry y="30" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-9" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-8">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-10" value="Id : Int (Auto)" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-8">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-36" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-7">
<mxGeometry y="60" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-37" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-36">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-38" value="Title : Text(30)" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-36">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-7">
<mxGeometry y="90" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-12" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-11">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-13" value="Notice : Bool" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-11">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-14" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-7">
<mxGeometry y="120" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-15" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-14">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-16" value="Author : Text (10)" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-14">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-17" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-7">
<mxGeometry y="150" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-18" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-17">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-19" value="Date : Date" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-17">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-30" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-7">
<mxGeometry y="180" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-31" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-30">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-32" value="View : Int" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-30">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-33" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-7">
<mxGeometry y="210" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-34" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-33">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="NpD_dlb7KBPNnQ73nk4W-35" value="Link : Text(200)" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="NpD_dlb7KBPNnQ73nk4W-33">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
29 changes: 29 additions & 0 deletions apps/info_reels/docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
version: '3.9'

services:

db:
image: postgres:14.2-alpine
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_DB: core
POSTGRES_USER: postgres
POSTGRES_PASSWORD: 1234
volumes:
- postgresql-data:/var/lib/postgresql/data

app:
build:
context: ../../../
dockerfile: .
restart: unless-stopped
ports:
- '8080:8080'
depends_on:
- db

volumes:
postgresql-data:
driver: local
27 changes: 27 additions & 0 deletions apps/info_reels/docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: '3.9'

services:

db:
image: postgres:14.2-alpine
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_DB: core
POSTGRES_USER: postgres
POSTGRES_PASSWORD: 1234
volumes:
- postgresql-data:/var/lib/postgresql/data

app:
image: info_reels:1.0
restart: unless-stopped
ports:
- '8080:8080'
depends_on:
- db

volumes:
postgresql-data:
driver: local
28 changes: 28 additions & 0 deletions apps/info_reels/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
version: '3.9'

services:

db:
image: postgres:14.2-alpine
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_DB: core
POSTGRES_USER: postgres
POSTGRES_PASSWORD: 1234
volumes:
- postgresql-data:/var/lib/postgresql/data

app:
image: creepereye/info_reels_2:1.0
restart: unless-stopped
ports:
- '8080:8080'
depends_on:
- db

volumes:
postgresql-data:
driver: local

Binary file added apps/info_reels/img_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/info_reels/img_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/info_reels/img_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
56 changes: 56 additions & 0 deletions apps/info_reels/src/jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# coding: utf-8
import random
import time
from datetime import datetime

import requests
from bs4 import BeautifulSoup

from .models import InforDao, SessionLocal


def crawling():
MINUTE = 60
random_min = 10
random_max = 20
start_index = 1
end_index = 10
db = SessionLocal()
while True:
try:
db.query(InforDao).delete()
for index in range(start_index, end_index):
url = 'https://www.syu.ac.kr/academic/academic-notice/page/' \
f'{index}/'
response = requests.get(url)
html = response.content.decode('utf-8')
soup = BeautifulSoup(html, 'html.parser')
notices = soup.findAll('th', class_='step1')
titles = soup.findAll('span', class_='tit')
authors = soup.findAll('td', class_='step3')
dates = soup.findAll('td', class_='step4')
views = soup.findAll('td', class_='step6')
links = soup.findAll('td', class_='step2')

for elements in zip(notices, titles, authors, dates, views,
links):
notice, title, author, date, view, link = elements
date_obj = datetime.strptime(
date.text.strip(),
'%Y.%m.%d').date() # 날짜 문자열 을 datetime.date 객체로 변환
notice = notice.text.strip()
infor = InforDao(
notice=int(notice) if notice.isdigit() else 9999999,
title=title.text.strip(),
author=author.text.strip(),
date=date_obj, # 변환된 date 객체 사용
view=int(view.text.strip().replace(',', '')),
link=link.find('a', class_='itembx')['href'])
db.add(infor)
except Exception as e:
print(f'Error occurred: {e}')
db.rollback()
finally:
db.commit()
db.close()
time.sleep(MINUTE * random.randint(random_min, random_max))
Loading