* postgresql 특징 : opensource, 국내 이용 느는 추세

 

* 사용법 :

- 설치 폴더(pg_hba.conf) 에서 외부 접속 Address 0.0.0.0/0 설정

- 서버 start ( CLI - bin folder 경로에서 : pg_ctl -D ../data start )

- 서버 종료 ( CLI - bin folder 경로에서 : pg_ctl -D ../data stop )

- creaate user , owner

- database 생성 owner 설정

- psycopg2 모듈을 이용해 python connection 객체 획득

 

import psycopg2 as pg
from faker import Faker

faker = Faker(locale=['ko_KR'])

conn = pg.connect(host="10.10.20.99", dbname="db_gwang", user="postgres", password="", port="5432")
# conn = pg.connect(host="127.0.0.1", dbname="db_gwang", user="postgres", password="", port="5432")
c = conn.cursor()

for i in range(1000):
    c.execute("insert into tb_sample(id, name) values (%s, %s)", (i, faker.name(), ))
conn.commit()

conn.close()

를 통해서 짝궁 db에 접속하여 가짜 이름 1000줄을 저장시켰다.

 

conn = pg.connect(host="10.10.20.99", dbname="db_gwang", user="postgres", password="", port="5432")
c = conn.cursor()

c.execute("select * from tb_sample")
result = c.fetchall()
for row in result:
    print(row)

conn.close()
(0, '김보람')
(1, '백서영')
(2, '권민준')
(3, '김진호')
(4, '송재호')
(5, '김지후')
(6, '엄건우')
(7, '김수민')
(8, '김수빈')
(9, '배영일')
(10, '곽수민')
(11, '이지혜')
(12, '김예준')
(13, '엄승현')
(14, '이정호')
(15, '김옥자')
(16, '김상훈')
(17, '서영순')
(18, '박현지')
(19, '홍지훈')
(20, '장수빈')
(21, '이경수')
....

감사합니다.

 

어제의 다짐에 이어, 오늘은 OOP 능력 향상을 위해 유명 파이썬 모듈의 소스를 분석해보기로 했다.

 

'아만보'라는 과거 유행어가 있다.

 

"아는만큼 보인다"의 준말인데, 수학과 과학이 연관되어 있지 않는 오픈 소스 중에 그나마 보일 게 많은

 

"Requests"라는 유명 파이썬 오픈소스를 GitHub에서 Clone하여 읽어보기로 했다.

 

출처는 다음과 같다.

 

 

https://github.com/psf/requests

 

GitHub - psf/requests: A simple, yet elegant, HTTP library.

A simple, yet elegant, HTTP library. Contribute to psf/requests development by creating an account on GitHub.

github.com

소스 파일, 테스트 패키지들이 깔끔하다.

 

※ 관전포인트 : 사실 왜 __init__.py가 있는지 몰랐는데, 오늘 이해했다.

(디렉토리 밑에 __init__.py 파일이 있으면 pycharm에서 패키지로 인식함. 패키지를 쓰면, 서로 다른 namespace를 사용하여 변수명이 겹치지 않고, code 재사용성, 공동작업 및 배포등이 편해지는 장점이 있음)

 

메인 패키지의 init을 보면 import만 한 페이지 이상이다. 또한, try, except문을 활용하여 import 의 예외처리를 하는 것이 인상적이다.

 

import warnings

import urllib3

from .exceptions import RequestsDependencyWarning

try:
    from charset_normalizer import __version__ as charset_normalizer_version
except ImportError:
    charset_normalizer_version = None

try:
    from chardet import __version__ as chardet_version
except ImportError:
    chardet_version = None


...

※ 관전포인트 : import error를 처리하는 방법도 우아하다.

 

 

SOCKSProxyManager 라는 함수는 본문에 사용되는 함수인데, imort할 때 error가 발생할 경우 오버라이딩되게 하였다. 그러니, 해당 함수가 동작할 때만 에러를 일으키게 되어 코드 내부에서 매번 except처리를 하지 않아도 되었다.

 

※ 관전포인트 : 추상 클래스를 쓸 필요가 없다. NotImplementedError로 처리

 

 
※ 관전포인트 : attribute를 미리 정리 해놓기

 

 

for ~ in 구문을 통해 __dict__ 보다 우아하게 표현할 수 있다.

 

※ 관전포인트 : init의 파라미터를 멀티라인으로..

 

 

※ 관전포인트 : 같은 패키지 내에서는 from . import [ClassName]

 

 

※ 관전포인트 : 다(多)기능의 함수를 편리하게 세분화? 시킬 수 있다.

 

※ 관전포인트 : all()의 활용방법

 

※ 관전포인트 : 동적인 attr 추가 (request header에 추가하는 것)

 

※ 관전포인트 : 파라미터에서 인자 뽑아쓰기

※ 관전포인트 : iterable 삭제하는 방법

 

※ 관전포인트 : 동적인 인스턴스 생성

※ 관전포인트 : 파이썬 에러 클래스 만들기 + 상속

 

※ 관전포인트 : 함수 하나로 여러 동작 시키기

 

 

더 분석할 게 많지만 시간관계상 오늘은 여기까지 하고 다음번에 또 계속 분석하겠습니다.

 

감사합니다.

 

 

교육과정 시퀀스 중에 발생한 어떤 이벤트로 인해 학습일지 업로드 시간이 변경되었다.

 

아침에 매번 체크하는 것이 지치기 때문에 크롤링 프로그램을 구동시켜 자동으로 체크하게끔 설계해본다.

 

그동안 제작을 미루고 있었는데, 교육 기간이 짧아지면서 늦게 만들수록 효용가치는 더 떨어진다.

 

selenium 을 통해 네이버 로그인을 통과하기엔 현재 내 기술력으로 불가능하기 때문에 업로드는 아직 무리라 생각되지만, 업로드 체크정도는 만들 수 있다.

 

그리하여 오전시간 틈틈히 제작했고 전에 실습해본 것이 있어 그다지 오래 걸리지 않았다.

 

# checker_daily.py
import datetime
import os
import time

from selenium import webdriver
from selenium.webdriver.common.by import By

from class_student import StudentBoard

driver = webdriver.Chrome()
driver.get("https://cafe.naver.com/startdev")

class_div = driver.find_element(by=By.ID, value="group579")

boards = list()

banner_tags = class_div.find_elements(by=By.TAG_NAME, value='li')
for li in banner_tags:
    a_href_link_WebElement = li.find_element(by=By.TAG_NAME, value='a')
    name = a_href_link_WebElement.text
    link = a_href_link_WebElement.get_attribute('href')

    if len(name) == 3 and name not in ['김진영']:
        boards.append(StudentBoard(name, link, driver))

result_list = list()
head = ["작성자", "마지막 글제목", "작성시간", "금일 업로드 여부"]

for board in boards:
    result_list.append(board.has_uploaded_daily())
    # time.sleep(3)

today_date = datetime.datetime.now().strftime("%m-%d")
with open(f'check_result/daily_check_{today_date}.csv', "w", encoding='utf-8') as file:
    for row in result_list:
        file.writelines(row + '\n')
# class_student.py
import re

import selenium.webdriver.chrome.webdriver
from selenium.webdriver.common.by import By


class StudentBoard:
    def __init__(self, name, link, driver):
        assert isinstance(driver, selenium.webdriver.chrome.webdriver.WebDriver)
        self.name = name
        self.link = link
        self.driver = driver
        self.is_uploaded_today = None

    def has_uploaded_daily(self):
        self.driver.get(self.link)
        self.driver.implicitly_wait(3)

        inner_frame = self.driver.find_element(by=By.ID, value='cafe_main')
        self.driver.switch_to.frame(inner_frame)

        board = self.driver.find_element(by=By.CSS_SELECTOR, value="#main-area > div:nth-child(4)")
        top_row = board.find_element(by=By.CSS_SELECTOR,
                                     value='#main-area > div:nth-child(4) > table > tbody > tr:nth-child(1)')
        post_title = top_row.find_element(by=By.CLASS_NAME, value='article').text
        if ',' in post_title:
            post_title = "\"" + post_title + "\""
        post_time = top_row.find_element(by=By.CLASS_NAME, value='td_date').text

        if len(post_time) == 5:
            self.is_uploaded_today = True
        else:
            self.is_uploaded_today = False

        return f"{self.name},{post_title},{post_time},{self.is_uploaded_today}"

파일은 csv로 저장하게끔 만들었고 결과물은 다음과 같다.

 

 

 

 

 

그다음 exe 파일로 변환하고..

 

 

 

그다음 윈도우 배치 프로그램을 통해 09:00AM에 자동으로 켜지게끔 작업스케줄러 설정

 

 

다음과 같이 윈도우 bat 파일을 만들어준다

 

 
 

 

 

그리고 컴퓨터 관리에 들어가서..

 

 
 

 

 
 

 

아까 만든 bat 파일을 맵핑한다

그러면 다음과 같이 스케줄러에 등록된 것을 확인할 수 있다.

 

내일부터는 변경된 폼으로 찾아뵙겠습니다.

 

감사합니다.

C:\Users\KDT107\Desktop\learn_cpp\init_folder\cpp_source>g++ -o RacingCar.cpp Car.cpp

 

틈틈히 C++ 를 공부해놔야할 것 같아 정리하는 문서입니다.

 

교재는 『윤성우의 C++ 열혈프로그래밍』를 사용하였고, 예제를 통해 실습 진행해보았습니다.

 

환경설정은 기존에 C언어를 공부하면서 설치했던 MinGw를 이용했습니다.

(MinGw에 C언어, C++언어 컴파일러 둘 다 탑재하고 있음)

 

 

 

기존에 해놓은게 있어서, 환경변수를 따로 건드린 것은 없었습니다.

 

또한, vscode 를 오랜만에 쓰려니, 단축키가 계속 헷갈려서 intellij keymap을 extension으로 설치하였습니다.

 

(Pycharm의 제작회사가 Intellij임)

 

 

 

살짝 다른 부분들이 있지만, 그래도 대부분(70~80%) 정도는 지원해주는 것 같아 큰 불편함 없이 적응했습니다.

 

포인터와 구조체 사용방법은 아직 완벽하게는 아니지만, 어느정도 숙지하고 책의 예제를 따라쳐보면서, 익숙해지는 시간을 가졌습니다.

 

C언어에서는 구조체에서 Attribute만 사용할 수 있는 불완전한 상태였는데,

 

C++로 넘어오면서, class가 사용가능해지고, 함수도 선언가능해졌습니다.

 

또한, Python에서는 "from somefilename import ClassName" 면 쉽게 다른 파일의 클래스를 사용할 수 있었지만,

 

C++로 들어오면서는 헤더파일의 이해와, #ifndef, #define, #endif, namespace의 개념에 대해 이해가 필요했습니다.

 

책에서 선언한 Car class 입니다.

 

/* Car.h */

#ifndef __CAR_H__
#define __CAR_H__

namespace CAR_CONST {
enum {
    ID_LEN = 20,
    MAX_SPD = 200,
    FUEL_STEP = 2,
    ACC_STEP = 10,
    BRK_STEP = 10
};
}

class Car {
   private:
    char gamerID[CAR_CONST::ID_LEN];
    int fuelGuage;
    int curSpeed;

   public:
    void InitMembers(char* ID, int fuel);
    void ShowCarState();
    void Accel();
    void Break();
};

#endif

 

/* Car.cpp */
#include "Car.h"

#include <cstring>
#include <iostream>
using namespace std;

void Car::InitMembers(char* ID, int fuel) {
    strcpy(gamerID, ID);
    fuelGuage = fuel;
    curSpeed = 0;
}

void Car::ShowCarState() {
    cout << "소유자ID: " << gamerID << endl;
    cout << "연료량: " << fuelGuage << "%" << endl;
    cout << "현재 속도: " << curSpeed << "km/s" << endl;
}

void Car::Accel() {
    if (fuelGuage <= 0) {
        return;
    } else {
        fuelGuage -= CAR_CONST::FUEL_STEP;
    }

    if (curSpeed + CAR_CONST::ACC_STEP >= CAR_CONST::MAX_SPD) {
        curSpeed = CAR_CONST::MAX_SPD;
        return;
    }
    curSpeed += CAR_CONST::ACC_STEP;
}

void Car::Break() {
    if (curSpeed < CAR_CONST::BRK_STEP) {
        curSpeed = 0;
        return;
    }
    curSpeed -= CAR_CONST::BRK_STEP;
}
/* RacingMain.cpp */
#include "Car.h"

int main() {
    Car run99;
    run99.InitMembers("run99", 100);
    run99.Accel();
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();
    return 0;
}

 

이렇게 작성하고 Build 를 실행했더니 다음과 같은 오류를 만났다.

Car.cpp 에서 세부적으로 선언한 함수들이 다 undefined 처리가 되었다.

 

빌드를 시작하는 중...
"C:\Program Files\mingw64\bin\g++.exe" -fdiagnostics-color=always -g C:\Users\KDT107\Desktop\learn_cpp\init_folder\cpp_source\RacingMain.cpp -o C:\Users\KDT107\Desktop\learn_cpp\init_folder\cpp_source\rooster.exe
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\KDT107\AppData\Local\Temp\ccrwiMAk.o: in function `main':
C:/Users/KDT107/Desktop/learn_cpp/init_folder/cpp_source/RacingMain.cpp:6: undefined reference to `Car::InitMembers(char const*, int)'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/Users/KDT107/Desktop/learn_cpp/init_folder/cpp_source/RacingMain.cpp:7: undefined reference to `Car::Accel()'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/Users/KDT107/Desktop/learn_cpp/init_folder/cpp_source/RacingMain.cpp:8: undefined reference to `Car::Accel()'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/Users/KDT107/Desktop/learn_cpp/init_folder/cpp_source/RacingMain.cpp:9: undefined reference to `Car::Accel()'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/Users/KDT107/Desktop/learn_cpp/init_folder/cpp_source/RacingMain.cpp:10: undefined reference to `Car::ShowCarState()'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/Users/KDT107/Desktop/learn_cpp/init_folder/cpp_source/RacingMain.cpp:11: undefined reference to `Car::Break()'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/Users/KDT107/Desktop/learn_cpp/init_folder/cpp_source/RacingMain.cpp:12: undefined reference to `Car::ShowCarState()'
collect2.exe: error: ld returned 1 exit status

빌드가 완료되었지만, 오류가 발생했습니다.

원인을 찾아보니 g++로 컴파일 할 때 다음과 같이 터미널에 작성해줘야한다는 것이다.

C:\Users\KDT107\Desktop\learn_cpp\init_folder\cpp_source>g++ -o Car.cpp RacingCar.cpp

그런데 실수로 순서를 바꿔서 넣었더니..

C:\Users\KDT107\Desktop\learn_cpp\init_folder\cpp_source>g++ -o Car.cpp RacingMain.cpp
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\KDT107\AppData\Local\Temp\ccao3DhY.o:RacingMain.cpp:(.text+0x22): undefined reference to `Car::InitMembers(char const*, int)'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\KDT107\AppData\Local\Temp\ccao3DhY.o:RacingMain.cpp:(.text+0x2e): undefined reference to `Car::Accel()'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\KDT107\AppData\Local\Temp\ccao3DhY.o:RacingMain.cpp:(.text+0x3a): undefined reference to `Car::Accel()'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\KDT107\AppData\Local\Temp\ccao3DhY.o:RacingMain.cpp:(.text+0x46): undefined reference to `Car::Accel()'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\KDT107\AppData\Local\Temp\ccao3DhY.o:RacingMain.cpp:(.text+0x52): undefined reference to `Car::ShowCarState()'        
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\KDT107\AppData\Local\Temp\ccao3DhY.o:RacingMain.cpp:(.text+0x5e): undefined reference to `Car::Break()'
c:/program files/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\KDT107\AppData\Local\Temp\ccao3DhY.o:RacingMain.cpp:(.text+0x6a): undefined reference to `Car::ShowCarState()'        
collect2.exe: error: ld returned 1 exit status

 

또 undefined 에러가 나왔다.

 

어떤 것을 먼저 참조시키느냐가 순서가 중요해진다는 걸 알았다...

 

순서를 옳게 했더니 잘 compile 되었다.

 

하지만, 터미널에서 매번 입력하는 것보단 vscode 에서 ctrl + F5 로 run 하고 싶었다.

 

방법을 찾아보니 StackOverFlow에 비슷한 상황이 있었다.

https://stackoverflow.com/questions/72843254/vscode-compile-c-with-external-classes-undefined-reference-to-myclassmycl

 

VSCode compile C++ with external classes - undefined reference to `MyClass::MyClass()'

Error when compiling my main file & external Class file using VScode. file structure: project/ --main.cpp --MyClass.cpp --MyClass.h MyClass.h #ifndef MY_CLASS_H #define MY_CLASS_H class My...

stackoverflow.com

 

#include "Car.cpp"
#include "Car.h"

int main() {
    Car run99;
    run99.InitMembers("run99", 100);
    run99.Accel();
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();   
    return 0;
}

#include 에 참조가 필요한 cpp file을 추가해줌으로써 추가 terminal command를 작성할 필요 없이 run이 정상적으로 되었다.

소유자ID: run99
연료량: 94%
현재 속도: 30km/s
소유자ID: run99
연료량: 94%
현재 속도: 20km/s

또한, 개발 중간에

 

 
C:\Users\KDT107\Desktop\learn_cpp\init_folder\cpp_source\RacingMain.cpp:6:23: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
    6 |     run99.InitMembers("run99", 100);
      |                       ^~~~~~~

빌드가 완료되었지만, 경고가 발생했습니다.
 *  터미널이 작업에서 다시 사용됩니다. 닫으려면 아무 키나 누르세요.

 

이런 경고가 발생했었는데, 원인은 Cpp 의 char * -> const char * 로 선언이 필요했다.

 

StackOverFlow에 확인해보니,

https://stackoverflow.com/questions/20944784/why-is-conversion-from-string-constant-to-char-valid-in-c-but-invalid-in-c

 

Why is conversion from string constant to 'char*' valid in C but invalid in C++

The C++11 Standard (ISO/IEC 14882:2011) says in § C.1.1: char* p = "abc"; // valid in C, invalid in C++ For the C++ it's OK as a pointer to a String Literal is harmful since any attempt to modify...

stackoverflow.com

 

기존에는 암시적으로 const char* 로 바꾸는 작업을 해줬는데, C++ 11버전으로 바뀌면서, 직접 형변환을 하지 않으면 경고를 날리게 되었다고 한다. 기존의 코드들은 작동하게끔 유지해야할 필요가 있어서 build는 되지만 안전하게 string을 parameter로 사용할 경우엔 const char* 를 사용하도록 한다.

 

Python class 에서 Cpp class를 선언해보는 과정을 겪어보았는데, 확실히 python에서 parameter에 제한이 없는 상태로 코딩하다가, 보다 엄격한 문법 규칙을 준수하다보니, 답답함과 뭔가 생산성의 저하..? 가 느껴졌다.

 

하지만, 그만큼 파이썬에서 이런 일련의 과정을 내부적으로 하고 있던 일이고, Python으로 prototype을 제작하고, Cpp로 본격 개발을 하는 경우가 많다고 하니, 두 언어 모두 결과적으로 숙련되어야할 숙명이라 생각한다.

 

틈틈히 Cpp 개발을 공부하여 익숙해져보도록 노력할 예정이다.

 

감사합니다.

 

 

오늘은 잠시 요구분석을 내려놓고 시간내어 진득하게 데이터 분석을 공부 해보려한다.

 

주제는 건강검진을 시행한 직장가입자와 지역가입자의 검진 정보를 이리저리 분석 및 시각화해보는 것이다.

 

https://www.data.go.kr/data/15007122/fileData.do#/layer_data_infomation

 

국민건강보험공단_건강검진정보_20211229

건강검진정보란 국민건강보험의 직장가입자와 40세 이상의 피부양자, 세대주인 지역가입자와 40세 이상의 지역가입자의 일반건강검진 결과와 이들 일반건강검진 대상자 중에 만40세와 만66세에

www.data.go.kr

 

 
 

최근 공개된 3개년 데이터를 합치려는 중에 발생한 문제

데이터 칼럼의 텍스트가 미묘하게 다르고, 순서도 조금씩 다르다.

또한 2018 ~ 2019 년도에는 결손치유무 치아마모증유무 제3대구치(사랑니)이상 칼럼이 존재했지만

20년도부터는 없어졌다.

 

데이터 정합성을 맞추려면 어떻게 해야할까 고민하는 와중에

 

Pandas 내장 함수 중, column 제거, column 순서 바꾸기, column 이름 바꾸기를 시도한다.

 

1. 칼럼 제거

def delete_column(df):
    df: pd.DataFrame
    # print('before: ', df.columns.values)
    to_delete_column_name = ['결손치', '치아마모증유무', '제3대구치(사랑니)']
    bad_column_list = list()
    column_list = df.columns.values.tolist()
    for column_name in column_list:
        for bad_column_name in to_delete_column_name:
            if bad_column_name in column_name:
                bad_column_list.append(column_name)
                break
    df.drop(bad_column_list, axis='columns', inplace=True)

    # print('after: ', df.columns.values)

    return df
 

이제 모든 칼럼이 31개로 맞다. 순서를 맞춰주어야한다.

 

2. 순서 바꾸기

def main():
    config_pd_option()
    year_list = [2018, 2019, 2020]
    df = load_as_pickle_dataframe(2018)
    df = change_column_order(df)
    # save_as_pickle_from_data_frame_(df, 2018)

def change_column_order(df):
    # 기존 칼럼 순서 list
    df:pd.DataFrame
    # column_list = df.columns.values.tolist()
    column_list = ['기준년도', '가입자일련번호', '시도코드', '성별코드', '연령대코드(5세단위)', '신장(5Cm단위)', '체중(5Kg단위)', '허리둘레', '시력(좌)', '시력(우)', '청력(좌)', '청력(우)', '수축기혈압', '이완기혈압', '식전혈당(공복혈당)', '총콜레스테롤', '트리글리세라이드', 'HDL콜레스테롤', 'LDL콜레스테롤', '혈색소', '요단백', '혈청크레아티닌', '(혈청지오티)AST', '(혈청지오티)ALT', '감마지티피', '흡연상태', '음주여부', '구강검진수검여부', '치아우식증유무', '치석', '데이터공개일자']
    print('before:', df.columns.values)
    df = df[column_list]
    print('after:', df.columns.values)
    print(df)
    return df
 

이제 모든 3개년 정보의 칼럼 순서도 맞추었다. 미묘하게 다른 이름을 통일시켜주자.

 

3. 칼럼 이름 통일하기

 

def change_column_name(df):
    df:pd.DataFrame
    column_list = ['기준년도', '가입자일련번호', '시도코드', '성별코드', '연령대코드(5세단위)', '신장(5Cm단위)', '체중(5Kg단위)', '허리둘레', '시력(좌)', '시력(우)',
                   '청력(좌)', '청력(우)', '수축기혈압', '이완기혈압', '식전혈당(공복혈당)', '총콜레스테롤', '트리글리세라이드', 'HDL콜레스테롤', 'LDL콜레스테롤',
                   '혈색소', '요단백', '혈청크레아티닌', '(혈청지오티)AST', '(혈청지오티)ALT', '감마지티피', '흡연상태', '음주여부', '구강검진수검여부', '치아우식증유무',
                   '치석', '데이터공개일자']
    df.columns = column_list
    return df
 

4. 이제 하나의 dataframe으로 합치고 싶다.

그러나 pandas는 1.4 버전 이후부터 (현재 2.0.1) dataframe 끼리 append 하는 것을 없앴다.(deprecated)

 

현재는 pd.concat([df1, df2]) 방식을 사용한다.

 

def main():
    config_pd_option()
    year_list = [2018, 2019, 2020]
    df = load_as_pickle_dataframe(2018)
    df:pd.DataFrame
    for y in year_list[1:]:
        a_df = load_as_pickle_dataframe(y)
        df = pd.concat([df,a_df])

    print(df
 

근데 문제가 있다..?

 

100만 + 100만(+@) + 100만 row 기 때문에, 300만 row 가 나와야하는데, 100만 row 밖에 안된다.

 

concat을 사용할 때는 index_ignore 옵션을 True로 주어야 인덱스를 덮어쓰지 않고, 그대로 추가된다.

 

{수정 후}

def main():
    config_pd_option()
    year_list = [2018, 2019, 2020]
    df = load_as_pickle_dataframe(2018)
    df:pd.DataFrame
    for y in year_list[1:]:
        a_df = load_as_pickle_dataframe(y)
        df = pd.concat([df,a_df], ignore_index=True)

    print(df)

    with open('healthcare_as_byte_for_last_3_years', 'wb') as file:
        pickle.dump(df, file)
 

옵션을 추가해주었더니, 이제 잘 나온다.

 

※ 참고로 csv -> pickle로 전환해서 사용하는 이유는 load가 빠르기 때문이다.

pickle은 bytes로 저장되는데 반해 csv는 매번 전체 string 그대로 읽어오고 ',' 를 기준으로 구분해서 읽어주기 때문에 로딩속도가 해보면 매우 실감나게 차이난다.

 

이제 데이터베이스로 값을 집어넣는다.

 

단위시간 대비 많은 실습을 하기 위해 SQLite browser를 사용했다.

 

테이블을 짜놓고 보려니, 문제가 발생했다.

뒤쪽 데이터들이 아주 맘대로 저장되어있다. 어떤건 integer, 어떤건 real(float)로 입력되어있고,

흡연상태는 1,2,3 이 무슨 뜻인지, 모르겠고 (공공데이터 메타데이터에도 설명이 없다)

bool 값에 대해서는 Y 또는 N 또는 1.0 또는 0, NaN(Not a Number) 가 들어가있다.

 

그리하여 칼럼마다 어떤 도메인을 갖고 있는지 분석해보았다.

시도코드 도메인 : {'49', '42', '27', '50', '28', '11', '26', '30', '36', '48', '43', '47', '46', '29', '44', '31', '45', '41'}
성별코드 도메인 : {'2', '1'}
연령대코드(5세단위) 도메인 : {'13', '16', '9', '15', '14', '17', '11', '8', '10', '6', '18', '5', '7', '12'}
신장(5Cm단위) 도메인 : {'155', '125', '130', '165', '145', '150', '195', '180', '185', '170', '175', '190', '160', '140', '135'}
체중(5Kg단위) 도메인 : {'65', '140', '120', '130', '95', '60', '70', '115', '125', '100', '145', '30', '110', '75', '55', '85', '80', '50', '90', '40', '105', '35', '135', '45'}
허리둘레 도메인 : {'', '121.8', '88.8', '59.8', '67.0', '72.9', 중략..}
시력(좌) 도메인 : {'', '0.15', '0.4', '1.7', '2.0', '2.5', '0.6', '1.1', '0.9', '1.6', '2.2', '0.7', 'nan', '0.3', '1.9', '0.8', '1.5', '9.9', '1.3', '2', '0.1', '1', '0.5', '0.2', '1.8', '1.4', '1.2', '1.0', '2.1'}
시력(우) 도메인 : {'', '0.15', '0.4', '1.7', '2.0', '2.5', '0.6', '1.1', '0.9', '1.6', '2.2', '0.7', 'nan', '0.3', '2.4', '1.9', '0.8', '1.5', '9.9', '1.3', '2', '0.1', '1', '0.5', '0.2', '1.8', '1.4', '1.2', '1.0', '2.1'}
청력(좌) 도메인 : {'', '2.0', 'nan', '2', '1', '1.0', '3.0'}
청력(우) 도메인 : {'', '2.0', 'nan', '2', '1', '1.0', '3.0'}
수축기혈압 도메인 : {'', '77', '209.0', '174', '136.0', '191.0', '211.0', '188', '109.0', '156.0', '125', '214.0', '153.0', 'nan', 중략..}
이완기혈압 도메인 : {'', '117.0', '69.0', '67.0', '165', '126', '36.0', '47.0', '37.0', '59', '37', '77', '43', '127', 'nan', 중략..}
식전혈당(공복혈당) 도메인 : {'', '392.0', '409', '263.0', '67.0', '547.0', '47.0', '37', '353.0', 'nan', 중략..}
총콜레스테롤 도메인 : {'', '263.0', '409', '392.0', '9', '67.0', '2238', 'nan', 중략..}
트리글리세라이드 도메인 : {'', '642.0', '790.0', '1287.0', '871.0', '209.0', 'nan', 중략..}
HDL콜레스테롤 도메인 : {'', '9', '67.0', '39.5', '47.0' ,'nan', 중략..}
LDL콜레스테롤 도메인 : {'', '263.0', '9', '67.0', '790.0', '2030', 'nan', 중략..}
혈색소 도메인 : {'', '9', '16.5', '14.4', '14.1', '20.2', 'nan', 중략..}
요단백 도메인 : {'', '2.0', 'nan', '2', '4', '3.0', '1', '3', '4.0', '1.0', '6', '6.0', '5', '5.0'}
혈청크레아티닌 도메인 : {'', '2.9', '9', '14.4', '14.1', '15.7', '4.4', '37', '0.4', '77', 'nan', 중략..}
(혈청지오티)AST 도메인 : {'', '209.0', '1006', '31', '23.3', '270', '349.0',  'nan', 중략..}
(혈청지오티)ALT 도메인 : {'', '790.0', '2030', '2380', '209.0', '483.0',  'nan', 중략..}
감마지티피 도메인 : {'', '642.0', '790.0', '871.0', '209.0', 'nan', 중략..}
흡연상태 도메인 : {'', '2.0', 'nan', '2', '1', '3', '1.0', '3.0'}
음주여부 도메인 : {'', 'nan', 'N', '0', 'Y', '1', '0.0', '1.0'}
구강검진수검여부 도메인 : {'Y', 'N', '0', '1'}
치아우식증유무 도메인 : {'', 'nan', '0', '1', '0.0', '1.0'}
치석 도메인 : {'', '2.0', 'nan', '2', 'N', '0', 'Y', '1', '0.0', '1.0'}
데이터공개일자 도메인 : {'2019-12-19', '2019-12-31', '2021-12-29'}
 

이제 이것들을 하나의 데이터 타입으로 통일시켜주어야한다.

소수점 중요한 데이터들은 float로 저장하고 Integer 칼럼은 Integer로 nullable 하게 column 제약을 설정한다

 

요약 : 'nan' or ' ' -> 'Null' 로 변환하여 db 저장함

 

또한, 공공데이터에 주기성 자료에 칼럼 내용에 대한 서술이 정리된 파일이 있었음을 늦게서야 발견했다.

칼럼 하나씩 값을 정리해나가는 과정 중에 만난 에러...

  File "C:\Users\KDT107\Desktop\data_analysis\anaysis_practice.py", line 173, in organize_domain
    temp_list.append(int(row))
                     ^^^^^^^^
ValueError: cannot convert float NaN to integer
 

DataFrame에 들어있는 NaN 값들은 python의 int함수로는 치환할 수 없다.

그렇다고 if row == np.NaN 으로 등호가 비교되지 않았다(시도 해봤다가 실패함)

 

np.isnan(row) 를 했을 때만 Nan인지 아닌지 알 수 있었다.

  File "C:\Users\KDT107\Desktop\data_analysis\venv\Lib\site-packages\pandas\core\dtypes\astype.py", line 150, in _astype_float_to_int_nansafe
    raise IntCastingNaNError(
pandas.errors.IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer: Error while type casting for column '청력(좌)'
 

또한 NaN이 들어간 column은 dtype 을 'int32'로 변환시키는 것이 불가능했다.

StackOverFlow(https://stackoverflow.com/questions/12708807/numpy-integer-nan) 에서 검색한 바로는 Float 열에만 NaN을 쓸 수 있고, 어떤 특별한 비트에 대응하는 값에만 NaN을 대응시켜서 그렇다는 것 같다. 따라서 Integer에는 적용할 수 없었는데, 정 필요하다면 -999999 같은 수에 대응시켜서 NaN을 인식하는 쪽으로 가이드해주었다.

 

Numpy integer nan

Is there a way to store NaN in a Numpy array of integers? I get: a=np.array([1],dtype=long) a[0]=np.nan Traceback (most recent call last): File "<stdin>", line 1, in <module> Value...

stackoverflow.com

 

하지만 나는 NaN일 경우 SQLite에 Null값을 입력할 예정이라 그대로 두었다.

 

이후의 내용은 내일 마저 진행 예정이다.

 

 

이전에도 조금씩 시각화 연습을 하고 있었기 때문에, 꾸준히 보니깐 조금씩 익숙해지는 것 같습니다.

 

전체 소스코드입니다.

 

import os
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager
import matplotlib.lines


def main():
    draw_violin_plots()
    set_plot_title('예제19')
    save_fig_png()
    showing_plot()


def showing_plot():
    plt.show()


# 기본 사용
def basic_using_matplotlib():
    plt.plot([1, 2, 3, 4])


def basic_using_matplotlib_2():
    plt.plot([1, 2, 3, 4], [1, 4, 9, 16])


def basic_using_matplotlib_3():
    plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'ro')
    plt.axis([0, 6, 0, 20])


def basic_using_matplotlib_4():
    # 0.2 씩 간격으로 균일한 등차
    t = np.arange(0., 5., 0.2)
    plt.plot(t, t, 'r--', t, t ** 2, 'bs', t, t ** 3, 'g^')


# 숫자 입력하기
def input_dataset_1():
    plt.plot([1, 2, 3, 4], [2, 3, 5, 10])


def input_dataset_2():
    # dictionary type 사용(레이블이 있는 데이터)
    data_dict = {'X축': [1, 2, 3, 4, 5], 'Y축': [2, 3, 5, 10, 8]}
    plt.plot('X축', 'Y축', data=data_dict)


# 축 레이블 설정
def set_axis_label(x_label_title, y_label_title):
    plt.xlabel(x_label_title)
    plt.ylabel(y_label_title)


def set_axis_label_with_space(x_label_title, y_label_title):
    plt.xlabel(x_label_title, labelpad=10)
    plt.ylabel(y_label_title, labelpad=20)


def set_axis_label_another_position(x_label_title, y_label_title):
    plt.xlabel(x_label_title, loc='right')
    plt.ylabel(y_label_title, loc='top')


# 범례 표시하기
def set_legend_basic():
    fig = plt.figure(1)
    fig: matplotlib.pyplot.Figure
    ax = fig.axes[0]
    ax: matplotlib.pyplot.Axes
    ax.legend(title='범례', labels=['첫번째'])


# stack example
def doing_sample():
    x = np.linspace(0, 6 * np.pi, 100)
    y = np.sin(x)
    plt.ion()

    fig = plt.figure()
    ax = fig.add_subplot(111)
    line1, = ax.plot(x, y, 'r-')  # Returns a tuple of line objects, thus the comma

    for phase in np.linspace(0, 10 * np.pi, 500):
        line1.set_ydata(np.sin(x + phase))
        fig.canvas.draw()
        fig.canvas.flush_events()


# 선 종류 지정하기
def set_line_type():
    plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'ro')
    plt.plot([2, 3, 4, 5], [1, 2, 3, 4], 'bs')


# 마커 지정하기
def set_markers():
    plt.plot([1, 2, 3, 4], [2, 3, 5, 10], 'bo--')


def set_markers_2():
    plt.plot([4, 5, 6], marker="H")
    plt.plot([3, 4, 5], marker="d")
    plt.plot([2, 3, 4], marker="x")
    plt.plot([1, 2, 3], marker=11)
    plt.plot([0, 1, 2], marker='$Z$')


# 색상 지정하기
def set_color():
    plt.plot([1, 2, 3, 4], [2, 3, 5, 10], color='#e35f62', marker='o', linestyle='--')


# 그래프 영역 채우기
def fill_sector():
    x = [1, 2, 3, 4]
    y = [2, 3, 5, 9]

    plt.plot(x, y)
    # plt.fill_between(x[1:3], y[1:3], alpha=0.5)
    plt.fill_between(x[1:], y[1:], alpha=0.5)


# 축 스케일 지정
def set_axis_log_scale():
    x = np.linspace(-10, 10, 1000)
    y = x ** 3
    plt.plot(x, y)
    plt.yscale('symlog')


# 여러 곡선 그리기
# pass

# 그리드 설정하기
def set_grid_on():
    # plt.grid(True)
    # plt.grid(True, axis='x')
    plt.grid(True, axis='y')


# 눈금 표시하기
def show_ticks():
    x = np.linspace(0., 2., 100, endpoint=True)
    plt.plot(x, x)
    plt.plot(x, x ** 2)
    plt.plot(x, x ** 3)
    plt.xticks([-0.5, 0, 1, 2, 2.5], labels=['하나', '둘', '셋', '넷', '다섯'])
    plt.yticks(np.arange(0, 10.5, 0.5))


# 타이틀 설정하기
def set_plot_title(title_str):
    font_dict = {
        'fontsize': 20,
        'fontweight': 'bold'
    }
    # plt.title(title_str)
    plt.title(title_str, fontdict=font_dict, loc='left', pad=20)  # 이렇게하면 2개가 생김


# 수평선/수직선 그리기
def draw_vertical_or_horizontal_lines():
    x = np.arange(0, 4.5, 0.5)
    plt.plot(x, x + 1, 'bo')
    plt.plot(x, x ** 2, 'g--')
    plt.plot(x, -2 * x + 3, 'r:')

    plt.axhline(3.0, 0.2, 1.0, color='gray', linestyle='-', linewidth=2)
    plt.hlines(6.0, 1.0, 2.5, color='cyan', linestyle='solid', linewidth=3)

    plt.axvline(3.0, 0.2, 1.0, color='lightblue', linestyle='-', linewidth=2)
    plt.vlines(6.0, 1.0, 2.5, color='purple', linestyle='solid', linewidth=3)


# 막대 그래프 그리기
def draw_vertical_bar():
    x = np.arange(4)
    years = ['2020', '2021', '2022', '2023']
    values = [120, 190, 230, 530]

    plt.bar(x, values, color=['r', 'y', 'y', 'g'], align='edge', width=0.2, edgecolor='lightgray', tick_label=years)


# 수평 막대 그리기
def draw_horizontal_bar():
    x = np.arange(5)
    trial_count = ['first', 'second', 'third', 'fourth', 'fifth']
    value = [17, 15, 16, 14, 12]
    color_list = [get_random_color() for x in range(5)]
    plt.barh(x, value, tick_label=trial_count, color=color_list)


# 산점도 그리기
def draw_scatters():
    n = 50
    x = np.random.rand(n)
    y = np.random.rand(n)
    area = (30 * np.random.rand(n)) ** 2
    # colors = np.random.rand(n)
    colors = [get_random_color() for x in range(n)]

    plt.scatter(x, y, s=area, c=colors)


# 3차원 산점도 그리기
def draw_3D_scatters():
    n = 50
    x = np.random.rand(n)
    y = np.random.rand(n)
    z = np.random.rand(n)
    area = (30 * np.random.rand(n)) ** 2
    colors = [get_random_color() for x in range(n)]

    figure = plt.figure(1)
    ax = figure.add_subplot(111, projection='3d')
    ax.scatter(x, y, z, s=area, color=colors, marker='o')


# 히스토그램 그리기
def draw_histogram():
    random_bwt = np.random.randint(40, 101, size=100)
    figure = plt.figure(1)
    ax = figure.axes[0]
    counts, edges, bars = ax.hist(random_bwt, bins=10, histtype='barstacked', color=get_random_color())
    plt.bar_label(bars)  # 바 상단에 숫자 첨부


# 에러바 표시하기
def draw_error_bar():
    x = [1, 2, 3, 4]
    y = [1, 4, 9, 16]
    yerr = [2.3, 3.1, 1.7, 2.5]
    plt.errorbar(x, y, yerr=yerr)


# 파이 차트 그리기
def draw_pie_chart():
    ratio = [34, 32, 16, 18]
    labels = ['Apple', 'Banana', 'Melon', 'Grapes']
    plt.pie(ratio, labels=labels, autopct='%.1f%%', startangle=260, counterclock=False)


# 히트맵 그리기
def draw_heatmap():
    arr = np.random.standard_normal((30, 40))
    plt.matshow(arr)
    plt.colorbar(shrink=0.5, aspect=10)


# 컬러맵 설정하기
def using_colormap():
    arr = np.random.standard_normal((12, 100))
    plt.subplot(2, 2, 1)
    plt.scatter(arr[0], arr[1], c=arr[2])
    plt.spring()
    plt.title('spring')

    plt.subplot(2, 2, 2)
    plt.scatter(arr[3], arr[4], c=arr[5])
    plt.summer()
    plt.title('summer')

    plt.subplot(2, 2, 3)
    plt.scatter(arr[6], arr[7], c=arr[8])
    plt.autumn()
    plt.title('autumn')

    plt.subplot(2, 2, 4)
    plt.scatter(arr[9], arr[10], c=arr[11])
    plt.winter()
    plt.title('winter')


# 이미지 저장하기
def save_fig_png():
    path = r'plot_imgs/'
    dir_list = os.listdir(path)
    plt.savefig(path + f'saved_figure_{len(dir_list) + 1:02d}.png')


# 박스 플롯 그리기
def draw_box_plots():
    data_a = np.random.normal(0, 2.0, 100)
    data_b = np.random.normal(-3.0, 1.5, 500)
    data_c = np.random.normal(1.2, 1.8, 1000)

    fig, ax = plt.subplots()
    ax: plt.Axes
    ax.boxplot([data_a, data_b, data_c], notch=True, whis=2.5, vert=False)
    ax.set_xlim(-10.0, 10.0)
    ax.set_xlabel('Value')
    ax.set_ylabel('Data Type')


# 바이올린 플롯 그리기
def draw_violin_plots():
    data_a = np.random.normal(0, 2.0, 1000)
    data_b = np.random.normal(-1.2, 3, 500)
    data_c = np.random.normal(-4, 3, 2000)

    fig, ax = plt.subplots()
    ax: plt.Axes
    violin = ax.violinplot([data_a, data_b, data_c], positions=[2, 3, 4],
                           showmeans=True, quantiles=[[0.1, 0.9], [], []])
    ax.set_ylim(-20.0, 10.0)
    ax.set_xticks([1, 2, 3, 4, 5])
    ax.set_xlabel('Data type')
    ax.set_ylabel('Value')

    violins = violin['bodies']
    violins[0].set_facecolor('lightblue')
    violins[1].set_facecolor('violet')
    violins[2].set_facecolor('pink')


# 다양한 패턴 채우기
def fill_pattern():
    x = [1, 2, 3]
    y = [1, 2, 3]
    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
    ax1: plt.Axes
    ax2: plt.Axes
    ax3: plt.Axes
    ax4: plt.Axes

    ax1.bar(x, y, color='aquamarine', edgecolor='black', hatch='/')
    ax2.bar(x, y, color='salmon', edgecolor='black', hatch='\\')
    ax3.bar(x, y, color='navajowhite', edgecolor='black', hatch='+')
    ax4.bar(x, y, color='lightskyblue', edgecolor='black', hatch='*')

    plt.tight_layout()


# numpy로 random hex code 만들기
def get_random_color():
    result_color = "#"
    rgb_numbers = np.random.randint(0, 256, size=3)
    result_color = result_color + ''.join('{:02X}'.format(a) for a in rgb_numbers)
    return result_color


if __name__ == '__main__':
    main()

 

그리고 오늘 그려본 것들의 일부입니다.

 

감사합니다.

 

Pycharm에서 다중커서를 지원하는걸 모르시는 분들도 있는데,

 

ctrl을 빠르게 두번 누른 상태에서 키보드 상하로 움직이면 선택이 가능합니다.

 

아니면 Alt를 누른 상태에서 클릭을 하면, 다중 커서를 지원해줍니다.

 

ESC를 누르면 취소가 되구요.

 

그동안 한땀한땀 번호를 매기는게 쉽지 않았는데, 이런 플러그인도 있어 소개해드립니다.

``ctrl`` + ``alt`` + ``s`` 를 누르면 pycharm setting에 들어가지는데, 왼쪽 상단 검색에서 'plugin'이라고 검색하고,

 

[Marketplace] 탭에서 "String Manipulation"을 설치해줍시다.

 

설치가 끝나면 다중 선택에서 마우스 우클릭으로 사용할 수 있습니다.

 

프로젝트 내에서는 보안정책 때문에 사용하기 어렵겠지만, 연습용 개인 프로젝트에서는 유용하게 쓰일 수 있으리라 생각됩니다.

 

감사합니다.

 

 

1. 크롤링이란?

Crawling : to move slowly with the body close to the ground : move on hands and knees. : to move along slowly. the bus crawled along

영미에서는 무릎과 손바닥을 바닥에 붙이고 엉금엉금 기어가는 행위를 크롤링이라 일컫는다. 웹상에서 정보를 싹싹 긁어모으는 행위가 이와 유사하여 명명됐다.

 

 

2. BS4

BeautifulSoup4 는 정적페이지에서 크롤링할 때 쓰이는 툴로, 빠르고 가볍기 때문에, 위키페이지 같은 곳이나, 로그인 권한이 필요 없는 상황에서 빠르고 간편하다.

 

파이썬 request module로 직접 html 소스를 받아와서 분석하는 툴이기 때문에 태그를 잘 걸러 필요한 정보를 정리하는 작업이 필요하다.

 

 

3. Selenium

3-1. 개요

기존에 프론트엔드 테스트 모듈로 개발이 되었으나, 막강하고 유용한 성능들로 인해, 다목적용으로 이용되는 듯하다.

요즘 웹 어플리케이션들은 자바스크립트를 통해 동작하는 경우가 많아 접속하고자하는 버튼들이 숨겨져있을 수 있고, 어떤 액션(ex. login)을 수행해야만 가능한 행동들이 많다. 또한, 사람마다 다르게 보여지는 동적 웹들이 주류를 이루고 있다.

 

Selenium 을 잘 접목하면 자동으로 호텔, 교통수단 booking을 할 수 있을 것으로 보이며, 아무래도 요즘 뜨는 chatGPT 외부 플러그인들이 이런 것들을 이용하지 않을까 싶다.

 

3-2. Selenium 상세 실습

그동안 내가 작성한 글들을 정리하는 소스를 만들어보았다. 이동녘 교수님께서 알려주신 방법을 그대로 따라한 것이지만, 혼자서 다시 쳐보는 연습을 해보고 있다. 사실 자기가 썼던 글 제목만 복사하는 정도는 로그인이 필요 없어서, 그 부분은 생략했다.

 

그리고, CSS-Selector 이거 참 물건이라고 느낀다. 크롤링을 수월하게 배운 것 같아 매우 기분이 좋습니다.

 

(맥 환경에서 테스트한 것이기 때문에, chromedriver에 exe 확장자가 따로 없는게 맞습니다)

 

[소스 코드]

 

# cafe 접속

from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait

# WEB DRIVER PATH 설정
WEB_DRIVER_PATH = '/Users/gwang/WebDriver/chromedriver/chromedriver'
s = Service(WEB_DRIVER_PATH)

# DRIVER를 통한 브라우저 실행
driver = webdriver.Chrome(service=s)
driver.get('https://www.naver.com')

# 전체 html 불러오기
total_html_page = driver.find_element(by=By.TAG_NAME, value='html')

# css-selector 복사
btn_cafe = driver.find_element(by=By.CSS_SELECTOR, value='#shortcutArea > ul > li:nth-child(2) > a')
btn_cafe.click()

# 새 탭 전환
driver.switch_to.window(driver.window_handles[1])

# 검색창에 start dev 입력
input_area = WebDriverWait(driver, timeout=3).until(
    lambda d: d.find_element(by=By.CSS_SELECTOR,
                             value='#header > div.snb_area > div > div.SearchArea > form > fieldset > div > div > div.FormInputText > input')
)

input_area.send_keys('start developer')
input_area.send_keys(Keys.ENTER)

btn_cafe_start_dev = WebDriverWait(driver, timeout=3).until(
    lambda d: d.find_element(by=By.CSS_SELECTOR,
                             value='#mainContainer > div > div > div.section_search_content > div > div.list_area.cafe_list_area > ul > li > div > div > a')
)
btn_cafe_start_dev.click()

# 새 탭 전환
driver.switch_to.window(driver.window_handles[2])

# 내 게시판 접속
link_my_board = WebDriverWait(driver, timeout=3).until(
    lambda d: d.find_element(by=By.CSS_SELECTOR, value='#menuLink590')
)
link_my_board.click()

# iFrame으로 전환
inner_frame = WebDriverWait(driver, timeout=3).until(
    lambda d: d.find_element(By.ID, 'cafe_main')
)
driver.switch_to.frame(inner_frame)


def print_posts(web_driver):
    # 게시판 리스트로 선택
    board_list = web_driver.find_elements(by=By.CSS_SELECTOR,
                                          value='#main-area > div:nth-child(4) > table > tbody > tr')

    for tr in board_list:
        post_id = tr.find_element(by=By.CLASS_NAME, value='inner_number').text
        post_title = tr.find_element(by=By.CLASS_NAME, value='article').text
        poster = tr.find_element(by=By.CLASS_NAME, value='m-tcol-c').text
        post_date = tr.find_element(by=By.CLASS_NAME, value='td_date').text
        print(f"{post_id:^11}|{post_title:^30}|{poster:^15}|{post_date:^15}")


page_list = driver.find_elements(by=By.CSS_SELECTOR, value='#main-area > div.prev-next > a')
for index in range(len(page_list)):
    print(f'{index} 페이지')
    driver.find_elements(by=By.CSS_SELECTOR, value='#main-area > div.prev-next > a')[index].click()
    print_posts(driver)

while True:
    pass

[출력화면]

0 페이지
   38158   |크롤링, 어디까지 해도 괜찮은걸까? [2023-06-05 학습일지]|   박광현KDT23    |  2023.06.05.  
   38157   |Pandas, SQLite 접목 복습 [2023-06-05 학습일지]|   박광현KDT23    |  2023.06.05.  
   38127   |소프트웨어 요구 명세서(SRS) 제작 일지 3일차 -완성- [2023-06-04]|   박광현KDT23    |  2023.06.04.  
   38115   |          StarUML 소개          |   박광현KDT23    |  2023.06.04.  
   38051   |소프트웨어 요구 명세서(SRS) 제작 일지 1일차 [2023-06-02]|   박광현KDT23    |  2023.06.02.  
   38012   |소프트웨어 요구 사양서(SRS) 제작 일지 0일차 [2023-06-01]|   박광현KDT23    |  2023.06.01.  
   37975   |matplotlib 한글 폰트 적용, 데이터 시각화 [2023-05-31 학습일지]|   박광현KDT23    |  2023.05.31.  
   37946   |SQLite 수업 정리 [2023-05-30 학습일지]|   박광현KDT23    |  2023.05.30.  
   37930   |파이썬으로 실시간 버스 도착 정보 가져오기 with 타요뻐스 [2023-05-29]|   박광현KDT23    |  2023.05.29.  
   37929   |Pandas, SQLite3 공부하기 [2023-05-29 학습일지]|   박광현KDT23    |  2023.05.29.  
   37923   |파이썬 궁금한 것 공부해서 정리하기(프리 토픽) [2023-05-27 학습일지]|   박광현KDT23    |  2023.05.27.  
   37888   |레전드오브복이 개발일지 14일차- 마지막날 - [2023-05-25]|   박광현KDT23    |  2023.05.25.  
   37841   |레전드오브복이 개발일지 13일차 [2023-05-24]|   박광현KDT23    |  2023.05.24.  
   37820   |레전드오브복이 개발일지 12일차 [2023-05-23]|   박광현KDT23    |  2023.05.23.  
   37771   |레전드오브복이 개발일지 11일차 [2023-05-22]|   박광현KDT23    |  2023.05.22.  
1 페이지
   37744   |레전드오브복이 개발일지 10일차 [2023-05-21]|   박광현KDT23    |  2023.05.21.  
   37711   |레전드오브복이 개발일지 9일차 [2023-05-20]|   박광현KDT23    |  2023.05.20.  
   37675   |레전드오브복이 개발일지 8일차 [2023-05-19]|   박광현KDT23    |  2023.05.19.  
   37637   |레전드오브복이 개발일지 7일차 [2023-05-18]|   박광현KDT23    |  2023.05.18.  
   37592   |레전드오브복이 개발일지 6일차 [2023-05-17]|   박광현KDT23    |  2023.05.17.  
   37543   |레전드오브복이 개발일지 5일차 [2023-05-16]|   박광현KDT23    |  2023.05.16.  
   37509   |레전드오브복이 개발일지 4일차 [2023-05-15]|   박광현KDT23    |  2023.05.15.  
   37486   |레전드 오브 복이 개발일지 - 3일차 - [2323-05-14 학습일지]|   박광현KDT23    |  2023.05.14.  
   37360   |변수명, 함수명은 어떻게 써야할까? (PEP8 간단 요약) [2023-05-09 학습일지]|   박광현KDT23    |  2023.05.09.  
   37359   |파이썬으로 유닛 테스트(Unit Test) 하기 [2023-05-09 학습일지]|   박광현KDT23    |  2023.05.09.  
   37328   |Qt Designer 사용법 발표 및 로또 번호 추첨기 [2023-05-08 학습일지]|   박광현KDT23    |  2023.05.08.  
   37317   |처음 시도한 Pair Programming Result [2023-05-07 학습일지]|   박광현KDT23    |  2023.05.07.  
   37269   |PyQt5 리그오브레전드 로그인창 따라하기 [2023-05-04 학습일지]|   박광현KDT23    |  2023.05.04.  
   37234   |PyQt5 모듈 확인, 오늘 공부한 것 [2023-05-03 학습일지]|   박광현KDT23    |  2023.05.03.  
   37196   | PyQt5 공부하기 [2023-05-02 학습일지] |   박광현KDT23    |  2023.05.02.  
2 페이지
   37124   |<파이썬으로 8만대장경 작성하기> 개발 실패 <2023-05-01 최종>|   박광현KDT23    |  2023.05.01.  
   37080   |<파이썬으로 8만 대장경 작성하기> 개발 계획서 [2023-04-27 시작]|   박광현KDT23    |  2023.04.27.  
   37029   |회원가입, 로그인 화면 구축 개발 실패 보고서 [2023-04-26]|   박광현KDT23    |  2023.04.26.  
   36993   |<개복치 키우기> 개발 완료 보고서 [2023-04-25 학습일지]|   박광현KDT23    |  2023.04.25.  
   36952   |<경주마와 아이들> 팀이 내주신 문제 풀이 중간 보고서 <2023-04-24 개발일지>|   박광현KDT23    |  2023.04.24.  
   36887   |for 반복문 사용하여 숫자 스무고개 만들기 [2023-04-21]|   박광현KDT23    |  2023.04.21.  
   36886   |for 반복문 사용하여 가위바위보 게임 만들기 (메시징 처리) [2023-04-21]|   박광현KDT23    |  2023.04.21.  
   36853   |<김주김> 팀이 내주신 문제 풀이 완료 [2023-04-20 학습일지]|   박광현KDT23    |  2023.04.20.  
   36849   |<동녘짱과 아이들> 팀이 내주신 문제 풀이 완료 [2023-04-20 학습일지]|   박광현KDT23    |  2023.04.20.  
   36848   |다시 돌아온 별찍기 파이썬 버전 [2023-04-20]|   박광현KDT23    |  2023.04.20.  
   36805   |<점프투용궁> 팀이 내주신 문제 풀이 완료 [2023-04-19 학습일지]|   박광현KDT23    |  2023.04.19.  
   36804   |<똘똘이> 팀이 내주신 문제 풀이 완료 [2023-04-19 학습일지]|   박광현KDT23    |  2023.04.19.  
   36803   |<이것도 찾어보던가> 팀이 내주신 문제 풀이 완료 [2023-04-19 학습일지]|   박광현KDT23    |  2023.04.19.  
   36801   |월요일 출제된 문제 다 풀어보기 [2023-04-19 학습일지]|   박광현KDT23    |  2023.04.19.  
   36703   |문제 출제 및 학우님들 문제 풀어보기 [2023-04-17 학습일지]|   박광현KDT23    |  2023.04.17.  
3 페이지
   36629   |Bool type 배운 것 정리[2023-04-14] |   박광현KDT23    |  2023.04.14.  
   36569   |Python Tuple, Dictionary, Set 자료형 배운 것, 개인 공부 조금 추가하여 정리 [2023-04-13 학습일지]|   박광현KDT23    |  2023.04.13.  
   36482   |행렬을 쓰는 이유, Numpy 개인 공부 [2023-04-12 학습일지]|   박광현KDT23    |  2023.04.12.  
   36410   |간단 요약하는 AI 역사 [2023-04-11 학습일지]|   박광현KDT23    |  2023.04.11.  
   36359   |      내가 정보처리기사를 공부하는 이유      |   박광현KDT23    |  2023.04.10.  
   36311   |<개인프로젝트> 이세계 간호사 시뮬레이터 만들기 [2023-04-09 학습일지]|   박광현KDT23    |  2023.04.09.  
   36174   |로또 시뮬레이션 프로그램 만들기, 개발 계획서, 개발 완료 보고서, 소스코드 [2023-04-06 학습일지]|   박광현KDT23    |  2023.04.06.  
   36122   |<나무 나무를 심자> 개발 계획서 및 소스, 개발 완료 계획서 [2023-04-05 학습일지]|   박광현KDT23    |  2023.04.05.  
   36100   |  [공유]아스키코드로 텍스트 이미지 만드는 방법   |   박광현KDT23    |  2023.04.04.  
   36063   |가위바위보 턴제 전투 게임 만들기, 개발 계획서, 소스 코드, 개발 완료 보고서 [2023-04-04 학습일지]|   박광현KDT23    |  2023.04.04.  
   36012   |숫자야구 만들기 개발 계획서, 개발 완료 보고서, 소스코드 [2023-04-03 학습일지]|   박광현KDT23    |  2023.04.03.  
   35934   |[과제 제출] <C언어로 김밥천국 POS기 프로그램> 개발 완료 및 보고서|   박광현KDT23    |  2023.04.01.  
   35902   | <C언어로 김밥천국 POS기 프로그램 개발 계획서> |   박광현KDT23    |  2023.03.31.  
   35888   |심폐소생술 순서도 그리기, list 자료구조 C언어로 구현 [2023-03-31 학습일지]|   박광현KDT23    |  2023.03.31.  
   35874   |   각 언어별, 다른 종류 IDE 쓸 때 깨알팁   |   박광현KDT23    |  2023.03.30.  
4 페이지
   35855   |C언어 한글 입력 받기 실패 & 배열 없이 로또 번호 생성, 정렬 [2023-03-30 학습일지]|   박광현KDT23    |  2023.03.30.  
   35830   |C 언어로 한글 파일 출력하기 [2023-03-29 학습일지]|   박광현KDT23    |  2023.03.30.  
   35744   |GitHub 24.6k star를 받은 AI 전문가 로드맵 [2023-03-28 학습일지]|   박광현KDT23    |  2023.03.28.  
   35693   | 가위바위보 게임 만들기 [23.03.27 학습일지] |   박광현KDT23    |  2023.03.27.  
   35649   |   이번 주 풀었던 퀴즈 & 과제 소스 코드들    |   박광현KDT23    |  2023.03.24.  
   35639   |C언어 변수, 그런데 메모리를 곁들인 [2023-03-24 학습일지]|   박광현KDT23    |  2023.03.24.  
   35564   |C 언어 포인터 쉽게 이해해보려 노력하기 [2023-03-22 학습일지]|   박광현KDT23    |  2023.03.22.  
   35522   |SQL 공부하기 for 정보처리기사 실기 [2023-03-21 학습일지]|   박광현KDT23    |  2023.03.21.  
   35482   |C의 탄생 이전엔 어떤 프로그래밍 언어들이 있었을까? [2023-03-20 학습일지]|   박광현KDT23    |  2023.03.20.  
   35460   |        좋은 개발자에 대한 고찰         |   박광현KDT23    |  2023.03.18.

 

감사합니다.

 

오늘은 Python의 BS4 와 Selenium을 사용한 크롤링에 대해서 배웠습니다.

 

필요한 정보를 한꺼번에 끌어올 수 있다는 장점이 있습니다. 하지만 늘 장점만 존재할까요?

 

한편, 역지사지로 생각해봅시다. 당신이 '서버 관리자'이고 회사의 경영과 밀접한 연관성이 있다고 '가정'해봅니다.

 

당신이 소중하게 모은 데이터들, 혹은 개인정보법에 보호되어야할 정보들이 로봇 프로그램에 의해

 

국적불문, 이름도 모르는 사람에게 싸그리 복사당하고 있습니다. 더불어 이들은 일반이용자들보다 단위시간당 훨씬 많은 사이트를 방문하고 정보를 요청합니다.

 

그래서, 서버 크기도 늘려야하고 운용 비용이 증가합니다. 이들이 너무 많은 요청을 한꺼번에 해서, 일반적인 이용자가 사이트 접속이 느려지는 상황입니다. 실제적으로 사이트를 운용하는데 도움이 되는 광고나 매출 증가에는 아무런 영향을 주지 않습니다. 이런 경우엔, 어떻게 해야할까요? 소송...

 

이런 경우도 있습니다. 2008년 구인구직 사이트의 정보를 그대로 크롤링하여 자신의 구인 구직 사이트에 올려 소송을 당한 사례가 있습니다. 부정경쟁행위로 5000만원 보상금과 소송비용을 부담하는 것 2016년까지 가서 최종판결이 내려졌습니다.

 

판례 참조 : 서울중앙지방법원 2016. 2. 17. 선고 2015가합517982 판결

 

2021년도에는 A숙박업소 정보제공 서비스 업체에서 B숙박업소 정보제공 서비스 업체를 데이터베이스 제작업자의 권리를 침해했다는 주장으로 부정경쟁방지법과 이런 크롤링 행위가 정보통신망을 침입행위에 해당하는 일로 정보통신망법 위배, A숙박업소의 서비스 제공에 방해를 주었다는 주장으로 컴퓨터등장애업무방해죄로 소송이 진행된 바 있습니다.

 

최종 판결에서는 A사의 API 접근방식에 대해 통상적인 패킷캡쳐로 알 수 있었고, 이에 대해 적절한 보호조치(권한 제한)을 하지 않은 것, 회사 내규에 명확한 권리를 명문화하지 않은 것의 이유로 정보통신망법 위반을 적용할 수 없다는 판결, 상당 부분 상대 회사에서 제공하는 데이터와 유사한 데이터를 제공하게 될 경우 저작권법 위배에 해당하나, B사가 제공하는 데이터는 A사 데이터베이스의 일부이며, 상당히 알려진 정보이기 때문에 B사의 행위가 A사의 이익에 부당하게 해친다고 보기 어렵다는 이유로 B사가 저작권법을 위배하지 않았다는 판결, B사의 크롤링 행동으로 A사가 정보처리 장애를 입었다는 주장에 대해 B사의 행동이 일부러 A사의 정보통신망에 부정한 명령을 하여 장애를 유발시켰다고 보기 어려워 범죄의 성립을 부정했습니다.

 

판례 참조 : 대법원 2022. 5. 12. 선고 2021도1533 판결

 

요약 및 돌이켜 말하면 크롤링을 하지말라고 회사내규에 명시되어있고, 로그인 같은 권한 보호장치가 있음에도 뚫고 크롤링을 과도하게 유발시키면서 데이터를 상업적으로 쓴다면... 소송입니다!

 

 

 

robots.txt" ??

 

웹로봇관련 국제 규약참고 http://www.robotstxt.org/

 

The Web Robots Pages

The Web Robots Pages Web Robots (also known as Web Wanderers, Crawlers, or Spiders), are programs that traverse the Web automatically. Search engines such as Google use them to index the web content, spammers use them to scan for email addresses, and they

www.robotstxt.org

 

 

 

여기서 Disallow: / 는 전체를 의미합니다. Allow /$ 는 메인페이지만 허용한다는 뜻이니, 메인 페이지 말고는 전체 로봇 접근을 금한다는 의미입니다. 그래서 tistory 같은 경우엔 로그인과 접근권한을 제외하고는 열어두었다고 볼 수 있어 구글에서 검색했을 때 티스토리 관련 블로그들이 많이 게제되는 경우가 많은 반면, 네이버블로그가 검색되는 일은 드문 경우가 이런 이유라고 추측됩니다.

 

모사이트는 크롤링 유저에 대해 선제적으로 IP밴 같은 접속 제한을 부여하기도 한다. 대인배 같이 크롤링을 허용하는 사이트들에 비해 이들의 행위를 너무 부당하다고 생각하지 않도록 하자.

 

[결론]

크롤링 이용자는 해당 서비스 약관을 준수하고, 상식적으로 해당 웹서비스에 무리가 가지 않을 정도로 가능하다는 것이고, 저작권법을 위배하지 않아야 한다는 것, 자신의 행위가 상대방 비즈니스에 위해를 가할 수 있는 행위임을 인지하자는 것.

 

서버 관리자는 크롤링에 대해 적절한 보안과 권한 조치를 취해야하며, 권리, 규약을 명확히 명시, robots.txt를 제공하여 크롤링 이용자들에게 명확한 허용범위를 안내할 수 있어야한다는 것이다.

 

 

감사합니다.

 

 

요구분석을 하다보면, 별의별 다양한 diagram이 있다는 것을 알게 된다. (정처기만 공부해도..)

계속 비슷한 패턴을 개발하면 상관 없겠지만, 어떤 새로운 시스템을 설계해야할 경우 새로운 모식도를 짜야할 수 있다.

 

파워포인트나 Google Slide 에서 제공하는 기능으로도 그릴 수 있으나, Diagram에 특화되어있는 오픈 소스 툴도 있다는 걸 소개해드리고자 한다.

 

우리는 기존 수업에서 Draw.io 를 통해서 순서도를 그려본 경험이 있다.

 

draw.io 는 웹에서 제공하지만, 윈도우에서 프로그램으로 제공하는 경우도 있다.

 

StarUML 이다. UML은 Unified Modeling Language 의 약자이며, 통합 모델링 언어라고 해석할 수 있다.

 

https://staruml.io/

 

 

공식사이트는 이곳이며, 여기서 무료로 설치 가능하다. 다만, 무료 버전의 경우엔 기능이 제한되어있는 부분이 있어 전문적으로 사용할 경우엔 10만원 정도 결제로 평생 이용가능하다.

 

 

이렇게 많은 다이어 그램을 지원하고 있으니, 궁금하다면 둘러보고 이용해봐도 좋을 것이다.

 

이 프로그램으로 오늘 그린 User Case Diagram이다.

 

참고로 file로 저장과 불러오기 기능을 지원하고 있습니다.

 

감사합니다.

 

'언어' 카테고리의 다른 글

각 언어별, 다른 종류 IDE 쓸 때 깨알팁  (1) 2023.05.28

matplotlib는 설치 직후 상태에선 한글을 지원해주지 않는다.

 

그래서 우리는 matplotlib의 폰트에 한글 폰트를 추가해주고(1) 기존의 font 캐시를 삭제해주고(2), 기본 폰트를 한글 폰트로 설정해주는 작업(3)을 거쳐야한다.

 

(최종 결과물)

 

1. 우선은 venv 가상환경 matplotlib에 fonts 폴더에 우리가 추가하고자하는 한글폰트를 복사한다.

2. 그다음 user (사용자) 폴더에 있는 .matplotlib 폴더에서 fontlist-(~~).json 파일을 지워준다.

3. 그리고, 아까 가상환경 내의 matplotlib 폴더의 mpl-data 폴더에서 matplotlibrc 를 메모장으로 열어준 뒤, font.family를 검색하고, 사용하고자 하는 폰트 이름을 입력한다.

순서는 상관없다. 3가지 조건을 달성하면 기본으로 이제 한글이 적용된다.

 

다음은 pandas로 겪은 에러상황이다. 미세정보 데이터를 가져오기 위해 에어코리아(환경부 산하 한국환경공단)에서 필요로하는 대기정보를 엑셀로 저장했다.

 

 

 

PM10의 데이터는 ten이라고 prefix를 붙여주었다.

확장자가 xls지만 pandas의 read_excel 함수로 잘 열렸기에 전혀 문제될 것 없는 것처럼 보였다.

 

 

하지만, pandas 로도 열리지 않고, engine을 이것저것 바꾸어봐도 dataframe으로 열리지 않는 문제가 발생했다.

 

 

한참을 찾아보아도 방법이 없던 중에, 이곳을 통해 해결 방법에 대한 실마리를 얻었다.

https://maeng-gun.github.io/excel/excel1/#%EC%A7%81%EC%9E%A5%EC%9D%B8-%EC%BD%94%EB%94%A9%EC%9D%98-%EA%B1%B8%EB%A6%BC%EB%8F%8C-%EB%AC%B8%EC%84%9C%EB%B3%B4%EC%95%88drm 

 

사내망 엑셀파일에 걸린 자물쇠를 뚫고 코딩하기

엑셀 자동화 (1) - xlwings 라이브러리 시작하기

maeng-gun.github.io

 

xlwings도 안먹힘. -> 다른 spreadsheet 프로그램으로 저장한다 -> LibreOffice 설치 -> 새로 xlsx 로 저장해보는 시도 끝에 판다스로 dataframe 객체를 만드는데 성공했다!

 

굳이 libreoffice를 설치할 필요 없이 excel 파일의 metadata로 인한 문제인 것 같았다. 따라서, 필요한 데이터만 웹 스프레드시트로 옮겨서 따로 xlxs 파일로 저장해도 정상적으로 dataframe을 만들었다.

 

그래서 만든 데이터는 다음과 같다.

def get_avg_pollution_from_air_by_date():
    conn = sqlite3.connect("sample_bus_info_2.db")
    c = conn.cursor()
    result_list = c.execute("""
        select 
         avg(time_1) as '1',
         avg(time_2) as '2',
         avg(time_3) as '3',
         avg(time_4) as '4',
         avg(time_5) as '5',
         avg(time_6) as '6',
         avg(time_7) as '7',
         avg(time_8) as '8',
         avg(time_9) as '9',
         avg(time_10) as '10',
         avg(time_11) as '11',
         avg(time_12) as '12',
         avg(time_13) as '13',
         avg(time_14) as '14',
         avg(time_15) as '15',
         avg(time_16) as '16',
         avg(time_17) as '17',
         avg(time_18) as '18',
         avg(time_19) as '19',
         avg(time_20) as '20',
         avg(time_21) as '21',
         avg(time_22) as '22',
         avg(time_23) as '23',
         avg(time_24) as '24'
         from air_pollution where measured_type = 'pm2.5' and date = '20230428' group by date;
        """, ).fetchone()
    conn.commit()
    conn.close()

    return list(range(1,25)), result_list
    
def get_data_by_all_bus_transaction():
    conn = sqlite3.connect("sample_bus_info_2.db")
    c = conn.cursor()
    list_x = list()
    list_y = list()
    result_list = c.execute("""
        select measured_time, sum(transaction_count) from tb_bus where date = '20230428' group by date, measured_time;
        """).fetchall()
    for row in result_list:
        list_x.append(row[0])
        list_y.append(row[1])
    conn.close()

    return list_x, list_y

 

오늘도 데이터 시각화를 만들어보는 연습을 진행했습니다.

 

감사합니다.

 

오늘의 퀘스트는 Titanic table을 통해 다음과 같은 데이터를 뽑아내는 것이다. 과연 할 수 있을 것인가?

 

 

 

 

1.

2.

3.

4.

5.

 

테이블 구조는 다음과 같다. 한글은 적절한 글자크기를 인식 못하는지, 자꾸 박스 틀을 깨버리는데, 해결 방법이 없는 것 같다.

 

 

 

 

셀프 물음

1.

4일동안 가장 승차가 많은 정류소 top 10을 구하세요.

2.

4일동안 아침 7시에 승차가 많은 정류소 top 10을 구하세요.

3.

4일동안 승차인원 가장 적은 정류소 top 10을 구하세요.

4.

4월 28일 이용객이 가장 많은 버스 노선 top 10을 구하세요.

5.

4일간 봉선 37의 평균 하루 승차인원은 몇 명인지 구하세요.

6.

4월 29일 문흥 18의 시간대별 승차인원은 어떻게 됩니까?

 

여기서 멈추지 않고 matplotlib으로 시각화를 해보았습니다.

 

7.

(시각화) 봉선 37의 시간대별 이용객이 4일동안 어떻게 변했는가?

def make_plain_subplots():
    fig, ax = plt.subplots()
    list_x_1, list_y_1 = get_data_by_date_and_bus_no('봉선37', '20230427')
    list_x_2, list_y_2 = get_data_by_date_and_bus_no('봉선37', '20230428')
    list_x_3, list_y_3 = get_data_by_date_and_bus_no('봉선37', '20230429')
    list_x_4, list_y_4 = get_data_by_date_and_bus_no('봉선37', '20230430')
    ax.plot(list_x_1, list_y_1, label='427')
    ax.plot(list_x_2, list_y_2, label='428')
    ax.plot(list_x_3, list_y_3, label='429')
    ax.plot(list_x_4, list_y_4, label='430')
    ax.legend()
    fig.suptitle('bus trend')
    plt.show()

def get_data_by_date_and_bus_no(bus_name, date):
    conn = sqlite3.connect("sample_bus_info_2.db")
    c = conn.cursor()
    list_x = list()
    list_y = list()

    result_list = c.execute("""
    select measured_time, transaction_count from tb_bus where bus_name=(?) and date = (?) and boarding_type='승차' and bus_stop_id=5218
        order by date asc;
    """, (bus_name, date,)).fetchall()
    for row in result_list:
        list_x.append(row[0])
        list_y.append(row[1])
    conn.commit()
    conn.close()

    return list_x, list_y

 

또한 오후엔 OOP와 Class 에 대해 짧게 발표를 했습니다.

 

소수의 학우님들의 요청을 받아 소규모 그룹으로 진행할 예정이었습니다만, 통 크게 전체 학우들을 대상으로

 

발표를 하라고 교수님께서 시간을 내어주셨기 때문에, 열심히 정보전달을 위해 노력해보았습니다.

 

제가 아는 것을 남들이 알도록 설명시킨다는게 참 쉽지 않다는걸 다시 한번 느꼈습니다.

 

그래도 몇몇 학우님들께서 조금은 이해하는데 도움이 되었다고 긍정적인 피드백을 주셔서, 많은 용기와 보람이 되었습니다.

 

감사합니다.

 

+ Recent posts