๋ก์คํธ์ํฌ ๋๊ธฐ์ด ์นด์นด์คํก ์๋ฆผ๋ด ๊ฐ๋ฐ๊ธฐ - 2
์๋น์ค ๋งํฌ : (https://pf.kakao.com/_dcPGj)
Github ๋งํฌ : (https://github.com/suitelab/lostark-wait-notifier)
![์ด๋ฏธ์ง](/static/21230511b359ba5ae392b6821162f736/8c557/loa1.png)
๊ธฐ์กด ์๋น์ค๊ฐ loaq์ธก์ ์์ฒญ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ํฌ๋กค๋งํ ์ ์๊ฒ ๋์๋ค.
~~์๋น์ค๊ฐ ์ฃผ๊ฑฐ๋ฒ๋ ค๋ฐ..~~
์๋น์ค์ ์น๊ตฌ์๋ ์ ์ ๋์ด๊ฐ๋๋ฐ ์ ๋๋ก ์ด์ํ์ง ๋ชปํด์ ์ฃ์ก์ค๋ฌ์ธ ๋ฐ๋ฆ์ด์๊ณ ,
์ด๋ ๊ฒ ์์๊ฐ ๋ง์ ์๋น์ค๋ฅผ ๊ผญ ๋ค์ ์ด์ํด๋ด์ผ๊ฒ ๋ค๋ ์๊ฐ์ ๊ฐ๊ณ ์์๋ค.
๊ทธ๋์ ์๋น์ค๋ฅผ ์ค๋จํ๊ณ ์๋ ์์ค์ (https://rubystarashe.github.io/lostark/)(์ดํ ๋๊ธฐ์ด์๋น์ค)๋ฅผ ์ด์ํ๊ณ ๊ณ์
๋ฃจ๋น์คํ๋์ด ๋ฐ์ดํฐ ์์ง์ ํผ์พํ ํ๋ฝํด์ฃผ์
์ ๋ค์ ์ด์ํ ์ ์๊ฒ ๋์๋ค.
๊ฐ๋ฐ ํ๋ก์ธ์ค๋ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ํ์๋ค.
- ๋๊ธฐ์ด ์๋น์ค์ ๋ฐ์ดํฐ๋ฅผ ํฌ๋กค๋งํ๋ค.
- ํฌ๋กค๋งํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ์ฌ DB์ ์ ์ฅํ๋ค.
- ์ค์ผ์ค๋ฌ๋ฅผ ์ฌ์ฉํ๋ค.
- ์ฌ์ฉ์์ ์์ฒญ์ ๋ง๋ค DB์์ ๋ฐ์ดํฐ๋ฅผ kakao ํ๋ฌ์ค์น๊ตฌ api๋ฅผ ํตํด ์๋น์ค ํ๋ค.
1. ๋๊ธฐ์ด์๋น์ค์ ๋ฐ์ดํฐ๋ฅผ ํฌ๋กค๋งํ๋ค.
~~ํฌ๋กค๋ฌ์ ๊ฒฝ์ฐ selenium๋ฅผ ์์ฃผ ์จ๋ดค์ด์ ๊ณ ๋ คํด๋ดค์ง๋ง ๋ธ๋ผ์ฐ์ ๋ฅผ ์ฌ์ฉํ๋ selenium ํน์ฑ์ request๋น ๋งค๋ฒ ๋น ๋ฅด๊ฒ ํฌ๋กค๋ง ํด์ผํ๋ ์ด๋ฒ ์๋น์ค์๋ ์ด์ธ๋ฆฌ์ง ์๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.~~
๋ผ๊ณ 1ํธ์ ์ ์์ง๋ง ๋๊ธฐ์ด์๋น์ค๊ฐ ๋ ๋๋ง์ ํตํด ์์ฑ๋๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก๋ DOM๊ตฌ์กฐ๋ฅผ ์์งํ ์ ์์๋ค.
๊ทธ๋์ selenium์ ํตํด ๋ธ๋ผ์ฐ์ง์ ํ๊ณ ๊ธ์ด์จ DOM์ BeautifulSoup๋ฅผ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌํ๋ค.
๋ฐ๋ก ๋๊ธฐ์ด์๋น์ค์ ๊ตฌ์กฐ๋ฅผ ํ์ ํด๋ณด์.
๋ฐ์ดํฐ๊ฐ ์์ฑ๋๊ธฐ ์ ์ ๋ชจ์ต
<body data-n-head="">
<div id="__nuxt">
<style>
#nuxt-loading {
visibility: hidden;
opacity: 0;
position: absolute;
left: 0;
...
๋ฐ์ดํฐ๊ฐ ์์ฑ๋ ํ์ ๋ชจ์ต
<div id="__nuxt">
...
<div class="box">
<div class="item">
<span class="data name">์ํ๋ ์ค</span>
<span class="data queue" style="opacity: 1; color: yellow;">9590</span>
</div>
<div class="item">
<span class="data name">์ํฌํฌ๋ฅด์ค</span>
<span class="data queue" style="opacity: 1; color: yellow;">7839</span>
</div>
...
ํฌ๋กค๋งํ๊ธฐ ์ข๊ฒ ๊ตฌ์กฐ๊ฐ ์ง์ฌ์ ธ์๋ค.
soup.select()
๋ฉ์๋๋ก span tag์ data name
ํ๊ทธ๋ฅผ ๋ถ๋ฌ์ค๋ฉด ์๋ฒ์ด๋ฆ ๋ฆฌ์คํธ,
data queue
ํ๊ทธ๋ฅผ ๋ถ๋ฌ์ค๋ฉด ์๋ฒ์ ๋๊ธฐ์ด ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ ๊ฒ์ด๋ค.
๊ทธ๋ฌ๋ฉด ๋ฐ๋ก ๊ตฌํํด๋ณด์.
for i in range(server_count):
server = soup.select('span.data.name')[i].text
queue = soup.select('span.data.queue')[i].text
items.append({"server": server, "queue": queue})
2. ํฌ๋กค๋งํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ์ฌ DB์ ์ ์ฅํ๋ค.
์๋ DB๊น์ง ์ฐ๊ณ ์ถ์ง ์์์ง๋ง selenium์ ์ฌ์ฉํ๊ฒ ๋๋ ํ๋ฒ ํฌ๋กค๋งํ๋๋ฐ ํ๊ท 4.5์ด๋ผ๋ ์๊ฐ์ด ๊ฑธ๋ ธ๋ค. ์นดํก๋ด์๊ฒ ๋๊ธฐ์ด ์์ฒญํ๊ณ ์ฝ 5์ด๊ฐ ๊ธฐ๋ค๋ฆฌ๊ฒ ๋๋ค๋ฉด ๋งค์ฐ ๋์ฐํ ๊ฒ์ด๋ค.
๊ทธ๋์ ๊ตฌ์ํ ์๊ฐ์ selenium ๋ธ๋ผ์ฐ์ ๋ฅผ ๋์๋๊ณ ์ฃผ๊ธฐ์ ์ผ๋ก ํฌ๋กค๋ง์ ํ์ฌ DB๋ก ์ ์ฅํ๋ ๋ฐฉ๋ฒ์ด์๋ค.
DB๋ ๋ง์ด ์ฐ์ด๋ MySql์ ์ฌ์ฉํ์๊ณ ํ ์ด๋ธ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ๋ค.
![์ด๋ฏธ์ง](/static/078a50b2270a4de7c7234a72b8e889a4/604ec/table.png)
server table์๋ ์ต์ด์ ์๋ฒ๋ฆฌ์คํธ๋ฅผ ์ ์ฅํด๋๊ณ ํฌ๋กค๋ง ํ ๋๋ง๋ค queue table์ insertํ๊ฒ ํ์๋ค.
def insert_queue_query(self, queues):
for i, queue in enumerate(queues):
sql_query = f"INSERT INTO queue(date_time, server_id, queue) VALUES (now(), {i + 1}, {queue['queue']});"
self.cursor.execute(sql_query)
self.conn.commit()
print('{0}๊ฐ์ ์ ๋ณด ์
๋ฐ์ดํธ ์๋ฃ: {1}'.format(len(queues), str(datetime.now())))
๊ทธ๋ฆฌ๊ณ table์ ์๊ฐ๋น ์ฝ 10,000๊ฑด์ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์ฌ ๊ฒ์ผ๋ก ์์๋์ด ํผํฌ๋จผ์ค๋ฅผ ์ํด ๋ฐ์ดํฐ๋ฅผ ์ผ์ ์ฃผ๊ธฐ๋ง๋ค ์ผ๋ถ๋ฅผ ์ญ์ ํ๊ฒ ํ์๋ค.
๋ง์ฝ ํต๊ณ๋ฑ์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๊ฒ ๋๋ค๋ฉด ์ด๋ถ๋ถ์ ๋ค์ ๊ณ ๋ คํด๋ด์ผ ํ ๊ฒ ๊ฐ๋ค.
def delete_data(self):
sql = """
DELETE FROM queue
ORDER BY date_time ASC LIMIT 9000;
"""
self.cursor.execute(sql)
self.conn.commit()
print('9000๊ฐ์ ์ ๋ณด ์ญ์ ์๋ฃ: {0}'.format(str(datetime.now())))
Crawler class์ __init__๋ถ๋ถ์์ ํฌ๋กฌ๋๋ผ์ด๋ฒ๊ฐ ์คํ๋๊ธฐ ๋๋ฌธ์ db์์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ ๋ (~~๋๋ฆฐ~~)ํฌ๋กฌ๋๋ผ์ด๋ฒ๋ฅผ ์คํํ ํ์๋ ์์๋ค.
DbTools class ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ select_only
ํ๋ผ๋ฏธํฐ๋ฅผ ๋ ์ผ๋ก์จ ์กฐํ๋ง ์ฌ์ฉํ ๊ฐ์ฒด์ ๊ฒฝ์ฐ ๋น ๋ฅธ ์๋๋ก ์คํํ ์ ์๊ฒ ํ์๋ค.
class DbTools:
def __init__(self, select_only = False):
self.conn = pymysql.connect(host=config.DATABASE_CONFIG["host"],
user=config.DATABASE_CONFIG["user"],
db=config.DATABASE_CONFIG["db"],
password=config.DATABASE_CONFIG["password"],
charset=config.DATABASE_CONFIG["charset"])
self.cursor = self.conn.cursor()
self.crawler = None
if not select_only:
self.crawler = Crawler()
3. ์ค์ผ์ค๋ฌ๋ฅผ ์ฌ์ฉํ๋ค.
์ค์ผ์ค๋ฌ๋ ์ฒ์ ์จ๋ดค์ง๋ง ์ฌ์ฉ๋ฒ์ด ๊ฐ๋จํด๋ณด์ด๋ schedule ๋ชจ๋์ ์ฌ์ฉํ์๋ค. ๋ค์๊ณผ ๊ฐ์ ์งง์ ์ฝ๋๋ก ์ค์ผ์ค๋ฌ๊ฐ ์์ฑ๋์๋ค.
์๋ฒ์ ๋ณ๊ฐ๋ก ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ๋๋ฉด์ 2์ด๋ง๋ค ํฌ๋กค๋งํ๊ณ 4000์ด(์ฝ 1์๊ฐ)๋ง๋ค ๋ฐ์ดํฐ๋ฅผ 9000๊ฑด์ ์ญ์ ํ๋ ์ค์ผ์ค๋ฌ๊ฐ ๋ ๊ฒ์ด๋ค.
# schedule.py
db = DbTools(select_only=False)
def insert_schedule():
db.save_data()
def delete_schedule():
db.delete_data()
if __name__ == "__main__":
print("scheduler is running! {}".format(datetime.now()))
schedule.every(2).seconds.do(insert_schedule)
schedule.every(4000).seconds.do(delete_schedule)
while True:
schedule.run_pending()
time.sleep(1)
4. ์ฌ์ฉ์์ ์์ฒญ์ ๋ง๋ค DB์์ ๋ฐ์ดํฐ๋ฅผ kakao ํ๋ฌ์ค์น๊ตฌ api๋ฅผ ํตํด ์๋น์ค ํ๋ค.
kakao api ์ฌ์ฉ๋ถ๋ถ์ ๊ธฐ์กด๊ณผ ํฌ๊ฒ ๋ณํ๊ฒ ์์ง๋ง message_button
ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํด๋ณด์๋ค.
(~~๋ฌผ๋ก ๋ธ๋ก๊ทธ ํ๋ณด๋ผ๋ ์ฌ๋ฆฌ์ฌ์์ ์ฑ์ฐ๊ธฐ ์ํด์~~)
data_send["message"].update({'message_button': {'label': '๊ฐ๋ฐ์ ์ค์ด๋ฏ ๋ธ๋ก๊ทธ', 'url': 'http://suitee.me'}})
![์ด๋ฏธ์ง](/static/b2471f0c27aaa39678b88bed7be0e9de/8c557/message.png)
๊ฐ๋ฐ์ ์ค์ด๋ฏ ๋ธ๋ก๊ทธ ๋ผ๋ ๋ฒํผ์ด ์์ฃผ ์ ์์ฑ๋์๋ค.
5. ๊ธฐํ
-
server๋ AWS EC2์ t3.micro ์ธ์คํด์ค๋ก Ubuntu๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค.
-
GUI๋ฅผ ์ค์นํ์ง ์์ ๊ด๊ณ๋ก putty๋ฅผ ์ฌ์ฉํ๊ณ ์๋๋ฐ ๋น์ฐํ putty๋ฅผ ์ข ๋ฃํ๋ฉด putty๋ก ์คํํ๊ณ ์๋ ํ๋ก๊ทธ๋จ์ ์ข ๋ฃ๊ฐ ๋๋ค.
๊ทธ๋์ ๋ฐฑ๊ทธ๋ผ์ด๋๋ฅผ ๋๋ฆฌ๋ ํ๋ก๊ทธ๋จ์ผ๋ก ์๋ node.js์ฉ์ผ๋ก npm์์ ์ ๊ณต๋๋ forever๋ฅผ ์ฌ์ฉํ์๋ค. -
์ค์น ํ ํด๋น ๋๋ ํ ๋ฆฌ์์ ๋ค์๊ณผ ๊ฐ์ด ๋ช ๋ น์ด๋ฅผ ์์ฑํ๋ฉด python ์๋น์ค์ง๋ง ์์ฃผ ์ ์์ ์ผ๋ก ์๋ํ๋ค.
$ forever start -c python3 kakao.py
$ forever start -c python3 scheduler.py
-
๋ก์คํธ์ํฌ ์ ๊ฒ๊ณต์ง์ฌํญ์ ๋ณด์ฌ์ฃผ๋ ๊ธฐ๋ฅ์ ๊ณ ๋ ค์ค์ด๋ค. ๋น ๋ฅธ์์ผ๋ด์ ์๋น์ค๊ฐ ์ ๊ณต๋ ์๋ ์์๊ฒ ๊ฐ๋ค.
-
์ฌ์๊ฐ ์๋ฆผ ๊ธฐ๋ฅ๋ ๊ณ ๋ ค์ค์ด๋ค.
-
์ด๋ฒ ์ฃผ๋ง๋ถํฐ ๋ก์คํธ์ํฌ๊ฐ ์๋ฒ๋ฅผ ๋๋์ ์ผ๋ก ์ฆ์คํ๋ค๊ณ ํ๋ค. ์ธ์ด์ผ ํ ์ง ์์ด์ผ ํ ์งโฆ ๋ด ์๋น์ค์ ์กดํ ์ฌ๋ถ๊ฐ ๋ฌ๋ ค ์์๋ฏ ์ถ๋ค. ๋ฌผ๋ก ๋๊ธฐ์ด์ด ์์ผ๋ฉด ๋์ฑ ํ๋ณตํ ๊ฒ ๊ฐ๋ค :)