내 스크래핑 방식은 페이지단위로 스크래핑을 하기 때문에, 스크래핑 주기에 따라 페이지가 다 넘어가지 않으면 당연하게도 이전 스크래핑 내용과 새로 스크래핑한 내용이 겹치게 된다. 이를 해결하기 위해 DB단에서 데이터를 제거하는게 가장 빠르고 편리했다.

 

게다가, 이것 때문에 mongodb에서 mariadb로 전환하기도 했다. mongodb에서는 중복제거가 매우 어렵다... 내가 너무 잘 몰라서 그런 것일수도 있는데, 인덱스를 사용하거나, 맵리듀스를 사용하거나... 아무튼 내가 원하는 대로는 결과가 잘 나오지 않았다.

 

mongodb에서 mariadb로 전환하는데는 조금 시간이 걸렸지만, 다행히 잘 적응하고 전환도 잘 했다. 그리고 쉽게 중복제거를 찾을 수 있었다.

 

DELETE a FROM 중복검사할테이블 a, 중복검사할테이블 b WHERE a.id > b.id AND a.열이름= b.열이름;

 

간단하게 말하면 같은 테이블을 a, b로 명명하고 "열"을 기준으로 같으면 제거하는 방식이다.

중간에 a.id > b.id 는 아마 id열로 뭔가 하는것같은데 이건 이해가 잘 안된다..ㅠㅠ 아무튼 id열도 있어야 한다.

 

파이썬에서 최종적으로 중복제거하는 구문은 다음과 같다.

 

conn = pymysql.connect(host="XXXXXXXXXXX", port=XXXXX, user="root", password="root", db="XXXXXe", charset="utf8")
curs = conn.cursor()

removeDuptitle = "DELETE a FROM bg_product_data a, bg_product_data b WHERE a.id > b.id AND a.title = b.title;"
curs.execute(removeDuptitle)
removeDupLink = "DELETE a FROM bg_product_data a, bg_product_data b WHERE a.id > b.id AND a.link = b.link;"
curs.execute(removeDupLink)

conn.commit()
conn.close()

 

그리고, 각 사이트에서 품절되거나, 뭔가 별로인 deal 정보라서 글을 내리는 경우, 짧게 "삭제합니다", "펑" 뭐 이런식으로 넣어놨는데, 이런것까지 스크래핑되지 않게 하기 위해 (정확히 말하면 스크래핑은 했지만 db에서는 삭제하기 위해) 아래와 같이 title부분의 글자수가 상당히 짧으면, 삭제하는 구문을 넣어봤다.

 

conn = pymysql.connect(host="XXXXXXXXXXX", port=XXXXX, user="root", password="root", db="XXXXXe", charset="utf8")
curs = conn.cursor()

removeShortTitle = "delete from bg_product_data where char_length(title) <= 8;"
curs.execute(removeShortTitle)

conn.commit()
conn.close()

이렇게 하면, title이 8글자 이하면 아예 삭제해버린다. 어차피 게시글이 8글자도 안되는 게시물은 없을 것이다... 아마.! ㅋㅋ

스파르타에서는 구글 웹폰트(https://fonts.google.com/)만 배웠지만, 구글 웹폰트는 한글쪽은 종류가 많이 없어서 다른 폰트들을 찾고 있었다. 가능하면 웹폰트를 사용하고 싶어서 여러가지 찾아보다가 괜찮은 사이트를 발견했다.

 

눈누 웹폰트 : https://noonnu.cc/

 

상업적 이용 가능한 무료 한글 폰트 모음 사이트 눈누

상업적으로 이용할 수 있는 무료 한글 폰트를 모아 놓은 사이트 눈누

noonnu.cc

이 사이트는 여러 업체에서 공개한 무료폰트들을 하나로 모아놓은 것 같은 사이트이다.

 

 

또한 각각의 폰트가 저작권의 어느 범위까지 사용가능한지도 간편하게 알려준다.

 

꽤 종류도 많고 사용법도 편리하다. 적용 방법은 다음과 같다.

 

<sytle> 태그에 아래와 같이 적용하고, (아래 내용은 각 폰트에 들어가보면 확인이 가능하다)

@font-face {
    font-family: 'TmoneyRoundWindRegular';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-07@1.0/TmoneyRoundWindRegular.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

 

예를들어, body에 모두 적용되는 CSS 또는 내가 원하는 곳만 적용할 CSS 내용에 해당 font-family 이름을 명시한다.

body {
    margin: 0;
    padding: 0;
    position: relative;
    height: 100%;
    background: white;
    font-family: 'TmoneyRoundWindRegular';  // 바로 이 부분!
    font-weight: 300;
    font-size: 17px;
    color: #777;
}

그러면 해당 폰트가 잘 적용된 것을 확인할 수 있다.

내가 만든 사이트는 일단 기본적으로 매우 밋밋하다. 어떤 효과를 줘야 할까 생각하다가, 마우스 오버 효과를 주면 어떨까 하는 생각이 들었다. 일단 기본은 이렇다.

 

 

여기 보면 아래에 제품 링크들에 대해 커서를 갖다대면 색깔을 바꾸도록 하려고 한다. 다행히 이 기능은 css에 hover라는 기능으로 구현되어 있었다. 아래와 같이 간단하게 구현이 가능하다.

 

// <head>태그의 <sytle>안에 아래와 같은 내용을 삽입한다.

.container { 
    width: 800px; 
    height: 70px; 
} 

.container:hover { 
    background: #DDECEE; 
}

// 이제 정의된 container 클래스를, 적용하고 싶은 부분에 해당 클래스를 명시해준다.

<ul class="guiz-awards-row container">
<li class="guiz-awards-title"><a href="${link}" target="_blank">">${title}</a>
<div class="guiz-awards-subtitle">${source} / ${date}</div>
</li>
</ul>

 

width와 height가 색깔이 변하는 범위(크기)를 설정하는 것이고, background가 바뀔 색깔을 정의하는 것이다.

 

 

결과는 다음과 같다.

 

마우스 커서를 갖다대면 위와 같은 색으로 변한다. 깔끔하니 좋다.

내가 만드는 사이트는 데스크탑에서 보는것도 좋지만, 모바일에서도 보면 좋을 것 같아서 모바일 및 데스크탑 겸용으로 만들고 싶었다. 그리고 그렇게 하려면 매우 복잡할 것 같았다. 그런데 생각보다 매우 쉬웠다.

 

viewport라는 메타태그 옵션이 있다. 뷰포트는 실제로 페이지가 보이는 영역 "자체"를 의미한다.

이 뷰포트의 설정을 어떻게 해라 라고 지정하면 그 설정대로 페이지가 보이게 된다.

 

예를들어 아래와 같이 설정한다면? 

<meta name="viewport" content="width=300"   ... (중략)

이런식으로 해버리면, 보이는 부분의 너비를 300에 맞춰버린다. 이런 값들을 모든 디바이스에 맞출 수는 없는 것이다.

따라서 다음과 같은 옵션이 필요하다.

 

width=device-width : 디바이스에 맞춰 너비를 조정하겠다는 것.

initial-scale=1 사이트 열리자 마자, 초기 배율을 100%로 한다는것. 1.5 150%

 

결론적으로, 다음과 같은 코드를 <head> 태그에 삽입하면, 해당 페이지는 어떤 장치에서는 크기에 맞춰서 보여주게 된다.

<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

 

# 참고 : 추가 옵션들

minimum-scale   터치로 축소할때 최대

maximum-scale   터치로 확대할 때 최대

user-scalable=no   확대축소를 못하게 함. 즉 위에 maximum등은 yes를 해둬야함.

 

 

# 참고 : 가상 모바일기기 에뮬레이터

PC에 설치해서 사용하는 프로그램이며, 가상으로 모바일기기의 크기별로 화면을 볼 수 있다.

아래와 같은 식.

 

스크래핑과 데이터베이스를 다 끝내고, 이제 API를 만들려고 한다. 그전에 이 특가 정보 사이트는 게시판으로 만들지 않으려고 한다. 댓글도 필요없고, 안에 게시글을 넣을 필요도 없고, 해당 링크로 넘어가기만 하면 되기 때문이다.

 

따라서 API로 DB정보를 모두 가져오고 가져온 내용을 웹에 표 형태로 붙여넣기만 하면 된다. 그래서, 날짜, 소스, 링크, 썸네일, 제목 등을 API를 통해 제공할 수 있게 flask로 API를 만들려고 했다. 그런데 이전에 배웠던 방식이라면 mariadb를 사용하여 API에 넣기 편하게 바로 딕셔너리로 데이터를 받았을 텐데, mariadb로 바꿨으니 DB 검색결과를 딕셔너리 형태로 변환해야 한다ㅠ 거참.. 뭐 하나 바꾸니 여러모로 피곤해진다.

 

그래도 이번엔 그렇게 어렵지는 않았다. 이전에 배운 내용들과, DB 관련해서 조금 찾아보니 쉽게 만들 수 있었다.

@app.route('/get_spdb')
def get_spdb():

    # DB 접속
	conn = pymysql.connect(host="XXXXXXXXXXX", port=XXXXX, user="root", password="root", db="XXXXXe", charset="utf8")
    # 커서를 사용
    curs = conn.cursor()
    # product_data 데이터베이스에서 모든 데이터를 가져오며, date 기준 내림차순으로 정렬한다.
    sql = "SELECT * FROM product_data ORDER BY date DESC;"
    ## sql 문 실행
    curs.execute(sql)
    ## 데이터 가져오기
    datas = list(curs.fetchall())
    # DB 접속 종료
    conn.close()

    # 최종 결과물을 담을 리스트 변수를 선언한다.
    result = []

    # DB 검색 결과를 딕셔너리 형태로 재생성한다.
    for data in datas:
        parsed_data = {'id' : data[0], 'title': data[1], 'link': data[2], 'date': data[3], 'source': data[4], 'thumbnail' : data[5]}
        result.append(parsed_data)

    #json화하여 결과를 리턴한다.
    return jsonify({'result': 'success', 'product_data': result})

일단 DB에서 SELECT 문으로 원하는 데이터를 가져오고, 그 데이터를 datas 라는 변수에 리스트 형태로 담았다. (리스트로 하지 않으면, 튜플 형태로 저장된다) 그리고 최종 결과물을 담을 변수를 result라는 이름으로 리스트 형태로 선언해두었다.

 

이제 for문으로, datas에 있는 리스트 요소(data)를 1개 (여기에는 id, title, link, date, source, thumbnail 이 있다) 씩 꺼내 0부터 5까지 총 6개의 내용을 가져오고, 그 내용들을 직접 딕셔너리형태로 변환했다. 그리고 그걸 json화 하여 출력한다.

 

결과는 다음과 같이 예쁘게 잘 나온다.

 

이제 이걸 html에 jquery를 통해 바로 붙여넣을 수 있다. 간단히 프로토타입으로 html을 만들어서 붙여 넣어 보았다.

 

이렇게 직접 화면으로 보니, 프로젝트가 꽤 진척되었다는게 느껴진다. 하지만 아직도 할 것이 많다...ㅠㅠ

각 특가 사이트에서 게시글을 크롤링하면, 크롤링 주기에 따라 데이터들이 겹칠 수 있다. 예를들어, 퀘이사존의 특가 사이트에서 1,2페이지를 크롤링하고 게시글이 20개가 크롤링 되었다고 치자. 처음엔 당연히 문제가 없다. 근데 1시간마다 크롤링을 한다면?

 

1시간마다 해당 사이트의 글이 얼마나 올라올지 알 수 없는데 5개가 추가로 올라왔다고 가정해보자. 내 크롤링은 20개를 크롤링하는데, 5개가 추가되었으므로 남은 15개는 이전에 크롤링했던 데이터이다. 즉 중복이 생긴다! 크롤링 할 때마다 이러한 중복 데이터를 제거해야 하는데, sql 계열의 데이터베이스에서는 쉽지만 nosql 계열 데이터베이스는... 뭔가 쉽지 않은 것 같다.

 

mongodb에서 여러가지를 찾아봤는데, 아예 잘 모르는 db라서 그런지... 감도 잘 안잡히는데 그래도 시간을 써가며 찾아본 결과 distinct를 쓰거나, 인덱스를 만들거나, 맵리듀스를 사용하거나..... 뭔가 방법은 많았는데 직접 해보니 잘 되지 않았다. 십지어 맵리듀스는 이런데 쓰는게 아닌 빅데이터에 쓰는 고급 기술이었다. 어렵다. ㅜㅜ

 

아무튼, 토요일 하루종일 이걸 붙들고 씨름을 했는데 도저히 답이 나오지 않았다. 결국 mariadb로 바꿀 수밖에 없었다. 류모씨는 나에게 패배자라고 했지만 도저히 답이 없다ㅠ

 

SQL 계열의 DB는 확실히 이전에 조금 해보았기 때문에 어렵지 않았다. 또한 쉽게 구글링하여 중복 제거를 할 수 있는 SQL 구문을 찾을 수 있었다. python과 연동하여 작동하는 구문을 찾는것은 따로 유투브에서 강의를 들어서 찾을 수 있었다. 이렇게 편안할 수가 있나.

 

 

아무튼 만든 코드는 다음과 같다.

## 크롤링한 데이터를 DB에 저장하기

conn = pymysql.connect(host="XXXXXXXXXXX", port=XXXXX, user="root", password="root", db="XXXXXe", charset="utf8")
curs = conn.cursor()

sql = "insert into product_data(title, link, date, source) values(%s, %s, %s, %s)"
curs.execute(sql, (finalDealTitle, finalDealLink, finalDealDate, '퀘이사존'))

conn.commit()
conn.close()


## 중복 데이터 정리

conn = pymysql.connect(host="XXXXXXXXXXX", port=XXXXX, user="root", password="root", db="XXXXXe", charset="utf8")
curs = conn.cursor()

removeDuptitle= "DELETE a FROM product_data a, product_data b WHERE a.id > b.id AND a.title = b.title;"
curs.execute(removeDuptitle)
removeDupLink = "DELETE a FROM product_data a, product_data b WHERE a.id > b.id AND a.link = b.link;"
curs.execute(removeDupLink)

conn.commit()
conn.close()

 

이제 크롤링과 DB는 거의 끝나간다. due date는 약 열흘정도 남았다. 좀만 더 하면 되겠지?

 

+ Recent posts