지도로 말하기/테마 지도 만들기

온누리상품권 지도 만들기 1편 - 웹 크로울링

코드아키택트 2024. 2. 17. 21:22
반응형

안녕하세요 집 DS입니다. 오늘은 온누리상품권 지도를 만든 이야기를 해보도록 하겠습니다.

완성한 온누리상품권 가맹점 지도

 

문제상황 : 직관적이지 않은 온누리상품권 가맹점 검색 시스템

온누리상품권을 받는 때가 있습니다. 여러 시장을 중심으로 사용할 수 있는 상품권입니다. 근데 네이버 지도로 검색해도 어떤 가맹점이 있는지 정확히 알기가 어렵습니다. 좀 더 정확히 말하면 "온누리상품권"이라고 검색하면 시장까지는 알려주지만 그 내부에 어떤 상점, 업종, 품목을 지원해 주는지 알기 어렵다는 점이 있습니다.

네이버지도에 "온누리상품권"검색 결과

 

그렇다면 상점을 검색할 순 없을까요? 검색할 수 있습니다. 다만 직관적으로 지도에 표시해주지 않는다는 문제가 있습니다. 그래서 생각했습니다. "온누리 상품권 지도"를 만들어보자. 아이디어는 간단했지만 과정은 꽤나 고됐습니다.

 

온누리상품권 가맹점 검색

1단계 : Selenium으로 데이터 가져오기

데이터가 없으면 어떤 작업도 할 수 없습니다. 제가 간단히 찾아본 결과로는 온누리 상품권 가맹점 정보를 가지고 있는 것은 "소상공인시장진흥공단" 밖에 없습니다.  하지만 API를 제공하고 있지 않습니다. 그러면 어떻게 하느냐 크로울링 말곤 방법이 없다.

크로울링 또는 스크래핑이라는 말들을 자주 합니다. 물론 저는 그 깊이는 없기 때문에 상세한 이야기는 넘어가도록 하겠습니다. 그럼 일단 스크래핑이 정통한 언어인 거 같으니 스크래핑이라 하겠습니다. 스크래핑 방식은 크게 두 가지가 있습니다. 하나는 해당 URL을 단순히 request와 같은 함수와 엮어서 가져오는 방법. 다른 하나는 마치 브라우저가 요청을 보내는 것처럼 해서 HTML을 가져오는 방법이 있습니다. 사이트마다 다르지만 요즘 무분별한 스크래핑을 방지하기 위해 후자를 이용해야 하는 경우가 많아 보입니다.

좀 더 길게 설명하면 전자의 방식은 프로그램에서 GUI 없이 그냥 보내는 방식으로 대량의 데이터를 컴퓨터의 연산속도에 비례해 매우 빠르게 가져올 수 있습니다. 그렇다는 것은 특정 사이트의 수많은 정보가 매우 빠른 속도로 복사되고 다른 이들이 사용하게 된다는 뜻도 됩니다. 해당 사업자 입장에서 서버에 부담이 크고, 정보로 먹고사는 입장에서 난감한 이야기입니다. 그래서 사람이 클릭하는 방식만 인정하는 사이트가 꽤나 많습니다.

하지만 프로그래머들을 항상 방법을 찾습니다. 마치 웹으로 요청을 보내는 것처럼 가장하는 방식이 있습니다. Python에서는 Selenium을 사용하면 됩니다.

설치 

설치는 간단합니다.

pip install selenium

위 구문을 통해 설치할 수 있습니다.

 

소상공인 데이터 주소

그럼 이제 요청할 주소를 알아야 합니다. F12를 눌러 휴리스틱 하게 탐색하거나, 기타 여러 가지 휴립스틱한 방법이 존재합니다. 그렇습니다. 휴리스틱 말곤 답이 없습니다. 해당 서비스에서는 검색을 눌러 어떤 창이 뜨는지 확인해 보면 대략 감을 잡을 수 있습니다.

여기서 우측 검색을 눌러봅니다

저는 여러 가지 시도해 봤지만 검색을 통해 나오는 URL이 가장 정확하다는 것을 알 수 있었습니다.

검색결과

그러면 위와 같이 서울에 존재하는 모든 가맹점포가 나오는 것을 볼 수 있습니다. 우리에게 필요한 것은 URL이기 때문에 URL을 보면 아래와 같습니다.

https://www.sbiz.or.kr/sijangtong/nation/onnuri/pop/onnuriShopListKeyPopup.do?cpage=1&county_cd=&shop_table=SJTT.MKT_PAPER_SHOP&city_cd=02&txtKey=A.MARKET_NAME&txtParam=

웹개발을 해보신 분들은 대략 알겠지만 어딘가로 get request를 보내는 것을 볼 수 있습니다. 그리고 로그인 없이 할 수 있다? 문제는 거의 풀렸습니다. 위 구문에서 보면 `cpage=1`이라고 된 부분이 제 눈을 사로잡았습니다. 즉, 해당 서비스는 뒤의 복잡 다단한 것들은 지역 등등을 뜻하지만 cpage뒤의 숫자는 페이지 번호를 뜻하는 것을 알 수 있습니다.

마지막 페이지

마지막 페이지까지 보면 1037이 마지막 페이지임을 알 수 있습니다. 그럼 이제 코드로 넘어가 보도록 하겠습니다.

 

2단계 코드 구성하기

from selenium import webdriver
import pandas as pd

from bs4 import BeautifulSoup

def get_html_from_url():
    options = webdriver.ChromeOptions()
    options.headless = True
    driver = webdriver.Chrome(options=options)
    i = 1
    i_end = 1400
    for k in range(i,i_end):
        url = f" https://www.sbiz.or.kr/sijangtong/nation/onnuri/pop/onnuriShopListKeyPopupAjax.do?cpage={k}&mkt_cd=&shop_table=SJTT.MKT_ELE_SHOP&deal_item=&shop_nm=&county_cd=&city_cd=02&txtParam=&txtKey=A.MARKET_NAME" 
        driver.get(url)
        html = driver.page_source
        yield html
    driver.quit()

html = get_html_from_url()


headings = None
cnts = []
j = 1
for i,item in enumerate(html):
    print(i)
    soup = BeautifulSoup(item)
    table = soup.find("table")
    trs = table.find_all("tr")
    for tr in trs[j:]:
        data = tr.find_all('td')
        data = [ele.get_text() for ele in data]
        cnts.append(data)
        if i == 0:
            j =2
print(cnts)
print(len(cnts))
df = pd.DataFrame(cnts)
print(df)
df.to_pickle("./traditonal.pkl")

코드는 위와 같이 구성했습니다. selenium을 통해 html을 가져온 후 html을 beautifulsoup로 파싱 하는 구조를 가지고 있습니다. 그 후 pkl로 저장하도록 했습니다. 사실 겉멋으로 pkl을 쓰긴 했는데 csv로 써도 성능상 큰 차이는 없어 보여서 csv로 저장하셔도 될 것 같습니다. 저는 컴퓨터를 맥으로 쓰고 있어서 selenium설정상 큰 문제는 없었습니다. 윈도의 경우 chrmoe의 exe파일을 지정하는 등 어려움이 있어 보였습니다. 

성능을 좀 빠르게 하기 위해 chrmoe창이 안 뜨는 headless 옵션을 넣었는데 성공적이진 않았습니다. 전체 과정 중 크리티컬 한 부분은 아니어서 일단 제겼습니다. 위 코드를 실행하면 아래와 같은 광경을 볼 수 있습니다.

크로울링 실행

 

이렇게 데이터를 쉽게 획득할 수 있었습니다. 

하지만 이건 개미지옥의 시작이었을 뿐이었는데..

 

열심히 하는 것보다 중요한 것은 잘하는 것이다
그러나 잘하기 위해서는 열심히 해야 한다
집 DS
반응형