'개발'에 해당되는 글 6건

개발



일반적으로 mysql을 다룰 때 mysql connector c api를 사용한다.

이미 훌륭한 예제들도 많거니와 트러블 슈팅에 대한 자료도 많아서 사용하기에 그리 어렵지 않다.



이번에 한글문제로 기존 mysql c api로 구축된 소스를 mysql c++ api로 전환하자마자 부딪힌 문제가 바로

Commands out of sync;


mysql c api의 경우에는 이를 해결하기 위한 솔루션이 많이 검색되는데 c++ api는 아무리 검색을 해도 찾을 수가 없던 중에,

중국 친구의 블로그에서 해결 방법을 알아내게 되었다.


https://stackoverflow.com/questions/17115919/mysql-commands-out-of-sync-error-c-connector 요기와

https://www.cnblogs.com/sixbeauty/p/4798879.html 요기를 보자


요점은 stored precedure를 호출하거나 multi query의 경우에는 result를 비워줘야 한다이다.

어떤 곳에서는 이 문제를 해결하기 위해 CLIENT_MULTI_STATEMENTS 나 CLIENT_MULTI_RESULTS 를 enable 시키면 된다고 하는데,

나의 경우에는 동일한 문제가 계속 발생했다.


위의 블로그 글을 참고하여서 result가 있던 없던 무조건 아래의 소스를 추가하였더니 짠~ 하고 해결이 되어 버렸다.


while(res->next())

{

    ;

}


while(pstmt->getMoreResults())

{

    res.reset(pstmt->getResultSet());

}


// shared_ptr<sql::PreparedStatement> pstmt

// shared_ptr<sql::ResultSet> res


포인트는 getMoreResults() 이 부분인데, 왜 그런지는 잘 모르겠다.

(난 전문 개발자가 아니니까 그냥 무시하기로 했다 ㅠ_-)


어쨌든 해결;;;




개발


구글 애드센스 게재 기념 새로운 포스트 시리즈를 작성하려 합니다.

이름하야 보안 정적 소스코드 분석기 만들기!!!


어디까지 진행될지는 모르겠지만 Java를 대상으로 틈날 때마다 조금씩 작성해 보겠습니다.



Static Analysis vs Dynamic Analysis


Static Analysis는 대상 프로그램의 실행 없이 프로그램을 분석하는 것을 의미합니다. Dynamic Analysis는 반대로 프로그램을 실행하면서 프로그램을 분석하는 것이 되겠죠.

전자의 경우 코드 작성 후 리팩토링을 한다거나, 코드 작성 중에 에러를 수정하는 등이 포함되고 후자의 경우 리버싱이나 퍼징 등이 포함됩니다.

사실 이건 이거다라고 정확하게 구분짓기가 모호하기 때문에 의미 정도만 알고 있는 것이 좋습니다. ^^;


두 방법은 서로 장단점을 가지고 있습니다.

예를 들어 Dynamic Analysis의 경우 어떤 값이 입력되었을 때의 출력 값이 명확하기 때문에 출력되는 값을 기반으로 여러가지 테스트를 할 수 있지만 실행되지 않는 코드에 대한 테스트는 불가능합니다.

반면 Static Analysis의 경우 코드로만 판단해야 하기 때문에 출력 값을 기반으로 하는 테스트가 어렵지만, 대신 빠르고 폭 넓은 테스트가 가능합니다.


이런 장단점이 있기 때문에 어느 방식이 더 좋다라고는 말할 수 없습니다.

Static Analysis의 경우 단점을 극복하기 위해 Symbolic Execution이나 Concolic Execution 같은 분석 방법을 사용하기도 합니다.


우리가 만들어 볼 것은 Static Analysis에 기반한 Analyzer입니다.



사람 vs 기계


분석 방법과 더불어 분석하는 주체가 사람이냐 기계냐에 따라서도 장단점이 나뉘어 집니다.

아시다시피 기계의 경우 빠른 속도, 쉬지 않고 일하는 장점이 있는 반면 오탐과 과탐이 많습니다.

사람의 경우는 아무래도 퍼포먼스 이슈가 빠지지 않죠.


예를 들어 String asdf = korkf0*a_21 와 같은 문자열이 있다고 가정해봅시다.

사람이라면 문자열을 보자마자 어딘가에 사용되는 암호화 key 라던가, 패스워드가 아닌지 의심할 수 있습니다.

반면 기계의 경우 password와 유사한 단어가 매칭되는 것도 아니고, 패턴이 digit로 이루어져 있지도 않고 신용카드, 주민등록번호 패턴과도 매칭되지 않아 무시할 확률이 높습니다.


사람이 눈으로 보자마자 정보를 인식하고 판단하는 정도로 기계가 동작하려면 요즘 유행하는 AI가 제대로 발전해야 하지 않을까 싶습니다. 따라서 대부분의 Static Analyzer들은 오탐/과탐/미탐 확률이 높습니다.




Taint Analysis 


앞으로 만들어 볼 Static Code Analyzer는 전통적인 Taint Analysis 방법을 사용할 겁니다.

Taint Analysis는 입력 값이 오염되었다고 가정하고 입력 값의 흐름에 포함된 모든 코드들도 역시 오염된 것으로 간주하여 분석하는 방법입니다.

이런 분석 방법을 사용하는 유명한 리버싱 툴로는 Pin이 있으며 Pin은 Taint Analysis를 적용해 Dynamic Analysis 를 합니다.




Control Flow Graph


Taint Analysis를 위해서는 입력 값이 흘러가는 길을 알고 있어야 합니다.

Control Flow Graph는 Basic Block라고 하는 Node들로 이루어진 그래프입니다. 여기서 Basic Block는 분기가 없는 코드의 흐름을 의미합니다.

예를 들어 Control Flow Graph는 아래와 같이 표현됩니다.


[http://www.drgarbage.com/control-flow-graph-factory/] 에서 발췌


위와 같이 Control Flow Graph가 완성되면 입력 값의 흐름을 추적할 수 있게 됩니다.


이제 이 배경지식을 가지고 다음 포스트에서 실제 프로그램을 만들어보도록 하겠습니다.



읽어주셔서 감사합니다.



'개발' 카테고리의 다른 글

Commands out of sync 문제  (0) 2017.11.27
따라하는 python 로또 분석(4)  (0) 2016.07.11
따라하는 python 로또 분석(3)  (0) 2016.07.04
따라하는 python 로또 분석(2)  (0) 2016.06.28
따라하는 python 로또 분석 (1)  (0) 2016.06.21
개발


이 글은 python 초보자들을 대상으로 하는 로또 데이터 분석에 관련된 글입니다.

이전 포스트는 아래 링크를 따라가시기 바랍니다.


2016/07/04 - [개발] - 따라하는 python 로또 분석(3)

2016/06/28 - [개발] - 따라하는 python 로또 분석(2)

2016/06/21 - [개발] - 따라하는 python 로또 분석 (1)



이번 포스트에서는 다른 분석을 한번 해 보도록 하겠습니다.


각 회차별 추첨 번호간 거리를 한번 알아보는 건 어떨까요? 갑자기 제가 궁금해졌습니다. -_-;


아래는 제 1회차 당첨번호 + 보너스 번호입니다.


10, 23, 29, 33, 37, 40, 16


첫 번째 번호가 10일 때 두 번째 번호 사이의 거리는 23 - 10 = 13입니다.

두 번째 번호와 세 번째 번호 사이의 거리는 얼마일까요? 제 질문이 어려웠나요? ^^;

정답은 29 - 23 = 6입니다.


이런 방식으로 각 번호 사이의 거리를 계산해보면 아래와 같습니다.

아, 물론 거리는 절대값입니다 ^^


첫 번째 자리 수가 10일 때, 13, 6, 4, 4, 3, 24



자, 이제 python 코드로 해볼까요?

참, 절대값으로 변환하는 python 함수는 abs() 입니다. ^^


아래는 거리를 계산하기 위해 추가된 distance  함수입니다.


def distance():
    db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
    cursor = db.cursor()

    try:
        cursor.execute("select * from data")
        results = cursor.fetchall()

        for row in results:
            print(row[0], "회차: ", end = "")
            print("1'st", row[1], "| ", end = "")

            for i in range(1, 7):
                print(abs(row[i + 1] - row[i]), " ", end="")

            print(" ")

    except:
        print(sys.exc_info()[0])

    cursor.close()
    db.close()


위의 코드를 추가하고 main 함수를 아래와 같이 수정합니다.



def main():
    last = getLast()
    dblast = checkLast()

    if dblast < last:
        print("최신 회차는 " + str(last) + " 회 이며, 데이터베이스에는 " + str(dblast) + "회 까지 저장되어 있습니다.")
        print("업데이트를 시작합니다.")
        crawler(dblast, last)

    insert()
    #analysis()
    distance()

if __name__ == "__main__":
    main()


이제 코드를 실행하면 아래와 같이 출력됩니다.



어느새 709회차 까지 왔군요. ^^;



그런데 위의 결과를 보니 각 데이터를 사이의 연관성이 잘 눈에 띄지 않습니다.

로또 번호는 정말로 랜덤인걸까요? 아니면 모수가 적어서 눈에 안 띄는 걸까요?


혹시 첫 번째로 뽑인 숫자들이 동일할 때 각 회차별 번호 간 거리는 어떻게 되는지 궁금하시지 않나요? ^^;


distance 함수를 아래와 같이 수정해 보겠습니다.

그냥 sql 문만 아래와 같이 수정하면 됩니다. ㅎㅎㅎ


cursor.execute("select * from data order by `1` asc")



수정된 distance 함수입니다.



def distance():
    db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
    cursor = db.cursor()

    try:
        cursor.execute("select * from data order by `1` asc")
        results = cursor.fetchall()

        for row in results:
            print("1'st", row[1], "| ", end="")

            for i in range(1, 7):
                print(abs(row[i + 1] - row[i]), " ", end="")

            print(" ")

    except:
        print(sys.exc_info()[0])

    cursor.close()
    db.close()


출력 결과를 보기 좋도록 하기 위해 회차 정보를 살짝 뺐습니다. ^^;


위의 코드를 실행한 결과는 아래와 같습니다.




예전 포스트에서의 기억을 떠올려보면 첫 번째 숫자로 가장 많이 나온 숫자는 1이었습니다.

위의 실행 결과에서는 잘렸지만, 스크롤을 올려 첫 번째 숫자로 1이 나왔을 때의 숫자 간격을 보면 아래와 같습니다.




제일 많이 나온 것 치고는 별로 공통되는 점은 없어 보입니다.

아쉽게도 이 방법으로는 로또 1등 당첨 숫자 예측이 어렵겠네요 ㅠ_-


위의 distance 함수를 조금 수정하면 각 자리별 숫자가 동일할 때의 간격을 뽑아볼 수도 있습니다.

이 부분은 여러분께 과제로 남기도록 하겠습니다. (귀찮아서 그러는 건 아니라고 믿어주세요;;;)



전체 소스코드는 아래에 올려두었습니다.

lotto.py




이상으로 이번 포스트를 마치겠습니다.

다음 포스트에서도 다른 방법으로 데이터를 분석해 보도록 하겠습니다.



읽어 주셔서 감사합니다.



'개발' 카테고리의 다른 글

Commands out of sync 문제  (0) 2017.11.27
Develop Security Static Code Analyzer (1)  (0) 2017.05.15
따라하는 python 로또 분석(3)  (0) 2016.07.04
따라하는 python 로또 분석(2)  (0) 2016.06.28
따라하는 python 로또 분석 (1)  (0) 2016.06.21
개발



이 글은 python 초보자들을 대상으로 하는 로또 데이터 분석에 관련된 글입니다.

이전 포스트는 아래 링크를 따라가시기 바랍니다.


2016/06/21 - [개발] - 따라하는 python 로또 분석 (1)


2016/06/28 - [개발] - 따라하는 python 로또 분석(2)





이번 포스트부터는 드디어 제목에 맞게 로또 데이터를 분석해 보도록 하겠습니다.


첫 시간에는 각 숫자의 출현 빈도를 알아보도록 하겠습니다.


뭐, 이거는 컴퓨터 전공이라면 자료구조나 알고리즘 시간에 다들 배우셨을 텐데요,

굳이 설명할 필요가 있나 싶지만 혹시 또 모르니까 진행해 보도록 하겠습니다.


숫자의 출현 빈도를 어떻게 계산할 수 있을까요?


리스트 또는 배열을 이용할 수 있는데요, 간단한 배열을 이용한 방식을 사용해 보도록 하겠습니다.


먼저 1부터 45까지의 배열을 생성합니다.

그리고 데이터베이스에 저장된 각 회차별 당첨번호들을 하나씩 순회하면서 해당 숫자의 배열의 카운트를 하나씩 증가시키면 됩니다.



배열 초기화

인덱스       ->          1    2   3   4   ....   45

데이터       ->        | 0 | 0 | 0 | 0 | .... | 0 |



최초의 배열은 위와 같이 모두 0으로 초기화 되어 있습니다.

만약 보너스 번호를 포함한 1회차 당첨 번호가 1, 11, 21, 31, 41,  2, 12 라면,

배열[1] = 배열[1]++, 배열[11] = 배열[11]++... 이런 식으로 증가시켜 주면 됩니다.



요 부분을 코드에 추가시켜 보겠습니다.

수정된 코드는 아래와 같습니다.



# lotto.py

import requests
from bs4 import BeautifulSoup
import MySQLdb
import sys

# 웹 크롤링 한 결과를 저장할 리스트
lotto_list = []

# 로또 웹 사이트의 첫 주소
main_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin"

# 각 회차별 당첨정보를 알 수 있는 주소
basic_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin&drwNo="


def getLast():
	resp = requests.get(main_url)
	soup = BeautifulSoup(resp.text, "lxml")
	line = str(soup.find("meta", {"id" : "desc", "name" : "description"})['content'])

	begin = line.find(" ")
	end = line.find("회")

	if begin == -1 or end == -1:
	    print("not found last lotto number")
	    exit()

	return int(line[begin + 1 : end])


def checkLast():
	db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
	cursor = db.cursor()

	sql = "SELECT MAX(count) FROM data"
	try:
	    cursor.execute(sql)
	    result = cursor.fetchone()
	except:
	    print(sys.exc_info()[0])

	cursor.close()
	db.close()

	return result[0]


def crawler(fromPos, toPos):

	for i in range(fromPos + 1, toPos + 1):
		
		crawler_url = basic_url + str(i)
		print("crawler: " + crawler_url)

		resp = requests.get(crawler_url)
		soup = BeautifulSoup(resp.text, "lxml")
		line = str(soup.find("meta", {"id" : "desc", "name" : "description"})['content'])

		begin = line.find("당첨번호")
		begin = line.find(" ", begin) + 1
		end = line.find(".", begin)
		numbers = line[begin:end]

		begin = line.find("총")
		begin = line.find(" ", begin) + 1
		end = line.find("명", begin)
		persons = line[begin:end]

		begin = line.find("당첨금액")
		begin = line.find(" ", begin) + 1
		end = line.find("원", begin)
		amount = line[begin:end]

		info = {}
		info["회차"] = i
		info["번호"] = numbers
		info["당첨자"] = persons
		info["금액"] = amount

		lotto_list.append(info)

def insert():
	db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
	cursor = db.cursor()

	for dic in lotto_list:
		count = dic["회차"]
		numbers = dic["번호"]
		persons = dic["당첨자"]
		amounts = dic["금액"]

		print("insert to database at " + str(count))

		numberlist = str(numbers).split(",")

		sql = "INSERT INTO `lotto`. `data`(`count`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `persion`, `amount`) " \
		      "VALUES('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%s')" \
		      % (count,
		         int(numberlist[0]),
		         int(numberlist[1]),
		         int(numberlist[2]),
		         int(numberlist[3]),
		         int(numberlist[4]),
		         int(numberlist[5].split("+")[0]),
		         int(numberlist[5].split("+")[1]),
		         int(persons),
		         str(amounts))

		try:
		    cursor.execute(sql)
		    db.commit()
		except:
		    print(sys.exc_info()[0])
		    db.rollback()
		    break

	cursor.close()
	db.close()

def analysis(myarray):
	db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
	cursor = db.cursor()

	# 처음 뽑힌 숫자들 전체를 조회
	sql = "select `1` from data"
    
	try:
		cursor.execute(sql)
		results = cursor.fetchall()

		# 해당 숫자의 뽑힌 횟수를 하나씩 증가
		for row in results:
			i = row[0]
			count = myarray[i]
			myarray[i] = count + 1
	except:
		print(sys.exc_info()[0])
		
	cursor.close()
	db.close()

def main():
	last = getLast()
	dblast = checkLast()

	if dblast < last:
		print("최신 회차는 " + str(last) + " 회 이며, 데이터베이스에는 " + str(dblast) + "회 까지 저장되어 있습니다.")
		print("업데이트를 시작합니다.")
		crawler(dblast, last)
		
	insert()

	# 0부터 45까지의 배열을 생성하고 0으로 초기화
	myarray = [0 for i in range(46)]
	analysis(myarray)

	for i in range(1, len(myarray)):
		if (i % 10) == 0:
			print("")
		print("[" + str(i) + ":" + str(myarray[i]) + "]", end=" ")
	print()

		
if __name__ == "__main__":
    main()


anaysis() 함수가 추가되었습니다.


myarray라는 0부터 45까지의 배열을 생성하여 0으로 초기화 한 후 analysis 함수에 전달합니다.



위 코드를 실행하면 아래와 같은 결과가 나옵니다.




로또 공식 사이트에서 제공하는 당첨 번호는 정렬된 번호가 아니라 뽑힌 순서별로 되어 있습니다. 


그러므로 위의 데이터 분석 결과를 보면,

매 회차 로또에서 첫 번째로 뽑히는 숫자의 출현 빈도는 1이 105번으로 가장 많습니다.

두 번째는 2가 73회의 출현 빈도를 가졌습니다.



이번에는 위를 모든 순서에 적용해서 가장 많이 출현한 숫자대로 정렬을 해 보겠습니다.

즉, 첫 번째 추첨에서 가장 많이 나온 숫자부터 보너스 숫자 추첨에서 가장 많이 나온 숫자까지를 뽑아보도록 하겠습니다.


analysis() 함수에 for 루프만 살짝 추가하면 됩니다. ^^

이렇게 수정된 코드는 아래와 같습니다.


# lotto.py

import requests
from bs4 import BeautifulSoup
import MySQLdb
import sys


# 웹 크롤링 한 결과를 저장할 리스트
lotto_list = []

# 로또 웹 사이트의 첫 주소
main_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin"

# 각 회차별 당첨정보를 알 수 있는 주소
basic_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin&drwNo="


def getLast():
    resp = requests.get(main_url)
    soup = BeautifulSoup(resp.text, "lxml")
    line = str(soup.find("meta", {"id": "desc", "name": "description"})['content'])

    begin = line.find(" ")
    end = line.find("회")

    if begin == -1 or end == -1:
        print("not found last lotto number")
        exit()

    return int(line[begin + 1: end])


def checkLast():
    db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
    cursor = db.cursor()

    sql = "SELECT MAX(count) FROM data"
    try:
        cursor.execute(sql)
        result = cursor.fetchone()
    except:
        print(sys.exc_info()[0])

    cursor.close()
    db.close()

    return result[0]


def crawler(fromPos, toPos):
    for i in range(fromPos + 1, toPos + 1):
        crawler_url = basic_url + str(i)
        print("crawler: " + crawler_url)

        resp = requests.get(crawler_url)
        soup = BeautifulSoup(resp.text, "lxml")
        line = str(soup.find("meta", {"id": "desc", "name": "description"})['content'])

        begin = line.find("당첨번호")
        begin = line.find(" ", begin) + 1
        end = line.find(".", begin)
        numbers = line[begin:end]

        begin = line.find("총")
        begin = line.find(" ", begin) + 1
        end = line.find("명", begin)
        persons = line[begin:end]

        begin = line.find("당첨금액")
        begin = line.find(" ", begin) + 1
        end = line.find("원", begin)
        amount = line[begin:end]

        info = {}
        info["회차"] = i
        info["번호"] = numbers
        info["당첨자"] = persons
        info["금액"] = amount

        lotto_list.append(info)


def insert():
    db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
    cursor = db.cursor()

    for dic in lotto_list:
        count = dic["회차"]
        numbers = dic["번호"]
        persons = dic["당첨자"]
        amounts = dic["금액"]

        print("insert to database at " + str(count))

        numberlist = str(numbers).split(",")

        sql = "INSERT INTO `lotto`. `data`(`count`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `persion`, `amount`) " \
              "VALUES('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%s')" \
              % (count,
                 int(numberlist[0]),
                 int(numberlist[1]),
                 int(numberlist[2]),
                 int(numberlist[3]),
                 int(numberlist[4]),
                 int(numberlist[5].split("+")[0]),
                 int(numberlist[5].split("+")[1]),
                 int(persons),
                 str(amounts))

        try:
            cursor.execute(sql)
            db.commit()
        except:
            print(sys.exc_info()[0])
            db.rollback()
            break

    cursor.close()
    db.close()


def analysis():
    db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
    cursor = db.cursor()

    for i in range(1, 8):

        sql = "select `"
        sql += str(i)
        sql += "` from data"

        try:
            cursor.execute(sql)
            results = cursor.fetchall()

            # 해당 숫자의 뽑힌 횟수를 하나씩 증가
            myarray = [0 for i in range(46)]
            for row in results:
                k = row[0]
                count = myarray[k]
                myarray[k] = count + 1
            print(i, myarray.index(max(myarray)))
        except:
            print(sys.exc_info()[0])

    cursor.close()
    db.close()


def main():
    last = getLast()
    dblast = checkLast()

    if dblast < last:
        print("최신 회차는 " + str(last) + " 회 이며, 데이터베이스에는 " + str(dblast) + "회 까지 저장되어 있습니다.")
        print("업데이트를 시작합니다.")
        crawler(dblast, last)

    insert()
    analysis()

if __name__ == "__main__":
    main()



위 코드를 실행하면 아래와 같은 결과를 볼 수 있습니다.




로또 추첨 708회차가 진행되는 동안 첫 번째 자리에 가장 많이 나온 번호는 1, 두 번째 자리에 가장 많이 나온 번호는 8, 세 번째는 20, 순서대로 27, 38, 45 였고, 보너스 번호는 43이 가장 많이 나왔습니다.



전체 소스 코드는 아래에 첨부하였습니다.

lotto.py




이것으로 이번 포스트를 마치도록 하겠습니다.


다음 포스트에서는 조금 더 다양한 방법으로 로또 데이터 분석을 시도해 보도록 하겠습니다.



읽어 주셔서 감사합니다.


'개발' 카테고리의 다른 글

Commands out of sync 문제  (0) 2017.11.27
Develop Security Static Code Analyzer (1)  (0) 2017.05.15
따라하는 python 로또 분석(4)  (0) 2016.07.11
따라하는 python 로또 분석(2)  (0) 2016.06.28
따라하는 python 로또 분석 (1)  (0) 2016.06.21
개발



이 글은 python 초보자들을 대상으로 하는 로또 데이터 분석에 관련된 글입니다.

이전 포스트는 아래 링크를 따라가시기 바랍니다.


2016/06/21 - [개발] - 따라하는 python 로또 분석 (1)




3. 추출한 데이터 저장하기


지금까지 우리는 python을 이용하여 로또 사이트에서 데이터를 추출하는 법을 다루었습니다.


이번엔 받아온 데이터를 데이터베이스에 저장해 볼 차례입니다.

sqlite나 mongodb등이 유행인데요, 저는 이미 mysql이 깔려있던 관계로 mysql을 그대로 사용하였습니다.



python 모듈 : MySQLdb


MySQLdb - python에서 mysql을 사용하기 위한 라이브러리.


이번에도 pip3를 이용하여 위의 모듈을 설치해 줍니다.


저는 lotto라는 데이터베이스를 만들고 data라는 테이블을 추가하였습니다.

data 테이블 구조는 아래와 같습니다.




보너스 숫자를 포함한 전체 7개의 로또 숫자를 각 column으로 구분하여서 저장하도록 했습니다.

또 당첨횟수, 당첨자수, 당첨금액은 각각 count, persion, amount라는 column에 저장하도록 하였습니다.

모든 column들은 not null 설정을, 당첨금액을 제외한 모든 column들은 INT형으로 설정하였습니다.

당첨횟수 count는 인덱스로 사용하기 위해 pk, unique, unsigned를 지정해 주었습니다. 

그리고 해당 데이터베이스에 연결되는 유저와 패스워드는 lotto/lotto로 설정하였습니다.



그럼 해당 설정을 이용하여 python에서 mysql로 연결이 잘 되는지를 테스트 해 볼 차례입니다.

수정된 코드는 아래와 같습니다.


# lotto.py

import requests
from bs4 import BeautifulSoup
import MySQLdb

def main():
	basic_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin&drwNo="
	for i in range(1, 707):
		resp = requests.get(basic_url + str(i))
		soup = BeautifulSoup(resp.text, "lxml")
		line = str(soup.find("meta", {"id" : "desc", "name" : "description"})['content'])
		
		print("당첨회차: " + str(i))

		begin = line.find("당첨번호")
		begin = line.find(" ", begin) + 1
		end = line.find(".", begin)
		numbers = line[begin:end]
		print("당첨번호: " + numbers)

		begin = line.find("총")
		begin = line.find(" ", begin) + 1
		end = line.find("명", begin)
		persons = line[begin:end]
		print("당첨인원: " + persons)

		begin = line.find("당첨금액")
		begin = line.find(" ", begin) + 1
		end = line.find("원", begin)
		amount = line[begin:end]
		print("당첨금액: " + amount)


		db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
		cursor = db.cursor()

		sql = "SELECT count(*) FROM data"
		cursor.execute(sql)
		results = cursor.fetchone()
		print("저장된 총 회차: " + str(results[0]))
		db.close()

		break
		
if __name__ == "__main__":
    main()


import MySQLdb를 추가하였고, 데이터베이스에 연결하기 위해서 connect를 호출합니다.

MySQLdb.connect(host, user, password, database)


cursor()를 받아와서 실행시키기 원하는 쿼리를 execute() 함수를 이용해 실행시키면 됩니다.

사용이 끝나면 close()를 이용해 연결을 닫아야 합니다.



위 코드를 실행한 결과는 아래와 같습니다.




저는 이미 데이터베이스에 707회차 까지의 데이터를 저장했기 때문에 707 이라는 숫자가 나왔지만, 이 글을 보고 따라하시는 분들은 아마 0이 나올 겁니다.


자 이제 지금까지 추출한 데이터를 이용해 데이터베이스에 저장하기만 하면 됩니다.

데이터베이스 저장은 일반적인 mysql 쿼리인 insert 구문을 이용하면 됩니다.


데이터베이스에 저장하는 코드를 추가하고 지금까지 만들어진 코드를 정리해보도록 하겠습니다.


먼저 웹 크롤링을 하는 부분과 데이터베이스 저장을 하는 부분을 함수로 구분했습니다.


웹 크롤링을 하는 부분에서는 다운로드 받은 HTML에서 필요한 데이터를 추출하여 리스트에 추가합니다.

웹 크롤링이 끝나면 리스트를 순회하면서 추출한 데이터를 하나씩 데이터베이스에 저장합니다.

데이터베이스 저장 중 에러 발생 시에는 rollback 하도록 에러 핸들링 부분을 추가하였습니다.



수정된 코드는 아래와 같습니다.

# lotto.py

import requests
from bs4 import BeautifulSoup
import MySQLdb
import sys

# 웹 크롤링 한 결과를 저장할 리스트
lotto_list = []

basic_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin&drwNo="

def crawler():
	for i in range(1, 707):
		
		crawler_url = basic_url + str(i)
		print("crawler: " + crawler_url)

		resp = requests.get(crawler_url)
		soup = BeautifulSoup(resp.text, "lxml")
		line = str(soup.find("meta", {"id" : "desc", "name" : "description"})['content'])

		begin = line.find("당첨번호")
		begin = line.find(" ", begin) + 1
		end = line.find(".", begin)
		numbers = line[begin:end]

		begin = line.find("총")
		begin = line.find(" ", begin) + 1
		end = line.find("명", begin)
		persons = line[begin:end]

		begin = line.find("당첨금액")
		begin = line.find(" ", begin) + 1
		end = line.find("원", begin)
		amount = line[begin:end]

		info = {}
		info["회차"] = i
		info["번호"] = numbers
		info["당첨자"] = persons
		info["금액"] = amount

		lotto_list.append(info)

def insert():
	db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
	cursor = db.cursor()

	for dic in lotto_list:
		count = dic["회차"]
		numbers = dic["번호"]
		persons = dic["당첨자"]
		amounts = dic["금액"]

		print("insert to database at " + str(count))

		numberlist = str(numbers).split(",")

		sql = "INSERT INTO `lotto`. `data`(`count`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `persion`, `amount`) " \
		      "VALUES('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%s')" \
		      % (count,
		         int(numberlist[0]),
		         int(numberlist[1]),
		         int(numberlist[2]),
		         int(numberlist[3]),
		         int(numberlist[4]),
		         int(numberlist[5].split("+")[0]),
		         int(numberlist[5].split("+")[1]),
		         int(persons),
		         str(amounts))

		try:
		    cursor.execute(sql)
		    db.commit()
		except:
		    print(sys.exc_info()[0])
		    db.rollback()
		    break

	db.close()

def main():
	crawler()
	insert()
		
if __name__ == "__main__":
    main()


해당 코드를 실행시키면 데이터베이스에 우리가 필요로 하는 데이터들이 무사히(?) 저장되었을 거라는 기대와는 달리 707회차 데이터가 빠져있는 것을 확인하실 수 있습니다.


for i in range(1, 707) 요 부분 때문에 실제로는 1부터 706까지만 웹 크롤링이 됩니다.

이 부분도 수정할겸 새로운 기능도 넣어볼까요?


현재까지 작성된 코드는 무식하게도 실행할 때마다 1회차부터의 결과를 다시 받아옵니다.

요즘처럼 바쁜 현대인이 귀중한 시간을 이렇게 낭비할 수는 없습니다.


그럼 어떻게 하면 될까요?


저는 로또 웹 사이트를 방문해서 가장 최신 회차와 데이터베이스에 저장된 가장 최신 회차를 비교하는 방식을 사용했습니다.

만약 데이터베이스에 저장된 회차보다 웹 사이트의 최신 회차가 더 크다면 해당 회차의 정보를 받아서 데이터베이스에 넣도록 합니다.



수정된 코드는 아래와 같습니다.


# lotto.py

import requests
from bs4 import BeautifulSoup
import MySQLdb
import sys

# 웹 크롤링 한 결과를 저장할 리스트
lotto_list = []

# 로또 웹 사이트의 첫 주소
main_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin"

# 각 회차별 당첨정보를 알 수 있는 주소
basic_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin&drwNo="


def getLast():
	resp = requests.get(main_url)
	soup = BeautifulSoup(resp.text, "lxml")
	line = str(soup.find("meta", {"id" : "desc", "name" : "description"})['content'])

	begin = line.find(" ")
	end = line.find("회")

	if begin == -1 or end == -1:
	    print("not found last lotto number")
	    exit()

	return int(line[begin + 1 : end])


def checkLast():
	db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
	cursor = db.cursor()

	sql = "SELECT MAX(count) FROM data"
	try:
	    cursor.execute(sql)
	    result = cursor.fetchone()
	except:
	    print(sys.exc_info()[0])

	db.close()

	return result[0]


def crawler(fromPos, toPos):

	for i in range(fromPos + 1, toPos + 1):
		
		crawler_url = basic_url + str(i)
		print("crawler: " + crawler_url)

		resp = requests.get(crawler_url)
		soup = BeautifulSoup(resp.text, "lxml")
		line = str(soup.find("meta", {"id" : "desc", "name" : "description"})['content'])

		begin = line.find("당첨번호")
		begin = line.find(" ", begin) + 1
		end = line.find(".", begin)
		numbers = line[begin:end]

		begin = line.find("총")
		begin = line.find(" ", begin) + 1
		end = line.find("명", begin)
		persons = line[begin:end]

		begin = line.find("당첨금액")
		begin = line.find(" ", begin) + 1
		end = line.find("원", begin)
		amount = line[begin:end]

		info = {}
		info["회차"] = i
		info["번호"] = numbers
		info["당첨자"] = persons
		info["금액"] = amount

		lotto_list.append(info)

def insert():
	db = MySQLdb.connect(host="localhost", user="lotto", passwd="lotto", db="lotto")
	cursor = db.cursor()

	for dic in lotto_list:
		count = dic["회차"]
		numbers = dic["번호"]
		persons = dic["당첨자"]
		amounts = dic["금액"]

		print("insert to database at " + str(count))

		numberlist = str(numbers).split(",")

		sql = "INSERT INTO `lotto`. `data`(`count`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `persion`, `amount`) " \
		      "VALUES('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%s')" \
		      % (count,
		         int(numberlist[0]),
		         int(numberlist[1]),
		         int(numberlist[2]),
		         int(numberlist[3]),
		         int(numberlist[4]),
		         int(numberlist[5].split("+")[0]),
		         int(numberlist[5].split("+")[1]),
		         int(persons),
		         str(amounts))

		try:
		    cursor.execute(sql)
		    db.commit()
		except:
		    print(sys.exc_info()[0])
		    db.rollback()
		    break

	db.close()

def main():
	last = getLast()	
	dblast = checkLast()	

	if dblast < last:
		print("최신 회차는 " + str(last) + " 회 이며, 데이터베이스에는 " + str(dblast) + "회 까지 저장되어 있습니다.")
		print("업데이트를 시작합니다.")
		crawler(dblast, last)
		
	insert()
		
if __name__ == "__main__":
    main()


위 코드를 실행하면 아래와 같이 최신 회차만 크롤링 한 후 데이터베이스에 저장하는 것을 볼 수 있습니다.




전체 소스코드는 아래에 첨부하였습니다.

lotto.py




이것으로 이번 포스트를 마치도록 하겠습니다.


다음 포스트에서는 이렇게 저장된 데이터를 이용해서 로또 사이트에서 제공하는 것과 같이 데이터를 분석하는 방법을 알아보도록 하겠습니다.



읽어주셔서 감사합니다.



'개발' 카테고리의 다른 글

Commands out of sync 문제  (0) 2017.11.27
Develop Security Static Code Analyzer (1)  (0) 2017.05.15
따라하는 python 로또 분석(4)  (0) 2016.07.11
따라하는 python 로또 분석(3)  (0) 2016.07.04
따라하는 python 로또 분석 (1)  (0) 2016.06.21
개발


이 글은 python 초보자들을 대상으로 하는 로또 데이터 분석에 관련된 글입니다.


개인적으로는 c++이 제일 편하지만 python에는 라이브러리가 많아 프로그래밍 초보들도 쉽게 만들 수 있다는 장점이 있습니다.

c++로 구현하려면...어휴;;


자. 그럼 시작해 볼까요?


준비물: python 3.x

python 모듈 : requests



1. 로또 당첨 기록 가져오기


먼저 지난 당첨 기록들을 가져와야 합니다.

로또 당첨 기록들은 로또 공식 홈페이지(http://nlotto.co.kr/)에서 가져오도록 하겠습니다.


로또 공식 홈페이지에 접속해서 각 회차별 데이터를 가져오는 크롤러를 만들어야 합니다.

requests 라는 모듈을 쓰면 쉽게 인터넷에 있는 데이터를 가져올 수 있습니다.


아래는 코드입니다.

# lotto.py

import requests

def main():
	main_url = "https://www.nlotto.co.kr/"
	response = requests.get(main_url)
	print(response)

if __name__ == "__main__":
    main()


requests 모듈을 이용해 2줄로 데이터를 가져왔습니다.

참, python3는 기본 인코딩이 utf-8이기 때문에 별도의 utf-8 설정을 할 필요가 없습니다.


위의 코드를 실행하면 아래와 같이 200 response code가 출력됩니다.

http status code 200은 서버가 정상적으로 요청을 처리했음을 뜻하는 값입니다.




가져온 HTML 코드를 보려면 response.text를 출력하면 됩니다.

아래와 같이 코드를 수정해 봅시다.




# lotto.py

import requests

def main():
	main_url = "https://www.nlotto.co.kr/"
	response = requests.get(main_url)
	print(response.text)

if __name__ == "__main__":
    main()


위 코드를 실행한 결과는 아래와 같습니다.




이제 웹 사이트에서 HTML을 가져오는 것까지 되었으니 진짜로 지난 당첨기록들을 가져올 차례입니다.

어디가 좋을까 웹 사이트를 둘러보다가 아래와 같은 곳을 발견했습니다.



위 그림의 빨간 박스에서 보이듯이 지난 회차를 하나씩 조회해 볼 수 있는 버튼이 있습니다.

회차를 선택하고 버튼을 누르면 아래와 같이 회차만 변경되는 URL을 확인할 수 있습니다. 숫자만 변경하면 되겠죠?


http://nlotto.co.kr/lotto645Confirm.do?method=byWin&drwNo=707




위 URL에서 HTML을 긁어오는 코드로 변경해 보겠습니다.



# lotto.py

import requests

def main():
	basic_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin&drwNo="
	for i in range(1, 707):
		resp = requests.get(basic_url + str(i))
		print(basic_url + str(i))
		print(resp)

if __name__ == "__main__":
    main()


1부터 707(현 시점에서 가장 최근 회차)까지 for루프를 돌면서 requests를 보내는 코드입니다.

이 코드를 실행하면 아래와 같이 출력됩니다.






2. HTML 파싱하기



이번에는 HTML을 파싱해서 필요한 데이터를 추출해 볼 차례입니다.


python 모듈 : BeautifulSoup4, lxml


BeautifulSoup4 - 가져온 HTML을 파싱하기 쉽도록 도와주는 라이브러리

lxml - BeautifulSoup에서 파싱할 때 xml 구조를 이용하도록 하기 위해 필요한 라이브러리.


pip3를 이용하여 위의 모듈들을 설치합니다.



먼저 HTML 코드를 파싱하도록 코드를 수정해 볼까요?

수정된 코드는 아래와 같습니다.



# lotto.py

import requests
from bs4 import BeautifulSoup

def main():
	basic_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin&drwNo="
	for i in range(1, 707):
		resp = requests.get(basic_url + str(i))
		soup = BeautifulSoup(resp.text, "lxml")
		
if __name__ == "__main__":
    main()


이전 코드에서 한 줄만 추가하면 됩니다. 참 쉽죠?

xml 형태로 파싱된 response가 soup 변수에 저장됩니다.


지금부터는 soup 변수를 이용해 찾고자 하는 데이터를 쉽게 찾을 수 있습니다.

soup를 이용해서 HTML 소스에서 회차, 당첨 번호, 당첨 금액들을 추출해보도록 하겠습니다.


웹 브라우저의 소스보기를 이용해서 우리가 추출하고자 하는 데이터가 HTML의 소스 내 어디에 위치하는지, 어떻게 추출할 수 있는지 찾아봐야 합니다.


소스를 살펴보다 보니 아래 화면과 같은 곳이 눈에 뜨입니다.



id가 desc, name이 description인 <meta> 태그를 찾아서 content의 내용을 추출하면 될 것 같습니다.

BeautifulSoup를 이용해 해당 라인을 찾는 코드를 추가해 봅니다.

수정된 코드는 아래와 같습니다.



# lotto.py

import requests
from bs4 import BeautifulSoup

def main():
	basic_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin&drwNo="
	for i in range(1, 707):
		resp = requests.get(basic_url + str(i))
		soup = BeautifulSoup(resp.text, "lxml")
		line = str(soup.find("meta", {"id" : "desc", "name" : "description"})['content'])
		print(line)
		break
		
if __name__ == "__main__":
    main()


위의 코드를 실행하면 아래와 같이 우리가 필요로 하는 데이터를 제대로 추출함을 볼 수 있습니다.




위의 라인을 적절히 파싱하면 당첨회차, 당첨 번호, 당첨 인원, 당첨금액으로 분리할 수 있습니다.

아래 코드를 보시죠.



# lotto.py

import requests
from bs4 import BeautifulSoup

def main():
	basic_url = "https://www.nlotto.co.kr/lotto645Confirm.do?method=byWin&drwNo="
	for i in range(1, 707):
		resp = requests.get(basic_url + str(i))
		soup = BeautifulSoup(resp.text, "lxml")
		line = str(soup.find("meta", {"id" : "desc", "name" : "description"})['content'])
		
		print("당첨회차: " + str(i))

		begin = line.find("당첨번호")
		begin = line.find(" ", begin) + 1
		end = line.find(".", begin)
		numbers = line[begin:end]
		print("당첨번호: " + numbers)

		begin = line.find("총")
		begin = line.find(" ", begin) + 1
		end = line.find("명", begin)
		persons = line[begin:end]
		print("당첨인원: " + persons)

		begin = line.find("당첨금액")
		begin = line.find(" ", begin) + 1
		end = line.find("원", begin)
		amount = line[begin:end]
		print("당첨금액: " + amount)

		break
		
if __name__ == "__main__":
    main()


위의 코드를 실행하면 아래의 결과를 얻을 수 있습니다.

제대로 추출했죠?





이번 포스팅은 여기까지 입니다.


다음 포스팅에서는 받아온 이렇게 추출한 데이터를 mysql에 저장해보도록 하겠습니다.


읽어주셔서 감사합니다.






'개발' 카테고리의 다른 글

Commands out of sync 문제  (0) 2017.11.27
Develop Security Static Code Analyzer (1)  (0) 2017.05.15
따라하는 python 로또 분석(4)  (0) 2016.07.11
따라하는 python 로또 분석(3)  (0) 2016.07.04
따라하는 python 로또 분석(2)  (0) 2016.06.28
1
블로그 이미지

Jest