Category Archives: Python

[Coursera] Applied Data Science with Python 수강 시작

Python 데이터 사이언스 과목을 Coursera에서 수강하기 시작했습니다.

이직 한 뒤로 계속해서 front-end만 보다보니, front-end를 싫어하는 건 아니지만 조금 현기증이 나려고 합니다 -_-;

원래 취미로 하던 Python 데이터 분석을 조금씩 놓고 있는 것 같아,

감도 잡고 강제로 공부도 할 겸 수강을 시작합니다.

 

전에 안좋은 추억이 있었던 만큼, 이번에는 다 결제하지 않고 하나씩 결제하려고 합니다.

일단 첫인상은 나쁘지 않네용.

 

배운 내용 종종 포스팅하겠습니다.

[Python] Selenium Webdriver 사용기

Selenium 은 코드 동작으로 브라우저의 액션을 만들 수 있는 도구다.

보통 Front 단의 웹 동작을 테스트하기 위해서 사용되는 도구이며, Python이나 자바등의 언어로 구현이 가능하다.

사용이 매우 쉽기 때문에 널리 쓰이고 있다.

이걸 쓰게 된 건 다른 건 아니고, together.kakao.com/ 때문이다.

다양한 사연에 모금을 할 수 있는 사이트인데, 모금함에 댓글을 달거나 응원만 눌러도, 1회당 100원씩 카카오에서 기부를 해준다.

그렇다면 !! 매크로로!! 죄다 댓글을 달아버리면 기부를 할 수 있는게 아닐까!!

하는 생각으로 시작하게 됐다.

API는 여기를 참조하길 바란다.

먼저 설치

pip install selenium

import 는 이렇게 했다

from selenium import webdriver

사용할 웹드라이버를 크롬이나 파이어폭스를 쓸 수 있다. 크롬의 경우 크롬 웹드라이버를 여기서 받을 수 있다.

먼저 간단하게 창을 열어보자.

from selenium import webdriver
import time

driver_path = "/Users/user/Downloads/chromedriver"
together_url = 'https://together.kakao.com/'
driver = webdriver.Chrome(driver_path)

if __name__ == "__main__":
    driver.set_window_size(1080, 768)
    driver.get(together_url)

    # 메인 윈도우
    current_window = driver.window_handles[0]

 

실행하면 크롬창이 열리는 것을 볼 수 있을 것이다.

driver.quit() 으로 driver 를 사용후에 정리 할 수 있다.

 

그 다음에는 selector를 이용하여 특정 element를 잡아보자.

find_element(by=’id’, value=None)
‘Private’ method used by the find_element_by_* methods.

Usage: Use the corresponding find_element_by_* instead of this.
Return type: WebElement
find_element_by_class_name(name)
Finds an element by class name.

Args:
  • name: The class name of the element to find.
Usage:

driver.find_element_by_class_name(‘foo’)

find_element_by_css_selector(css_selector)
Finds an element by css selector.

Args:
  • css_selector: The css selector to use when finding elements.
Usage:

driver.find_element_by_css_selector(‘#foo’)

find_element_by_id(id_)
Finds an element by id.

Args:
  • id_ – The id of the element to be found.
Usage:

driver.find_element_by_id(‘foo’)

Finds an element by link text.

Args:
  • link_text: The text of the element to be found.
Usage:

driver.find_element_by_link_text(‘Sign In’)

find_element_by_name(name)
Finds an element by name.

Args:
  • name: The name of the element to find.
Usage:

driver.find_element_by_name(‘foo’)

Finds an element by a partial match of its link text.

Args:
  • link_text: The text of the element to partially match on.
Usage:

driver.find_element_by_partial_link_text(‘Sign’)

find_element_by_tag_name(name)
Finds an element by tag name.

Args:
  • name: The tag name of the element to find.
Usage:

driver.find_element_by_tag_name(‘foo’)

find_element_by_xpath(xpath)
Finds an element by xpath.

Args:
  • xpath – The xpath locator of the element to find.
Usage:

driver.find_element_by_xpath(‘//div/td[1]’)

find_elements(by=’id’, value=None)
‘Private’ method used by the find_elements_by_* methods.

Usage: Use the corresponding find_elements_by_* instead of this.
Return type: list of WebElement
find_elements_by_class_name(name)
Finds elements by class name.

Args:
  • name: The class name of the elements to find.
Usage:

driver.find_elements_by_class_name(‘foo’)

find_elements_by_css_selector(css_selector)
Finds elements by css selector.

Args:
  • css_selector: The css selector to use when finding elements.
Usage:

driver.find_elements_by_css_selector(‘.foo’)

find_elements_by_id(id_)
Finds multiple elements by id.

Args:
  • id_ – The id of the elements to be found.
Usage:

driver.find_elements_by_id(‘foo’)

Finds elements by link text.

Args:
  • link_text: The text of the elements to be found.
Usage:

driver.find_elements_by_link_text(‘Sign In’)

find_elements_by_name(name)
Finds elements by name.

Args:
  • name: The name of the elements to find.
Usage:

driver.find_elements_by_name(‘foo’)

Finds elements by a partial match of their link text.

Args:
  • link_text: The text of the element to partial match on.
Usage:

driver.find_element_by_partial_link_text(‘Sign’)

find_elements_by_tag_name(name)
Finds elements by tag name.

Args:
  • name: The tag name the use when finding elements.
Usage:

driver.find_elements_by_tag_name(‘foo’)

find_elements_by_xpath(xpath)
Finds multiple elements by xpath.

Args:
  • xpath – The xpath locator of the elements to be found.
Usage:

driver.find_elements_by_xpath(“//div[contains(@class, ‘foo’)]”)

등 등 이렇게 다양하게 활용 할 수 있다.

허접하게 셀렉터를 이용하여 액션을 몇개 구현해 보자면,

# css selector를 이용
def click_element(element):
    driver.find_element_by_css_selector(element).click()

# xpath를 이용
def click_element_by_xpath(name):
    driver.find_element_by_xpath(name).click()


def input_text(element, text):
    driver.find_element_by_css_selector(element).send_keys(text)

 

클릭을 하거나, inputbox에 값을 입력 할 수 있을 것이다.

이제 이런과정에 몇 가지 노가다만 더해진다면, 모든 모금함에 댓글을 매크로 처럼 달아버리는 것은 일도 아닐 것이다.

다만 로그인을 수시로 해야하는 터라 (driver를 실행할때마다 새로 로그인함) 5번 이상 로그인 하면 captcha문자가 떠서 테스트가 불가능 하다.

ㅋㅋㅋㅋ

아무튼 지간에, 모든 모금함에 댓글과 응원을 하는(?) 잉여 짓을 완료 하였다.

 

 

[Python] mybatis sql 자동생성 프로그램 (tkinter + pymysql)

이름은 거창하게 했는데 사실 별거 아니다.

mybatis를 하면 column이름을 camelcase로 바꾸고 (물론 자동으로 알아서 가져가지만 as 로 명시적으로 써주고 있다.)

그대로 vo 만들고 update문에 if문 걸어주고 암튼 여간 귀찮은 일이 아니다.

그래서 이를 자동화 해주는 스크립트를 짜보았따.

이걸 쓰면 서 배웠던 것은 pymysql활용과 tkinter활용정도가 아닐까 싶다.

tkinter를 exe로 배포하지는 않았따.

이 프로그램이 아무도 필요하지 않기 때문이다. 이미 내가 다 만들었기 때문이다.

 

라이브러리

import pymysql


def get_connection():
    connection = pymysql.connect(host='127.0.0.1',
                                 port=53306,
                                 user='user',
                                 password='user',
                                 db='user',
                                 charset='utf8mb4',
                                 cursorclass=pymysql.cursors.DictCursor)
    return connection


def get_column_list(table_name):
    connection = get_connection()
    result_list = []

    try:
        with connection.cursor() as cursor:
            # Read a single record
            sql = '''
            select COLUMN_NAME, COLUMN_DEFAULT, DATA_TYPE
              from information_schema.`COLUMNS`
             where TABLE_NAME = %s
            '''
            cursor.execute(sql, (table_name,))
            result_set = cursor.fetchall()
            for row in result_set:
                result_list.append(row)
    finally:
        connection.close()

    return result_list


def mybatis_camelcase_converter(str):
    """
    COLUMN_TYPE 이런 형식의 컬럼을 앞이 소문자인 camelcase로 바꿔준다.
    :param str: column name
    :return: camelcase str
    """
    result = str.title()
    result = result.replace('_', '')
    result = result[0].lower() + result[1:]
    return result


def create_insert_statement(table_name):
    """
    테이블 명을 받아서 insert 구문을 만든다.
    :param table_name: 테이블명
    :return: str 형식의 insert 구문
    """
    result_dict = get_column_list(table_name)

    result_str = 'INSERT INTO ' + table_name
    column_str = '('

    for i in result_dict:
        if i['COLUMN_DEFAULT'] is None:
            column_str = column_str + '\n' + i['COLUMN_NAME'] + ","

    column_str = column_str[0:len(column_str) - 1]
    column_str += '\n)\nVALUES'

    values_str = '('

    for i in result_dict:
        if i['COLUMN_DEFAULT'] is None:
            values_str = values_str + '\n' + "#{" + mybatis_camelcase_converter(i['COLUMN_NAME']) + "},"

    values_str = values_str[0:len(values_str) - 1]
    values_str += '\n)\n'

    insert_statement = result_str + column_str + values_str
    return insert_statement


def create_update_statement(table_name):
    """
    테이블 명을 받아서 update 구문을 만든다.
    :param table_name: 테이블명
    :return: str 형식의 update 구문    
    """
    result_dict = get_column_list(table_name)
    result_str = 'UPDATE ' + table_name + "\n"
    result_str += '<set>\n'
    first = True
    for i in result_dict:
        org_str = i['COLUMN_NAME']
        cvt_str = mybatis_camelcase_converter(i['COLUMN_NAME'])

        if i['COLUMN_DEFAULT'] is None:
            result_str += '<if test="' + cvt_str + ' != null and ' + cvt_str + ''' != ''">\n'''
            result_str += org_str + " = " + '#{' + cvt_str + '},\n'
            result_str += '</if>\n'

    result_str += '</set>'
    return result_str


def create_vo_variables(table_name):
    """
    테이블 명을 받아서 vo에 쓸 변수 리스트를 뽑아낸다.
    :param table_name: 테이블명 str
    :return: 변수리스트 str
    """
    result_dict = get_column_list(table_name)
    result_str = ''

    for i in result_dict:
        org_str = i['COLUMN_NAME']
        cvt_str = mybatis_camelcase_converter(i['COLUMN_NAME'])
        data_type = ''
        double = False

        if i['DATA_TYPE'] == 'int':
            data_type = 'int'
        elif i['DATA_TYPE'] == 'date':
            data_type = 'Date'
        elif i['DATA_TYPE'] == 'datetime':
            data_type = 'Timestamp'
        elif i['DATA_TYPE'] in ['longtext', 'varchar']:
            data_type = 'String'
        elif i['DATA_TYPE'] == 'point':
            double = True
            data_type = 'double'
        elif i['DATA_TYPE'] == 'tinyint':
            data_type = 'boolean'

        if double:
            result_str += 'private ' + data_type + ' x' + cvt_str[0].upper() + cvt_str[1:] + ';\n'
            result_str += 'private ' + data_type + ' y' + cvt_str[0].upper() + cvt_str[1:] + ';\n'
        else:
            result_str += 'private ' + data_type + ' ' + cvt_str + ';\n'

    return result_str


if __name__ == "__main__":
    table_name = 'sms'
    print(get_column_list(table_name))
    print(create_insert_statement(table_name))
    print(create_update_statement(table_name))
    print(create_vo_variables(table_name))

실행 결과

[{'COLUMN_NAME': 'sms_id', 'COLUMN_DEFAULT': None, 'DATA_TYPE': 'int'}, {'COLUMN_NAME': 'auth_number', 'COLUMN_DEFAULT': None, 'DATA_TYPE': 'int'}, {'COLUMN_NAME': 'is_used', 'COLUMN_DEFAULT': None, 'DATA_TYPE': 'tinyint'}, {'COLUMN_NAME': 'created_at', 'COLUMN_DEFAULT': 'CURRENT_TIMESTAMP', 'DATA_TYPE': 'datetime'}, {'COLUMN_NAME': 'updated_at', 'COLUMN_DEFAULT': 'CURRENT_TIMESTAMP', 'DATA_TYPE': 'datetime'}]
INSERT INTO sms(
sms_id,
auth_number,
is_used
)
VALUES(
#{smsId},
#{authNumber},
#{isUsed}
)

UPDATE sms
<set>
<if test="smsId != null and smsId != ''">
sms_id = #{smsId},
</if>
<if test="authNumber != null and authNumber != ''">
auth_number = #{authNumber},
</if>
<if test="isUsed != null and isUsed != ''">
is_used = #{isUsed},
</if>
</set>
private int smsId;
private int authNumber;
private boolean isUsed;
private Timestamp createdAt;
private Timestamp updatedAt;

 

설명이 필요 없을 것 같다. 소스가 병맛인건 내가 졸면서 했기 때문이다.

댓글로 욕하지 마시고 그냥 혀만 차고 가시길 바란다.

 

Application 부분

import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
import mysqlconnector

win = tk.Tk()

win.title("mysql_creator")

win.resizable(0, 0)

radio_val = tk.StringVar()

# label
a_label = ttk.Label(win, text="INSERT TABLE NAME: ")
a_label.grid(column=0, row=0)

# input box
name = tk.StringVar()
text_edit = ttk.Entry(win, width=10, textvariable=name)
text_edit.grid(column=1, row=0)


# 라이브러리와 연동하여 결과를 가져옴.
def get_result(event):
    if len(name.get()) > 0:
        result_str = ''
        if radio_val.get() == "i":
            result_str = mysqlconnector.create_insert_statement(name.get())
            scr.delete('0.0', tk.END)
            scr.insert(tk.INSERT, result_str)
        elif radio_val.get() == "u":
            result_str = mysqlconnector.create_update_statement(name.get())
            scr.delete('0.0', tk.END)
            scr.insert(tk.INSERT, result_str)
        elif radio_val.get() == "v":
            result_str = mysqlconnector.create_vo_variables(name.get())
            scr.delete('0.0', tk.END)
            scr.insert(tk.INSERT, result_str)
        # 결과를 클립보드에 자동으로 복사함.
        win.clipboard_clear()
        win.clipboard_append(result_str)
        return
    else:
        a_label.configure(foreground='red')

# 엔터키에 이벤트 바인딩
text_edit.bind('<Return>', get_result)
events = tk.Event
submit_button = ttk.Button(win, text="create it!")
# 클릭에 이벤트 바인딩
submit_button.bind('<Button>', get_result)
submit_button.grid(column=2, row=0)

result_txt = tk.StringVar()
scr = scrolledtext.ScrolledText(win, width=100, height=30, wrap=tk.WORD, undo=True)
scr.grid(column=0, columnspan=15)

MODES = [
    ("Insert", "i"),
    ("update", "u"),
    ("VO", "v"),
]

# 라디오 버튼 초기화
radio_val.set("i")  

i = 3
for text, mode in MODES:
    b = tk.Radiobutton(win, text=text, variable=radio_val, value=mode)
    b.grid(column=i, row=0)
    i += 1

win.mainloop()

 

어려운 것은 없다. tkinter처음 써보는 분들이 참고 하시면 도움이 될 것 같기도 하다.

근데 요즘 누가 os application을 만드려나 모르겠다.

 

완성하면

mysqlcreator1

mysqlcreator2

mysqlcreator3

mysqlcreator4

 

마무리

  •  원래는 beautify 까지 해주려고 했는데, 귀찮아서 포기했다.
  •  또 vo 에서 getter, setter, tostring 등도 자동 생성해주려고 했는데 그냥 intellij에서 alt+insert 를 쓰자
  •  간단하게 내가 필요한 프로그램을 빠르게 만든다는 점에서는 굉장히 즐거운 작업이었다.
  •  그러나 누군가에게 내 소스코드를 까보여주었을 때의 부끄러움도 앞으로 고려해봐야겠다.

 

[Python] pymysql로 select 절 가져오기

pymysql은 python으로 mysql에 연결할 수 있는 라이브러리다.

간단하게 informationschema에서 table을 변수로 받아 컬럼정보를 가져오는 select 절을 날리는 코드를 짜보았다.

import pymysql

def get_column_list(table_name):
    connection = pymysql.connect(host='127.0.0.1',
                                 port=53306,
                                 user='root',
                                 password='root',
                                 db='root',
                                 charset='utf8mb4',
                                 cursorclass=pymysql.cursors.DictCursor)
# utf8mb4 는 mysql에서 사용하는 4byte utf-8 문자열을 의미한다.
    result_list = []

    try:
        with connection.cursor() as cursor:
            # Read a single record
            sql = '''
            select COLUMN_NAME, COLUMN_DEFAULT, DATA_TYPE
              from information_schema.`COLUMNS`
             where TABLE_NAME = %s
            '''
            cursor.execute(sql, (table_name,))
            result_set = cursor.fetchall()
            for row in result_set:
                result_list.append(row)
    finally:
        connection.close()

    return result_list


print(get_column_list('sms'))

결과

[
{
"COLUMN_DEFAULT":None,
"DATA_TYPE":"int",
"COLUMN_NAME":"sms_id"
},
{
"COLUMN_DEFAULT":None,
"DATA_TYPE":"int",
"COLUMN_NAME":"auth_number"
},
{
"COLUMN_DEFAULT":None,
"DATA_TYPE":"tinyint",
"COLUMN_NAME":"is_used"
},
{
"COLUMN_DEFAULT":"CURRENT_TIMESTAMP",
"DATA_TYPE":"datetime",
"COLUMN_NAME":"created_at"
},
{
"COLUMN_DEFAULT":"CURRENT_TIMESTAMP",
"DATA_TYPE":"datetime",
"COLUMN_NAME":"updated_at"
}
]

물론 이결과는 beautifier를 거친 결과다.

[Python] tkinter 엔터키에 이벤트 주기

같은 이벤트를 각각 버튼과 엔터키에 바인딩 하려면 어떻게 해야할까?

보통 컴포넌트를 생성하면서 command=event_name
이런식으로 이벤트를 준다.

깔끔하게 이벤트 처리를 하기 위해선, command를 쓰는 것 보다 bind를 쓰는게 낫다.

# 엔터키에 이벤트가 바인딩 된다.
text_edit.bind('<Return>', submit_button_by_enter)
# 마우스 클릭에 이벤트가 바인딩 된다.
submit_button.bind('<Button>', submit_button_by_enter)

이런식으로 bind 메소드를 활용하여 깔끔하게 이벤트를 바인딩 하자.

마우스클릭 이벤트와 엔터이벤트를 구분하면서 한 function으로 해결하려면 이방법 을 써도 된다.

 

참고

"""Container for the properties of an event.

    Instances of this type are generated if one of the following events occurs:

    KeyPress, KeyRelease - for keyboard events
    ButtonPress, ButtonRelease, Motion, Enter, Leave, MouseWheel - for mouse events
    Visibility, Unmap, Map, Expose, FocusIn, FocusOut, Circulate,
    Colormap, Gravity, Reparent, Property, Destroy, Activate,
    Deactivate - for window events.

    If a callback function for one of these events is registered
    using bind, bind_all, bind_class, or tag_bind, the callback is
    called with an Event as first argument. It will have the
    following attributes (in braces are the event types for which
    the attribute is valid):

        serial - serial number of event
    num - mouse button pressed (ButtonPress, ButtonRelease)
    focus - whether the window has the focus (Enter, Leave)
    height - height of the exposed window (Configure, Expose)
    width - width of the exposed window (Configure, Expose)
    keycode - keycode of the pressed key (KeyPress, KeyRelease)
    state - state of the event as a number (ButtonPress, ButtonRelease,
                            Enter, KeyPress, KeyRelease,
                            Leave, Motion)
    state - state as a string (Visibility)
    time - when the event occurred
    x - x-position of the mouse
    y - y-position of the mouse
    x_root - x-position of the mouse on the screen
             (ButtonPress, ButtonRelease, KeyPress, KeyRelease, Motion)
    y_root - y-position of the mouse on the screen
             (ButtonPress, ButtonRelease, KeyPress, KeyRelease, Motion)
    char - pressed character (KeyPress, KeyRelease)
    send_event - see X/Windows documentation
    keysym - keysym of the event as a string (KeyPress, KeyRelease)
    keysym_num - keysym of the event as a number (KeyPress, KeyRelease)
    type - type of the event as a number
    widget - widget in which the event occurred
    delta - delta of wheel movement (MouseWheel)
    """