Python 웹 크롤링 기초 내용 응용(feat. sqlalchemy를 이용한 크롤링 데이터 저장)

ybear90·2020년 2월 11일
0

Python

목록 보기
8/8

앞서 requests, beautifulsoup 을 이용하여 빌보드 차트를 크롤링 해보는 예제를 진행해 봤다. 이를 응용하여 다른 사이트(마이뱅크 은행별 환율정보 페이지) 에 대해 크롤링을 진행해 보았다.

추가로 sqlalchemy를 이용하여 크롤링한 데이터를 저장할 db테이블 생성 및 저장까지 같이 진행해 보려고 한다.

sqlalchemy는 무엇 ?

django framework에서도 DB모델링을 위해 ORM을 사용했듯이 python 환경에서 사용가능한 ORM의 일종이다. Sqlalchemy를 사용함으로서 얻을 수 있는 장점은 아래와 같으며

  1. 객체 지향적인 코드로 더 직관적이고 비즈니스 로직에 집중하기 좋다
  2. 재사용 및 유지보수의 편리성이 증가
  3. DBMS에 대한 종속성이 줄어듬

단점은 아래와 같다

  1. 완벽한 ORM으로만 서비스를 구현하기가 어렵다.
  2. 프로시저가 많은 시스템에선 ORM의 장점을 활용하기 어렵다

sqlalchemy setup

$ pip install sqlalchemy

create db engine

db를 어떻게 만들건지 정의하고 connection을 맺는 부분이다

from sqlalchemy import create_engine

# echo=True를 선언할 경우 실제 테이블 생성 쿼리문을 보여준다
engine = create_engine('sqlite:///<db-name>.db', echo=True)

declare mapping

db테이블을 만들었다고 바로 쓸 수 있는 것이 아니라 Base 클래스를 선언해 주고 실제 모델이 될 클래스를 정의하는 식으로 해야 했다.

Base = declarative_base()

class Currency(Base):
    __tablename__   = 'currency_'+db_name+'_'+now_date_time
    id              = Column(Integer, primary_key=True)
    country_name    = Column(String(50))
    buying_cost     = Column(String(20))
    bfee_ratio      = Column(String(20))
    selling_cost    = Column(String(20))
    slfee_ratio     = Column(String(20))
    sending_cost    = Column(String(20))
    sefee_ratio     = Column(String(20))
    dealing_br      = Column(String(20))

이렇게 한다고 바로 DB 테이블이 만들어 지는 것이 아니라 아래와 같이 실제로 table을 만들어 주어야 한다

Currency.__table__.create(bind=engine, checkfirst=True)

만든 DB테이블을 사용하려면 아래와 같은 처리를 또 해주어야 한다 sqlalchemy를 통해 실제로 만들어진 DB와 세션을 맺는 방법이다.

from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()

세션을 맺었다면 아래와 같은 방식으로 db에 데이터를 저장할 수 있다.

# save in database(sqlite db)
for raw_data in currency_list:
    input_data = Currency(
        country_name=raw_data['country_name'],
        buying_cost=raw_data['buying_cost'],
        bfee_ratio=raw_data['bfee_ratio'],
        selling_cost=raw_data['selling_cost'],
        slfee_ratio=raw_data['slfee_ratio'],
        sending_cost=raw_data['sending_cost'],
        sefee_ratio=raw_data['sefee_ratio'],
        dealing_br=raw_data['dealing_br']
    )
    session.add(input_data)
    session.commit()

넣어줄 때마다 db모델 객체를 생성하여 넣고 실제로 session에 더하고 commit까지 해주어야 실제 db에 데이터가 들어가게 된다. 일종의 git과 유사한 방식으로 보여진다.

넣어준 데이터를 확인하고 싶으면 아래와 같은 방식으로 쿼리를 수행해 주면 된다(all() 말고도 다양하다)

query_request = session.query(Currency).all()

# 반복문을 이용해서 결과 출력
for row in query_request:
    print(row.country_name, '|', row.buying_cost, '|',\
         row.bfee_ratio, '|', row.selling_cost, '|',
         row.slfee_ratio, '|', row.sending_cost, '|',
         row.sefee_ratio, '|', row.dealing_br)

db를 정의하고 실제 생성하고 데이터를 넣어주기위해 session을 생성하고 session을 통해 add, commit으로 데이터를 직접 db에 넣어주고 query메소드를 통해 데이터의 접근하는 방식까지 ORM을 사용하지만 하나서 부터 열까지 직접 설정해 주는 식으로 DB를 활용하게 되었다.

실제 크롤러 프로젝트에 적용

앞서 포스팅했던 간단한 크롤러와 sqlalchemy를 활용하여 데이터의 저장하는 것 까지 하나의 소스코드로 합쳐 구현을 해 보았다. 소스코드는 크게 두 부분으로 다음과 같다.

import datetime
import requests
from bs4 import BeautifulSoup
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql import *

bank_dict = {
    '005' : 'KEB하나은행',
    '020' : '우리은행',
    '004' : 'KB국민은행',
    '088' : '신한은행',
    '011' : '농협은행',
    '003' : '기업은행',
    '023' : '한국스탠다드차티드은행',
    '027' : '한국시티은행',
    '007' : '수협은행',
    '032' : '부산은행',
    '031' : '대구은행',
    '039' : '경남은행',
    '035' : '제주은행'
}

print("환율 확인 및 db 저장이 가능한 은행 목록입니다: \n", bank_dict)
bank_code = input('은행 코드를 입력해주세요(세자리 입력 필수) : ')

BASE_URL        = 'https://www.mibank.me/exchange/bank/index.php?search_code='+bank_code
db_name         = bank_dict[bank_code]
now_date_time   = datetime.datetime.now().strftime('%Y-%m-%d_%H:%H:%S')

engine = create_engine('sqlite:///currency_'+db_name+'_'+now_date_time+'.db')
Base = declarative_base()

class Currency(Base):
    __tablename__   = 'currency_'+db_name+'_'+now_date_time
    id              = Column(Integer, primary_key=True)
    country_name    = Column(String(50))
    buying_cost     = Column(String(20))
    bfee_ratio      = Column(String(20))
    selling_cost    = Column(String(20))
    slfee_ratio     = Column(String(20))
    sending_cost    = Column(String(20))
    sefee_ratio     = Column(String(20))
    dealing_br      = Column(String(20))

Currency.__table__.create(bind=engine, checkfirst=True)

Session = sessionmaker(bind=engine)
session = Session()

환율 정보를 수집할 은행 코드를 입력하고 db 모델링 및 이름설정과 크롤링을 위한 URL변수 설정 하는 부분이다. 은행코드 부분은 크롤링 할 페이지에서 해당 은행을 개발자 도구에서 선택했을 때 뜨는 코드를 직접 하드코딩으로 넣었다. 실제 크롤링 기법으로 수집해볼 생각이였지만 아직은 배워나가는 단계라 시도를 했다 좌절을 맛봤다.

(크롤링을 진행했던 마이뱅크 환율페이지)

(은행 코드를 확인하는 방법)

req = requests.get(BASE_URL)
html = req.text
soup = BeautifulSoup(html, 'html.parser')

country_name        = soup.select('tbody > tr > td.first > a')
buying_cost         = soup.select('tbody > tr > td:nth-child(3)')
bfee_ratio          = soup.select('tbody > tr > td:nth-child(4)')
selling_cost        = soup.select('tbody > tr > td:nth-child(5)')
slfee_ratio         = soup.select('tbody > tr > td:nth-child(6)')
sending_cost        = soup.select('tbody > tr > td:nth-child(7)')
sefee_ratio         = soup.select('tbody > tr > td:nth-child(8)')
dealing_br          = soup.select('tbody > tr > td:nth-child(9)')

currency_list = []

# currency data appending in currency_list
for item in zip(country_name, buying_cost, bfee_ratio, selling_cost,
    slfee_ratio, sending_cost, sefee_ratio, dealing_br):
    currency_list.append(
        {
            'country_name'  : item[0].text,
            'buying_cost'   : item[1].text,
            'bfee_ratio'    : item[2].text,
            'selling_cost'  : item[3].text,
            'slfee_ratio'   : item[4].text,
            'sending_cost'  : item[5].text,
            'sefee_ratio'   : item[6].text,
            'dealing_br'    : item[7].text,   
        }
    )


# save in database(sqlite db)
for raw_data in currency_list:
    input_data = Currency(
        country_name=raw_data['country_name'],
        buying_cost=raw_data['buying_cost'],
        bfee_ratio=raw_data['bfee_ratio'],
        selling_cost=raw_data['selling_cost'],
        slfee_ratio=raw_data['slfee_ratio'],
        sending_cost=raw_data['sending_cost'],
        sefee_ratio=raw_data['sefee_ratio'],
        dealing_br=raw_data['dealing_br']
    )
    session.add(input_data)
    session.commit()

query_request = session.query(Currency).all()

# check the db results
print("아래와 같은 환율정보 db가 생성되었습니다.")

for row in query_request:
    print(row.country_name, '|', row.buying_cost, '|',\
         row.bfee_ratio, '|', row.selling_cost, '|',
         row.slfee_ratio, '|', row.sending_cost, '|',
         row.sefee_ratio, '|', row.dealing_br)

설정한 값들을 기준으로 실제 크롤링 및 db 데이터 저장이 이뤄지는 부분이다. css selector를 이용하여 데이터 크롤링을 하였고 db 컬럼 형식에 맞게 수집 후 저장하였다. 이상없이 실행 되었다면 아래와 같은 결과를 얻을 수 있었다.

코드 실행 결과

율 확인 및 db 저장이 가능한 은행 목록입니다: 
 {'005': 'KEB하나은행', '020': '우리은행', '004': 'KB국민은행', '088': '신한은행', '011': '농협은행', '003': '기업은행', '023': '한국스
탠다드차티드은행', '027': '한국시티은행', '007': '수협은행', '032': '부산은행', '031': '대구은행', '039': '경남은행', '035': '제주은행'}
은행 코드를 입력해주세요(세자리 입력 필수) : 020
아래와 같은 환율정보 db가 생성되었습니다.
외화 | 현찰 살 때 | 수수료율 | 현찰 팔 때 | 수수료율 | 송금 보낼 때 | 수수료율 | 매매기준율
중국 | 178.26 | 4.99% | 161.30 | 4.99% | 171.47 | 1.00% | 169.78
일본 | 1096.60 | 1.75% | 1058.88 | 1.75% | 1088.19 | 0.97% | 1077.74
미국 | 1204.72 | 1.75% | 1163.28 | 1.75% | 1195.40 | 0.96% | 1184.00
유럽연합 | 1317.18 | 1.97% | 1266.30 | 1.97% | 1304.65 | 1.00% | 1291.74
홍콩 | 155.49 | 1.97% | 149.49 | 1.97% | 154.01 | 1.00% | 152.49
대만 | 41.77 | 5.99% | 37.05 | 5.99% | - | - | 39.41
싱가폴 | 870.11 | 2.00% | 835.99 | 2.00% | 861.58 | 1.00% | 853.05
태국 | 38.68 | 1.98% | 37.18 | 1.98% | 38.30 | 0.98% | 37.93
필리핀 | 25.51 | 8.97% | 22.01 | 5.98% | 23.64 | 0.98% | 23.41
베트남 | 5.70 | 11.76% | 4.50 | 11.76% | 5.15 | 0.98% | 5.10
영국 | 1557.80 | 1.97% | 1497.62 | 1.97% | 1542.98 | 1.00% | 1527.71
호주 | 810.11 | 1.97% | 778.81 | 1.97% | 802.40 | 1.00% | 794.46
캐나다 | 908.11 | 1.97% | 873.03 | 1.97% | 899.47 | 1.00% | 890.57
브라질 | 303.87 | 11.00% | 240.91 | 12.00% | - | - | 273.76
멕시코 | 70.36 | 11.00% | 55.79 | 11.99% | 64.02 | 0.99% | 63.39
뉴질랜드 | 771.59 | 1.97% | 741.79 | 1.97% | 764.25 | 1.00% | 756.69
인도네시아 | 9.22 | 5.98% | 8.10 | 6.90% | 8.78 | 0.92% | 8.70
말레이시아 | 296.77 | 4.00% | 271.10 | 5.00% | - | - | 285.36
터키 | 212.90 | 8.00% | 181.36 | 8.00% | 199.10 | 1.00% | 197.13
인도 | 17.93 | 7.95% | 15.62 | 5.96% | 16.77 | 0.96% | 16.61
몽골 | - | - | - | - | - | - | 0.43
이스라엘 | 373.65 | 8.00% | 318.31 | 8.00% | - | - | 345.98
사우디 | 331.46 | 5.00% | 295.17 | 6.50% | 318.83 | 1.00% | 315.68
쿠웨이트 | 4161.19 | 7.00% | 3616.75 | 7.00% | 3927.85 | 1.00% | 3888.97
바레인 | 3329.12 | 6.00% | 2889.43 | 8.00% | 3172.08 | 1.00% | 3140.68
U.A.E | 338.49 | 5.00% | 301.43 | 6.50% | 325.60 | 1.00% | 322.38
카자흐스탄 | - | - | - | - | - | - | 3.14
카타르 | - | - | - | - | - | - | 325.24
파키스탄 | - | - | - | - | - | - | 7.68
방글라데시 | - | - | - | - | - | - | 13.99
브루나이 | 886.28 | 4.00% | 801.07 | 6.00% | - | - | 852.20
요르단 | 1736.80 | 4.00% | 1536.40 | 8.00% | - | - | 1670.00
스위스 | 1234.98 | 1.97% | 1187.28 | 1.97% | 1223.24 | 1.00% | 1211.13
러시아 | 19.76 | 6.98% | 16.26 | 11.97% | 18.65 | 0.97% | 18.47
스웨덴 | 125.05 | 2.00% | 120.15 | 2.00% | 123.82 | 1.00% | 122.60
덴마크 | 176.36 | 2.00% | 169.46 | 2.00% | 174.63 | 0.99% | 172.91
노르웨이 | 130.08 | 2.00% | 124.98 | 2.00% | 128.80 | 1.00% | 127.53
헝가리 | 4.13 | 7.83% | 3.53 | 7.83% | 3.86 | 0.78% | 3.83
체코 | 55.72 | 7.98% | 47.48 | 7.98% | - | - | 51.60
폴란드 | 327.25 | 8.00% | 278.77 | 8.00% | 306.04 | 1.00% | 303.01
남아공 | 85.43 | 7.99% | 72.79 | 7.99% | 79.90 | 1.00% | 79.11
이집트 | - | - | - | - | - | - | 75.26

실제 db 생성은 다음과 같다

Reference

profile
wanna be good developer

0개의 댓글