Categories
아무코드저장

GSMARENA 스마트폰 정보 가져와 엑셀로 저장하기

회사에서 하던 업무가 변경되어서 이제는 더 이상 코딩할 일이 없을 줄 알았지만, 다시 코딩을 해야 할 순간을 만났다. 그건 다름 아닌 정보 수집…!

GSMARENA 사이트에 가 보면 거의 뭐 전세계에 출시된 스마트폰 정보들을 다 볼 수 있는데, 가끔 여기서 뭔가 찾아야 하거나 혹은 한 두가지 정보들의 추세를 확인하거나 해야 할 경우가 생긴다. 근데 이 가끔이 때론 생각지도 못한 순간에 찾아와 사람을 허탈하게 만들기도 한다.

다행인지 불행인지 나도 어느 덧 고참 직원이 되었고, 후배님이 나 대신 이런 수고를 하는 모습을 우연히 관찰만 했었는데, 그야말로 인간 크롤러처럼 페이지 열고 Ctrl + C / Ctrl + V 해서 엑셀에 붙여넣고 그 지난한 시간 후에 다시 수집된 데이터들을 놓고 해석하는 작업을 보다가 이건 뭔가 아니지 않나 싶어서 간만에 다시 파이썬 코드를 잡았다.

import requests 
from bs4 import BeautifulSoup 
from time import sleep 
from random import uniform 
from openpyxl import Workbook 
 
# Welcome message 
print('-----------------------------------------------------------') 
print('                      G S M A R E N A') 
print('-----------------------------------------------------------') 
print(' !Please make sure that you have the following libraries:') 
print(' 1) BeautifulSoup4 2) openpyxl 3) requests') 
print(' I recommend to install Anaconda3 for easy to use')
print(' and you should install *Tor* for secure networking') 
print('_______________Python script by Heegeun Park_______________\n\n') 

# Get target brands and limitation of product number from prompt
print('\n[>] Please input target brands (e.g: samsung (or) samsung apple google)')
t = input('>>> ')
targets = t.lower()
if len(targets) < 2:
    print('[!] Please input target brands... your input is:', t)
    exit()
l = input('>>> Set limitation of product number (1~200): ')
limit = int(l)
if limit < 1 or limit > 200:
    print('[!] Please input a valid number for setting limitation of product number... your input is:', l)
    exit()
 
# Set common variables 
_gsmarena = 'https://www.gsmarena.com/' 
_target_brands = targets.split(' ')
_prod_selector = 'div#review-body div.makers ul li a' 
_page_selector = 'div.review-nav div.nav-pages a' 
_excel_save_path = 'gsmarena_{}.xlsx'.format('_'.join(_target_brands)) 
_excel_workbook = Workbook() 
_excel_default_sheet = _excel_workbook.active 
_gathering_product_limitation = limit
_min_wait_second = 30
_max_wait_second = 90
_session = requests.Session() 
_headers = { 
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit 537.36 (KHTML, like Gecko) Chrome", 
    'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" 
} 
_proxies = { 
    'http': 'socks5://127.0.0.1:9050', 
    'https': 'socks5://127.0.0.1:9050' 
} 
 
# Check my proxy IP for secure network 
real_ip = requests.get('http://icanhazip.com') 
fake_ip = requests.get('http://icanhazip.com', proxies=_proxies) 
print('[@] Real IP: ', real_ip.text.strip(), ', Tor IP : ', fake_ip.text.strip()) 
 
# It needs for server 
def wait_a_second(): 
    f = uniform(_min_wait_second, _max_wait_second) 
    sleep(f) 
 
# Get a page by using Tor network 
def get_page(url): 
    page = _session.get(url, headers=_headers, proxies=_proxies) 
    if page.status_code != 200: 
        print('[!] Failed to connect at ', url) 
        return None 
    wait_a_second() 
    print('[#] Get a page from: ', url)
    return page 

# Get a page and parsing html DOM
def html_select(url, pick):
    page = get_page(url)
    if page == None:
        return None
    dom = BeautifulSoup(page.content, 'html.parser')
    sel = dom.select(pick)
    return sel

# Parsing camera information at given url
def parse_camera_info(url):
    targets = {'cam1modules': '', 'cam1features': '', 'cam1video': '', 'cam2modules': '', 'cam2features': '', 'cam2video': ''}
    page = get_page(url)
    if page == None:
        return None
    dom = BeautifulSoup(page.content, 'html.parser')
    keys = targets.keys()
    for key in keys:
        sel = dom.select('td[data-spec=' + key + ']')
        if len(sel) == 0:
            continue
        value = sel[0].text.replace('<br />', '').strip().lower()
        if len(value) < 3:
            continue
        targets[key] = value
    
    # If this page is not a smartphone, just skip 
    k_len = 0
    for k in keys:
        if targets[k] == '':
            k_len += 1
    if k_len == len(keys):
        print('[?] This device might be not a smartphone... ', url)
        return None

    return targets

# Get a brand page
brands = html_select(_gsmarena, "div.brandmenu-v2 ul li a")
if brands == None:
    print('[!] Failed to connect GSMARENA... T_T)')

# Gathering product list for each smartphone brand
for b in brands:
    brand_name = b.text.lower().strip()
    brand_link = _gsmarena + b['href']

    # Check brand white list
    if brand_name not in _target_brands:
        print('[~] [', brand_name, '] is not my favorite... Just skip.')
        continue

    brand_all_pages = []
    brand_first_page = get_page(brand_link)
    if brand_first_page == None:
        continue

    brand_first_list = BeautifulSoup(brand_first_page.content, 'html.parser')
    phones_in_first_page = brand_first_list.select(_prod_selector)
    brand_all_pages.append(phones_in_first_page)

    # Get product lists from list page on GSMARENA
    brand_pages = brand_first_list.select(_page_selector)
    for p in brand_pages:
        page_link = _gsmarena + p['href']
        phones_in_other_page = html_select(page_link, _prod_selector)
        brand_all_pages.append(phones_in_other_page)

    print('[v] All [', brand_name, '] product links have been updated!')

    # Initialize Excel object
    _excel_workbook.create_sheet(title=brand_name)
    _xls = _excel_workbook[brand_name]

    # Gathering all phone specifications in $brand_name up to _gathering_product_limitation
    limit = 1
    row = 5
    for phones_in_page in brand_all_pages:
        for phone in phones_in_page:

            phone_url = _gsmarena + phone['href']
            cam_info = parse_camera_info(phone_url)
            if cam_info == None:
                continue
            keys = cam_info.keys()

            _xls['D3'] = 'MAIN CAMERA'
            _xls['G3'] = 'FRONT CAMERA'
            _xls['C4'] = 'PRODUCT'
            _xls['D4'] = 'MODULES'
            _xls['E4'] = 'FEATURES'
            _xls['F4'] = 'VIDEO'
            _xls['G4'] = 'MODULES'
            _xls['H4'] = 'FEATURES'
            _xls['I4'] = 'VIDEO'

            # Writing informations to given row
            srow = str(row)
            icol = ord('D')
            i_ = 0
            _xls['C'+srow] = phone.text
            for key in keys:
                point = chr(icol + i_) + srow
                _xls[point] = cam_info[key]
                i_ += 1
            row += 1

            print('[+] The (', phone.text.strip(), ') information has been added to Excel. [{}/{}]'.format(limit, _gathering_product_limitation))

            # check the limitation of gathering information
            if limit >= _gathering_product_limitation:
                print('[~] It has been exceeded the limitation.')
                break
            else:
                limit += 1
        
        # check the limitation and get out of loop if already exceeded
        if limit >= _gathering_product_limitation:
            break

    print('[*] [', brand_name, '] products have been updated!')

# Save all information to Excel file
_excel_workbook.remove(_excel_default_sheet)
_excel_workbook.save(_excel_save_path)
print('[Done] Please check this file: ', _excel_save_path)

위의 코드는 아직 완성본은 아니나, 기능 상에는 거의 문제가 없도록 구성되어 있다. 정말로 단순하게 처음에 만들었는데, 이거 너무 간만에 해서 감이 다 떨어져 버렸는지 생각지도 못한 문제들이 있어서 애를 먹었다.

첫번째 문제는 Too many request…가 뜨면서 막히는 문제. 이건 서버 입장에서 공격 당하는 거나 마찬가지니까 최소한의 방어를 위해 당연히 발동되어야 하는 거지만, 크롤러 성능 테스트를 하다가 알 수 없는 이유로 예외가 줄줄줄 터져 나가는 걸 보다 보니 이걸 아무래도 해결해야겠다 싶었다.

그래서 고심끝에, 지금 이 블로그를 돌려주고 있는 라즈베리파이에 Tor 네트워크를 설치하고, 파이썬에서는 프록시로 Tor 네트워크에 접속하여 다시 변경된 IP로 GSMARENA에 접속해서 페이지를 가져 오도록 변경했다. 프록시에 대한 개념만 수박 겉핥기식으로 알고 있다가 이런 일을 겪고서야 정신 차리고 공부를 했다니 부끄럽다.

두번째 문제는 수집된 데이터들의 중간 저장이다. 이건 생각만 해두고 아직 반영을 안했다. 왜냐면 저 코드로 데이터를 수집해서 원하는 결과는 일단 만들어서 보고까지 끝냈기 때문이다. ㅋㅋㅋ

중간 저장이 필요한 이유는 매번 새롭게 수집할 필요가 없기 때문이다. 기존에 수집된 내용들은 정말 특별한 이유가 없는 한 변경되지 않는다. 따라서 수집한 데이터를 우선 임시로 텍스트 파일에 저장해두고, 다음 번 크롤링에서는 임시 파일이 있으면 페이지를 읽지 말고 그냥 그 임시 파일을 읽어서 분석하면 그만이다. 제일 좋은 건 DB에 연동해서 저장해 두는 거겠지만…

이번 코딩을 하면서 간만에 도전 정신에 불타 올랐다. 프록시를 제대로 써 본 첫 케이스이고, 파이썬 beautifulsoup4의 위엄과 requests 대단함, 거기에 openpyxl의 편리함까지 정말로 감동 먹었다. 내 스크립트 언어의 시작점은 PHP 이지만, 그 끝은 단연코 Python 이 되리라.

코드 백업도 해둘 겸, 혹시 누군가가 필요할까 싶어서 이 곳 새로운 블로그에도 첫 코딩 글로 남겨 둔다. 크롤링은 정말 필요할 때만 하도록 하자.

One reply on “GSMARENA 스마트폰 정보 가져와 엑셀로 저장하기”

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다