Files
hufs-notice-crawler/README.md
nkey ca460453af
All checks were successful
hufs-notice-crawler-cicd / build_push_deploy (push) Successful in 8m35s
Feat: [main] hufs-notice-crawler CI/CD까지 구현 완료
2026-03-17 17:18:16 +09:00

163 lines
5.3 KiB
Markdown

# HUFS 컴퓨터공학부 공지 크롤러
`HUFS 컴퓨터공학부` 사이트의 다음 게시판을 크롤링하는 `FastAPI` 백엔드입니다.
- 공지사항
- 자료실
- 취업정보
`n8n``POST /api/v1/crawl`을 주기적으로 호출하면, 서버는 게시판을 다시 확인하고 `PostgreSQL`에 저장된 기존 글과 비교해 새 글만 반환합니다.
## 문서
- 서비스 설명: [`README.md`](/C:/Users/USER/Desktop/notice_crawler/README.md)
- 운영/배포: [`README.operation.md`](/C:/Users/USER/Desktop/notice_crawler/README.operation.md)
- 테스트: [`README.test.md`](/C:/Users/USER/Desktop/notice_crawler/README.test.md)
- n8n 연동: [`README.n8n.md`](/C:/Users/USER/Desktop/notice_crawler/README.n8n.md)
## 주요 기능
- 공지사항, 자료실, 취업정보 게시판 크롤링
- 게시판별 `article_id` 기준 신규 글 판별
- 제목, 작성자, 게시일, 본문 텍스트, 첨부파일 링크 정리
- 사용자에게 보이는 `subview.do?enc=...` 링크 반환
- 최초 1회 실행 시 `bootstrap mode`로 기존 글 알림 폭주 방지
- `new_posts_count == 0`일 때만 `latest_posts_by_board` 제공
- Docker Hub 이미지 배포 및 `docker compose pull` 운영 지원
## 동작 방식
1. `n8n``POST /api/v1/crawl` 요청을 보냅니다.
2. 서버가 세 게시판 목록 페이지를 크롤링합니다.
3. 각 게시글의 `article_id`를 DB와 비교합니다.
4. DB에 없는 글만 상세 페이지를 추가 크롤링합니다.
5. 정리된 데이터를 응답으로 반환하고 DB에 저장합니다.
6. 다음 실행부터는 이미 저장된 글은 제외됩니다.
## bootstrap mode
최초 실행 시 예전 글 알림이 한꺼번에 나가는 것을 막기 위해 `bootstrap mode`를 사용합니다.
판단 기준:
- `scraped_posts` 테이블이 비어 있으면 `bootstrap_mode = true`
- 저장된 글이 하나라도 있으면 `bootstrap_mode = false`
동작:
- `bootstrap_mode = true`
- 기존 글을 DB에 저장만 함
- `new_posts_count = 0`
- `new_posts = []`
- 이후부터는 일반 신규 감지 모드로 동작
즉, 첫 실행에서 기존 공지가 한꺼번에 Discord/Slack으로 쏟아지는 문제를 막습니다.
## API
### `GET /health`
서버 상태 확인용입니다.
응답:
```json
{ "status": "ok" }
```
### `POST /api/v1/crawl`
세 게시판을 크롤링해 새 글만 반환합니다.
응답 필드:
- `checked_at`: 크롤링 시각
- `bootstrap_mode`: bootstrap 실행 여부
- `bootstrap_inserted_count`: bootstrap 시 저장된 글 수
- `new_posts_count`: 실제 신규 글 수
- `new_posts`: 신규 글 목록
- `latest_posts_by_board`: 게시판별 최신 글
- `new_posts_count == 0`일 때만 포함
- 별도 추가 요청이 아니라 실제 크롤링 결과를 재사용
응답 예시:
```json
{
"checked_at": "2026-03-17T00:00:00Z",
"bootstrap_mode": false,
"bootstrap_inserted_count": 0,
"new_posts_count": 1,
"new_posts": [
{
"board_key": "notice",
"board_name": "공지사항",
"board_id": 1926,
"article_id": 249714,
"title": "예시 제목",
"post_url": "https://computer.hufs.ac.kr/computer/10058/subview.do?enc=...",
"author": "computer",
"published_at": "2026-03-17T00:00:00",
"summary": "본문 요약",
"content_text": "정리된 본문 텍스트",
"attachments": [
{
"name": "첨부파일.pdf",
"url": "https://computer.hufs.ac.kr/..."
}
]
}
],
"latest_posts_by_board": []
}
```
## 환경 변수
`.env.example`을 참고해서 `.env`를 준비합니다.
```env
APP_ENV=production
DB_USER=postgres
DB_PASSWORD=postgres
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=hufs_notice_crawler
BASE_URL=https://computer.hufs.ac.kr
REQUEST_TIMEOUT_SECONDS=15
MAX_PAGES_PER_BOARD=5
DOCKER_IMAGE=your-dockerhub-id/hufs-notice-crawler:latest
```
- `APP_ENV`: 실행 환경
- `DB_USER`: PostgreSQL 사용자 -> 공통 .env에 있음
- `DB_PASSWORD`: PostgreSQL 비밀번호 -> 공통 .env에 있음
- `POSTGRES_HOST`: PostgreSQL 호스트
- `POSTGRES_PORT`: PostgreSQL 포트
- `POSTGRES_DB`: 데이터베이스 이름
- `BASE_URL`: 기본 크롤링 대상 사이트
- `REQUEST_TIMEOUT_SECONDS`: 외부 요청 타임아웃
- `MAX_PAGES_PER_BOARD`: 게시판별 최대 확인 페이지 수
- `DOCKER_IMAGE`: Docker Hub 이미지 이름
## 주요 파일
- [`app/main.py`](/C:/Users/USER/Desktop/notice_crawler/app/main.py)
- FastAPI 엔드포인트
- [`app/service.py`](/C:/Users/USER/Desktop/notice_crawler/app/service.py)
- 크롤링 실행, bootstrap, DB 저장 로직
- [`app/crawler.py`](/C:/Users/USER/Desktop/notice_crawler/app/crawler.py)
- 게시판 목록/상세 크롤러
- [`app/models.py`](/C:/Users/USER/Desktop/notice_crawler/app/models.py)
- SQLAlchemy 모델
- [`sql/schema.sql`](/C:/Users/USER/Desktop/notice_crawler/sql/schema.sql)
- PostgreSQL 스키마
- [`docker-compose.yml`](/C:/Users/USER/Desktop/notice_crawler/docker-compose.yml)
- Docker Hub 이미지 pull 기반 실행
## 참고
- 신규 여부 판단 기준은 게시판별 `article_id`입니다.
- 반환 링크는 `artclView.do`가 아니라 실제 사용자용 `subview.do?enc=...` 형식입니다.
- HTML 구조가 바뀌면 [`app/crawler.py`](/C:/Users/USER/Desktop/notice_crawler/app/crawler.py)의 selector 조정이 필요할 수 있습니다.