2024. 2. 1. 12:11ㆍProject 해축갤/고민
예전에 쿼리 튜닝을 한 상황의 가정은 다음과 같았습니다.
손흥민이 월드컵에서 골을 넣으면 해축갤에 미친 듯한 드립(게시물)이 올라오고 퍼갈 것이다.
이때 '%흥민%' 이라는 키워드로 검색이 많이 일어날 것이다.
그래서 게시물에 약 1억 건의 데이터를 넣고 느려진 게시물 조회 쿼리를 개선을 했었습니다. (4분20초 -> 0.0019초)
2024.01.29 - [Project 해축갤/데이터베이스] - 1억 데이터 쿼리 최적화: 풀텍스트 인덱스의 한계와 LIKE 절의 귀환
그 후, 서비스 고민을 하다 이런 생각들이 들었습니다.
💡 사실상 가장 빈번하게 조회가 되는, 가장 많은 트래픽을 받는 건 인기 게시물이지 않을까?
이 화면에서 우리는 밑에 있는 글보다는 상단에 있는 인기 게시물에 더 눈이 갑니다.
해축갤 디자이너도 그렇게 유도하도록 웹의 UI 를 그렇게 배치하기도 했지만.
그렇다면 다음 의문점이 듭니다.
인기글의 기준은 뭘까?
(정보) 개념글 기준, 올리는 방법 - 201910~202110 스트리머 갤러리
빠르게 요약하자면 좋아요 수, 댓글 수의 기준대로 인기글이 기준입니다.
즉 사람들에게 가장 많은 관심을 받는 글이라는 건 자명합니다.
하지만 갑자기 이런 고민에 빠졌습니다.
인기글이라서 해서 실제로 조회는 얼마 안 될 수도 있지 않을까?(사실 무조건 높을 거 같지만)실제로 측정으로 수치를 봐야 하나?
마침 이 글을 쓰기 시작한 1월 31일의 오전1시에는 한국 vs 사우디 16강이 있었습니다.
극적인 승리이기도 하니, 평소의 인기게시물보다
크롤링으로 얻을 수 있는 조회수, 즉 트래픽을 관측할 수 있을거라는 생각에
좋다고 생각, 바로 분석 및 코드를 짰습니다.
HTML 코드를 열어보자.
개발자도구로 볼때 .concept_txtlist 안에 li
태그들이 인기게시물임을 확인할 수 있다.
그리고 각각의 제목은 <a>로
하이퍼링크가 걸려있어 원본 게시물의 링크가 걸려있고
이를 타고 들어가면 다음과 같이 조회수를 확인할 수 있다.
💡 조회수 == 게시물 조회 API 콜 == 게시물 조회 쿼리 실행
라는 공식을 세워본다면, 조회수는
조회 API 호출 횟수,
조회 쿼리 실행 횟수라고도 볼 수 있습니다.
데이터 수집 및 분석
목표는 해외축구 갤러리의 인기 게시물들의 조회수를 추적하고 분석하는 것.
이를 위해 Java를 사용하여 크롤링 코드를 작성하고 얻은 데이터를 기반으로 분석을 진행할 예정입니다.
1.main( ) 메서드
public static void main(String[] args) {
try {
List<Post> postsList = trackPopularPosts();
savePostsToJsonFile(postsList, "popular_posts.json");
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
main 메서드입니다.
trackPopularPosts 를 호출하여 웹사이트에서 게시물 데이터를 가져오고,
그 결과를 savePostsToJsonFile 에 전달하여 JSON 파일로 저장합니다.
2. 게시물 추적 - trackPopularPosts( )
public static List<Post> trackPopularPosts() throws IOException {
// URL 정의 및 Jsoup으로 문서 파싱
String url = "http://gall.dcinside.com/board/lists/?id=football_new8"; // 해축갤 주소
Document postDocs = Jsoup.connect(url).get();
// 게시물 선택 및 현재 날짜 설정
Elements posts = postDocs.select(".concept_txtlist li a");
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String currentDate = formatter.format(new Date());
// 각 게시물 처리
List<Post> postsList = new ArrayList<>();
for (Element post : posts) {
// ...
}
return postsList;
}
지정된 URL에서 Jsoup을 사용하여 웹 페이지의 HTML을 파싱합니다.
웹 페이지에서 게시물을 선택하고 각 게시물에 대한 데이터(제목, 링크, 조회수)를 추출,
Post 객체에 저장한 후, 이를 List<Post>에 추가합니다.
3. 게시물 처리 루프, POJO - trackPopularPosts( ) 내부
@Getter
@Builder
public class Post {
private String title;
private String link;
private int totalViews;
private String lastUpdated;
}
먼저 Jackson 을 사용하기 위한 POJO Object 인 Post 를 생성해줍니다.
for (Element post : posts) {
String postLink = "";
try {
// 게시물 제목 및 링크 추출
String postTitle = post.text();
postLink = post.attr("href").replace("https", "http");
// 게시물 링크 정리 및 조회수 추출
if (postLink.contains("amp;")) {
postLink = postLink.replace("amp;", "");
}
Document postDoc = Jsoup.connect(postLink).get();
Element view = postDoc.selectFirst(".view_content_wrap .gall_count");
int count = Integer.parseInt(view.text().replaceAll("[^\\d]", ""));
// Post 객체 생성 및 추가
Post postObject = Post.builder()
.title(postTitle)
.link(postLink)
.totalViews(count)
.lastUpdated(currentDate)
.build();
postsList.add(postObject);
} catch (NumberFormatException e) {
System.err.println("Parsing error for post: " + postLink + " - " + e.getMessage());
} catch (Exception e) {
System.err.println("Error processing post: " + postLink);
}
}
그리고 각 게시물의 제목과 링크를 추출 후 상세 페이지로 이동하여 조회수를 가져옵니다.
이 데이터는 Post 객체에 저장되고, 이 객체는 리스트에 추가됩니다.
예외가 발생할 경우, 오류 메시지를 출력합니다.
4. JSON 파일 저장 - savePostsToJsonFile( )
public static void savePostsToJsonFile(List<Post> postsList, String fileName) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String json = mapper.writeValueAsString(postsList);
Files.write(Paths.get(fileName), json.getBytes());
System.out.println("Saved posts to " + fileName);
}
List<Post>를 입력으로 받아 Jackson 라이브러리의 ObjectMapper를 사용,JSON 문자열로 변환합니다.
문자열은 지정된 파일 이름으로 파일 시스템에 저장됩니다.
JSON 출력은 가독성을 위해 들여쓰기로 정렬됩니다.
5. 결과물 확인 및 결론
수집된 JSON 데이터는 다음과 같습니다. (1월31일 13시경 기준)
[
{
"last_updated": "2024-01-31 13:01:09",
"link": "http://gall.dcinside.com/board/view/?id=football_new8&no=6129145",
"total_views": 13631,
"title": "찬밥이 요르단, 사우디한테 턴오버 존나 당하는거 보면"
},
{
"last_updated": "2024-01-31 13:01:09",
"link": "http://gall.dcinside.com/board/view/?id=football_new8&no=6129111",
"total_views": 13306,
"title": "확실한건 지금 대표팀을 흔드는건 엠생 손까임"
},
{
"last_updated": "2024-01-31 13:01:09",
"link": "http://gall.dcinside.com/board/view/?id=football_new8&no=6129254",
"total_views": 27720,
"title": "오늘자 손흥민 머리채 잡는 사우디 선수...gif"
},
...
]
이렇게 수집된 JSON 데이터를 chart.js 를 통해 시각화한 결과는 다음과 같습니다.
(matplotlib 도 써봤는데 차트가 너무 못생겨서 chart.js 썼습니다🥲)
즉, 카타르 아시안컵 16강정도 경기에서
다이나믹한 승부차기로 승리한 날(1/31 01:00)의 조회수는 총 197,368 입니다.
이날 측정한 시간은 13:00, 즉 12시간 정도 안에 몰린 트래픽은 197,368 로 볼 수 있습니다.
이를 TPS 로 환산한다면 대략 다음과 같습니다.
197,368(조회수) / 12(시) * 3600(초) ≈ 4.5687 (tps)
즉 중요한 경기가 있는날 해축갤의 인기게시물의 TPS 는 4.5687 정도입니다.
생각보다 TPS 가 그렇게 높지는 않아서 의외입니다...!
(대한민국이 월드컵 우승이라도 해야 폭발하려나...?)
평소와 비교해보기
인기게시물에 197,368회 조회수가 몰렸고, 위 게시물들의 평균 조회수는 17,943 회입니다.
그렇다면 일반게시물들의 조회수는 얼마나 몰릴까요?
저 중 13,829 회의 조회수를 달성한 인기 게시물을 제외한
일반 게시물의 평균 조회수는 약 35.1 회입니다.
즉 인기와 일반 게시물의 평균 조회수의 차이는 약 511.19 배입니다.
우리는 이런 공식을 세워볼 수 있습니다.
💡 인기 게시물은 일반 게시물보다 평균적으로 조회수가 511.19 배 정도 몰린다!
결론
- 오늘은 중요한 경기가 있는날 해축갤에 트래픽이 얼마나 몰리는지를,
- Java 크롤러를 통해 조회수를 계산, 그리고 이를 수치로 나타내는 방법,
- 그리고 인기 게시물과 일반 게시물의 조회수의 차이
이 3 가지에 대해 알아봤습니다!
코드
'Project 해축갤 > 고민' 카테고리의 다른 글
사이드 프로젝트여도 기획이 중요한 이유 (feat. 그게 그저 게시판 프로젝트여도...) (1) | 2023.10.30 |
---|