[출처 : http://www.i-fam.net/water/93]


오랫만에 파이썬을 사용하다 보니 이것 저것 문제가 한둘이 아니다. 손에 익지 않은 도구를 잡고 사용한다는 것이 이렇게 힘든 일인가보다. 웹사이트에서 로딩한 문자열에서 원하는 문자열을 찾는 것이 잘 되지 않아 이리저리 고민하다보니 결국 문자열 인코딩이 다르다는 것을 알았다. 특히나 XML 은 UTF-8 인코딩을 주로 사용한다. 그러다보니 한글윈도우즈에 저장되는 한글코드와는 인코딩이 달라 동일한 글자라하더라도 Binary 코드 자체가 다른 것이다. 이를 일치하게 만들어 주기 위해서는 코드 변환을 해야한다.

코드를 변환하는 방법은 의외로 간단했다. 입력된 문자들은 모두 ASCII 코드로 인식되므로 일단 본래의 인코딩으로 인식할 수 있도록 만들어 줘야한다. 이때 사용하는 함수가 unicode(string, encoding) 함수이다. 이렇게 원래의 인코딩으로 변환을 하고 나면 다른 인코딩으로 변환이 가능하다. 한예를 들어보면 다음과 같다.

hanguel = unicode('한글', 'euc-kr').encode('utf-8')

이는 기본적으로 윈도우즈에서 사용되는 euc-kr 인코딩의 한글을 utf-8 인코딩의 한글로 변환하는 것이다. 하지만 파이썬 소스 파일 자체가 UTF-8 로 저장되어 있다면 말이 달라질 수도 있다. unicode('한글', 'utf-8') 만으로도 제대로 인코딩된 한글이 만들어 질 수 있는 것이다.

웹에서 utf-8 로 로딩한 문자열을 윈도즈에서 사용하는 문자열로 바꾸기 위해서는 반대의 과정을 수행하면 된다. 
소스가 다른 문자열의 비교나 검색에 문제가 있다면 일단 인코딩을 의심해 보자.

다음은 인코딩과 관련된 내용을 잘 정리한 외국 사이트의 내용을 번역한 것이다. 대충 번역한 것이라 참조만 하기 바라며, 본문 중 애매한 내용은 원문을 참조하기 바란다.

원본링크 : http://evanjones.ca/python-utf8.html (새 창으로 열기)

기초
파이썬에는 두가지 형태의 문자열이 있다.  바이트 문자열과 유니코드 문자열이 그것이다. 여러분이 생각하는 것처럼 바이트 문자열은 바이트의 시퀀스이다. 파이썬은 여러분 컴퓨터의 기본 로케일을 사용하여 바이트를 문자들로 변환한다. Mac OS X 에서는 기본 로케일이 실제로 UTF-8 이지만 그 외 대부분의 시스템에서는 기본 로케일이 아마도 ASCII 일 것이다.
다음 코드는 바이트 문자열을 생성한다.

byteString = "hello world! (in my default locale)"

그리고 다음 코드는 유니코드 문자열을 생성한다.

unicodeString = u"hello Unicode world!"

바이트 문자열을 유니코드 문자열로 변환하고 다시 바이트 문자열로 변환하는 코드는 다음과 같다.

s = "hello byte string"
u = unicode(s)
backToBytes = u.encode()

위의 코드는 여러분의 기본 문자집합을 변환에 사용한다. 그러나 로케일의 문자열집합을 신뢰하는 것은 나쁜 생각이다. 왜냐하면 여러분의 응용프로그램을 타일랜드에서 있는 누군가가 자신의 컴퓨터에서 실행하고자 한다면 바로 멈춰 버릴 것이기 때문이다. 대부분의 경우 문자열의 인코딩을 명확하게 정의해주는 것이 훨씬 낫다.

s = "hello normal string"
u = unicode(s, "utf-8")
backToBytes = u.encode("utf-8")

이제 바이트 문자열 s 는 유니코드 문자열 u 를 생성하여 UTF-8 바이트 시퀀스로 취급할 수 있을 것이다. 다음 라인은 UTF-8 의 표현인 u 를 다시 바이트 코드 문자열인 backToBytes 로 저장한다.

유니코드 문자열로 작업하기
감사하게도 파이썬은 모든 면에서 유니코드 문자열과 바이트 문자열이 동등하게 취급된다고 가정한다. 그러나 여러분은 만약 객체가 문자열인지 보려고 테스트 할 때 여러분 자신의 코드에 주의를 기울일 필요가 있다. 이런 식으로 하지마라.

if isinstance(s, str): # 나쁜 예: 유니코드 문자열에 대해 참이 아님

대신 basestring 이라는 일반 문자열 기반 클래스를 사용하라.

if isinstance(s, basestring): # 유니코드와 바이트 문자열에 대해 모두 참

UTF-8 파일 읽기
여러분은 파일에서 읽은 문자열을 수동을 변환할 수 있다. 그러나 다음과 같은 방법을 사용하면 좀더 쉽게 할 수 있다.

import codecs
fileObj = codecs.open( "someFile", "r", "utf-8")
u = fileObj.read() # 파일에 있는 UTF-8 바이트 들이 유니코드 문자열로 넘어옴

codecs module은 여러분이 하고자하는 모든 변환에 사용될 수 있다. 여러분은 또한 저장을 위해 파일을 열 수 있고, 여러분이 write 에 전달한 유니코드 문자열을 은 여러분이 선택한 인코딩이 무엇이건 변환되어 저장될 것이다.

XML과 minidom으로 작업하기
나는 XML 관련작업에 대부분 내가 친숙한 minidom module 을 사용한다. 불행히도 XML은 단지 바이트 문자열만을 다룬다. 따라서 minidom 함수에 넘겨주기 전에 유니코드 문자열로 인코딩해야 한다. 예제는 다음과 같다.

import xml.dom.minidom
xmlData = u"<français>Comment ça va ? Très bien ?</français>"
dom = xml.dom.minidom.parseString( xmlData )

마지막 열은 다음과 같은 예외를 발생시킨다.

UnicodeEncodeError: 'ascii' codec can't encode character '\ue7' in position 5: ordinal not in range(128). 

이런 에러 피하려면 minidom으로 문자열을 전달하기 전에 적절한 포맷으로 유니코드 문자열을 인코딩해야한다. 다음과 같이 해보자.

import xml.dom.minidom
xmlData = u"<français>Comment ça va ? Très bien ?</français>"
dom = xml.dom.minidom.parseString( xmlData.encode( "utf-8" ) )

minidom은 Latin-1 또는 UTF-16 등 어떠한 바이트 문자열의 어떤 포멧이던 다룰 수 있다. 하지만 XML 문서가 인코딩선언(eg. <?xml version="1.0" encoding="Latin-1"?>)을 가지고 있어야 정상적으로 작동한다. 만약 인코딩 선언이 빠진다면 minidom 은 인코딩을 UTF-8로 가정한다. 모든 시스템에서의 호환을 보장하려면, 당신의 모든 XML 문서에 인코딩선언을 포함하는 습관을 갖는 것이 좋다. 
minidom의 dom.toxml() 또는 dom.toprettyxml() 을 호출하여 생성되는 XML 결과는 유니코드 문자열로 넘어온다. encoding="utf-8" 이라는 여분의 매개변수를 전달하여 인코딩된 바이트 문자열을 얻을 수 있다.
[출처 : http://corioli.tistory.com/17]


5.1 파일 목록 얻기

(1) glob.glob(wildcard) - 유닉스 경로명 패턴 스타일로 파일 목록을 얻을 수 있다.


(2) os.listdir(path) - 지정된 디렉토리의 전체 파일 목록을 얻을 수 있다.


(3) dircache.listdir(path) - os.listdir(path)와 동일한 파일 목록을 전달한다.

path가 변경되지 않았을 때, dircache.listdir()은 다시 디렉토리 구조를 읽지 않고 이미 읽은 정보를 활용

dircache.annotate(head, list) - 일반 파일명과 디렉토리명을 구분해주는 함수


5.2 디렉토리 다루기

os.chdir(path) - 작업하고 있는 디렉토리 변경

os.getcwd() - 현재 프로세스의 작업 디렉토리 얻기

기타 여러 함수가 있다.


5.3 파일 이름 다루기

os.path.abspath(filename) - 파일의 상대 경로를 절대 경로로 바꾸는 함수

os.path.exists(filename) - 주어진 경로의 파일이 있는지 확인하는 함수

os.curdir() - 현재 디렉토리 얻기

os.pardir() - 부모 디렉토리 얻기

os.sep() - 디렉토리 분리 문자 얻기


5.4 경로명 분리하기

os.path.basename(filename) - 파일명만 추출

os.path.dirname(filename) - 디렉토리 경로 추출

os.path.split(filename) - 경로와 파일명을 분리

os.path.splitdrive(filename) - 드라이브명과 나머지 분리 (MS Windows의 경우)

os.path.splitext(filename) - 확장자와 나머지 분리

[출처 : http://python.kr/viewtopic.php?t=25357&start=0&postdays=0&postorder=asc&highlight=]

파이썬으로 두 문자열간의 유사도를 측정하는 edit distance를 구현해 보았습니다. 

근데 영어는 예상한 대로 잘 나오는데 한글의 경우 2byte라 그런지 예상대로 

잘 안됩니다. 

내혈안 -> 냉혈한 을 계산 할 경우 3 이 나옵니다. 

자소분해한 것을 가지고 계산해도 

ㄴㅐㅎㅕㄹㅇㅏㄴ -> ㄴㅐㅇㅎㅕㄹㅎㅏㄴ 3이 나옵니다. 

영문처럼 위의 문자열 변경시 2군데만 수정하면 되므로 2 가 나오도록 하고 싶은 
데 어찌 하면 될까요?

> 답변

유니코드로 바꿔서 처리하세요. 

코드:

In [6]: edit.levenshtein(u"내혈안", u"냉혈한") 
Out[6]: 2 

In [7]: edit.levenshtein("내혈안", "냉혈한") 
Out[7]: 3 

감사합니다. 알려주신대로 unicode로 변경하여 처리하니 원하는대로 유사도가 나옵니다. 

코드:

def edit_distance(first , second): 
    first_len , second_len = len(first) , len(second) 
  
    if first_len > second_len : 
        first , second = second , first 
        first_len , second_len = second_len , first_len 
  
    print first_len 
    print second_len 
    current = range(first_len+1) 
  
    for i in range(1,second_len+1): 
        previous , current = current , [i]+[0]*second_len 
  
        for j in range(1,first_len+1): 
            add , delete = previous[j]+1 , current[j-1]+1 
            change = previous[j-1] 
            if first[j-1] != second[i-1]: 
                change = change + 1 
  
            current[j] = min(add , delete , change) 
  
    return current[first_len] 



sFirstStr = sys.argv[1] 
sSecondStr = sys.argv[2] 
  
sFirst = unicode(sFirstStr,'cp949') 
sSecond = unicode(sSecondStr,'cp949') 


nDistanceValue = edit_distance(sFirst,sSecond) 
  
print nDistanceValue 
[출처 : http://songhl1.tistory.com/7]

상속 - is a,  합성 - has a

아마 객체 지향 언어를 공부했다면, 위의 단어가 떠오를 것이다. python 역시 객체지향이기 때문에 합성과 상속을 사용한다.

Example : Inheritance (상속)
---------------------------------------
# Inheritance Example
class ListChild(list):

    def listMake(self, A):
        tlist = self[:]                             # list copy
        for x in A:
              if x not in tlist:                    # If the element is new and is not in list
                    tlist.append(x)             # Append new element to list
         return ListChild(tlist)             # return updated list
---------------------------------------

Example : Compostion(합성)
---------------------------------------
class ListChild:
    def __init__(self, d=None):
        self.data = d
    def listMake(self, A):
        res = self.data[:]                      # list copy
        for x in A:
             if x not in res:
                   res.append(x)
        return Set(res)
    def __getitem__(self, k):
        return self.data[k]
    def __repr__(self):
        return 'self.data'
---------------------------------------
위에서 data를 res로 복사했다고 생각하기 쉬운데, 사실 그게 아니라 data가 실제 list data가 있는 곳을 point 하고 있는 것이다. 즉, res = self.data[:] 이렇게 하면 data를 새로운 공간에 복사하는 것이 아니라 res 역시 list data가 존재하는 곳을 point 하고 있는 것이다. 
python에서는 이를 C와 같이 pointer라고 부르기 보다 symbol 이라 한다. (맞나..;;)

위에서 본 바와 같이 inheritance와 composition의 차이점을 짐작할 수 있을 것이다. 하지만 좀 더 이를 구체적으로 적어보자면,
- 합성이 구현하기 쉽다고 한다... 개인차인듯(내생각)
- 상속은 super class를 상속받아서 새로 method만 추가하면 되지만, super class에서 어떠한 method들이 제공되는지 알고 있어야 한다. 즉 super class의 정보를 모른다면 잘 사용하기 힘들다. 반면에 합성은 적어도 어떠한 method들을 사용할지 명확히 안다. (자기 안에 다 있으니까)
- 상속의 경우 super class의 수정은 조심해야 한다. 다른 클래스들이 참조할 수도 있으니, 반면에 합성은 수정이 쉽다.
- 상속의 경우 다형성을 제공, 즉 sub classing이 용이 (당연하건가..)
- 상속은 superclass의 이미지를 갖게 되므로 superclass의 종료시점을 가지게 된다. 반면에 합성은 객체의 생성과 소멸을 정할 수 있다.
- 두 기법은 당연히 같이 사용된다. 그게 좋다. 예를 들면 super class는 동물이고 이를 '사자'가 상속한다. 또한 '사자'는 '갈기'를 가지고 있다. '갈기'를 표현하기 위해서는 합성(has-a)을 사용하는 것이 좋다. 
 * 물론 여기서 일관성이 유지되는가를 고려해줘야 한다. 즉, 어떤 class가 여러가지로 표현이 가능하다면, (ex. 사람을 표현할 때, 이 사람은 고용인 일 수도 있으며, 엔지니어 일 수도 있고, 가계주인 일 수 있다. 동시에 말이다.) 이럴 경우에는 일관성을 위해서 합성을 사용하는 것이 좋을 것 같다.
[출처 : http://blog.jangc.net/17]


목표 : 레이싱 모델 사진 모으기 -_-*
세부사항 : 레이싱 모델 갤러리의 임이의 두 게시물 사이의 게시물들에 첨부되어 있는 모델 사진 다운받기.
알고 있어야 하는 것들 : HTML, 정규식


진행은 인터프리터를 켜놓고 따라하면 될꺼야.

우선 게시물 기본 주소를 얻어오자!
( http://gall.dcinside.com/list.php?id=racinggirl&no= )
이건 뭐..프로그래밍으로 하는게 아니니 설명 패스!

그리고 까먹기 전에 아무 변수에나 넣어두자.
알다시피 파이썬은 아무때나 변수를 만들 수 있으니까 필요할때 만들면 돼~

>>> racingGirlUrl = 'http://gall.dcinside.com/list.php?id=racinggirl&no='


파이썬에서 문자열은 '나 "로 감싸면 되고 차이는 없어. 문자열이 여러줄일땐 '''나 """로 감싸면 되고. 뭐 그냥 그렇다구요~

저 주소를 어떻게 쓸꺼냐면, 주소 뒤에 게시물 번호만 바꿔서 for로 돌리는거야..

>>> for no in range(170710, 170720):
>>>     print racingGirlUrl + str(no)


웁스, 차근차근 설명하려 했는데 단번에 너무 많은게 나와버렸네.
 * for
 * range
 * 들여쓰기
이정도가 저거에서 볼만한 게 아닌가 싶네.

# 들여쓰기
우선 들여쓰기부터 보면, C나 자바는 함수나, for, if등 구역을 나눌 때 {}를 사용하잖아?
그런데 {}사용하면서도 사람들이 들여쓰기도 다 하잖아. 그래서 파이썬은 {}를 안쓰고 그냥 들여쓰기를
구역 구분으로 사용해. 
{}가 컴퓨터를 위한거고 들여쓰기가 인간을 위한 것이었다면 파이썬은 그냥 들여쓰기만 사용해서 컴퓨터와 인간이 같은걸 보게 하는거지.

# for
파이썬에서 for문은 시퀀스 자료형 탐험용이라고 보면 될 것 같아.
그러니까... 
for A in B: 라고하면 B는 시퀀스 자료형 오는거고 A는 우리가 하니씩 받을 변수가 되는거지.

아, 그리고 if, while, for등의 끝에는 :를 붙여줘.

# range
range는 주어진 숫자 사이의 리스트를 만드는 함순데.
range(10)하면 [0,1,2,3,4,5,6,7,8,9]가 되는거고
range(5, 10)하면 [5,6,7,8,9]가 되는거고, 막 이래.

# 기타
print는 화면 출력, str은 문자열로 변경.
문자열끼리 연결할때는 +로 연결해주면 되는거야~


자자. 이제 주소도 생성했으니 게시물 속 알맹이를 얻어와야지!
파이썬은 기본으로 제공되는 모듈이 참 많은데 그중에 urllib라는게 있어.
모듈을 불러오는건 두가지 방법이 있는데 그건 그냥 넘어가고.
모듈 불러오기!

>>> import urllib

모듈 불러오고 하는건 생긴게 자바랑 비슷하게 생겼더라고.
아무튼 저 모듈에 urllib.urlopen이란 함수가 있는데 url에 연결해서 파일 오브젝트를 넘겨줘..

우리가 필요한건 아까 그 for문에서 만든 주소니까 거기에 추가하면 되겠지.

>>> for no in range(170710, 170720):
>>>     url = racingGirlUrl + str(no)
>>>     f = urllib.urlopen(url)


파이썬이 스크립트 언어라서 좋은게, 모듈이나 클래스 등의 변수, 메소드 등이 뭐가 있나 모르겠으면
dir해보면 대충 알 수 있어. 이게 참 좋더라.

>>> dir(f) 해보면 대충
['__doc__', '__init__', '__iter__', '__module__', '__repr__', 'close','fileno', 'fp', 'geturl', 'headers', 'info', 'next', 'read','readline', 'readlines', 'url']


이런식으로 나올꺼야.
뭐..이름만 나오니까 메소든지 변순지는 알 수 없지만...대충 감은 오잖아?

>>> f.read()   를 하면 아까 읽어들인 페이지의 HTML이 좍~ 출력되겠지-_- 
근데 이렇게 하면 for문이 끝나고 마지막에 남은 f의 내용만 본거니까 나머지도 다 보기위해 이것도 for문으로 넣자.

>>> for no in range(170710, 170720):
>>>     url = racingGirlUrl + str(no)
>>>     f = urllib.urlopen(url)
>>>     html = f.read()

아무튼 게시물의 html도 읽어왔으니 이제 사진 주소를 찾아야지!

이것도 코딩으론 모르겠고. 손으로 직접-_-;
대충 html을 보니까.. http://image.dcinside.com/download.php로 시작하는걸 받으면 되겠네.

이제 정규식을 쓰자고.
파이썬 정규식 모듈은 re야. 
(모듈 이름 쓰기가 얼마나 귀찮았으면 두자로 했을까. 게으른 사람들 같으니-_-;)

>>> import re


뭐..정규식 모듈이 해주는게 이것저것 있지만 우리가 필요한건 문자열에서 원하는 문자열만 뽑아내는거니 그것만 보자구.
(아까 말했듯, dir(re)를 해보면 re 모듈에 들어있는 클래스, 함수 등을 볼 수 있다오~)

우리가 찾는 함수는 이것! re.findall

아 근데 이 함수 인자가 어떻게돼더라...잘 모르겠을 땐
>>> help(re.findall)  해보면 대충 설명이 나오지.

내가 사용할 정규식은 http://image.dcinside.com/download.php[^']+ 이거야.
(정규식에 대한 설명은 패스.)

합쳐서 아까 그 for문에 넣어주면 

>>> for no in range(170710, 170720):
>>>     url = racingGirlUrl + str(no)
>>>     f = urllib.urlopen(url)
>>>     html = f.read()
>>>     imageUrlList = re.findall("http://image.dcinside.com/download.php[^']+", html)


이렇게 되겠다. 하하 이제 사진 주소들도 얻어왔으니 다운받아서 저장하는 것만 남았군. 

파이썬 기본 자료형에 리스트라는게 있는데, 뭐 이름에서도 알 수 있듯 시퀀스 자료형이야.
리스트의 문법은 []로 감싸는거고, 
[]는 빈거 [1]는 1이 있는 리스트, [1, 'bob'] 이런 1이랑 'bob'이 있는 리스트. [1, 'bob', ['gopa']] 이건 리스트 안에 리스트가 있는거.

이 얘길 갑자기 왜하냐면 방금 findall하면 넘어오는게 찾은 주소들의 리스트거든. 
우리가 하려는건 받은 리스트를 순서대로 돌면서 그 주소의 사진을 urlopen으로 열어서 내 컴퓨터에 저장하는 것.
여태까지 했던거의 반복이니까 설명 없이 바로 작성할께.

>>> fileNo = 0
>>> for no in range(170710, 170720):
>>>     url = racingGirlUrl + str(no)
>>>     f = urllib.urlopen(url)
>>>     html = f.read()
>>>     imageUrlList = re.findall("http://image.dcinside.com/download.php[^']+", html)
>>>     for url in imageUrlList:
>>>         contents = urllib.urlopen(url).read()
>>>         file(str(fileNo)+'.jpg', 'w').write(contents)
>>>         fileNo = fileNo + 1


이정도쯤 되겠네.

쓰면서 for문 안에껄 함수로 빼낼껄. 하는 후회를 하긴 했는데, 뒤늦게 생각난거라 고치기가 귀찮네.

완성 소스
-----------------------
import re
import urllib
fileNo = 0
racingGirlUrl = 'http://gall.dcinside.com/list.php?id=racinggirl&no='
for no in range(170710, 170720):
        url = racingGirlUrl + str(no)
        f = urllib.urlopen(url)
        html = f.read()
        imageUrlList = re.findall("http://image.dcinside.com/download.php[^']+", html)
        print imageUrlList
        for url in imageUrlList:
                print fileNo
                contents = urllib.urlopen(url).read()
                file(str(fileNo)+'.jpg', 'wb').write(contents)
                fileNo = fileNo + 1


-----------------
아~ 레모들 이쁘네~

간단한 것 몇가지 추가하자면,
파이썬에서 주석은 #로 시작하면 되고,
html 파싱해서 뭐 찾고 그러는거면 파이썬엔 beautifulSoap이란걸 많이 쓰더라. 편해.


----
이랬음 좋겠다 저랬음 좋겠다 글올려줘~

+ Recent posts