목적
PS Plus 게임 카탈로그는 영구적으로 등록되는 게임이 아니다.
위 이미지에 ‘2023/2/22 오후…’
처럼 만료일이 정해져 있는데, 만료일은 3개월 이내가 되면 사이트에 노출되어 확인할 수 있다.
그러나 만료일이 예정된 것만 따로 찾아볼 수 없으며, 리스트에서 직접 하나씩 눌러서 확인해야 하기 때문에 어떤 게임이 만료되는지 확인하기 매우매우매우 귀찮다. (약 250~300개의 게임을 모두 눌러서 확인해야 한다…)
그래서 웹스크래핑으로 모든 게임들의 목록과 PS4/PS5 지원 여부도 추출하고, 그 중에서 만료 예정일도 함께 추출하는 것이 목적이다.
기획
- 웹 스크래핑으로 PS Store 게임 카탈로그(https://store.playstation.com/ko-kr/category/05a2d027-cedc-4ac0-abeb-8fc26fec7180/1)의 정보를 수집해 모든 게임 카탈로그를 찾고, 그 중 만료 예정일을 찾는 것이 핵심이다.
- 수집할 정보
- 커버 이미지+게임 이름, 태그(PS4/PS5 구분 또는 에디션), 만료예정일, 게임 상세정보 링크
- 정보를 추출해 웹페이지에 보기 쉽게 게임 이름 순으로 정렬 (만료일 순으로 정렬할까 하다가 만료되는 게임이 무조건 10개 미만인 것 같아 게임 이름 순으로 결정)
- 추후 필요하다면 excel로 추출하는 것도 고려
개발
1. 추출
기본 게임 목록들은 ‘게임 카탈로그 - 모든 게임’ 에서 추출 https://store.playstation.com/ko-kr/category/05a2d027-cedc-4ac0-abeb-8fc26fec7180
def extract_ps_expired(games, page):
ps_url = "https://store.playstation.com"
url = f"{ps_url}/ko-kr/category/05a2d027-cedc-4ac0-abeb-8fc26fec7180/{page}"
res = requests.get(url, headers={"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15"})
if res.status_code == 200:
soup = BeautifulSoup(res.text, "html.parser")
psw_grid_list = soup.find("ul", {"class": "psw-grid-list"})
psw_content_link_list = psw_grid_list.find_all("a", {"class": "psw-content-link"})
for psw_content_link in psw_content_link_list:
psw_cover_list = psw_content_link.find_all("img", {"class": "psw-l-fit-cover"})
psw_cover = psw_cover_list[0]["src"]
href = psw_content_link["href"]
href = href.replace("/en-us", "/ko-kr")
sub_url = ps_url + href
res_detail = requests.get(sub_url, headers={"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15"})
if res_detail.status_code == 200:
soup_detail = BeautifulSoup(res_detail.text, "html.parser")
game_title = soup_detail.find("div", {"class": "pdp-game-title"})
area_game_name = game_title.find("h1")
area_game_tag_list = game_title.findAll("span", {"class": "psw-t-tag"})
area_cta = soup_detail.find("div", {"class": "pdp-cta"})
area_ps_plus = area_cta.find("span", {"class": "psw-c-t-ps-plus"})
if area_ps_plus is not None:
area_ps_plus_parent = area_ps_plus.find_parent("span", {"class": "psw-l-line-wrap"})
area_ps_expired = area_ps_plus_parent.find("span", {"class": "psw-c-t-2"})
else:
area_ps_expired = None
game_name = area_game_name.get_text()
game_tags = []
for area_game_tag in area_game_tag_list:
game_tags.append(area_game_tag.get_text())
ps_expired_flag = False
ps_expired = "종료 예정 없음"
if area_ps_expired is not None:
ps_expired_flag = True
ps_expired = area_ps_expired.get_text()
game = {
"game_name": game_name,
"game_tags": game_tags,
"ps_expired_flag": ps_expired_flag,
"ps_expired": ps_expired,
"psw_cover": psw_cover,
"sub_url": sub_url
}
games.append(game)
return games
BeautifulSoup
를 활용해 추출을 했다.
규칙만 찾으면 쉽게 만들 수 있다.
해당 목록에서 각 게임 클릭 시 넘어가는 상세보기 페이지에서 원하는 정보 추출
단, 종료 예정일이 없는 경우는 None
처리에 주의해야 한다.
2. 페이징
def extract_ps_expired_paging():
ps_url = "https://store.playstation.com"
url = f"{ps_url}/ko-kr/category/05a2d027-cedc-4ac0-abeb-8fc26fec7180/1"
res = requests.get(url, headers={"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15"})
games = []
if res.status_code == 200:
soup = BeautifulSoup(res.text, "html.parser")
page_button_list = soup.find_all("button", {"class": "psw-page-button"})
# 전체 페이지 수 추출
last_page_value = int(page_button_list[len(page_button_list) - 1]["value"])
for i in range(last_page_value):
games = extract_ps_expired(games, i+1)
# 게임 이름으로 정렬
games = sorted(games, key=lambda d: d['game_name'])
return games
게임 목록 하단에 전체 페이지 수가 노출된다. 마지막 수가 곧 페이징 수 이므로 해당 수를 가져와 자체 페이징을 진행한다.
3. 최초 페이지
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../static/index.css" />
<title>{{title}}</title>
</head>
<body class="body-index">
<h1>{{title}}</h1>
<form name="frm" action="/search" method="get">
<input type="hidden" name="keyword" value="" />
<div>
<!-- <input type="text" name="keyword" placeholder="input a job and press enter" required> -->
<button type="button" class="custom-btn btn-index" onClick="clickBtn('all');"><span>전체</span></button>
<button type="button" class="custom-btn btn-index" onClick="clickBtn('expired');"><span>만료예정</span></button>
</div>
</form>
<script type="text/javascript">
const f = document.frm;
function clickBtn(keyword) {
f.keyword.value = keyword;
f.submit();
}
</script>
</body>
</html>
모든 게임 목록을 보고 싶을 수도 있고, 만료 예정만 보고 싶을 수도 있으니
전체/만료예정
을 구분해서 표시하도록 한다.
4. 추출 페이지
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../static/search.css" />
<title>{{title}}</title>
</head>
<body>
<section>
<div>
<span>📌</span><span class="find-title">게임 카탈로그 : {{games|length}}건</span>
</div>
<hr>
<div class="tbl-header">
<table cellpadding="0" cellspacing="0" border="0">
<thead>
<tr>
<th>게임</th>
<th>태그</th>
<th>만료 예정일</th>
<th>링크</th>
</tr>
</thead>
</table>
</div>
<div class="tbl-content">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
{% for game in games %}
<tr>
<td>
<div class="company">
<img src="{{game['psw_cover']}}"/>
<span>{{game['game_name']}}</span>
</div>
</td>
<td>
{% for game_tag in game['game_tags'] %}
<span>{{game_tag}}</span>
{% endfor %}
</td>
<td>
<span>{{game['ps_expired']}}</span>
</td>
<td><a href="{{game['sub_url']}}" class="export" target="_blank">링크</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- // remoteok -->
</section>
</body>
</html>
추출한 정보를 그대로 출력한다.
5. 메인
from flask import Flask, render_template, request
from extractors.ps_expired import extract_ps_expired_paging
app = Flask("PSPlusScrapper")
title = "PS Plus Game Catalog"
@app.route("/")
def index():
return render_template("index.html", title=title)
@app.route("/search")
def search():
keyword = request.args.get("keyword")
before_games = extract_ps_expired_paging()
games = []
if keyword == "all":
games = before_games
else :
for game in before_games:
if game["ps_expired_flag"]:
games.append(game)
return render_template("search.html", games=games)
app.run("0.0.0.0")
search()
에서 게임 목록을 보여줄 때 전체/만료예정
에 따라 구분하는 keyword
가 있다.
결과
🔥 github
https://github.com/gogoma-code/PSPlus-expired-scraping
참고자료
🔥 Beautiful Soup: Build a Web Scraper With Python – Real Python
'Programming > Python' 카테고리의 다른 글
Python :: virtual enviroment setting for windows :: 윈도우에서 가상환경 세팅하기 (0) | 2022.09.14 |
---|---|
Python :: virtualenv setting with homebrew for macOS M1 :: 맥에서 homebrew로 가상환경 설정하기 (0) | 2022.09.10 |