three.js start project 코드

자바스크립트의 모듈 기반으로 three.js를 도입하기 위한 뼈대입니다. App이라는 클래스를 하나 만들 것이고 이 App 클래스를 통해 three.js의 초기화 및 정육면체를 회전시켜 보겠습니다.

먼저 HTML 코드는 다음과 같습니다.

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="style.css">
    <script type="module" src="app.js">
</head>
<body>
</body>
</html> 

DOM은 없고 css와 js 파일만을 참조하고 있습니다. DOM은 js 파일인 app.js에서 동적으로 추가해 줍니다. 먼저 css에 대한 style.css 파일의 코드는 다음과 같습니다.

* {
    outline: none;
    padding: 0;
    margin: 0;
}

3차원 출력이 웹 브라이저 전체 화면을 차지하는 경우라면 html과 css는 변경되지 않으며 모든 작업은 js 파일에서 처리됩니다. 이제 app.js 파일을 살펴보면 다음과 같습니다.

import * as THREE from 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r126/three.module.min.js'

class App {
    constructor() {
        this.initialize();
        this.render();
    }

    initialize() {}
    update() {}
    render() {}
    resize() {}
}

window.onload = function() {
    new App()
}

App 클래스는 4개의 매서드를 갖습니다. initialize는 WebGL을 위한 객체 초기화 및 Mesh, Camera, 화면 크기 변경에 따른 이벤트 등록을 담당하는데 코드는 다음과 같습니다.

initialize() {
    this.domWebGL = document.createElement('div');
    document.body.appendChild(this.domWebGL);

    let scene = new THREE.Scene();
    let renderer = new THREE.WebGLRenderer();

    renderer.setClearColor(0x000000, 1.0);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);

    this.domWebGL.appendChild(renderer.domElement);  
    window.onresize = this.resize.bind(this); 

    let cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
    let cubeMaterial = new THREE.MeshNormalMaterial();
    let cube = new THREE.Mesh(cubeGeometry, cubeMaterial);

    cube.position.x = 0;
    cube.position.y = 0;
    cube.position.z = 0;

    scene.add(cube);

    let camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 100);
    camera.position.x = 3;
    camera.position.y = 3;
    camera.position.z = 3;
    camera.lookAt(scene.position);
    scene.add(camera);

    this.camera = camera;
    this.renderer = renderer;
    this.scene = scene;
    this.cube = cube;
}

render 매서드는 실제 3차원 화면을 렌더링을 수행하며 애니메이션 처리를 위해 내부적으로 update를 호출합니다. 이 두 매서드는 다음과 같습니다.

update() {
    this.cube.rotation.x += 0.01;
    this.cube.rotation.y += 0.02;
}

render() {
    this.update();
    this.renderer.render(this.scene, this.camera);
    requestAnimationFrame(this.render.bind(this));

}

웹 브라우저의 크기가 변경될 때 3차원 렌더링 되는 화면 크기도 그에 맞춰 변경해줘야 합니다. 이를 위한 resize 매서드는 다음과 같습니다.

resize() {
    let camera = this.camera;
    let renderer = this.renderer;
    let scene = this.scene;

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.render(scene, camera);
}

실행해 보면 아래처럼 정육면체의 매쉬가 회전하는 화면을 볼 수 있습니다.

JavaScript로 웹 브라우저의 콘솔 로그(Console Log) 스타일 지정하기

자바스크립트로 웹 앱을 개발할 때 디버깅 방법 중 콘솔에 필요한 로그를 출력하는 것은 매우 유용합니다. 이런 유용함에 더해 원하는 로그에 대한 문자열의 색상 등과 같은 스타일을 지정할 수도 있는데요. 아래의 코드가 바로 그 예시입니다.

console.log(
    '%cHello %cWorld. %cMy Name is %cDip2K.', 
    'color: red', 
    'color: yellow',
    'color: gray',
    'color: white;font-weight: 900'
)

출력 결과는 다음과 같습니다.

backdrop-filter로 반투명 효과 주기

유리 효과는 현대적인 UI를 개발하는데 사용하는 효과 중 대표적인 기술 중 하나입니다. 아래와 같은 결과를 얻고자 합니다.

먼저 DOM 요소입니다.

다음은 스타일 요소입니다.

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}

body {
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
}

.container {
    width: 700px;
    height: 250px;
    position: relative;
}

div div {
    position: absolute;
    display: inline-block;
}

.left-circle {
    left: 0px;
    width: 250px;
    height: 250px;
    border-radius: 50%;
    background: linear-gradient(#f9d924, #ff2c24)
}

.right-circle {
    right: 0px;
    width: 250px;
    height: 250px;
    border-radius: 50%;
    background: linear-gradient(#01d6ff, #0f24f9)
}

.center-box {
    left: 50%;
    transform: translateX(-50%);
    width: 350px;
    height: 250px;
    border-radius: 45px;
    z-index: 1;
    backdrop-filter: blur(10px);
    border: 1px solid rgba(0,0,0,0.1);
}

clip-path로 원하는 영역만 표현하기

원하는 형태의 폴리곤 영역만 표시하고자 할때 clip-path를 사용합니다. 예를 들어 아래와 같은 결과를 보면..

위의 결과는 2개의 div 요소로 표현되는데, 아래와 같습니다.

GIS DEVELOPER
GIS DEVELOPER

스타일은 다음과 같습니다.

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}

div {
    position: fixed;
    background-color: black;
    width: 100%;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 5em;
    font-weight: 700;
}

div:nth-child(1) {
    color: white;
}

div:nth-child(2) {
    color: black;
    clip-path: circle(1.4em at center center);
    background-color: white;
}

clip-path로 지정된 도형의 형태로 잘려진 것만 표시됩니다.

동적 웹 페이지에서 데이터 크롤링

이 글은 동적 웹 페이지에서 주소 데이터를 취득하기 위한 파이썬 코드를 설명합니다.

동적 페이지라함은 웹 페이지에서 사용자의 클릭 등과 같은 조작을 통해 AJAX 호출이 발생하여 그 결과가 페이지의 일부분에 반영되어 변경되는 것을 의미합니다. 예를 들어 아래의 커피빈 페이지에서 매장 정보를 확인하기 위해 사용자는 다음과 같은 절차를 통해 매장의 이름과 주소 그리고 전화번호를 확인할 수 있습니다.

위의 그림을 글로 설명하면, 먼저 사용자는 정보를 파악할 매장에 대한 “자세히 보기” 버튼을 클릭하면 웹브라우저가 연결된 javascript 코드를 실행하여 해당 매장의 상세 정보가 동일한 페이지에 동적으로 표시됩니다.

이러한 사용자의 조작을 자동화하기 위해서는 Selenium 라이브러리를 사용합니다. 이 라이브러리는 웹 브라우저를 코드를 통해 제어하기 위한 라이브러리이며, 내부적으로는 Web Driver라는 프로그램을 사용합니다. 아울러 HTML 페이지를 해석하여 원하는 정보를 추출할 수 있는 BeautifulSoup 라이브러리도 필요합니다.

BeautifulSoup는 다음과 같은 pip 명령을 통해 설치할 수 있습니다.

pip install beautifulsoup4

Selenium은 다음과 같은 pip 명령으로 설치할 수 있습니다.

pip install selenium

언급했듯이 selenium은 Web Driver가 필요한데 Windows의 Chrome에 대한 드라이버는 현재 시점에서 다음 url을 통해 다운로드 받을 수 있습니다.

https://chromedriver.storage.googleapis.com/index.html?path=88.0.4324.96/

다운로드 받은 파일명은 chromedriver_win32.zip이며 압축을 풀면 chromedriver.exe가 생성됩니다.

이제 준비가 완료되었으므로 코드를 살펴보겠습니다. 먼저 필요한 라이브러리를 import 합니다.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from bs4 import BeautifulSoup
import pandas as pd

pandas는 크롤링한 데이터를 csv 파일로 저장하기 위해 사용됩니다.

다음으로 Web Driver에 대한 객체를 생성합니다.

wd = webdriver.Chrome('./WebDriver/chromedriver.exe')

이제 다음의 코드로 사용자의 클릭을 통해 호출되는 자바스크립트 함수를 Web Driver를 통해 자동화합니다.

result = []
for i in range(1, 100):
    try:
        wd.get('https://www.coffeebeankorea.com/store/store.asp')
        wd.execute_script('storePop2(%d)' %i)

        element = WebDriverWait(wd, 2).until(
            expected_conditions.presence_of_element_located((By.CLASS_NAME, "store_table")))

        html = wd.page_source
        soup = BeautifulSoup(html, 'html.parser')

        store_name = soup.select('.store_txt > h2')[0].string

        store_info = soup.select('table.store_table > tbody > tr > td')

        store_address = list(store_info[2])[0]
        store_phone = store_info[3].string

        result.append([store_name, store_address,  store_phone])

        print(f'{i} : {store_name} {store_address} {store_phone}')
    except:
        print(f'{i} : not exist')
        continue

wd.quit()

df = pd.DataFrame(result, columns = ('name', 'address', 'phone'))
df.to_csv('./CoffeeBean.csv', encoding='utf-8', mode='w', index=False)

print('Completed..')

위의 코드는 1-99번까지의 인자값에 대한 자바스크립트 함수(storePop2)를 호출하며 호출된 결과인 html 문자열에서 필요한 정보를 추출하여 CSV 파일로 저장하는 코드입니다. 실행 결과로 저장되는 CSV 파일의 일부 내용은 다음과 같습니다.

name,address,phone
학동역 DT점,서울시 강남구 학동로 211 1층 ,02-3444-9973
수서점,서울시 강남구 광평로 280 수서동 724호 ,02-3412-2326
차병원점,서울시 강남구 논현로 566 강남차병원1층 ,02-538-7615
강남대로점,서울시 서초구 강남대로 369 1층 ,02-588-5778
메가박스점,서울 강남구 삼성동 159 코엑스몰 지하2층 ,02-6002-3320
.
.
.

이렇게 만들어진 파일을 통해 실제 공간 좌표로 변환하기 위한 지오코딩 툴은 Geocoder-Xr를 사용하시는 것을 추천드립니다.

끝으로 이 글은 “데이터 과학 기반의 파이썬 빅데이터 분석(저자 이지영)”이라는 서적을 원저자의 허락 하에 참조하여 작성하였습니다.

이 책은 빅데이터에 대한 깊이 있는 이해를 돕는 이론으로 시작해 빅데이터를 수집하는 구체적인 방법과 이렇게 수집된 데이터를 파이썬 언어를 이용하여 분석하고 분석 결과를 효과적으로 시각화하는 방법을 구체적으로 설명하고 있습니다. 크롤링에 대한 좋은 예제를 이 블로그를 통해 공개할 수 있도록 허락해주신 이 책의 저자이신 이지영님에게 감사드립니다.