๐Ÿณ

๋กœ์ŠคํŠธ์•„ํฌ ๋Œ€๊ธฐ์—ด ์นด์นด์˜คํ†ก ์•Œ๋ฆผ๋ด‡ ๊ฐœ๋ฐœ๊ธฐ

์„œ๋น„์Šค ๋งํฌ : (https://pf.kakao.com/_dcPGj)
Github ๋งํฌ : (https://github.com/suitelab/lostark-wait-notifier)

๋“ค์–ด๊ฐ€๊ธฐ ์ „์—

(http://loaq.kr/)๋Š” ๋ฐ์ดํ„ฐ ์žฌ๋ฐฐํฌ๋ฅผ ๊ธˆ์ง€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค!
์ด ๊ฒŒ์‹œ๋ฌผ์€ ๊ธฐ์กด์— ๋งŒ๋“ค์—ˆ๋˜ ์„œ๋น„์Šค๋กœ์จ ํ˜„์žฌ์˜ ์šด์˜๋ฐฉ๋ฒ•์ด ์•„๋‹™๋‹ˆ๋‹ค. ํ›„๊ธฐ๋กœ๋งŒ ๋ด์ฃผ์„ธ์š”!

ํ˜„์žฌ ์šด์˜์ค‘์ธ ์„œ๋น„์Šค์˜ ๊ฐœ๋ฐœ ํ›„๊ธฐ๋Š”
(http://suitee.me/2018/11/18/lostark-wait-notifier-2/)์— ์˜ฌ๋ ค๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€

์š”์ฆ˜ ๋กœ์ŠคํŠธ์•„ํฌ๋ฅผ ์žฌ๋ฐŒ๊ฒŒ ์ฆ๊ธฐ๊ณ  ์žˆ๋‹ค.(~~ํฌ์ŠคํŒ…์ด ๋œธํ•ด์ง€๋Š” ์›ํ‰~~)

ํ•˜์ง€๋งŒ ์ข€์ฒ˜๋Ÿผ ์ค„์–ด๋“ค์ง€ ์•Š๋Š” ๋Œ€๊ธฐ์—ด๋•Œ๋ฌธ์— ํ‰์ผ ์˜คํ›„ 5์‹œ, ์ฃผ๋ง ์˜ค์ „ 10์‹œ ์ดํ›„์—๋Š” ๋Œ€๊ธฐ์—ด๊ณผ์˜ ์ „์Ÿ์„ ์น˜๋ค„์•ผํ•œ๋‹ค.
ํ•„์ž๊ฐ€ ํ”Œ๋ ˆ์ดํ•˜๋Š” ์„œ๋ฒ„์ธ ์•Œ๋ฐ๋ฐ”๋ž€ ๊ธฐ์ค€ ํ‰๊ท  ๋Œ€๊ธฐ์—ด์€ 15,000๋ช… ์ •๋„ ๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ๋Œ€๋žต ์‹œ๊ฐ„์„ ์žฌ๋ณธ ๊ฒฐ๊ณผ 2์‹œ๊ฐ„ ๋„˜๊ฒŒ ๋Œ€๊ธฐ๋ฅผ ํ•˜์˜€๋‹ค.

์ด๋ฏธ์ง€ http://loaq.kr/

๋กœ์ŠคํŠธ์•„ํฌ๋ฅผ ์ฆ๊ฒจํ•˜๋Š” ์œ ์ €๋“ค์˜ ๊ฒฝ์šฐ ๋Œ€๊ธฐ์—ด ํ˜„ํ™ฉ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์ € ์‚ฌ์ดํŠธ๋ฅผ ๊ฝค๋‚˜ ๋งŽ์ด ์•Œ๊ณ ์žˆ๊ณ  ์ด์šฉํ•  ๊ฒƒ์ด๋‹ค. ๋ฌผ๋ก  ํ•„์ž๋„ ์ข…์ข… ์ด์šฉํ•˜๋Š” ํŽธ์ด๋‹ค.

๋งค๋ฒˆ ์‚ฌ์ดํŠธ์— ์ฃผ์†Œ๋ฅผ ์น˜๊ณ  ๋“ค์–ด๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋Š”๊ฒŒ ๊ท€์ฐฎ์•„์ง€๋˜ ์ฐฐ๋‚˜์— ์นด์นด์˜คํ†ก ํ”Œ๋Ÿฌ์Šค์นœ๊ตฌ API๋ฅผ ์ด์šฉํ•˜์—ฌ ๋Œ€๊ธฐ์—ด ์ •๋ณด๋ฅผ ํŽธํ•˜๊ฒŒ ์นดํ†ก์œผ๋กœ ๋ฐ›์•„๋ณด๋ฉด ์–ด๋–จ๊นŒ ํ•˜๋Š” ์ƒ๊ฐ์ด ๋“ค๊ฒŒ ๋˜์—ˆ๋‹ค. ๊ฒŒ์ž„ํ”Œ๋ ˆ์ด์™€ ๊ฐœ๋ฐœ์„ ๋™์‹œ์— ํ•  ์ˆ˜ ์žˆ๋Š” ์ฐฌ์Šค์˜€๋‹ค.

loaq.kr์˜ ๋Œ€๊ธฐ์—ด ๋ฐ์ดํ„ฐ์˜ ์›์ฒœ์„ ์•Œ๊ณ ์‹ถ์—ˆ์ง€๋งŒ ๊ฒฐ๊ตญ ์•Œ์•„๋‚ด์ง€ ๋ชปํ•˜์˜€๋‹ค. ๋กœ์ŠคํŠธ์•„ํฌ ์ธก์—์„œ ๋ณ„๋„์˜ API๋Š” ์ œ๊ณตํ•ด์ฃผ์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์•˜๋‹ค.
๊ทธ๋ฆฌํ•˜์—ฌ ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์ƒํ•˜์˜€๋‹ค.

  1. loaq.kr์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํฌ๋กค๋งํ•œ๋‹ค.
  2. ํฌ๋กค๋งํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜์—ฌ jsonํ˜•ํƒœ๋กœ ์ œ๊ณตํ•œ๋‹ค.
  3. jsonํ˜•ํƒœ๋กœ ์ œ๊ณต๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ kakao ํ”Œ๋Ÿฌ์Šค์นœ๊ตฌ api๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค ํ•œ๋‹ค.

1. loaq.kr์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํฌ๋กค๋งํ•œ๋‹ค.

ํฌ๋กค๋Ÿฌ์˜ ๊ฒฝ์šฐ selenium๋ฅผ ์ž์ฃผ ์จ๋ดค์–ด์„œ ๊ณ ๋ คํ•ด๋ดค์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์‚ฌ์šฉํ•˜๋Š” selenium ํŠน์„ฑ์ƒ request๋‹น ๋งค๋ฒˆ ๋น ๋ฅด๊ฒŒ ํฌ๋กค๋ง ํ•ด์•ผํ•˜๋Š” ์ด๋ฒˆ ์„œ๋น„์Šค์—๋Š” ์–ด์šธ๋ฆฌ์ง€ ์•Š๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.
๊ทธ๋ž˜์„œ ๋Œ€์ค‘์ ์œผ๋กœ ๋งŽ์ด ์‚ฌ๋ž‘๋ฐ›๋Š” BeautifulSoup๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

์šฐ์„  loaq.kr์˜ ๊ตฌ์กฐ๋ฅผ ํŒŒ์•…ํ–ˆ๋‹ค.

(divclassName="server-list")
  (divclassName="row")
    (divclassName="cell col-center")
        ๋ฃจํŽ˜์˜จ
    (/div)
    (divclassName="cell col-center")
      (div)
        (spanclassName="red")2348(/span)
         
      (/div)
      (div)
        (spanclassName="small-text bottom-text blue")
           (โ–ผ1183)
        (/span)
      (/div)
    (/div)
    (divclassName="cell col-center")
        (a href="/server/1")์ƒ์„ธ๋ณด๊ธฐ(/a)  
    (/div)
  (/div)
...

์„œ๋ฒ„ ํ•œ๊ฐœ์˜ row๊ตฌ์กฐ๋Š” ์ €๋ ‡๊ฒŒ ์ด๋ฃจ์–ด์ ธ ์žˆ์—ˆ๊ณ  ์„œ๋ฒ„๋ช…๊ณผ ๋Œ€๊ธฐ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋Œ์–ด์˜ค๋ ค๋ฉด ์ ํ•ฉํ•œ selector๊ฐ€ div.server-list๋ผ๊ณ  ์ƒ๊ฐ๋˜์–ด์„œ ๊ทธ๋ ‡๊ฒŒ ์ •ํ–ˆ๋‹ค.

๊ณต๋ฐฑ, (โ–ผ1183)์™€ ๊ฐ™์€ ๋“ฑ๋ฝ๋ฐ์ดํ„ฐ, ์ƒ์„ธ๋ณด๊ธฐ, ํ…Œ์ด๋ธ”์˜ ํ—ค๋” ๋“ฑ๋“ฑ ๋ถˆํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ •๋ฆฌํ–ˆ๋‹ค.

for tag in soup.select('div.server-list'):
    server_name += tag.text

server_name = server_name.replace(' ', '').split("\n")

server_name = [x for x in server_name
               if "(" not in x and "์ƒ์„ธ" not in x and
               "์„œ๋ฒ„" not in x and "๋Œ€๊ธฐ์—ด" not in x and
               "\xa0" not in x and "" != x]

2. ํฌ๋กค๋งํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜์—ฌ jsonํ˜•ํƒœ๋กœ ์ œ๊ณตํ•œ๋‹ค.

์ •์ œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ค€๋น„ ๋˜์—ˆ์œผ๋‹ˆ ์ด์ œ json์œผ๋กœ ์ œ๊ณตํ•  ์ค€๋น„๋ฅผ ํ•œ๋‹ค.

{
  "server_time": "2018-11-18 15:49:49",
  "items": [
    {
      "server": "๋ฃจํŽ˜์˜จ",
      "wait": "13744"
    }
  ]
}

json์˜ ํ˜•ํƒœ๋ฅผ ์œ„์™€ ๊ฐ™์ด ์ค€๋น„ํ•ด๋ณด์•˜๋‹ค.
์ฒ˜์Œ์œผ๋กœ ๋งŒ๋“ค์–ด๋ณด๋Š” api์—ฌ์„œ ๊ทธ๋Ÿฐ์ง€ ํ˜•ํƒœ๊ฐ€ ์•„์ง ์„ฑ์— ์ฐจ์ง„์•Š๋Š”๋‹ค.
๊ทธ๋ž˜๋„ ์ด์ •๋„ ๋ชจ์–‘์ƒˆ๋ฉด ์–ด๋Š์ •๋„ ์ ์ ˆํžˆ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋“ค์„ ๋‹ด๊ณ  ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๊ฐ€ ๋˜์—ˆ๋‹ค๊ณ  ์ƒ๊ฐ๋œ๋‹ค.

servers = {}
servers['server_time'] = soup.select('div.time')[0].text
.replace("  ","").replace('\n', '')

for i, item in enumerate(server_name):
    if i % 2 == 0:
        items.append({"server": item, "wait": server_name[i + 1]})

servers["items"] = items

3. jsonํ˜•ํƒœ๋กœ ์ œ๊ณต๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ kakao ํ”Œ๋Ÿฌ์Šค์นœ๊ตฌ api๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค ํ•œ๋‹ค.

์ฒ˜์Œ์—” node.js๋ฅผ ์ด์šฉํ•ด์„œ ๋น„๋™๊ธฐ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•ด๋ณผ ์ƒ๊ฐ์ด์—ˆ์ง€๋งŒ ํฌ๋กค๋Ÿฌ์™€ ๊ฐ™์€ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ •์‹ ๊ฑด๊ฐ•์— ์ข‹์„๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ์ฃผ๋งˆ๋“ฑ์ฒ˜๋Ÿผ ์Šค์ณ ์ง€๋‚˜๊ฐ”๋‹ค.
๊ทธ๋ž˜์„œ python์˜ web framework์ค‘ ํ•˜๋‚˜์ธ flask๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์•˜๋‹ค. django๋Š” ๋ช‡ ๋ฒˆ ์‚ฌ์šฉํ•ด๋ดค์ง€๋งŒ flask๋Š” ์ด๋Ÿฐ ์ž‘์€ ์„œ๋น„์Šค์— ์–ด์šธ๋ฆฐ๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋˜์–ด์„œ ์ฑ„ํƒํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

flask์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ด ๊ธ€์˜ ์ฃผ์ œ์—์„œ ๋ฒ—์–ด๋‚˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ์— ๋”์šฑ ๋‹ค๋ค„๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.
์ด๋ฏธ์ง€ ํ”Œ๋Ÿฌ์Šค์นœ๊ตฌ ๊ณ„์ •์„ ๋งŒ๋“  ํ›„ ์Šค๋งˆํŠธ์ฑ„ํŒ…-API๊ฐ€ ์‚ฌ์ง„๊ณผ ๊ฐ™์ด ์„ค์ • ๋˜์–ด์žˆ์–ด์•ผํ•œ๋‹ค.

๋Œ€ํ™”๋ฐฉ์— ์ฒซ ์ง„์ž…์‹œ ๋ณด์—ฌ์ค„ keyboard๋ฅผ ์ •์˜ํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.

@app.route('/keyboard')
def Keyboard():
    dataSend = {
        "type": "buttons",
        "buttons": ["๋Œ€๊ธฐ์—ด", "๋ช…๋ น์–ด"]
    }
    return jsonify(dataSend)

๋‹ค์Œ์€ ๊ฐ ์ƒํ™ฉ(๋ฉ”๋‰ด)๋ณ„ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌํ˜„ํ•œ message์ด๋‹ค.
ํ˜„์žฌ ์šฐ๋ฆฌ์˜ ์„œ๋น„์Šค๋Š” ๋งค์šฐ ๋‹จ์ˆœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋‹ค.
keyboard์˜ ๋Œ€๊ธฐ์—ด ๋ฒ„ํŠผ์ด ํด๋ฆญ๋˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ํ˜•ํƒœ์ด๋‹ค. ํฌ๋กค๋Ÿฌ์—์„œ ๋ฐ›์•„์˜จ json ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•ด ์‹ค์ œ๋กœ ๋ณด์—ฌ์ค„ message๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.
๋งˆ์ง€๋ง‰์— dataSend["keyboard"] = {"type": "buttons", "buttons": ["๋Œ€๊ธฐ์—ด", "๋ช…๋ น์–ด"]} ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ชจ๋“  ํ™”๋ฉด์—์„œ keyboard๊ฐ€ ๋™์ž‘ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ค€๋‹ค.

@app.route('/message', methods=['POST'])
def Message():
    dataReceive = request.get_json()
    content = dataReceive['content']
    if content == u"๋Œ€๊ธฐ์—ด":
        cr = Crawler()
        json_file = cr.start()

        text = f"์„œ๋ฒ„์‹œ๊ฐ„ - {json_file['server_time']}\n\n"

        for item in json_file['items']:
            text += f"{item['server']} - {item['wait']}\n"

        dataSend = {
            "message": {
                "text": text
            }
        }
    elif content == u"๋ช…๋ น์–ด":
        dataSend = {
            "message": {
                "text": "1. ๋Œ€๊ธฐ์—ด\n2. ๋ช…๋ น์–ด"
            }
        }
    else:
        dataSend = {
            "message": {
                "text": "๋ช…๋ น์–ด๋ฅผ ๋‹ค์‹œ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”. 1. ๋Œ€๊ธฐ์—ด, 2.๋ช…๋ น์–ด"
            }
        }

    dataSend["keyboard"] = {"type": "buttons", "buttons": ["๋Œ€๊ธฐ์—ด", "๋ช…๋ น์–ด"]}
    return jsonify(dataSend)

ํ•˜๋ฃจ๋งŒ์— ๊ธ‰์กฐํ•œ ํ”„๋กœ๊ทธ๋žจ์ด๋ผ ์™„์„ฑ๋„๊ฐ€ ๋งŽ์ด ๋–จ์–ด์ง€์ง€๋งŒ ํ˜„์žฌ๋„ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ์ด์šฉํ•ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•  ๋”ฐ๋ฆ„์ด๋‹ค ๐Ÿ˜Š

์ด๋ฏธ์ง€

(http://www.inven.co.kr/board/lostark/4821/5381)
๋กœ์ŠคํŠธ์•„ํฌ ์ธ๋ฒค์—์„œ๋„ ๋งŽ์€ ๋ถ„๋“ค์ด ์ด์šฉํ•ด์ฃผ์‹œ๊ณ  ๊ณ„์‹ ๋‹ค.

์ด๋ฏธ์ง€

(https://pf.kakao.com/_dcPGj)

ํ˜„์žฌ ๊ธฐ์ค€์œผ๋กœ 3000๋ช…์„ ๋ŒํŒŒํ–ˆ๋‹ค! โค๏ธโค๏ธโค๏ธ
์ด๋ฒˆ ์„œ๋น„์Šค ๊ฐœ๋ฐœ์€ ๊ท€์ฐฎ์Œ์„ ๋ฒ—์–ด๋‚˜๋ ค๊ณ  ์‹œ์ž‘ํ•œ ๋ชธ๋ถ€๋ฆผ์— ๋น„ํ•ด ๊ฝค๋‚˜ ๋ณด๋žŒ์ฐฌ ๊ฐœ๋ฐœ์ด์—ˆ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค.

์•ž์œผ๋กœ๋„ ์ด๋Ÿฐ ์•„์ด๋””์–ด๊ฐ€ ๋– ์˜ค๋ฅด๋ฉด ํŽธ๋ฆฌํ•จ์„ ์œ„ํ•ด ํŽธ๋ฆฌํ•จ์„ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ๋  ๊ฒƒ์ด๋‹ค.

๐Ÿ›ด

yoon.homme
yoon.homme

๊ธฐ์ˆ ๊ณผ ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜์˜ ํž˜์ด ์„ธ์ƒ์„ ๋ฐ”๊พผ๋‹ค๊ณ  ๋ฏฟ์Šต๋‹ˆ๋‹ค.

ํŽธ๋ฆฌํ•œ ์„ธ์ƒ์œผ๋กœ ๋‚˜์•„๊ฐ€๊ธฐ ์œ„ํ•ด ๊ณ ๋ฏผํ•˜๊ณ  ๊ฐœ๋ฐœํ•ฉ๋‹ˆ๋‹ค.