Python의 반복문

보통 for 구문을 사용해서 0부터 99까지 반복한다고 하면 다른 프로그래밍 언어에선 이런 느낌이 됩니다.

for (let i = 0; i < 99; ++i) {
  do_something(i);
}

하지만 Python에선 for문이 저런 구조가 아닙니다. 그렇기에 우리는 range를 쓴다고 알고 있습니다.

for i in range(100):
    do_something(i)

그럼, range는 뭘까요? range(100)을 실행하면 어떻게 되길래 우리가 원하는 반복문이 되버리는 걸까요? 일단 Python 2에서 확인해보도록 합시다.

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> type(range(10))
<type 'list'>
>>> for i in range(10):
...   print i**3
...
0
1
8
27
64
125
216
343
512
729

100은 블로그에 담기엔 예시결과로는 너무 큰 관계로 10으로 줄였습니다. 실행하면 list로 0 이상 10 미만의 숫자가 들어가는 것을 확인할 수 있습니다. 도움말을 읽으면 당연히 알 수 있지만, range 함수는 정해진 범위를 list로 만들어줍니다. for문은 range의 결과로 나온 list에서 1개씩 뽑아서 사용하는 것이구요. Python의 for는 다른 언어에서의 foreach라고 부르는 것들과 비슷합니다.

for (let i of myArray) {
  do_something(i);
}

Python의 이런 반복은 얼핏보면 편하지만 함정이 있습니다. 만약 0부터 1000억까지 반복을 해야한다면 어떨까요? range(100000000)이라고 적으면 0부터 1000억-1 만큼의 인자가 들어있는 list가 생겨날 것입니다. list에 들어있는 것도 결국 컴퓨터가 저장하고 있어야 하는데 이대로 가다간 메모리가 버틸까요? 이 방법으로 접근하면 너무나도 비효율적입니다. Python으론 이런 방법밖에 없는걸까요?

이 문제를 해결하기 위해 Python 2에는 xrange라는 함수가 있습니다. Python 3에서는 기존의 range를 xrange가 대체했습니다. 위의 예제들을 xrange로 바꿔서 다시 해보죠.

>>> xrange(10)
xrange(10)
>>> type(xrange(10))
<type 'xrange'>
>>> for i in xrange(10):
...   print i**3
...
0
1
8
27
64
125
216
343
512
729

일단 반복문이 잘 돌아간다는 것은 알 수 있지만 그 외의 값은 미궁속입니다.

Python 2의 xrange / Python 3 range 동작 살펴보기

일단 xrange가 어떻게 for문에 값을 전달하는지 알아봅시다. 먼저 xrange 값을 하나 만듭니다. 그리고 iter함수로 iterator를 받습니다.

>>> ten = xrange(10)
>>> it = iter(ten)
>>> it
<rangeiterator object at 0x10f27bde0>

그러면 이제 받은 값으로 ten의 구성요소에 하나씩 접속해볼 수 있습니다.

>>> next(it)
0
>>> next(it)
1
>>> next(it)
2

그리고 next 함수를 이용해서 값을 원하는 시점에 하나씩 받아올 수 있습니다. for문은 이 방식을 이용해서 xrange값의 iterator를 받고 내부적으로 next를 호출해서 사용합니다.

이것은 Python 3의 range도 동일합니다.

>>> ten = range(10)
>>> ten
range(0, 10)
>>> it = iter(ten)
>>> it
<range_iterator object at 0x106c70ed0>
>>> next(it)
0
>>> next(it)
1
>>> next(it)
2

Generator

하지만 이건 Python에서 기본 지원하는 경우이고, 만약 수없이 많은 자료를 돌아다니면서 반복문을 돌려야한다면 어떨까요? 가령 수백만줄의 파일을 읽어서 한줄씩 처리중이었다면 어떻게 해야할까요? 모두 list에 올려놓고 하기엔 메모리가 버티지 못할 것입니다. 그 경우를 위해 Generator가 있습니다.

가볍게 0에서 9까지의 세제곱을 출력하는 Generator를 만들어서 사용해봅시다.

def gen():
    for i in range(10):
        yield i ** 3

for x in gen():
    print(x)

여기서 gen 함수가 바로 generator입니다.

>>> gen()
<generator object gen at 0x106aec1a8>

yield

Generator에서는 yield라는 키워드를 사용합니다. yield의 동작을 알아봅시다.

def gen():
    yield 'one'
    yield 'two'
    yield 'three'

g = gen()
print(next(g))  # one
print(next(g))  # two
print(next(g))  # three
print(next(g))  # raise StopIteration

yield는 함수 실행 중간에 빠져나올 수 있는 generator를 만들 때 사용합니다. return이었다면 'one'이 반환되고 끝났겠지만 실제로는 그 뒤로도 다시 사용할 수 있었죠.

yield는 단순히 값을 내보낼 수만 있는 것은 아니고, 넣어줄 수도 있습니다.

def gen():
    val = 111111
    while True:
        val = (yield val) * 111111

g = gen()
print(next(g))  # 111111
print(g.send(2))  # 222222
print(g.send(3))  # 333333

뭐에 쓰지?

위에서도 언급했지만 대용량 자료 처리등은 메모리에 모두 올려놓고 할 수 없습니다. 그런 경우 한 줄씩 읽은 뒤 generator를 이용한 반복처리를 하면 편합니다. 실제로도 Flask에서의 대용량 파일 전송, Sphinx의 확장 개발등에 사용됩니다.

요약

  1. yield 키워드를 사용하면 generator를 만들 수 있다.
  2. generator는 한번에 끝나지 않고 여러번에 걸쳐 입출력을 받을 수 있다.
  3. 대용량 처리 함수 제작 등에 편리하다
  4. 따라서, list가 필요한게 아니라면 Python 2에서 반복은 xrange를 쓰자.





function convertJsonStr($row) {

if(is_null($row)) { 

return "{}"

}

 

 

$r = "{"

for($i = 0; $i < count($row); $i++) { 

$r .= "{"

$curr = $row[$i]; 

$end = count($curr); 

$ep = 0; 

foreach($curr as $k => $v) { 

$ep++; 

$r .= "\"" . $k . "\":\"" . $v . "\""

if($ep == $end) {

// 할일없음 

}

else {

$r .= ", "

}

}

 

 

$r .= $row[i]; 

$r .= "}"

if($i < count($row) - 1) {

$r .= ", "

}

}

$r .= "}"

 

 

return $r; 

}


'Program > PHP' 카테고리의 다른 글

API  (0) 2017.02.21
cannot create mssql.so on ubuntu 16.04 and php5.6  (0) 2017.02.17
PHP7 에서 PHP5.6 사용하기  (0) 2016.11.11
SQL Relay php connection  (0) 2016.07.17
PHP PHP EXCEL (PHP 엑셀 읽기 쓰기)  (0) 2016.01.21

자꾸 가물가물해서 퍼왔습니다.


http://byneonkid.tistory.com/98


이번엔 Python을 가상 환경에서 설치하는 방법을 알아봅시다.



왜 Python을 가상 환경에서 설치해야 할까요?



Python의 버전은 현재 2.7 버전과 3.x 버전이 존재합니다. 아마 프로그래밍을 조금 해보신 분들 중에 Java 라는 언어로 프로그래밍을 많이 해보신 분들이라면, 이런 버전의 차이에 굉장히 반감을 하거나 민감하게 반응하실 수 있습니다.


그렇습니다. 그 이유 중에 하나는 바로 여러분들이 프로그램을 개발하다 반드시 사용하게 되는 바로 "라이브러리"라는 녀석 때문이죠. 물론 공개된 라이브러리들은 대부분 최신 버전에 맞춰 수정이 되긴 하지만, 자신이 개발하는 프로젝트 내지 프로그램이 한 라이브러리만 쓰는 것은 아니기 때문에 모든 라이브러리에 버전을 맞추려면 여러 가지 고려해야될 사항이 많아집니다. 


Java의 경우, Java 7 버전과 Java 8 버전을 동시에 설치해야하는 번거로움이 생깁니다. 물론 설치에 대한 번거로움이 끝이 아니죠. 여기에 자신이 어떤 버전의 Java를 쓸 것인지 물어보지도 않고, 그냥 갔다가 컴파일 해버려서 라이브러리 간에 버전 충돌이 일어나는 일이 생기기도 합니다. 여간 불편한 것이 아니죠..


그래서 Python에는 이 개발 환경을 분리시키기 위해 가상 환경이라는 것을 제공합니다.

그림으로 보면, 현재 여러분 PC에 현재 Python이 설치되어 있고, 현재 설치된 Python을 두 공간으로 분리해서 위 프로젝트는 2.7버전, 아래 프로젝트는 3.x 버전으로 환경을 나누게 되면 버전 충돌 문제가 해소됩니다.


Python을 가상 환경으로 구축하는 방법에는 여러 가지가 있지만, 가장 대표적인 방법은 Virtualenv, Anaconda, Docker  방법이 있습니다.


그 중 가장 쉽고 편한 것이 Virtualenv이고, Docker는 최근에 나온 컨테이너 형태의 환경이기 때문에 Python의 가상 환경 뿐만이 아니라 서버의 가상 환경으로도 사용하실 수도 있고, 만약 개발하는 프로젝트가 웹 서버 등의 프로젝트라면, Virtualenv보다는 Docker가 더 좋을 수 있습니다.


이 포스트에서는 간단히 Virtualenv를 사용해 가상 환경을 구축하는 방법을 알아보도록 하겠습니다.



Virtualenv 설치

Virtualenv의 설치는 pip로 간단히 설치할 수 있습니다.


Bash
$ sudo pip install --upgrade virtualenv

Linux나 OS X의 경우, sudo 명령어를 이용하여 virtualenv를 설치하시면 됩니다.

혹은 Ubuntu Repository에서도 받으실 수 있습니다.


Batch
C:\> pip install --upgrade virtualenv

Windows는 관리자 명령으로 명령 프롬포트를 열어주시고, 위 명령어를 타이핑 하시면 됩니다.



가상 환경 구축

이제 Virtualenv를 설치했으니 이를 이용해 가상 환경을 직접 만들어보도록 하겠습니다.

매우 쉬우니, 그냥 보고 따라하기만 하시면 됩니다.


Bash
$ mkdir CVProject

여러분들이 원하시는 경로에 디렉토리를 만들어줍니다. Linux, OS X 에서는 mkdir 명령어를 사용합니다.


Batch
C:\Users\K.I.D> md CVProject

Windows는 md 명령어를 사용합니다. (사실 Windows에서 그냥 새 폴더 하나 만든다고 생각하시면 됩니다 ^^;)


Bash
$ virtualenv -p ${PYTHON_VERSION} ${DIRECTORY}

virtualenv의 p 옵션을 이용해서 원하시는 Python 버전을 입력하시고, 위에서 만든 디렉토리 이름을 치면, 해당 디렉토리에 Python 가상 환경이 생성됩니다.

ex) Python 2.x 버전은 그냥 Python, Python 3.x 버전은 Python3 라고 입력.




가상 환경 실행

이제 구축을 해봤으니 가상 환경이 제대로 실행되는지 확인해보겠습니다.


Bash
$ source CVProject/bin/activate.sh

Linux, OS X는 위 명령어를 이용해 가상 환경을 실행합니다.


Batch
C:\Users\K.I.D> CVProject\Scripts\activate

Windows는 Scripts 폴더에 실행 파일이 미리 만들어져 있으므로 이를 실행하면 됩니다.


Bash
(CVProject) $ python --version

그럼 이제는 쉘 앞에 자신의 프로젝트 폴더명이 앞에 보일 것입니다.


Batch
CVProject) C:\Users\K.I.D> python --version

Windows는 위와 같이 보입니다.


python --version 명령어를 입력해, 자신이 원하는 Python의 버전으로 구축이 되었는지를 확인합니다.


Bash
$ whereis python

Linux에서 whereis 명령어를 사용해 python의 PATH를 확인합니다. 마지막 부분에 자신의 가상 환경 경로가 표시됨을 확인할 수 있습니다.



각종 라이브러리 등의 설치는 반드시 (${가상환경 이름})이 나온 상태에서 진행하셔야만 가상 환경에 라이브러리가 설치됩니다. 그렇지 않은 상태에서는 시스템에 설치되므로 반드시 확인바랍니다.



autoenv 사용 (Linux, OS X only)

virtualenv를 사용하려하니 일일이 source 명령어를 사용하는게 매우 번거롭습니다. 그렇다고 터미널이나 쉘을 띄울 때마다 가상 환경을 실행하자니 그것도 문제이지요?


그래서 autoenv를 사용해보기로 했습니다.


Bash
$ sudo pip install --upgrade autoenv

autoenv는 kennethreitz님이 개발하신 코드로 virtualenv 디렉토리에 접근하면 자동으로 원하는 커맨드를 입력하게 하는 코드입니다.


작동 방식은 다음과 같습니다.


사실 커맨드 실행이 맞는 표현이겠지만, 우리가 원하는 것은 Virtualenv 실행이 목적이기 때문에, 그렇게 적었습니다 ^^;

이제 실행 방식을 알았으니 자신의 virtualenv 공간에 .env 파일을 생성해봅시다.


Bash
$ vim .env

vim 혹은 nano, emacs 같은 에디터를 이용해 .env 파일을 생성합니다.


Bash
source ~/CVProject/bin/activate

간단하게 virtualenv만 실행시키는 커맨드를 적었습니다.


Bash
source ~/CVProject/bin/activate
clear
echo ''
echo ' I'm CVProject'
echo ' enjoy Coding!'
echo ''
echo '           Created by. Neon K.I.D'
echo ''

고급된 방법(?)으로 스크립트를 작성하실 수도 있습니다.

그럼 이제, 터미널과 쉘을 새로 실행시켜, CVProject 폴더에 들어가면..


Bash
$ cd CVProject

Virtualenv 환경에 접근이 가능하다는 것을 알 수 있습니다.


"그런데, 아 나는 이것도 불편해, 간단한 명령어로 처리할 수 있는 방법이 없을까?"


그러시는 분들은, alias를 사용해보는 방법도 있으니, 해당 방법을 써보는 것도 나쁘지 않겠습니다.


Bash
$ alias cv='cd CVProject'

저는 간단히 상대 경로를 넣었지만 원래 alias를 하실 때는 절대 경로를 하셔야 됩니다 ^^;

명령어가 잘 동작하는지 확인했으면, 이제 프로필에 넣어주면 되겠죠?


Bash
$ vim .bashrc

bashrc 파일을 로드하고, alias 부근이나 아무 군데에 다음과 같이 추가합니다.


Bash
alias cv='cd CVProject'

추가하고 저장 한 뒤, 새로운 쉘이나 터미널을 실행하고, cv 명령어만 입력하면 자동으로 가상 환경에 들어가집니다. 



마치며... 

여러가지 내용이 섞여있어 어떤 말인지 모르시는 분들이 많으실 수도 있습니다. 개발 환경을 구축하는 데는 마치 코딩과 같아 여러분들이 얼마든지 튜닝만 한다면 편한 개발 환경이 될 수도 있고, 그렇지 않을 수 있습니다.


Python의 가상 환경은 그만큼의 메리트가 존재합니다. (언어의 사용도 그만큼 메리트가 있었으면.... (?) ) Java에도 개인적으로 이런 환경이 나오기를 원하지만, 그 환경을 Docker가 그나마 메꿔주고 있기 때문에 참 Docker 정말 좋습니다 ^^


여기까지 Virtualenv를 이용한 Python 개발 환경 구축이었습니다.



출처: http://byneonkid.tistory.com/98 [긁적긁적.....]

text = 'foo = 23 + 42 * 10'

tokens = [('NAME', 'foo'), ('EQ', '='), ('NUM', '23'), ('PLUS', '+'),
('NUM', '42'), ('TIMES', '*'), ('NUM', '10')]

import re
NAME = r'(?P<NAME>[a-zA-Z_][a-zA-Z_0-9]*)'
NUM = r'(?P<NUM>\d+)'
PLUS = r'(?P<PLUS>\+)'
TIMES = r'(?P<TIMES>\*)'
EQ = r'(?P<EQ>=)'
WS = r'(?P<WS>\s+)'

master_pat = re.compile('|'.join([NAME, NUM, PLUS, TIMES, EQ, WS]))

from collections import namedtuple
import json
Token = namedtuple('Token', ['type', 'value'])

def generate_tokens(pat, text):
scanner = pat.scanner(text)
for m in iter(scanner.match, None):
yield Token(m.lastgroup, m.group())

for tok in generate_tokens(master_pat, 'foo = 42'):
print(tok)

tokens = (tok for tok in generate_tokens(master_pat, text) if tok.type != 'WS')
#print(list(tokens))
jj = {}
for tok in tokens:
#print(tok)
#t = "'{0}':'{1}'".format(tok.type, tok.value)
#t = ':'.join([tok.type, tok.value])
jj[tok.type] = tok.value

print(jj)

jj2 = json.dumps(jj)
print(jj2)

print("")
# 테스트용 Dictionary
customer = {
'id': 152352,
'name': '강진수',
'history': [
{'date': '2015-03-11', 'item': 'iPhone'},
{'date': '2016-02-23', 'item': 'Monitor'},
]
}

# JSON 인코딩
jsonString = json.dumps(customer)

# 문자열 출력
print(jsonString)
print(type(jsonString)) # class str



이터레이터(iterator): 반복 가능한 객체
이터레이션: 반복 가능한 객체에서 해당 값을 가져오는 행위
iter 함수 : list나 dict를 이터레이터로 만들어주는 함수


제너레이터: 이터레이터를 만들어 주는 것
왜 제너레이터를 사용하는 가? 메모리 효율적 사용을 위해

Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.

>>> import sys
>>> 
>>> a=[i for i in range(100)if i %2]
>>> b=(i for i in range(100)if i %2)
>>> 
>>> type(a)
<class 'list'>
>>> type(b)
<class 'generator'>

>>> sys.getsizeof(a)
528
>>> sys.getsizeof(b)
88

>>> aa=[i for i in range(1000)if i %2]
>>> bb=(i for i in range(1000)if i %2)
>>> sys.getsizeof(aa)
4272
>>> sys.getsizeof(bb)
88
>>> sys.getsizeof([i for i in range(100)if i %2])
528
>>> sys.getsizeof((i for i in range(100)if i %2))
88
>>> sys.getsizeof([i for i in range(1000)if i %2])
4272
>>> sys.getsizeof((i for i in range(1000)if i %2))
88
>>> class Star:
def __init__(self):
self.name="없음"
self.age=0
print("생성자")
def __del__(self):
print("소멸자")
def Disp(self):
print(__name__)

>>> def sub():
s1=Star()

>>> sub()
생성자
소멸자
>>> 
>>> 
>>> class Star:
def __init__(self,name,age):
self.name=name
self.age=age
print("생성자")
def __del__(self):
print("소멸자")
def Disp(self):
print(__name__)
print("name=",self.name,"age=",self.age)

>>> def sub():
s1=Star("송혜교",35)
s1.Disp()

>>> sub()
생성자
__main__
name= 송혜교 age= 35
소멸자
>>> 
http://www.flowdas.com/blog/generators-in-python/


지난 글에서는, 생산자-소비자 패턴(Producer-Consumer Pattern)의 소비자 쪽의 코드를, 함축적이면서도 자연스럽게 기술할 수 있는 방법을 제공하는 이터레이터를 살펴봤습니다. 오늘은 생산자 쪽을 들여다봅니다.

앞서 Range 라는 클래스를 통해 이터레이터를 살펴보았습니다만, 이 코드에는 불편한 부분이 있습니다. 대부분의 작업은 __next__() 메쏘드를 통해 구현되는데, 매번 예전의 상태를 복구한 후, 작업을 수행하고, 다시 상태를 저장하는 작업을 반복하게 됩니다. Range 와 같은 간단한 작업에서야 문제될 것이 없지만, 알고리즘을 기술하는 자연스러운 방식에서 벗어나 있기 때문에, 작업이 복잡해질수록 점점 문제가 됩니다. 이런 스타일의 프로그래밍은 이벤트 주도형 프로그래밍(event-driven programming)에서 자주 등장하는데, 비동기 IO(Asynchronous IO) 나 GUI 프로그래밍을 해 보셨다면 이미 익숙할 겁니다.(지금 우리가 걷고 있는 길은 이 문제들에 대한 직접적인 해결책으로 이어집니다. 하지만 아직은 갈 길이 멉니다.)

파이썬에서는 제너레이터(generator)라는 이터레이터의 확장을 제공합니다. 이터레이터가 단지 프로토콜인데 반해, 제너레이터는 yield 라는 전용의 키워드를 통해 문법적인 특별함을 더합니다. Range 를 제너레이터로 다시 구현해 보겠습니다. (되도록 이터레이터 구현과 유사하게 코드를 구성했습니다.)

>>> def Range(n):
...     c = 0
...     while c < n:
...         yield c
...         c += 1
>>> for x in Range(3):
...     print(x)
0
1
2

파이썬은 함수 정의에 yield 키워드가 등장하면 그 함수 전체를 제너레이터로 인식하고, 특별한 VM 코드를 생성해냅니다. 함수의 코드를 재구성해서 같은 일을 하는 이터레이터를 만든 다음, 원래 함수는 그 이터레이터 인스턴스를 돌려주는 함수로 바꾼다고 보시면 됩니다.

>>> Range(3)
<generator object Range at 0x1017d3370>

제너레이터는 이터레이터입니다.

>>> r = Range(3)
>>> next(r)
0
>>> next(r)
1
>>> next(r)
2
>>> next(r)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
StopIteration

이터레이터 구현과 제너레이터 구현간에는 이런 문법적인 차이가 있습니다.

  • 이터레이터 구현에서 __next__() 의 return 값은, 제너레이터 구현에서는 yield 값으로 대체됩니다.
  • 제너레이터에서 return 문은 StopIteration 예외를 일으킵니다. 함수의 끝에 도달해도 마찬가지 입니다. 파이썬 3 에서는 return 문이 값을 포함할 수도 있지만, 오늘 다룰 범위를 벗어납니다.

제너레이터를 위해 파이썬이 만들어내는 이터레이터를 제너레이터-이터레이터(generator-iterator)라고 부르는데, 이터레이터는 프로토콜인 반면 제너레이터-이터레이터는 실제 구현이 제공되는 것이라, 일반적인 이터레이터에서 지정하지 않은 동작들이 구체적으로 정의되기도 합니다.

가령 예외의 경우, 이터레이터의 __next__() 가 StopIteration 또는 그 외의 예외를 일으킨 후에 이터레이터가 어떤 상태가 되는지는 정의되어있지 않습니다. 하지만 제너레이터의 경우는 항상 이렇게 됩니다.

  • 일단 StopIteration 예외가 일어나면 그 이후에는 계속 StopIteration 예외를 일으킵니다.
  • StopIteration 이외의 예외가 일어나면, 일단 caller 에게 예외가 전달됩니다. 하지만 그 이후로는 계속 StopIteration 예외를 일으킵니다.

재진입(Reentrancy) 문제도 이터레이터에서는 정의되어있지 않습니다. 가령 __next__() 메쏘드 내에서 직접적이던 간접적이던 next(self)를 호출했을 때 어떤 일이 일어나야 할지 지정되어 있지 않다는 것입니다. 그러니 재귀적인 구현도 생각해볼 수 있습니다. 하지만 제너레이터에서는 재진입이 허락되지 않습니다.

>>> this = (lambda: (yield next(this)))()
>>> next(this)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "<console>", line 1, in <lambda>
ValueError: generator already executing

그런데 이 예에서 볼 수 있듯이 lambda 로 제너레이터를 만들어도 문제가 되지는 않습니다. 그런데 lambda 의 바디에는 문장(statement)이 아니라 표현식(expression) 이 온다는 것을 기억하십니까? yield 를 둘러싼 괄호가 yield 가 표현식임을 알려줍니다. 예 지금까지 저희는 yield 를 문장처럼 사용해 왔습니다만, 사실 yield 는 표현식입니다. 그러면 그 값은 뭘까요?

지금까지의 예 에서 yield 표현식(yield expression)의 값은 None 입니다.

>>> def G():
    while True:
        input = yield 'output'
        print(repr(input))
>>> g = G()
>>> next(g)
'output'
>>> next(g)
None
'output'

처음 인쇄된 'output' 은 next(g) 의 반환 값이고, 다음 next(g) 호출의 처음에 인쇄된 None 이 yield 표현식의 결과값입니다. 순서를 잘 기억하세요, 첫 번째 next() 에서 yield 로 값을 전달한 후에, 다음 next() 가 호출될 때야 비로소 yield 표현식이 값을 전달합니다.

caller 가 제너레이터로 값을 전달해 준다면 yield 표현식은 None 이외의 값을 가질 수 있습니다. 이를 위해 제너레이터에는 send() 라는 메쏘드가 제공됩니다.

>>> g = G()
>>> next(g)
'output'
>>> g.send('input')
'input'
'output'

None 이 send() 가 전달한 'input'으로 대체된 것을 확인하실 수 있습니다. 주의할 부분은 첫 번째 next(g) 대신 g.send('input') 을 사용할 수 없다는 것입니다.

>>> g = G()
>>> g.send('input')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator

이유는 앞에서 주의를 부탁한 실행 순서 때문입니다. 첫 번째 yield 표현식은 최초의 yield 가 일어난 후에 전달됩니다. 즉 send() 메쏘드는 yield 로 값을 전달한 후에, caller 가 next() 나 send() 를 호출해주기를 기다리고 있는 제너레이터에만 사용될 수 있습니다. 꼭 send()를 사용해야 한다면 None 을 전달할 수 있습니다.

>>> g = G()
>>> g.send(None)
'output'

next(g) 는 g.send(None) 과 같은 의미로 해석됩니다.

제너레이터에는 send() 외에도 throw() 와 close() 라는 메쏘드가 더 제공됩니다. 이 메쏘드들은 나중에 다룰 기회가 있을 겁니다만, 제너레이터에 이런 기능들이 들어간 목적이 무엇일까요? 그 것은 바로 다음에 다룰 코루틴(coroutine) 때문입니다.

http://www.flowdas.com/blog/iterators-in-python/


대부분의 파이썬 튜토리얼들과는 달리 파이썬 3.3을 중심으로 기술하고 파이썬 2를 포함한 하위 버전에서 차이를 보이면 따로 설명합니다.

파이썬의 시퀀스는 for 루프로 탐색할 수 있습니다.

>>> for e in [1,2,3]: # 리스트
...     print(e)
1
2
3
>>> for e in (1,2,3): # 튜플
...     print(e)
1
2
3
>>> for e in 'abc': # 문자열
...     print(e)
a
b
c

for 루프는 시퀀스가 아닌 딕셔너리(dictionary)에서도 가능합니다.

>>> for k in {'a':1, 'b':2, 'c':3}: # 딕셔너리
...     print(k)
b
c
a

이 경우 딕셔너리의 키를 탐색하게 되는데, 키가 전달되는 순서는 정해지지 않았습니다. 때문에 여러분이 직접 실행할 경우, 위의 예와는 다른 순서로 키가 인쇄될 수 있습니다. 이터레이터(iterator)를 제공한다면 시퀀스가 아닌 타입도 for 루프로 탐색할 수 있습니다. 이터레이터는 내장 함수 iter() 를 사용해서 얻습니다.

>>> iter({})
<dict_keyiterator object at 0x108f7afc8>

이렇게 얻은 이터레이터를 탐색하려면 내장 함수 next() 를 사용하면 됩니다.

>>> it = iter({'a':1, 'b':2, 'c':3})
>>> print(next(it))
b
>>> print(next(it))
c
>>> print(next(it))
a
>>> next(it)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
StopIteration

next() 를 사용해 한 번에 하나씩 탐색해 나가다가, StopIteration 예외가 발생하면 끝냅니다. 이러한 탐색 법에 대한 약속을 이터레이터 프로토콜(Iterator Protocol)이라고 부르는데, for 루프는 간결한 문법을 제공합니다.

>>> for e in container:
...     print(e)

는 다음과 같은 while 루프와 동일한 결과를 줍니다.

>>> it = iter(container)
>>> while True:
...     try:
...         e = next(it)
...         print(e)
...     except StopIteration:
...         break

사용자가 정의한 클래스가 이터레이터를 지원하려면 __iter__() 메쏘드를 정의하면 됩니다. iter() 는 객체의 __iter__() 메쏘드가 돌려주는 값을 이터레이터로 사용합니다. next() 에 이터레이터를 전달하면 이터레이터의 __next__() 메쏘드를 호출합니다. 때문에 최소한의 이터레이터는 이렇게 구성할 수 있습니다.

>>> class Range:
...     def __init__(self, n):
...         self.n = n
...         self.c = 0
...     def __iter__(self):
...         return self # 이터레이터인 경우는 자신을 돌려주면 됩니다.
...     def __next__(self):
...         if self.c < self.n:
...             v = self.c
...             self.c += 1
...             return v
...         else:
...             raise StopIteration
...     next = __next__ # 파이썬 2 에서는 __next__ 대신 next 를 사용합니다.
>>> for x in Range(3):
...     print(x)
0
1
2

Range 클래스는 이터레이터 외의 용도가 없습니다. 하지만 딕셔너리는 별도의 용도가 있고, 사실 딕셔너리는 이터레이터가 아닙니다.

>>> next({})
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: 'dict' object is not an iterator

딕셔너리를 for 루프에 직접 사용할 수 있는 이유는 딕셔너리 역시 __iter__() 메쏘드를 제공하고 있기 때문입니다. 비슷한 방법으로 RangeFactory 라는 클래스를 만들어보면:

>>> class RangeFactory:
...     def __init__(self, n):
...         self.n = n
...     def __iter__(self):
...         return Range(self.n)
>>> for x in RangeFactory(3):
...     print(x)
0
1
2

__next__() 메쏘드를 제공하고 있지 않기 때문에 RangeFactory 의 인스턴스는 이터레이터가 아닙니다만, __iter__() 메쏘드를 통해 이터레이터(이 경우에는 Range 인스턴스)를 제공하고 있기 때문에 for 루프에서 사용될 수 있습니다. 사실은 이 용법 때문에 이터레이터에도 큰 의미 없는 __iter__() 메쏘드를 만들어주도록 요구하고 있습니다.

이렇게 for 루프에 이터레이터를 직접 요구하지 않고, 이터레이터를 만드는 객체를 요구함으로써, 많은 경우에 for 루프가 깔끔해지기는 하지만, 간혹 혼란을 야기할 수 있는 경우도 발생할 수 있습니다.

>>> r = Range(3)
>>> for x in r:
...     print(x)
...     if x == 1:
...         break
0
1
>>> for x in r:
...     print(x)
2

두 개의 for 루프는 동일한 Range 인스턴스를 사용하고 있습니다. 반면에 RangeFactory 를 사용하는 경우는:

>>> r = RangeFactory(3)
>>> for x in r:
...     print(x)
...     if x == 1:
...         break
0
1
>>> for x in r:
...     print(x)
0
1
2

이 경우는 for 루프를 시작할 때마다 새로운 이터레이터(Range 인스턴스)가 만들어집니다. 때문에 매번 새로운 루프가 시작되는 것이지요. for 루프가 이터레이터를 재사용하는 것으로 보일 때는, 인스턴스가 이터레이터인지 이터레이터를 제공하는 객체인지를 확인하는 것이 중요할 수 있습니다.

프로그래밍을 하다 보면 한쪽에서 생산하고(produce), 다른 쪽에서 소비(consume) 하는 상황을 자주 만납니다. 이를 생산자-소비자 패턴(Producer-Consumer Pattern)이라고 하는데, 이터레이터는 소비자 쪽의 코드를 함축적이면서도 자연스럽게 기술할 수 있는 방법을 제공합니다. 이제 필요한 것은 생산자 쪽의 코드를 자연스럽게 구성할 수 있는 방법입니다. 다음에 다룰 제너레이터(Generator) 가 파이썬의 대답입니다.



import pymongo

con = pymongo.Connection('localhost', port=27017)


tweets = con.db.tweets


import requests, json

url = 'http://search.twitter.com/search.json?q=python%20pandas'

data = json.loads(requests.get(url).text)


for tweet in data['results']:

    tweets.save(tweet)


cursor = tweets.find({'from_user' : 'wesmckinn'})


tweet_fields = ['created_at', 'from_user', 'id', 'text']

result = pd.DataFrame(list(cursor), columns=tweet_fields)

지난번에 다룬 keras를 활용해서 LSTM(Long Short-Term Memory)을 이용한 감성 분석을 해보겠습니다.

 

데이터는 이전과 마찬가지로 train/test 각각 2천개 씩 배정해서 모델을 학습하고, test set에 대한 accuracy를 구하는 과정으로 진행됩니다.

 

 

Keras에는 imdb 데이터가 이미 전처리되어 저장되어있기 때문에 그대로 불러옵니다. 이때 상위 5000개의 단어만 활용하겠습니다.

 

train/test 데이터를 알맞은 형태로 바꿔주고

 

lstm을 이용해서 모델을 만들어줍니다. 모델에 대한 설명은 다른 포스팅을 통해 다루겠습니다.

 

위와 같은 옵션으로 컴파일링을 하고 fitting을 시킨 다음 test data를 학습합니다.

 

0.76의 accuracy가 나왔습니다.

 

지금까지 여러가지 statistical methods를 통해 분석을 해보았습니다. 그 결과 2000개의 train data에 대해서

SentiWordnet : 71%

Naive Bayes : 82%

Doc2Vec : 91.8%

LSTM : 76%

의 정확도를 보였습니다.

물론 가장 기본적인 모형만 만들었기 때문에 parameter를 튜닝하거나 모델을 변형해서 더 나은 accuracy를 만들수 있을 것입니다.

저번 시간의 Naive Bayes Classifier에 이어서 Doc2Vec에 대해 알아보겠습니다.

 

Doc2Vec은 벡터 공간 모형의 일종으로 간단한 신경망 모형을 통해 만들어지는 모델입니다.

Word2Vec과 유사하지만 단어가 아닌 문장, 문단, 문서를 통해 벡터를 만들기 때문에 단어간의 유사성 뿐만 아니라 문서의 구성요소간의 유사성을 정의하고 계산할 수 있습니다.

즉, 문맥을 고려한 분석이 가능하다는 것이 가장 큰 장점입니다.

 

Doc2Vec을 활용한 감성 분석을 하기 위해 먼저 gensim 외 필요한 패키지를 불러옵니다.

 

Doc2Vec 모델 생성을 위한 gensim 패키지!

 

Text preprocessing 을 위한 nltk패키지

 

Naive Bayes와 같은 방식을 통해 전처리와 train/test data셋을 생성합니다.

 

리뷰 리스트와 레이블 리스트를 합쳐서 LabeledLineSentence 객체를 생성합니다.

그 다음 Doc2Vec모델을 다음과 같이 학습시킵니다.

size 는 feature 벡터의 차원입니다.

window는 문서의 내에서의 예측을 위한 예측된 단어와 문맥의 단어들 사이의 최대 거리입니다.

dm은 트레이닝 알고리즘으로 distributed memory가 default 값입니다.

alpha값은 초기 학습률(learning rate)이고 min_alpha는 alpha값이 학습과정에서 선형으로 줄어들어서 도달하는 최소 값입니다.

min_count 이하의 total frequency를 가진 단어들은 모두 무시됩니다.

workers는 cpu의 코어 수에 따라 multi-threads를 지원해서 병렬처리하는 옵션입니다.

그 밖의 옵션에 대한 설명은 https://radimrehurek.com/gensim/models/doc2vec.html을 참고해 주세요.

 

 

이제 생성된 모델을 통해서 앞서 NBC와 같이 train 셋에 대해 감성 분석을 시행하겠습니다.

logistic regression classifier를 이용해서 긍, 부정으로 분류하였습니다.

 

test 셋에서 출력해본 결과 약 91.65%의 정확도를 보였습니다. NBC보다 높은 정확도를 보였지만 전체데이터(25000)를 대상으로 한 경우 정확도가 79%대로 떨어지는 모습을 보였습니다.

 

특정 분석법이 절대 우위에 있는 것이 아니라 다양한 분석방법을 시행해보고 결과를 비교해 보는 것이 좋은 방법 같습니다.

다음 시간에는 감성 분석의 마지막 시간으로 인공신경망(ANN)을 활용한 감성분석을 해보도록 하겠습니다.

저번 포스팅에서는 Sentiment Analysis(SA)의 방법 중 하나인 knowledge based technique에 대해서 살펴봤습니다.

이번에는 Statistical methods을 통해서 감성 분석을 해보겠습니다.

 

  1. Naive Bayes classifier

나이브 베이즈 분류기를 다루기 전에 먼저 TF-IDF(Term Frequency – Inverse Document Frequency)와 같은 벡터 공간 모형에 대해서 알아보겠습니다.

TF_IDF는 이와 같은 형태로 문장에서 해당하는 단어의 등장 여부에 따라서 다음과 같이 나타냅니다. 이 때 단어의 등장여부를 T/F로 보는 boolean model, 단어의 등장 횟수로 보는 frequency model 둘다 가능합니다.

여기서 자주 등장하는 단어를 통해 하나하나의 dimension을 생성하는 것이 벡터 공간 모형의 기초적인 형태입니다. 그리고 이러한 feature에 기계학습 알고리즘을 통해서 train을 진행합니다.

먼저 긍정 학습 데이터에서의 모든 토큰을 words라는 리스트에 저장합니다.  이때  Stopwords도 함께 제거해줍니다

 

같은 방식으로 부정 학습 데이터에서 Stopwords 를 제거하고 words리스트에 붙여 넣습니다.

 

words리스트를 이용해서 3000 dimension의 word feature를 생성합니다.

 

word feature를 통해서 분석할 데이터의 feature를 생성하는 함수도 만들어 줍니다.

이제 첫번째 리뷰 데이터의 문장들을 이용해서 feature를 생성해보겠습니다.

 

 

첫 번째 리뷰 데이터의 feature생성 결과입니다.

 

같은 방법으로 1000개의 리뷰 데이터에 대해 feature set을 생성해서 리스트에 저장합니다.

 

본격적으로 나이브 베이즈 분류기(Naive Bayes Classifier)를 통해 감성분석을 시작하겠습니다. 분류기는 nltk에 내장되어 있기 때문에 따로 설치할 것은 없습니다.

가장 먼저 2000개의 영화 리뷰를 긍/부정으로 분류해보겠습니다.

training,test set을 위한 feature set을 만들어준다음

 

순서대로 앞의 2000 set은 training 나머지는 test set으로 분류합니다

 

그 다음 nltk의 나이브 베이즈 분류기를 실행해주면 됩니다. 전처리에 비해 정말 간단합니다…

 

분류기를 training한 뒤에는 test set에서 얼마나 잘 작동하는지 살펴봅니다.

 

82%의 분류율이 나옵니다. 샘플 데이터라서 그런지 생각보다 높은 분류율을 보여주는 것 같네요. 같은 방법으로 전체 데이터(train 25000, test 25000)으로 한 결과 82.756의 정확도로 2000개의 train 데이터의 경우와 비교해 크게 향상되진 않았습니다.

 

다음 포스팅에서는 나머지 감성 분석 방법들에 대해 알아보겠습니다.

책, 영화등과 같은 매체를 보고나면 그에 대한 피드백이 발생합니다. 어떤 사람들은 주위 지인들에게 평가를 공유하기도 하고 어떤 사람들은 어떤 인상을 받았는지 인터넷 상에 글을 남깁니다. 이번 포스팅에서는 이러한 리뷰들의 단어에 긍/부정을 평가하여 그 리뷰, 더나아가 그 영화에 대한 감정이 긍정적인지 부정적인지 살펴보려고 합니다.

Sentiment Analysis(SA) 에는 크게 세 가지 방식이 있습니다.

  • knowledge-based techniques
  • statistical methods
  • hybrid approaches

먼저 knowledge-based technique은 단어들의 감정의 정도를 평가하는 사전을 만들고 이를 활용해서 단어 또는 글의 감정 상태를 평가하는 방법입니다.

예를 들어 SentiWordnet은 단어 마다의 긍/부정 척도를 더한 대표적인 db로 Python의 nltk에 포함되어 있습니다.

 

다음으로 statistical method는 SVM(support vector machine)이나 ‘bag of words’등 다양한 ML 방법론을 활용해서 단어의 감정을 분류하는 방법입니다. Python의 nltk 패키지에 naive bayes classifier도 통계적 방법 중 하나입니다.

 

마지막으로 hybrid approaches의 경우 두 방법을 적절하게 혼합하여 활용하는 방법입니다.

 

SA에서 가장 힘든 부분 중 하나는 비정형인 자연어의 노이즈들이 많다는 점입니다. 인간의 경우 다양한 문맥에 따라서 흐름을 쉽게 파악할 수 있지만 기계는 어렵습니다.

예를 들어 ‘내가 치킨을 싫어하는 것은 아니야.’ 라는 문장을 보았을 때 사람들은 이 문장이 postive 또는  neutral 하다고 쉽게 판단하지만 긍/부정 단어에 점수를 매기는 knowledge-based technique에서는 negative 문장으로 해석될 수 있습니다. 이처럼 이중 부정문을 다루거나 비속어, 은유, 비유 또는 풍자 등과 같은 skill을 학습하기 힘들기 때문에 현재까지는 한계점이 뚜렸하다고 생각합니다.

 

네이버 평점 9점대에 빛나는 바로 그 영화! – sarcastic review의 대표적인 사례

 

이제 본격적으로 샘플 데이터를 활용해서 SA를 해보겠습니다.

http://ai.stanford.edu/~amaas/data/sentiment/

에서 데이터셋을 받습니다. 이 데이터는 IMDb라는 미국 영화 사이트의 리뷰를 50,000개 수집하여 리뷰마다 각각 긍/부정 분류로 나눈 데이터 셋입니다.

os 패키지는 operating system의 약자로 운영체제에서 쓰는 여러 기능들을 python에서도 쓸수 있게 해주는 패키지입니다.

먼저 긍정 리뷰중 하나를 review에 담았습니다.

 

제대로 리뷰가 담긴것을 확인하시면 for문을 이용해서 전체 긍정 리뷰를 pos_train_list에 저장합니다.

 

train positive 리뷰를 모두 저장했다면 이제 knowledge-based techniques 중 하나인 SentiWordNet에 대해 알아보겠습니다.

[SentiSynset(‘hate.n.01’), SentiSynset(‘hate.v.01’)]

 

sentiwordnet을 쓰기 위해 nltk 패키지를 import하고 ‘hate’라는 단어를 불러왔습니다. filter object 타입을 list로 바꾸면 n, v로 각각 명사 동사의 유의어가 나온것을 볼 수 있습니다.

0.0

0.75

hate라는 동사의 긍/부정 스코어를 확인하면 각각 0, 0.75점 입니다.

 

단어마다 여러 유의어가 존재하는 경우가 있습니다. 그래서 편의를 위해 특정 단어의 모든 유의어의 긍/부정 스코어를 각각 평균한 것으로 쓰기 위해 함수를 정의합니다.

(0.625, 0.03125)

love의 동사 유의어 점수 평균은 0.625이다.

 

2.5/4=0.625이므로 잘 동작하는 것을 알 수 있습니다.

 

원리를 그대로 사용해서 for문을 이용하면 문장에서의 긍/부정 지수도 쉽게 구할 수 있습니다.

 

이제 본격적으로 아까 다운받은 영화 리뷰 자료에서 postive train 리뷰 10가지와 negative train 리뷰 10가지를 불러와 SA를 해보겠습니다.

긍정적 리뷰와 부정적 리뷰를 순서대로 10개씩 불러왔기 때문에 actual에는 1(긍정)을 10번 반복하고 0(부정)을 10번 반복했습니다. 그 다음 과정은 앞과 마찬가지로 리뷰를 10개씩 불러와서 tokenize를 하고 pos_tag를 붙여서 리스트로 만들어주는 작업입니다. 합산된 긍/부정 스코어를 if문을 통해 대소비교를 하는데, 긍정 수치가 높으면 1을 반환하고 그렇지 않다면 0을 predicted에 반환합니다.

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1]
Number of correct instance: 12
Number of incorrect instance: 8

코드를 실행하면 다음과 같은 결과가 출력됩니다. 60%의 확률로 긍/부정 리뷰를 맞추는 성능을 보이고 있습니다.

 

다음 포스팅에서는 statistical methods중 하나인 Naive Bayes를 통한 감성 분석에 대해 알아보겠습니다.

첫번째 글에 이어서 이번에는 단어들간의 관계를 살펴보도록 하겠습니다.

similarity를 구해 리뷰의 단어 중 문맥상 서로 유사한 단어를 살펴보겠습니다.

film movies superhero seen action movie actors character heath performance modern villain second goodbetter aniconic amore batpod iconic watched ------------------------------------------------------------jack time sequel dramas

 

리뷰 텍스트 문맥상 ‘Batman’,’Joker’와 각각 유사하다고 판단된 단어들입니다.

 

텍스트의 연어(Collocation)을 출력해보면 아래와 같습니다.

dark knight; heath ledger; christian bale; harvey dent; comic book; christopher nolan; brucewayne; aaron eckhart; gary oldman; two face; maggie gyllenhaal; morgan freeman; rachel dawes;special effects; batman begins; gotham city; 've seen; district attorney; michael caine; superhero

빈번하게 같이 사용되는 단어들이 출력된 것을 확인할 수 있습니다.

 

마지막으로 연관 단어 그래프를 만들어 보도록 하겠습니다.

리뷰의 텍스트를 바탕으로 같은 문장에서 등장하는 명사들끼리 연결된 그래프를 만드려고 합니다. 먼저 unique 명사들을 추출해서 uniqueNouns 리스트에 추가하는 과정입니다.

위와 같이 행렬을 생성한 다음

이중 for문을 이용해 각 원소에 명사가 들어온 경우 1의 값을 반환하게 합니다.

그리고 이 행렬의 전치와 곱하여 co-occurrence matrix(동시 발생 행렬)를 생성합니다

행렬에서 값이 30이상은 결과 값을 이용해 노드를 만들었습니다.

그래프를 출력하면 위와 같이 명사들 간의 연관 관계를 표현한 그래프가 그려집니다.


http://www.imdb.com/title/tt0110912/?ref_=nv_sr_1

영어로 된 영화 평점 사이트에서 리뷰를 크롤링하고 간단한 텍스트 마이닝을 해보겠습니다.

 

먼저 토큰화(tokenize), 기본형 형태로 만들기(lemmatize)와 stopwords를 제거하는 작업을 했습니다.

Dark knight라는 영화의 리뷰 100개를 크롤링하여 만든 텍스트 파일을 불러와서 위의 순서로 코딩했습니다. 참고로 stopwords의 경우 소문자의 형태만 인식하기 때문에 .lower()를 통해 토큰을 전부 소문자로 변환했습니다.

 

결과를 다시 텍스트 파일로 저장하고…

이제 이 리뷰에서 가장 많이 등장하는 명사를 추출해보겠습니다.

혹시 중간에 nltk패키지의 오류로 실행이 안되면 nltk.download()를 입력해서 필요한 것을 받을 수 있습니다.

[('movie', 340), ('batman', 249), ('film', 247), ('joker', 177), ('dark', 113), ('ledger', 100),('knight', 95), ('heath', 88), ('time', 83), ('action', 67)]

결과를 출력하면 다음과 같이 리뷰에서 가장 많이 등장한 10개의 단어가 빈도 순으로 출력된 것을 볼 수 있습니다.

동사와 형용사도 동일한 방법으로 추출하시면 됩니다.

 

전체 리뷰의 토큰 개수를 구할 수도 있습니다.

20575 6069

그래프를 이용해서 토큰의 등장 횟수를 시각화 할 수도 있습니다

 

‘이나 ‘s와 같은 보통의 상황에서는 불필요한 단어들이 많이 등장하는데 stopwords에서 추가적으로 옵션을 두어 필터링이 되도록 하는 방법도 있습니다.


시각화에서 데이터가 어떤 특징을 지니고 있는지 확인했으니 이번에는 영화 리뷰 간 유사성을 계산해보겠습니다. 이와 같은 분석 말고도 영화 평에 대한 감성 분석과 같은 여러 분석 방법들이 있습니다.

먼저 분석하려는 영화의 리뷰들을 String 형식의 데이터로 받아보겠습니다.

review_list=[]
for n in range(30):
url = ‘http://movie.daum.net/moviedb/grade?movieId=97728&type=netizen&page={}’.format(n+1)
webpage = urlopen(url)
source = BeautifulSoup(webpage,’html.parser’,from_encoding=’utf-8′)
reviews = source.findAll(‘p’,{‘class’: ‘desc_review’})
for review in reviews:
review_list.append(review.get_text().strip().replace(‘\n’,”).replace(‘\t’,”).replace(‘\r’,”))

file = open(‘okja.txt’,’w’,encoding=’utf-8′)

for review in review_list:
file.write(review+’\n’)

file.close()

doc1 = ”
file = open(‘okja.txt‘,’r’,encoding=’utf-8′)
lines = file.readlines()
for line in lines:
doc1 += line
file.close()

영화 ‘옥자’의 리뷰를 받아와서 doc1에 저장하였습니다. 이처럼 나머지 영화들도 doc2, doc3 과 같은 형식으로 저장했습니다.

 

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
corpus = [doc1, doc2, doc3]
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
X=X.todense()

sklearn 모듈을 이용해서 TF-IDF로 벡터화를 하고 코사인 유사도를 사용합니다. 세 리뷰의 명사들 간의 코사인 유사도를 활용해서 유사성이 얼마나 있는지 알아보겠습니다.

print(“similarity between ‘okja’ and ‘monster’:”,cosine_similarity(X[0],X[1]))
print(“similarity between ‘okja’ and ‘real’:”,cosine_similarity(X[0],X[2]))
print(“similarity between ‘monster’ and ‘real’:”,cosine_similarity(X[1],X[2]))

 

 

>similarity between ‘okja’ and ‘monster’: [[ 0.26134023]]

>similarity between ‘okja’ and ‘real’: [[ 0.3445885]]

>similarity between ‘monster’ and ‘real’: [[ 0.27664574]]

구한 코사인 유사도를 프린트하면 다음과 같은 결과가 나오게 됩니다.

 

이번에는 저번 포스팅에서 처리한 데이터를 이용해서 시각화를 해보겠습니다.

먼저 예전에 설치한 pytagcloud 모듈을 불러옵니다. 이 때 pytagcloud는 기본적으로 한국어 폰트를 지원하지 않기 때문에 사용자가 직접 한국어 폰트를 추가해야 합니다.

https://www.google.com/get/noto/

위 사이트에 접속하셔서 Noto Sans CJK KR을 검색하셔서 파일을 아래의 경로로 받아주시면 됩니다.

C:\Users\[사용자 id]\Anaconda3\Lib\site-packages\pytagcloud\fonts

받은 다음 압축을 풀고 fonts 폴더에 otf파일을 붙여넣은 뒤, fonts.json 파일을 텍스트 에디터를 이용해서 열어줍니다.

(저는 Notepad++을 사용합니다.)

{
“name”: “Noto Sans CJK”,
“ttf”: “NotoSansCJKkr-Medium.otf”,
“web”: “http://fonts.googleapis.com/css?family=Nobile”
}

그리고 위의 문장을 추가하시면 됩니다. 혹시라도 중괄호 사이에 쉼표가 빠진다면 정상적으로 동작하지 않기 때문에 주의하셔야 합니다.

import pytagcloud, random, webbrowser
from konlpy.tag import Twitter
from collections import Counter

다시 notebook으로 돌아와서 위와 같이 입력합니다.

 

def get_tags(text,ntags=100,multiplier=1):
t = Twitter()
nouns = []
for sentence in text:
for noun in t.nouns(sentence):

nouns.append(noun)
count = Counter(nouns)
return [{‘color’:color(),’tag’:n,’size’:2*c*multiplier} for n,c in count.most_common(ntags)]

get_tags라는 함수를 만들어서 워드클라우드에 포함될 명사를 추출했습니다. return에서 텍스트의 color와 size를 조절할 수 있습니다.

def draw_cloud(tags, filename, fontname = ‘Noto Sans CJK’,size = (1100,660)):
pytagcloud.create_tag_image(tags,filename,fontname=fontname,size=size)
webbrowser.open(filename)

다음으로 워드클라우드가 출력되는 space를 만들겠습니다. font와 size를 각각 조절할 수 있습니다.

r = lambda: random.randint(0,255)
color = lambda: (r(),r(),r())

입력되는 단어를 구분하기 위해 색을 추출하는 과정입니다. rgb값(0~255)에 랜덤 값을 각각 부여해서 새로운 색상이 나타나도록 했습니다.

okjak = []
file=open(‘okja.txt’,’r’,encoding=’utf-8′)
lines = file .readlines()
for line in lines:
okjak.append(line)
file.close()

tags = get_tags(okjak)
draw_cloud(tags,’wc.png’)

이제 okja.txt를 python으로 불러와서 앞서 작성한 2개의 function을 이용해서 워드클라우드를 작성합니다.


위와 같이 워드클라우드가 생성된 것을 확인 할 수 있습니다.

okja = []
file = open(‘okja.txt’,’r’,encoding=’utf-8′)
lines=file.readlines()

for line in lines:
okja.append(line)
file.close()

먼저 저번 포스팅에서 수집한 데이터를 불러와서 옥자라는 객체에 저장합니다.

from konlpy.tag import Twitter

twitter = Twitter()

sentences_tag=[]
for sentence in okja:
sentences_tag.append(twitter.pos(sentence))

그다음 저번에 설치한 KoNLPy 모듈을 불러와서 sentences_tag라는 리스트 형태로 저장합니다.

print(sentences_tag)
print(‘-‘*30)
print(len(sentences_tag))

를 입력하시면,

 

이와 같은 형태로 추출된 token과 token의 형태소 분석 결과를 출력합니다. 300개의 token이 출력된 것을 볼 수 있습니다.

 

이제 자주 사용되는 단어를 추출하겠습니다.

noun_adj_list = []

for sentence in sentences_tag:
for word, tag in sentence:
if tag in [‘Noun’,’Adjective’]:
noun_adj_list.append(word)

먼저 분석에 중요한 품사로 판단되는 명사와 형용사 token만 추출하여 noun_adj_list에 담았습니다.

from collections import Counter
counts = Counter(noun_adj_list)
print(counts.most_common(10))

collections모듈을 이용해서 most frequent word 10가지를 출력한 결과입니다.

 

 

 

텍스트를 분석 역시 일반적인 데이터 분석처럼 데이터 수집 -> 데이터 전처리 -> EDA -> 데이터 분석 -> knowledge 도출의 단계를 거칩니다. 하지만 unstructured data인 관계로 데이터의 수집과 전처리 과정에서 큰 차이가 나타납니다.

이번 포스팅에서는 movie.daum.net의 영화 리뷰 댓글을 이용해서 데이터를 수집하려고 합니다.

Jupyter notebook에서 새로운 스크립트를 만들고 제일 윗 부분에

#-*- coding: utf-8 -*-

를 입력합니다 기본적으로 윈도우 환경의 Python은 ANSI 형식을 따르기 때문에 한글이 깨지는 경우가 발생합니다. 그래서 기본 인코딩 형식을 utf-8로 바꾸셔야 텍스트를 불러왔을때 깨지지 않게 됩니다.

 

from urllib.request import urlopen

from bs4 import BeautifulSoup

전에 설치한 크롤링 모듈을 불러옵니다.

 

먼저 한 페이지의 리뷰들을 불러오는 과정을 살펴보겠습니다.

url=’http://movie.daum.net/moviedb/grade?movieId=97728&type=netizen&page=2′

webpage=urlopen(url)

먼저 urlopen을 이용해 url주소의 리뷰 데이터를 객체에 저장합니다.

source = BeautifulSoup(webpage,’html.parser’,from_encoding=’utf-8′)

HTML형식으로 된 데이터를 parsing하여 텍스트 형태로 변환합니다.

reviews = source.findAll(‘p’,{‘class’: ‘desc_review’})

for review in reviews:

print(review.get_text().strip())

HTML 소스 코드를 보면 <p class=”desc_review”>내용</p> 형태로 데이터가 저장되어 있습니다. 그래서 reviews라는 객체에 이와 같은 양식을 모두 찾아 list형식으로 변환했습니다. 또한 get_text()를 이용해 텍스트 데이터만 추출하고 strip()을 통해 공백을 제거했습니다.

2페이지의 리뷰들을 모두 추출했는데 for문을 통해 url을 리스트 형식으로 만들어 여러 페이지의 데이터를 추출할 수 있습니다.

review_list=[]
for n in range(10):
url = ‘http://movie.daum.net/moviedb/grade?movieId=97728&type=netizen&page={}’.format(n+1)
webpage = urlopen(url)
source = BeautifulSoup(webpage,’html.parser’,from_encoding=’utf-8′)
reviews = source.findAll(‘p’,{‘class’: ‘desc_review’})
for review in reviews:
review_list.append(review.get_text().strip().replace(‘\n’,”).replace(‘\t’,”).replace(‘\r’,”))

그 다음 추출한 텍스트 데이터를 txt파일로 저장합니다.

file = open(‘okja.txt’,’w’,encoding=’utf-8′)

for review in review_list:
file.write(review+’\n’)
file.close()

 

이번 포스팅에서는 Python을 활용하여 웹페이지에서 텍스트를 추출하고 이를 워드클라우드로 나타내고 텍스트들간의 유사도를 활용해 관련성을 구해보겠습니다.

 

파이썬 텍스트 마이닝은 간단한 편이지만 몇 가지 모듈을 설치해야 합니다.

웹 크롤링을 위해 Beautifulsoup4, 한국어 텍스트 분석을 위한 KoNLPy,워드 클라우드를 위한 pygame,simplejson,pytagcloud 가 필요합니다.

 

>pip install beautifulsoup4

cmd라인에서 다음과 같이 입력하여 먼저 beautifulsoup4 모듈을 설치하시고

 

http://www.lfd.uci.edu/~gohlke/pythonlibs/#jpype 로 가셔서 자신의 세팅에 맞는 JPype1-0.x.x-cp36-…whl 파일을 받으시면 됩니다. 글 작성 기준 버전은 0.6.2인데 최신버전으로 받으시면 될겁니다. 참고로 KoNLPy를 설치하시기 전에 Java가 설치되어있는지 확인하시고 없다면 설치해주시면 됩니다.

>pip install JPype1 …… .whl

cmd라인에서 파일을 받은 경로로 이동하신 뒤(cd 폴더명), 받은 파일의 이름을 install 뒤에 입력하시면 모듈이 설치됩니다.

 

>pip install pygame

>pip install simplejson

>pip install pytagcloud

cmd라인에 3개의 모듈을 설치하시면 됩니다.


pip install konlpy

다음 포스팅에서는 설치된 모듈을 통해 웹페이지에서 크롤링한 텍스트를 시각화 해보겠습니다.

맥에서 Sierra로 변경했더니 Rstudio가 개판됐다.


간단한 해결방법 제시한다.

1. system('defaults write org.R-project.R force.LANG en_US.UTF-8')

2. java 다시 설치하고

3 경로 지정하자. sudo ln --s $(/usr/libexec/java_home)/jre/lib/server/libjvm.dylib /usr/local/lib




명사 분리 추출 후, 단어 사용 빈도 계산하기


이번에는 동아일보, 한겨레신문에서 '사드'관련 기사를 크롤링해 저장한 텍스트파일을 이용해 단어 사용 빈도를 계산하는 프로그램을 만들어보겠습니다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
""" 형태소 분석기
    명사 추출 및 빈도수 체크
    python [모듈 이름] [텍스트 파일명.txt] [결과파일명.txt]
"""
 
import sys
from konlpy.tag import Twitter
from collections import Counter
 
 
def get_tags(text, ntags=50):
    spliter = Twitter()
    nouns = spliter.nouns(text)
    count = Counter(nouns)
    return_list = []
    for n, c in count.most_common(ntags):
        temp = {'tag': n, 'count': c}
        return_list.append(temp)
    return return_list
 
 
def main(argv):
    if len(argv) != 4:
        print('python [모듈 이름] [텍스트 파일명.txt] [단어 개수] [결과파일명.txt]')
        return
    text_file_name = argv[1]
    noun_count = int(argv[2])
    output_file_name = argv[3]
    open_text_file = open(text_file_name, 'r')
    text = open_text_file.read()
    tags = get_tags(text, noun_count)
    open_text_file.close()
    open_output_file = open(output_file_name, 'w')
    for tag in tags:
        noun = tag['tag']
        count = tag['count']
        open_output_file.write('{} {}\n'.format(noun, count))
    open_output_file.close()
 
 
if __name__ == '__main__':
    main(sys.argv)
cs

위 코드는 사용자로부터 텍스트를 입력받아 명사를 분리/추출해 단어사용 빈도를 계산하는 모듈입니다.

코드 한줄한줄 자세히 살펴보겠습니다.




6
7
8
import sys
from konlpy.tag import Twitter
from collections import Counter
cs

이전 글의 크롤러 모듈에서와 마찬가지로 해당 모듈을 실행할 때, 터미널에서 사용자로부터 인자를 받기 위해 'sys.argv'를 사용하려고 'sys'를 임포트했습니다. 그리고 명사를 분리/추출하기 위해 한국어 형태소 분석기인 'konlpy'를 임포트했습니다.  특히, 저는 여기서 'konlpy'의 여러 품사 태깅 클래스 중 'Twitter'를 사용했습니다.


(konlpy.tag 안에는 Kkma, Komoran, Hannanum, Twitter, Mecab 등의 여러 품사 태깅 클래스가 있습니다. 여기서 저는 Twitter를 사용했습니다. Hannanum 클래스의 경우, 정확한 이유는 모르겠지만 텍스트의 길이가 길어져 사용된 단어가 많아지면 올바르게 실행이 안되는 문제가 있는 것 같습니다. 참고하시기 바랍니다. konlpy의 설치법과 더 많은 정보를 원하시는 분들은 'konlpy 공식 홈페이지' 를 참조하시기 바랍니다. )


collections의 Counter객체는 counting hashable container 객체로서 간단히 말해, 빈도수 계산을 위한 사전형태의 데이터타입입니다. 'konlpy'를 통해 품사 태깅을 한 후, 명사만 따로 모아 빈도수를 계산하기 위해 임포트했습니다.




11
12
13
14
15
16
17
18
19
def get_tags(text, ntags=50):
    spliter = Twitter()
    nouns = spliter.nouns(text)
    count = Counter(nouns)
    return_list = []
    for n, c in count.most_common(ntags):
        temp = {'tag': n, 'count': c}
        return_list.append(temp)
    return return_list
cs

위 코드는 텍스트에서 명사를 분리/추출한 후, 빈도 계산을 하는 함수입니다.

해당 함수의 인자 중, 'text'는 분석에 사용할 텍스트이며, 'ntags'는 분리한 명사 중 결과를 출력할 명사의 갯수입니다. 'ntags'는 키워드 인자로 디폴트값으로 50을 설정했습니다. 즉, 사용자로부터 아무 입력이 없으면 해당 함수는 빈도수 기준으로 상위 50개의 명사의 결과만 출력합니다. 


함수가 실행되면 우선 'Twitter'객체를 생성합니다. 이후, 'Twitter'객체의 'nouns'메소드를 이용해 'text'에서 명사만 분리/추출합니다. 'Twitter'객체의 'nouns'메소드는 결과 값으로 순환 가능한 객체를 반환합니다. 다시 말해, 참조변수 'nouns'에는 'Twitter'객체의 'nouns'메소드에 의해 분리/추출된 명사들이 순환가능한 객체로 저장되어 있습니다.

('Twitter'객체를 통해 'nouns'품사말고도 'Josa', 'Verb', 'Eomi' 등의 다른 품사도 추출할 수 있습니다. 공식 홈페이지를 참조세요.)


이후, 이 객체를 이용해 'Counter'객체를 생성하고, 이를 참조변수 'count'에 할당합니다. 위에서 설명했듯이 'Counter'객체는 빈도 계산을 위한 사전 객체로 명사의 사용 빈도를 계산하기 위해 생성했습니다. 그 후, 명사별 빈도수를 저장하기 위한 리스트인 'return_list'를 선언합니다.


그 다음 for문에서는 다음과 같은 작업이 수행됩니다. 'Counter'객체의 'most_common'메소드는 정수를 입력받아 객체 안의 명사 중, 빈도수가 큰 명사부터 순서대로 입력받은 정수갯수만큼 저장되어 있는 객체를 반환합니다. 따라서 for문을 통해 해당 객체에 접근을 한 후, 참조변수 'n'에는 명사를, 참조변수 'c'에는 해당 명사의 빈도수를 할당받아 'tag'키값에는 명사(n)를, 'count'키값에는 해당 명사의 빈도를 갖는 딕셔너리를 만들어 저장하고 이를 결과 리스트인 'return_list'에 순서대로 삽입합니다. 해당 함수의 'ntags'는 디폴트값으로 50이 설정되어 있으므로 별도의 설정이 없다면 해당 함수는 빈도수 기준 상위 50개의 명사만 결과값으로 반환할 것입니다.


for문이 종료된 후에는 'return_list'를 반환합니다. 'return_list'의 예시 형태는 다음과 같습니다.


[{'tags': '명사1', 'count': N1}, {'tags': '명사2', 'count': N2}, ... ,{'tags': '명사50', 'count': N50}]

(N1 >= N2 >= N3 >= .... >= N50)


만약 반환된 결과가 'ntags'수만큼 나오지 않았다면, 사용된 단어가 그 수보다 적은 것 입니다.


 


22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def main(argv):
    if len(argv) != 4:
        print('python [모듈 이름] [텍스트 파일명.txt] [단어 개수] [결과파일명.txt]')
        return
    text_file_name = argv[1]
    noun_count = int(argv[2])
    output_file_name = argv[3]
    open_text_file = open(text_file_name, 'r')
    text = open_text_file.read()
    tags = get_tags(text, noun_count)
    open_text_file.close()
    open_output_file = open(output_file_name, 'w')
    for tag in tags:
        noun = tag['tag']
        count = tag['count']
        open_output_file.write('{} {}\n'.format(noun, count))
    open_output_file.close()
 
 
if __name__ == '__main__':
    main(sys.argv)
cs

위 코드는 메인 함수입니다. 사용자로부터 읽어 올 파일명, 뽑아올 명사 갯수, 결과를 저장할 파일명을 입력받아 각각 'text_file_name', 'noun_count', 'output_file_name'에 할당합니다. 'noun_count'의 경우 정수형으로 캐스팅했습니다.


이후, 분석할 텍스트를 읽어와 'text'에 할당하고, 위에서 설명한 'get_tags'함수를 통해 명사 분석 결과를 참조변수 'tags'에 할당합니다. 그 다음 결과파일명을 통해 결과파일을 생성하고 for문을 통해 'tags'에 저장된 분리된 명사와 해당 명사의 사용 빈도수를 결과 파일에 콤마(,)로 구분해 write합니다.


결과 파일 형식은 다음과 같습니다.


명사1, N1

명사2, N2

명사3, N3

        .

        .

        .


(N1 >= N2 >= N3 >= ...)




(좌 동아일보, 우 한겨레신문)


위 모듈을 통해 동아일보와 한겨레신문에서 사드관련 기사를 크롤링해와 사용된 명사의 빈도수를 추출한 결과입니다. 두 신문사의 기사 모두 총 50개의 명사를 추출했습니다. 보시다시피 두 신문사 모두 '사드'와 '배치'를 제일 많이 사용한 것을 알 수 있습니다. 그 이후에 사용된 단어와 빈도수 순위는 신문사별로 조금씩 달랐습니다. 


중간중간 '수', '등', '그', '며' 등의 무의미한 명사와 명사형태가 아닌 단어도 추출된 것이 보이는데, 이는 'Twitter' 품사 태깅 클래스에서 걸러주지 못한 것이므로 수작업으로 제거한 후, 시각화 또는 분석에 사용하시면 됩니다.




다음 마지막 글에서는 계산한 명사의 빈도수를 바탕으로 워드 클라우드로 시각화하는 프로그램을 만들어보도록 하겠습니다.




연관된 글

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 2 (네이버 뉴스 크롤링하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 3 (동아일보, 한겨레 '사드'관련 기사 크롤링하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 4 (단어 사용 빈도 체크하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 5 (워드클라우드 만들기) 미작성



출처: http://yoonpunk.tistory.com/7 [윤빵꾸의 공부노트]

동아일보, 한겨레 신문에서 '사드'관련 신문기사 크롤링하기


이번 글에서는 지난 글에서 연습한 네이버 신문기사 크롤링 모듈을 응용 확장해, 동아일보와 한겨레 신문에서 특정 키워드와 관련된 신문기사의 본문 내용을 대량으로 긁어오는 크롤러를 만들어보도록 하겠습니다. 


 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
""" 동아일보 특정 키워드를 포함하는, 특정 날짜 이전 기사 내용 크롤러(정확도순 검색)
    python [모듈 이름] [키워드] [가져올 페이지 숫자] [결과 파일명]
    한 페이지에 기사 15개
"""
 
import sys
from bs4 import BeautifulSoup
import urllib.request
from urllib.parse import quote
 
TARGET_URL_BEFORE_PAGE_NUM = "http://news.donga.com/search?p="
TARGET_URL_BEFORE_KEWORD = '&query='
TARGET_URL_REST = '&check_news=1&more=1&sorting=3&search_date=1&v1=&v2=&range=3'
 
 
# 기사 검색 페이지에서 기사 제목에 링크된 기사 본문 주소 받아오기
def get_link_from_news_title(page_num, URL, output_file):
    for i in range(page_num):
        current_page_num = 1 + i*15
        position = URL.index('=')
        URL_with_page_num = URL[: position+1+ str(current_page_num) \
                            + URL[position+1 :]
        source_code_from_URL = urllib.request.urlopen(URL_with_page_num)
        soup = BeautifulSoup(source_code_from_URL, 'lxml',
                             from_encoding='utf-8')
        for title in soup.find_all('p''tit'):
            title_link = title.select('a')
            article_URL = title_link[0]['href']
            get_text(article_URL, output_file)
 
 
# 기사 본문 내용 긁어오기 (위 함수 내부에서 기사 본문 주소 받아 사용되는 함수)
def get_text(URL, output_file):
    source_code_from_url = urllib.request.urlopen(URL)
    soup = BeautifulSoup(source_code_from_url, 'lxml', from_encoding='utf-8')
    content_of_article = soup.select('div.article_txt')
    for item in content_of_article:
        string_item = str(item.find_all(text=True))
        output_file.write(string_item)
 
 
# 메인함수
def main(argv):
    if len(argv) != 4:
        print("python [모듈이름] [키워드] [가져올 페이지 숫자] [결과 파일명]")
        return
    keyword = argv[1]
    page_num = int(argv[2])
    output_file_name = argv[3]
    target_URL = TARGET_URL_BEFORE_PAGE_NUM + TARGET_URL_BEFORE_KEWORD \
                 + quote(keyword) + TARGET_URL_REST
    output_file = open(output_file_name, 'w')
    get_link_from_news_title(page_num, target_URL, output_file)
    output_file.close()
 
 
if __name__ == '__main__':
    main(sys.argv)
 
 
cs

위 코드는 동아일보에서 특정 키워드를 가지는 신문기사를 대량으로 크롤링하기 위한 크롤러 모듈입니다.  

전체적인 모듈의 동작 과정은 다음과 같습니다.


첫째, 특정 키워드를 통해 신문 기사를 검색해 관련된 기사 목록을 얻어 옴

둘째, 신문사의 기사 목록 페이지의 URL주소 패턴을 분석해, 반복문으로 여러 목록 페이지를 돌며 올라와 있는 기사 제목에 연결된 모든 링크 주소(기사 본문 URL 주소)를 얻어 옴

셋째, 얻어낸 기사의 링크 주소 하나하나에 접근해, 기사 본문 내용이 담긴 HTML 요소를 찾아 해당 요소만 긁어와 본문 내용을 하나의 파일에 저장


그럼 아래에서 한줄한줄 코드를 살펴보며 자세히 설명하도록 하겠습니다.




1
2
3
4
import sys
from bs4 import BeautifulSoup
import urllib.request
from urllib.parse import quote
cs

해당 모듈에서 크롤링을 위해 사용할 라이브러리를 임포트한 부분입니다.

해당 모듈을 실행할 때, 터미널에서 사용자로부터 인자를 받기 위해 'sys.argv'를 사용하려고 'sys'를 임포트했고, 타겟 URL에 요청을 보내고 응답을 받기 위해 'urllib'을, 받은 응답(HTML 코드)를 파싱하기 위해 'BeautifulSoup'을 임포트했습니다.

이전 예제와는 다르게 'urllib.parse'에서 'quote'함수를 임포트했는데, 'quote'는 'urlopen'에서 인자로 사용되는 URL주소(이하 타겟 주소)에 한글(UTF_8)이 포함되었을 때, 이를 아스키(ASCII)형식으로 바꿔주기 위한 함수 입니다.

이 내용은 해당 함수를 사용할 때, 다시 한번 자세히 설명하겠습니다.




1
2
3
TARGET_URL_BEFORE_PAGE_NUM = "http://news.donga.com/search?p="
TARGET_URL_BEFORE_KEWORD = '&query='
TARGET_URL_REST = '&check_news=1&more=1&sorting=3&search_date=1&v1=&v2=&range=3'
cs

이 부분은 'urlopen'으로 요청을 보낼 타겟 주소를 세 부분으로 나누어 상수에 할당한 부분입니다.

타겟 주소를 왜 세 부분으로 나누어놓았는지 알기 위해선 'urlopen'으로 처음 접근할 동아일보의 기사 검색 페이지(이하 타겟 페이지)를 찾아 해당 페이지의 URL패턴을 찾아내야 합니다. 




위는 동아일보 메인 페이지입니다. 

URL 패턴을 찾기 위해 검색 키워드로 '사드'를 사용해 우측 상단 검색 공간에서 검색해보겠습니다.




사드로 검색한 결과 페이지입니다. 우리는 정확히 사드와 관련된 신문 기사만 필요하기 때문에 본 화면에서 주황색으로 칠해진 카테고리 탭을 뉴스로 한정 짓고, 카테고리 탭 바로 밑의 검색필터에서 정렬은 정확도순으로 범위는 동아일보로 기간은 전체로 다시 한번 검색을 설정하도록 하겠습니다.




위의 설정을 마친 화면입니다. 

웹동아일보에서 총 823건이 검색되었으나 첫 화면에 보이는 기사의 갯수는 총 5개 입니다. 따라서 본 페이지는 아직 크롤링의 타겟 페이지로는 부족합니다.

더 많은 기사를 보기 위해 아래의 주황색으로 링크된 더보기를 클릭하도록 하겠습니다.




위 화면은 더보기를 누른 화면입니다. 

이전과는 다르게 많은 기사들이 한 페이지당 15개씩 게시되어 나타나는 것을 알 수 있습니다.

(한 사진에 해당 웹페이지 화면은 다 담을 수 없어 하단 부분만 사진으로 담았습니다.)

우리는 이 화면 뿐 아니라 원하는 갯수 만큼의 기사 목록 페이지(2페이지, 3페이지 ...)에 있는 각각의 기사에 접근해 본문 내용을 수집해야 합니다. 

사 내용을 수집하기 위해선 실제 기사 페이지로 접근해야 하기 때문에 파란색으로 링크처리가 된 기사의 제목에서 연결된 기사 본문 URL주소를 뽑아와야 합니다. 

그럼 먼저 이 페이지의 URL주소를 한번 살펴보겠습니다.


http://news.donga.com/search?check_news=1&more=1&sorting=3&range=3&query=사드

사진 속 URL주소창을 보게 되면 위 화면의 URL주소는 위와 같습니다.

'check_news=1'은 뉴스로 한정지은 것을, 'more=1'은 더보기를 누른 상태임을, 'sorting=3'과 'range=3'은 각각 정확도순 정렬이며, 전체기간검색임을, 마지막으로 'query=사드'는 우리가 검색한 키워드임을 지레짐작 할 수 있습니다.

 

하지만 아직도 이 페이지가 우리가 크롤링할 타겟 페이지가 되기엔 부족합니다.

우리는 1번 페이지의 15개의 기사만 가져올 것이 아니라 2번 페이지, 3번 페이지 등등 원하는 페이지 수 만큼 기사를 크롤링해야하기 때문입니다. 

그러나 이상하게도 위 URL주소에는 페이지에 해당 하는 내용이 있어 보이진 않습니다.


사실 페이지에 관한 내용은 숨겨져 있습니다. 

하단의 페이지 숫자를 눌러 2번째 페이지로 들어가 보겠습니다.  




위 화면은 두번째 페이지로 넘어 온 화면입니다.

첫번째 화면의 URL 주소와는 달리 두번째 페이지의 URL주소가 달라진 것을 확인할 수 있습니다.


http://news.donga.com/search?p=16&query=사드&check_news=1&more=1&sorting=3&search_date=1&v1=&v2=&range=3


기존의 'query=사드'위치가 앞쪽으로 이동해 왔으며 페이지숫자로 추정되는 'p=16'이 나타났습니다.

16의 숫자가 페이지를 어떤 식으로 정의하는지 알아보기 위해 다시 여러 페이지를 눌러 URL 주소를 찾아 본 결과 아래와 같이 변했습니다.


1번 페이지: http://news.donga.com/search?p=1&query=사드&check_news=1&more=1&sorting=3&search_date=1&v1=&v2=&range=3

2번 페이지: http://news.donga.com/search?p=16&query=사드&check_news=1&more=1&sorting=3&search_date=1&v1=&v2=&range=3

3번 페이지: http://news.donga.com/search?p=31&query=사드&check_news=1&more=1&sorting=3&search_date=1&v1=&v2=&range=3

4번 페이지: http://news.donga.com/search?p=46&query=사드&check_news=1&more=1&sorting=3&search_date=1&v1=&v2=&range=3


URL 주소가 변화하는 것을 살펴보면 페이지가 증가할 때마다 'p'의 값이 15씩 더해지는 것을 알 수 있습니다.

한 페이지에 나타나는 기사의 수가 15개인 것으로 보아 한 페이지가 증가할 때마다 p의 값을 15씩 늘려 페이지를 구분한 것을 알아낼 수 있습니다. 

그럼 다시 한번 타겟 주소를 상수화한 부분을 살펴보겠습니다.




11
12
13
14
TARGET_URL_BEFORE_PAGE_NUM = "http://news.donga.com/search?p="
TARGET_URL_BEFORE_KEWORD = '&query='
TARGET_URL_REST = '&check_news=1&more=1&sorting=3&search_date=' \
                  '1&v1=&v2=&range=1'
cs

'사드'를 통해 검색된 기사 목록 전체를 둘러보기 위해선 'query=사드'로 설정되어야 하며 페이지 번호를 'p=1', 'p=16', 'p=31'... 처럼 원하는 페이지를 나타내는 수 만큼 변경시켜가며 전부 둘러봐야 하는 것을 알 수 있습니다. 

따라서 위 처럼 페이지수를 나타내는 'p='부분과 검색 키워드를 나타내는 '&query='부분의 정보를 사용자로 부터 입력받아 하나의 타겟 주소로 결합하기 위해서 타겟 주소를 세 부분으로 나눈 것입니다.

그럼 타겟 주소가 어떻게 결합되어 사용되는지 알아보기 위해 크롤러 모듈에서 메인함수를 먼저 살펴보겠습니다.




42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 메인함수
def main(argv):
    if len(argv) != 4:
        print("python [모듈이름] [키워드] [가져올 페이지 숫자] [결과 파일명]")
        return
    keyword = argv[1]
    page_num = int(argv[2])
    output_file_name = argv[3]
    target_URL = TARGET_URL_BEFORE_PAGE_NUM + TARGET_URL_BEFORE_KEWORD \
                 + quote(keyword) + TARGET_URL_REST
    output_file = open(output_file_name, 'w')
    get_link_from_news_title(page_num, target_URL, output_file)
    output_file.close()
 
 
if __name__ == '__main__':
    main(sys.argv)
cs

위 코드는 메인함수 부분입니다.

'sys.argv'를 통해 사용자로부터 검색 키워드, 페이지 숫자, 결과 파일명을 각각 'keyword', 'page_num', 'output_file_name'에  입력받습니다.

'page_num'은 정수형으로 쓰여야 하므로 int형으로 변환해서 사용합니다.


이후, target_URL 변수에 모듈 위에서 정의해놓은 URL상수들을 사용자로부터 입력받은 정보와 결합시킵니다.

이때, 'TARGET_URL_BEFORE_PAGE_NUM'뒤에 사용자로부터 입력받은 페이지 숫자를 결합하지는 않고 검색 키워드인 'keword'만 'TARGET_URL_BEFORE_KEWORD'뒤에 'quote'(urllib.parse의 메소드)메소드만 사용해 결합합니다.

(페이지 숫자를 결합하지 않는 이유는 아래에서 설명하겠습니다.)

'quote' 메소드를 'keword'에 사용하는 이유는 우리가 검색할 때 사용하는 언어는 한글('UTF-8')이기 때문입니다.

URL주소에는 'ASCII' 표현 방식 이외의 문자표기법은 사용될 수 없기 때문에 '사드'라는 'UTF-8' 방식의 문자를 'ASCII' 방식으로 변환해야하기 위해 'quute'메소드를 'keword'에 사용했습니다.


그렇게 완성된 타겟 주소(사실 페이지 숫자가 아직 포함되지 않았으므로 완전한 형태의 타겟 주소는 아닙니다.)를 'get_link_from_news_title'함수를 이용해 기사를 크롤링해와 파일로 저장합니다.




16
17
18
19
20
21
22
23
24
25
26
27
28
39
# 기사 검색 페이지에서 기사 제목에 링크된 기사 본문 주소 받아오기
def get_link_from_news_title(page_num, URL, output_file):
    for i in range(page_num):
        current_page_num = 1 + i*15
        position = URL.index('=')
        URL_with_page_num = URL[: position+1+ str(current_page_num) \
                            + URL[position+1 :]
        source_code_from_URL = urllib.request.urlopen(URL_with_page_num)
        soup = BeautifulSoup(source_code_from_URL, 'lxml',
                             from_encoding='utf-8')
        for title in soup.find_all('p''tit'):
            title_link = title.select('a')
            article_URL = title_link[0]['href']
            get_text(article_URL, output_file)
cs

위 함수는 메인함수에서 크롤링하기 위한 함수인 'get_link_from_news_title'함수 입니다.

인자로는 크롤링해 올 기사 목록 수인 page_num과, 타겟 주소인 URL, 그리고 본문 내용을 저장할 결과파일명인 'output_file'을 인자로 받습니다.


함수 안 첫번째 반복문은 여러 기사 목록을 돌며 기사 제목에 링크된 기사 본문 주소(URL주소)를 얻어 오는 부분입니다.

이 반복문에서는 기사를 긁어올 페이지 수만큼 코드를 반복합니다.

'current_page_num'에 'i'를 이용해서 긁어올 페이지 수만큼의 현재 페이지를 만듭니다.

(i=0 일때 current_page_num=1, i=1 일때 current_page_num=16, i=2 일때 current_page_num=31 ...)


그 후, 인자로 받은 URL에서 첫번째로 '='문자가 나오는 위치를 'position'에 할당한 후, 해당 'position'뒤에 'current_page_num'을 스트링으로 변환한 후 삽입합니다. 

(21~22번째 줄 코드가 이해가 가지 않는다면 문자열 슬라이스를 검색해 살펴보시길 바랍니다.)


URL에서 처음으로 '='문자가 나오는 위치는 아까 위에서 URL 주소에 마저 결합하지 않았던 페이지 숫자를 나타내는 부분이기 때문에 위와 같은 방법으로 완전한 타겟 주소를 만들수 있으며 반복문을 통해 1번 페이지부터, 원하는 페이지(예를 들어 원하는 페이지 수가 10이면 i의 최대값은 9가 되고, 그때의 'current_page_num'은 136이 됩니다.)까지의 모든 URL주소를 만들어 낼 수 있습니다. 


이렇게 만들어진 'URL_with_page_num'을 이용해 'urlopen'으로 요청을 하고 응답을 받아 'BeaurifulSoup'객체인 'soup'을 만듭니다.


이제부터는 모든 기사 목록에 올라온 기사 제목에서 기사 본문 주소(URL주소)를 추출해올 수가 있습니다.

하단의 26번째 줄 부터 시작되는 반복문은 기사 제목에 링크된 기사 본문 주소(URL주소)를 얻어오기 위한 부분입니다.

이를 설명하기 앞서 다시 한번 동아일보 기사 검색 페이지 화면을 보도록 하겠습니다.




'사드'로 검색한 기사 목록 첫번째 페이지입니다.

크롬 개발자 도구를 통해 첫번째 기사의 제목(링크)부분이 어딘지 찾아봤습니다.




class가 'tit'인 p태그 안의 첫번째 a태그에 연결된 URL주소가 해당 기사 본문 URL가 포함된 것을 알 수 있습니다.

직접 더 살펴본 결과, 2번째 기사, 3번째 기사 .... 15번째 기사 제목 모두  연결된 기사 본문 URL주소가 위와 같은 형식이었습니다.

따라서 우리는 위에서 만든 'soup'객체에서 class='tit인 p태그를 모두 뽑아와 그 안에 있는 첫번째 a태그의 'href'의 내용을  가져온다면 모든 기사의 내용을 크롤링할 수 있습니다.

그럼 'get_link_from_news_title'함수의 하단 반복문을 다시 보도록 하겠습니다.




26
27
28
29
        for title in soup.find_all('p''tit'):
            title_link = title.select('a')
            article_URL = title_link[0]['href']
            get_text(article_URL, output_file)
cs

위에서 생성한 'soup'객체에서 'find_all' 메소드를 통해 class가 'tit'인 p태그를 모두 가져와 반복문을 통해 'title'에 하나씩 할당했으며 'select'메소드를 통해 모든 a태그를 'title_link'에 저장했습니다.

('select' 메소드는 인자로 주어지는 태그를 모두 가지고 오며, 해당 객체는 인덱스로 태그 하나하나에 접근할 수 있습니다. 자세한 사항은 BeautifulSoup 문서를 참조하시기 바랍니다.) 


기사 본문 URL 주소는 p태그 안 첫번째 a태그에 저장되어 있으므로 'title_link'의 0번 인덱스로 첫번째 a태그에 접근했으며 키 값으로 'href'를 이용해 'href'로 연결된 URL주소를 'article_URL'에 저장했습니다. 

(역시나 BeutifulSoup 객체의 자세한 사용법은  BeautifulSoup 문서를 참조하시기 바랍니다.) 


2개의 반복문을 통해 최종적으로 얻어진 기사 본문 URL주소를 'get_text'함수에 결과 파일명과 함께 전달합니다.

'get_text'메소드는 실제적으로 각 기사의 본문 내용을 크롤링해오는 함수입니다.


 


32
33
34
35
36
37
38
39
40
# 기사 본문 내용 긁어오기 (위 함수 내부에서 기사 본문 주소 받아 사용되는 함수)
def get_text(URL, output_file):
    source_code_from_url = urllib.request.urlopen(URL)
    soup = BeautifulSoup(source_code_from_url, 'lxml', from_encoding='utf-8')
    content_of_article = soup.select('div.article_txt')
    for item in content_of_article:
        string_item = str(item.find_all(text=True))
        output_file.write(string_item)
 
cs

위는 기사 본문 주소를 받아 본문 내용을 긁어오는 'get_text'함수 입니다.

이전 글에서 네이버뉴스를 긁어오기 위해 사용했던 함수와 큰 차이는 없습니다.

이 함수는 기사 URL 주소를 통해 응답을 받아 'BeautifulSoup'객체를 만들고, 본문을 추출해 결과 파일에 쓰는 동작을 합니다.

다만 이 함수에서 다른 점이 있다면, 동아일보에서는 기사의 본문 내용이 class가 'article_txt'인 div태그안에 포함되어 있었기 때문에 'select'메소드를 통해  class가 'article_txt'인 div태그안 텍스트 요소만 뽑아온 점입니다.


다시 한번 두 함수를 정리해보면 다음과 같습니다.

'get_link_from_title'함수를 통해 특정 키워드로 검색한 기사 목록의 모든 기사 주소들을 추출해오며

해당 함수 내부에서 추출한 기사 주소들을 이용해 'get_text'함수를 호출에 결과 파일에  기사 본문 내용을 누적해 저장하는 방식으로 두 함수는 동작하게 됩니다. 




터미널을 통해 크롤러 모듈을 실행시켜 사드 관련 기사를 10페이지(150개)를 크롤링해 결과 파일을 'result_articles.txt'에 저장했습니다.

그 저장 결과는 다음과 같습니다.




기사가 잘 크롤링 된 것을 알 수 있습니다. 

텍스트 중간 자바스크립트 코드도 포함된 것이 보이는데, 아마 본문 내용이 포함된 태그에 같이 포함된 코드인 것으로 추정됩니다.

해당 코드 뿐 아니라 불필요한 문자는 이전글에서 설명한 텍스트 정제 모듈로 제거해서 분석에 사용하시면 됩니다.


한겨레신문에서 기사를 크롤링해 오는 방법 또한 동아일보에서 크롤링해 온 방법과 동일합니다.

다만 한겨레신문에서 사용하는 별도의 URL패턴이 있을 것이며 기사 본문 내용을 구성하는 HTML코드 또한 동아일보와 다를 것입니다.

위 크롤러 모듈에서 해당 사항만 한겨레신문에 맞게 변경하여 사용한다면 동일하게 '사드'와 관련된 신문 기사를 크롤링해 오실 수 있을겁니다.


한겨레신문을 위한 크롤러 모듈은 아래의 코드를 참조하시기 바랍니다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
""" 한겨레 신문 특정 키워드를 포함하는, 특정 날짜 이전 기사 내용 크롤러(정확도순 검색)
    python [모듈이름] [키워드] [가져올 페이지 숫자] [가져올 기사의 최근 날짜]
    [결과 파일명.txt]
    한페이지에 10개
"""
 
import sys
from bs4 import BeautifulSoup
import urllib.request
from urllib.parse import quote
 
TARGET_URL_BEFORE_KEWORD = 'http://search.hani.co.kr/Search?command=query&' \
                          'keyword='
TARGET_URL_BEFORE_UNTIL_DATE = '&media=news&sort=s&period=all&datefrom=' \
                               '2000.01.01&dateto='
TARGET_URL_REST = '&pageseq='
 
 
def get_link_from_news_title(page_num, URL, output_file):
    for i in range(page_num):
        URL_with_page_num = URL + str(i)
        source_code_from_URL = urllib.request.urlopen(URL_with_page_num)
        soup = BeautifulSoup(source_code_from_URL, 'lxml',
                             from_encoding='utf-8')
        for item in soup.select('dt > a'):
            article_URL = item['href']
            get_text(article_URL, output_file)
 
 
def get_text(URL, output_file):
    source_code_from_url = urllib.request.urlopen(URL)
    soup = BeautifulSoup(source_code_from_url, 'lxml', from_encoding='utf-8')
    content_of_article = soup.select('div.text')
    for item in content_of_article:
        string_item = str(item.find_all(text=True))
        output_file.write(string_item)
 
 
def main(argv):
    if len(sys.argv) != 5:
        print("python [모듈이름] [키워드] [가져올 페이지 숫자] "
              "[가져올 기사의 최근 날짜] [결과 파일명.txt]")
        return
    keyword = argv[1]
    page_num = int(argv[2])
    until_date = argv[3]
    output_file_name = argv[4]
    target_URL = TARGET_URL_BEFORE_KEWORD + quote(keyword) \
                 + TARGET_URL_BEFORE_UNTIL_DATE + until_date + TARGET_URL_REST
    output_file = open(output_file_name, 'w')
    get_link_from_news_title(page_num, target_URL, output_file)
    output_file.close()
 
 
if __name__ == '__main__':
    main(sys.argv)
 
 
cs




다음 글에선 크롤링한 기사 본문 내용을 바탕으로 단어 사용 빈도를 체크하는 프로그램을 만들어보도록 하겠습니다.



연관된 글

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 2 (네이버 뉴스 크롤링하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 3 (동아일보, 한겨레 '사드'관련 기사 크롤링하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 4 (단어 사용 빈도 체크하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 5 (워드클라우드 만들기) 미작성



출처: http://yoonpunk.tistory.com/6 [윤빵꾸의 공부노트]

네이버 뉴스 크롤링하기


이번 글에서는 동아일보와 한겨레신문에서 특정 키워드를 포함하는 기사를 긁어오기 전 예제로 네이버포털의 뉴스기사를 긁어 오는 것을 먼저 연습하도록 하겠습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"""네이버 뉴스 기사 웹 크롤러 모듈"""
 
from bs4 import BeautifulSoup
import urllib.request
 
# 출력 파일 명
OUTPUT_FILE_NAME = 'output.txt'
# 긁어 올 URL
URL = 'http://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=103&oid=055'\
      '&aid=0000445667'
 
 
# 크롤링 함수
def get_text(URL):
    source_code_from_URL = urllib.request.urlopen(URL)
    soup = BeautifulSoup(source_code_from_URL, 'lxml', from_encoding='utf-8')
    text = ''
    for item in soup.find_all('div', id='articleBodyContents'):
        text = text + str(item.find_all(text=True))
    return text
 
 
# 메인 함수
def main():
    open_output_file = open(OUTPUT_FILE_NAME, 'w')
    result_text = get_text(URL)
    open_output_file.write(result_text)
    open_output_file.close()
    
 
if __name__ == '__main__':
    main()
cs

위 코드는 네이버뉴스를 긁어오기 위한 크롤러 모듈 입니다.

그럼 코드를 하나하나 살펴보도록 하겠습니다.



1
2
3
4
5
"""네이버 뉴스 기사 웹 크롤러 모듈"""
 
from bs4 import BeautifulSoup
import urllib.request
 
cs
크롤링을 하기 위해 두 가지 라이브러리를 임포트했습니다.

HTTP REQUEST를 보내고 HTTP RESPONSE를 받기 위한 urllib과 

urllib을 통해 받은 RESPONSE(HTML)를 파싱하기 위한 Beautiful Soup 입니다.

urllib, Beautiful Soup이 임포트되지 않는다면 해당 라이브러리를 먼저 설치하시기 바랍니다.



6
7
8
9
10
11
# 출력 파일 명
OUTPUT_FILE_NAME = 'output.txt'
# 긁어 올 URL
URL = 'http://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=103&oid=055'\
      '&aid=0000445667'
 
cs
그 후에 기사내용을 긁어와 저장할 파일명과 긁어 올 기사의 URL주소를 상수로 할당했습니다.

URL주소를 백슬래시를 사용해 두 줄로 나누었습니다. 이는 한 행이 80자가 넘어가는 것을 방지하기 위해서 입니다.

신문기사 URL주소는 본인이 크롤링하길 원하는 뉴스기사의 URL주소를 복사해 오시면 됩니다. 



저는 네이버 뉴스에서 동영상이 포함된 일기예보 기사를 골라 복사해왔습니다.

동영상이 포함된 기사였지만 일기예보 내용이 텍스트로 본문에 포함되어 있으므로 해당 기사의 본문 내용(텍스트)만 긁어오도록 하겠습니다.



13
14
15
16
17
18
19
20
# 크롤링 함수
def get_text(URL):
    source_code_from_URL = urllib.request.urlopen(URL)
    soup = BeautifulSoup(source_code_from_URL, 'lxml', from_encoding='utf-8')
    text = ''
    for item in soup.find_all('div', id='articleBodyContents'):
        text = text + str(item.find_all(text=True))
    return text
cs

위는 해당 URL주소로 요청을 보내고 받아 기사 내용을 파싱해 하나의 문자열로 저장하는 함수입니다.



14
15
16
def get_text(URL):
    source_code_from_URL = urllib.request.urlopen(URL)
    sou= BeautifulSoup(source_code_from_URL, 'lxml', from_encoding='utf-8')
cs

우선 15번째 줄에서 urlopen메소드를 통해 URL주소에 대한 요청을 보내 source_code_from_URL 변수에 그 결과를 저장했습니다. 

그 후, HTML코드를 파싱하기 위해 16번째 줄에서 source_code_from_URL을 이용, BeautifulSoup객체를 생성해 soup에 할당했습니다. 

BeautifulSoup객체 생성자의 2번째 인자로 'lxml'을 사용해 기존 'html'방식 대신 'lxml'방식으로 파싱을 했고, 한글 내용이 포함된 기사이기 때문에 from_encoding 키워드 인자로 'utf-8'을 넣어 'UTF-8'방식으로 인코딩을 했습니다. 

'lxml'방식으로 BeautifulSoup 객체를 생성할때 오류가 난다면 'lxml' 라이브러리 또한 설치해주시길 바랍니다.


이 후, soup객체에서 원하는 부분(HTML요소)만 가지고 와야 합니다. 우선 우리가 가지고 올 기사의 본문 내용이 포함된 태그(HTML 요소)가 무엇인지를 찾아야합니다.



위 사진은 신문기사 페이지에서 크롬 개발자도구를 이용해 본문 내용이 포함된 태그를 찾은 사진입니다.



자세히 살펴보면 본문 내용은 id가 'articleBodyContents'인 'div'클래스 안에 담겨 있음을 알 수 있습니다.

따라서 우리가 생성한 객체 soup에서 위에서의 'div'클래스를 가져오기만 하면 됩니다.



17
18
19
20
21
    text = ''
    for item in soup.find_all('div', id='articleBodyContents'):
        text = text + str(item.find_all(text=True))
    return text
 
cs

우선 본문 내용을 저장하기 위해 text에 빈 문자열을 할당합니다.

이 후, soup객체의 find_all 메소드를 이용해 id가 'articleBodyContents'인 'div'클래스를 모두 뽑아내고, for문을 통해 뽑힌 요소 하나하나에 다시 한번 직접 접근합니다.

for문으로 뽑혀진 각 요소 item에 find_all 메소드를 사용, text 키워드 인자에 True를 넣어 텍스트 요소만 뽑아 문자열로 치환한 후, text 문자열에 이어 붙였습니다. 

그 후, text를 반환했습니다.

좀 더 자세 BeautifulSoup객체의 사용법은 공식 문서를 참조하시기 바랍니다.


본문 내용이 포함된 요소가 'div'클래스 하나인데 for문을 쓴 이유는 첫번째로는 find_all 메소드의 반환 객체가 인자로 주어진 조건을 만족하는 모든 요소들을 순환가능한 객체(그 타입이 리스트인지 아닌지는 잘 모르겠네요...)로 주어지기 때문에 사용했으며, 두번째로는 다른 신문사의 경우, 본문 내용이 동일한 id를 갖는 동일한 여러개의 태그에 나뉘어 할당되어있는 경우도 있기 때문입니다.



23
24
25
26
27
28
29
30
31
32
# 메인 함수
def main():
    open_output_file = open(OUTPUT_FILE_NAME, 'w')
    result_text = get_text(URL)
    open_output_file.write(result_text)
    open_output_file.close()
    
 
if __name__ == '__main__':
    main()
cs

위는 그 이후의 코드입니다. 

메인 함수를 별도로 만들고 __name__을 이용해 main함수를 실행시켰습니다.

메인함수에서는 위에서 할당한 OUTPUT_FILE_NAME상수를 통해 txt파일을 생성하고, get_text함수를 사용해 기사내용을 result_text에 할당했습니다. 

이 후, 오픈한 output_file에 기사를 쓰고 닫았습니다.



터미널에서 해당 크롤러모듈을 실행한 결과 'output.txt' 파일이 생성되었으며



'output.txt' 파일의 내용 또한 우리가 긁어오고자 한 신문기사의 내용이 성공적으로 긁어와졌음을 확인할 수 있습니다.

다만 '\n', '\r'등의 문자와 워드클라우드 시각화에 필요 없는 특수문자의 제거가 필요해 보입니다. 

따라서 텍스트 정제 모듈을 따로 만들어 추출된 기사에서 필요없는 문자를 제거하였습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
""" 텍스트 정제 모듈
    영어, 특수기호 모두 제거
"""
 
import re
 
# 입,출력 파일명
INPUT_FILE_NAME = 'output.txt'
OUTPUT_FILE_NAME = 'output_cleand.txt'
 
 
# 클리닝 함수
def clean_text(text):
    cleaned_text = re.sub('[a-zA-Z]''', text)
    cleaned_text = re.sub('[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"]',
                          '', cleaned_text)
    return cleaned_text
    
 
# 메인 함수
def main():
    read_file = open(INPUT_FILE_NAME, 'r')
    write_file = open(OUTPUT_FILE_NAME, 'w')
    text = read_file.read()
    text = clean_text(text)
    write_file.write(text)
    read_file.close()
    write_file.close()
 
 
if __name__ == "__main__":
    main()
cs

위 코드는 텍스트 정제 모듈입니다.



1
2
3
4
5
6
7
8
9
10
""" 텍스트 정제 모듈
    영어, 특수기호 모두 제거
"""
 
import re
 
# 입,출력 파일명
INPUT_FILE_NAME = 'output.txt'
OUTPUT_FILE_NAME = 'output_cleand.txt'
 
cs

정규표현식을 활용하기 위해 're' 라이브러리를 임포트하고, 정제할 파일의 이름을 INPUT_FILE_NAME으로, 결과 파일 이름을 OUTPUT_FILE_NAME으로 할당했습니다.



12
13
14
15
16
17
# 클리닝 함수
def clean_text(text):
    cleaned_text = re.sub('[a-zA-Z]''', text)
    cleaned_text = re.sub('[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"]',
                          '', cleaned_text)
    return cleaned_text
cs

이 후, 문자열을 입력 받아 영어와 특수문자를 제거하는 클리닝 함수를 정의했습니다.

14번째 줄은 대소문자 영어를 제거하는 코드, 15번째 줄은 특수문자를 제거하는 코드입니다.

're'라이브러리의 자세한 사용법은 공식 문서를 참조하시기 바랍니다.



20
21
22
23
24
25
26
27
28
29
30
31
32
# 메인 함수
def main():
    read_file = open(INPUT_FILE_NAME, 'r')
    write_file = open(OUTPUT_FILE_NAME, 'w')
    text = read_file.read()
    text = clean_text(text)
    write_file.write(text)
    read_file.close()
    write_file.close()
 
 
if __name__ == "__main__":
    main()
cs

이번 모듈도 마찬가지로 main를 정의하고 __name__을 이용해 main함수를 실행했습니다.

main함수에서 위에서 상수로 정의한 입력파일명을 이용해 입력파일에서 문자열을 읽고, clean_text함수를 이용해 본문 내용을 정제했습니다.

이후 출력파일명을 이용해 출력파일을 열고,  출력파일에 정제한 본문 내용을 썼습니다.



터미널을 통해 해당 정제모듈을 실행한 결과 'output_cleand.txt'이 생성되었고,



'output_cleand.txt' 파일을 열어 본 결과, 영어와 불필요한 문자들이 제거되었음을 확인할 수 있습니다.



위에서 설명한 크롤러모듈과 정제모듈을 저처럼 따로 나눌 필요는 없습니다. 한 개의 모듈에 모두 구현해 사용해도 무방합니다. 

다음 글에서는 이번에 다룬 크롤러 모듈을 수정, 확장해 동아일보와 한겨레일보에서 '사드' 키워드를 갖는 신문기사를 대량으로  크롤링해보도록 하겠습니다.



연관된 글

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 2 (네이버 뉴스 크롤링하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 3 (동아일보, 한겨레 '사드'관련 기사 크롤링하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 4 (단어 사용 빈도 체크하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 5 (워드클라우드 만들기) 미작성



출처: http://yoonpunk.tistory.com/4 [윤빵꾸의 공부노트]

article_crawling.py

article_crawling1.py

cleaner.py

crawling.py

output_cleand.txt

output.txt

result_acticls1.txt

result_articles.txt

words_result.txt

words_result1.txt

words.py

동일한 키워드를 가지는 신문 기사를 논조가 다른 신문사에서 긁어와 사용된 단어를 워드클라우드로 시각화해 분석해보는 예제입니다.

키워드는 '사드'를 사용했고, 신문사는 동아일보, 한겨레신문 두 신문사에서 각각 150개씩 기사를 긁어왔습니다.

결과는 아래와 같습니다. (왼쪽이 동아일보, 오른쪽이 한겨레신문 입니다.)




키워드가 '사드'인 만큼, 워드클라우드 모양을 미사일로 만들어봤습니다.

워드클라우드를 살펴보면 동아일보는 '중국'을 한겨레신문에서는 '미국'을 가장 많이 사용했음을 알 수 있습니다.

분석환경은 우분투 16.04에서 파이썬 3.5.2버전을 사용했습니다.

다음 글 부터 사용된 라이브러리와 코드를 차근차근 살펴보도록 하겠습니다.



연관된 글

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 1 (개요)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 2 (네이버 뉴스 크롤링하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 3 (동아일보, 한겨레 '사드'관련 기사 크롤링하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 4 (단어 사용 빈도 체크하기)

특정 키워드를 포함하는 신문기사 웹크롤링 & 워드클라우드 시각화 분석 - 5 (워드클라우드 만들기) 미작성



출처: http://yoonpunk.tistory.com/3 [윤빵꾸의 공부노트]

설치하고 돌려 보면 에러 발생


ImportError: No module named 'jpype'


해결방안 - 직접 설치해서 해결함 (python3.5)


순서

1. https://pypi.python.org/pypi/JPype1

2. 최신 패키지 다운로드

3. 압축 풀고 

4. 해당 디렉토리로 이동

5. 설치 

   % python3.5 setup.py build 

   % python3.5 setup.py install



출처: http://sfixer.tistory.com/entry/Python3-konlpy-설치시-jpype-관련-실패-해결-방안 [Block Busting]

R에서 파이썬까지…데이터과학 학습 사이트 8

 

1. 데이터과학 입문

코세라 / 워싱턴대 데이터과학 입문

 

 

2. R, Python 프로그래밍

데이터캠프 / R 입문

 

코드카데미 파이썬

구글 파이썬 클래스

 


 

3. 기계 학습

코세라 / 스탠포드 머신 러닝 / 앤드류 응

 

4. 통계학

유튜브 / 프린스턴대 통계 1

 

5. 데이터 시각화

D3 튜토리얼 / 스콧 머레이 블로그

 

 

 

보다 심도 있는 강좌들 및 기타 데이터 과학 오픈소스 강좌가 알고 싶다면, 다음의 웹페이지를 참고하자.

깃허브 : 데이터 사이언스 마스터즈

 

출처 : http://www.bloter.net/archives/237013



출처: http://extraman.tistory.com/400 [맨땅에 헤딩하기]

출처: http://www.dbguide.net/knowledge.db?cmd=specialist_view&boardUid=168680&boardConfigUid=92&boardStep=&categoryUid

 

 

아는 사람은 다 아는 모르는 사람은 절대 모를 R에 대한 것들

 

 

R에 대한 도움 얻기

stackoverflow.com : 프로그래밍에 대한 질문

crossvalidation.com : 통계에 대한 질문

R-Help mailling lists : stat.ethz.ch/mailman/listinfo의 r-help

대부분 구글에서 질문을 질의하면 r-help로 연결을 해준다.

 

필요한 함수 찾기

help.search(“apriori”) 또는 ??apriori

또한 sos 패키지를 활용해서 찾아볼 수 있다.

install.packages("sos")

library(sos)

findFn("apriori") # 혹은 ???apriori

RSiteSearch()함수의 일부 기능을 하는 함수로서 http://search.r-project.org/ 의 결과를 data.frame으로 반환하는데이를 출력하면 html페이지로 웹브라우저를 통해 보여준다하지만 좀더 자세한 결과를 보고 싶다면 RSiteSearch()함수를 사용하는 걸 추천한다

패키지를 로딩했으나 어떤 함수가 있는지 궁금할때는 아래 명령어를 사용한다.

help(package="arules")

 

함수 동작 방식 살펴보기

“::”연사자를 이용한 패키지 내부 함수 살피기

R > KoNLP::HangulAutomata

function (input, isKeystroke = F, isForceConv = F)

{

if (!is.character(input) | nchar(input) == 0) {

stop("Input must be legitimate character!")

}

if (isKeystroke) {

if (!exists("KoKeystrokeAutomata", envir = KoNLP:::.KoNLPEnv)) {

assign("KoKeystrokeAutomata", .jnew("kr/pe/freesearch/korean/KoKeystrokeAutomata",

isForceConv), KoNLP:::.KoNLPEnv)

}

keyAuto <- get("KoKeystrokeAutomata", envir = KoNLP:::.KoNLPEnv)

KoHangulAuto <- .jcast(keyAuto, "kr/pe/freesearch/korean/KoHangulAutomata")

}

else {

if (!exists("KoJamoAutomata", envir = KoNLP:::.KoNLPEnv)) {

assign("KoJamoAutomata", .jnew("kr/pe/freesearch/korean/KoJamoAutomata",

isForceConv), KoNLP:::.KoNLPEnv)

}

JamoAuto <- get("KoJamoAutomata", envir = KoNLP:::.KoNLPEnv)

KoHangulAuto <- .jcast(JamoAuto, "kr/pe/freesearch/korean/KoHangulAutomata")

}

.jcall(KoHangulAuto, "V", "setForceConvert", isForceConv)

out <- .jcall(KoHangulAuto, "S", "convert", input)

.jcall(KoHangulAuto, "V", "clear")

Encoding(out) <- "UTF-8"

return(out)

}

<environment namespace:konlp=""></environment>

패키지가 로딩되어 있다면 단수히 함수명만 명시함으로써 내부 코드를 살펴볼 수 있다.

만일 패키지 내부의 숨겨진 함수를 확인하고 싶다면 “:::”연산자를 사용하면 접근 가능하다.

R > KoNLP:::.onLoad

function (libname, pkgname)

{

ret <- .jinit(parameters = "-Dfile.encoding=UTF-8")

if (ret < 0) {

stop("Could not create VM.")

}

else {

packageStartupMessage("Java initialized.")

}

.jpackage(pkgname, lib.loc = libname)

}

<environment namespace:konlp=""></environment>

대부분 코드로 함수가 작성되어 있는 경우 이런 방법으로 확인 가능하다그러나 고전적인 코드의 경우 포트란 혹은 C로 작성이 되어 있을 경우가 많고 필자의 경우 텍스트 마이닝 코드는 자바로 작성하였는데 이에 대한 확인을 하기 위해서는 패키지 소스코드 전체를 볼 필요가 있다이 경우에는 CRAN에서 제공하는 tar.gz으로 묶인 패키지 파일을 풀어보는게 필요하다.

올바른 함수코드 작성하기

R에서 함수 작성은 크게 어렵지 않으나 함수코드를 작성하는데 어느정도 규약을 지켜주는게 필요하다.

일단 함수 들여쓰기의 경우 RStudio의 편집기를 쓰면 좋은데이는 RStudio가 코드 작성에는 최적이기 때문이다. R함수의 경우 한 라인이 길어질 가능성이 많기 때문에 들여쓰기의 경우 2개 공백 정도가 적당하며 물론 tab문자의 크기도 2개 공백이 좋다게다가 빈번히 주석을 달아줄 필요가 있는데또한 RStudio에서 편리한 옵션들을 제공한다.

다른 사람들의 함수를 읽어볼 필요가 있을때는 formatR패키지에서 제공하는 tidy.dir() 함수를 사용하면 좋은데한 가지 단점은 이를 한글 윈도우에서 동작시키면 모든 한글이 깨진다는 문제가 있다따라서 주의를 요한다.

R에 대한 코딩 가이드도 존재하는데,

http://google-styleguide.googlecode.com/svn/trunk/google-r-style.html

https://github.com/hadley/devtools/wiki/Style

위 두 페이지는 꼭 한번 읽어볼만 한 페이지들이다.

디버깅하기

에러가 나면 가장 먼저 확인해야 될 명령어는 아마도 traceback()일 것이다이 명령어는 call stack을 추적해 에러가 난 함수 호출을 출력해 준다이를 통해서 어느 함수에서 에러가 난 것인지 확인할 수 있다.

browser()라는 함수가 있는데이 함수는 다른 디버그 기능과 함께 쓸 때 가치를 발휘한다예를 들어 본인의 함수에서 특정 부분에 print()문을 넣어서 디버깅 하고 싶은 부분이 있다면, browser()라는 함수를 자신이 디버깅 하고 싶은 코드 중간에 삽입해서 print()문을 대신하게 할 수 있다함수실행시 browser()함수를 만나게 되면 browser 모드로 들어가게 되며 흡사 gdb의 디버깅 화면을 연상하게 하는 행동을 할 수 있다예를 들어 browser()코드가 들어간 부분이 실행되는 당시의 모든 객체를 보고자 한다면 objects()함수를 실행하면 객체 리스트를 볼 수 있으며 해당 객체명을 입력하면 객체에 담겨진 데이터를 출력해 볼 수 있다.

이외에 browser()와 함께 동작하는 debug(), options(error=recover), trace()와 같은 함수들이 있는데이들에 대해서는 추후 시간이 되면 정리해 공유하도록 하겠다.

즐거운 소식은 RStudio가 추후 디버깅에 대한 기능을 IDE에 추가할 예정이니 독자들은 이 툴을 기대해 보는 것도 좋을거 같다.

소스코드 관리

R로 만들어진 소스코드 파일은 관리 대상이다.

개발자가 아닌 대부분의 R사용자들은 SVN과 Git에 대해서 어려움을 가지고 있을 것인데자신의 코드가 관리되고 발전이 되길 원한다면 반드시 이런 형상관리 툴 정도는 간단하게나마 다룰 수 있어야 된다.

RStudio가 Git과 SVN에 대한 인터페이스를 제공하고 있으니 이를 활용하는 것도 좋고이것들이 어렵다면 dropbox와 같은 공유 드라이브 애플리케이션을 사용해도 좋을 것이다역시 이 부분도 방대한 양이기 때문에 추후 기회가 되면 정리해 보겠다.

소스코드 관리

필자의 경우 분석 리포트를 작성하거나혹은 컬럼을 쓰거나 하는 등의 데이터를 다루는 모든 일은 프로젝트로 관리한다.

되도록 코드와 문서는 함께 관리되게 끔 하고 언제든지 문서를 생성할 수 있게끔 만들어 둔다. Latex을 사용할 수 있는 사용자는 knitr나 Sweave와 같은 패키지를 통해 쉽게 분석 관련 문서를 관리할 수 있으며이것이 힘든 분들은 R Markdown을 사용해 관리하면 된다.

그리고 모든 데이터 생성은 원본 데이터를 기반으로 생성되게끔 해준다이는 원본을 훼손 시키지 않으려는 노력 이상의의미를 가지고 있고 원본에서 어떻게 전처리된 데이터를 생성하게 끔 하는지 그 과정도 굉장히 중요하기 때문이다. 만일 원본 데이터가 정말 크다면 전처리된 적은 데이터를 가지고 있으며 이를 어떻게 생성했으니 전처리 과정도 기술해서 남겨야 할 것이다.


'Program > R' 카테고리의 다른 글

Rstudio package 설치시 에러  (0) 2017.09.10

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using GooglePlayGames;

using UnityEngine.UI;


public class GoogleManager : MonoBehaviour {


public Text tx_id;

public Text tx_userName;

//public Text tx_Email;

public FacebookManager fb;


void Start()

{

PlayGamesPlatform.DebugLogEnabled = true;

PlayGamesPlatform.Activate ();

LogIn ();

}


public void LogIn()

{

Social.localUser.Authenticate ((bool success) => {

if (success) {

Debug.Log("You've successfully logged in");

tx_id.text = Social.localUser.id;

tx_userName.text = Social.localUser.userName;

//tx_Email.text = ((PlayGamesLocalUser)Social.localUser).Email + "-" + PlayGamesPlatform.Instance.GetUserEmail();

//fb.LogIn();

} else {

Debug.Log("Login failed for some reason : " + Social.Active);

Debug.Log("UserName : " + Social.localUser.userName);

Debug.Log("Id : " + Social.localUser.id);

}

/*

GooglePlayGames.OurUtils.PlayGamesHelperObject.RunOnGameThread(

() => {

Debug.Log("Local user's email is " + ((PlayGamesLocalUser)Social.localUser).Email);

});

*/

});

}


public void LogOut()

{

((PlayGamesPlatform)Social.Active).SignOut ();

tx_id.text = "No ID";

tx_userName.text = "No UserName";

//tx_Email.text = "No Email";

}

}



페이스북 앱을 만들었을경우 invitable_friends 를 가져 올수 있는데


유저의 해당 앱에 초대할 친구 목록이다 즉 가입이 안된 친구들 목록이다


나의 친구중


//해당앱에 초대된 유저

FB.API ("/me/friends?fields=id,name,picture", HttpMethod.GET, FriendListCallBack);

​//해당앱에 초대가 안된 유저 
FB.API ("/me/invitable_friends?fields=id,name,picture", HttpMethod.GET, InvitableFriendListCallBack);



​ using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using Facebook.Unity;
using Facebook.MiniJSON;



public class FBHolder : MonoBehaviour {
    public GameObject DialogLoggedIn;
    public GameObject DialogLoggedOut;
    public GameObject DialogUserName;
    public GameObject DialogPrifilePic;

    public GameObject FriendListGm;
    public GameObject Invitable_FriendListGM;

    private Dictionary<string,string> friendListDic;
    private Dictionary<string,string> invitable_friendListDic;

    void Awake(){
        FB.Init (SetInit, OnHideUnity);    
    }

    private void SetInit(){

        //Screen.SetResolution( Screen.width, Screen.width * 16 / 9, true );

        Debug.Log ("FB init donw");

        if (FB.IsLoggedIn) {
            Debug.Log ("Fb Logged In");
        } else {
            Debug.Log ("Fb Not Logged In");
        }

        //DealWidthFBMenu (FB.IsLoggedIn);
    }


    private void OnHideUnity(bool isGameShown){
        if(isGameShown){
            Time.timeScale = 0;
        }else{
            Time.timeScale = 1;
        }
    }

    public void FbLogin(){

        List<string> permissions = new List<string> ();
        permissions.Add ("public_profile");

        FB.LogInWithReadPermissions (permissions, AuthCallBack);
    }

    void AuthCallBack(IResult result){
        if (result.Error != null) {
            Debug.Log (result.Error);
        } else {
            if (FB.IsLoggedIn) {
                Debug.Log ("Fb Logged In AuthCallBack");

            } else {
                Debug.Log ("Fb Not Logged In AuthCallBack");
            }

            DealWidthFBMenu (FB.IsLoggedIn);
        }
    }



    void DealWidthFBMenu(bool isLoggedIn){
        if (isLoggedIn) {
            DialogLoggedIn.SetActive (true);
            DialogLoggedOut.SetActive (false);

            FB.API ("/me?fields=first_name", HttpMethod.GET, DialogUserNameCallBack);
            FB.API ("/me/picture?type=square&height=128&width=128", HttpMethod.GET, DialogUserProfileImgCallBack);
            FB.API ("/me/friends?fields=id,name,picture", HttpMethod.GET, FriendListCallBack);
            FB.API ("/me/invitable_friends?fields=id,name,picture", HttpMethod.GET, InvitableFriendListCallBack);
        } else {
            DialogLoggedIn.SetActive (false);
            DialogLoggedOut.SetActive (true);
        }
    }


    void DialogUserNameCallBack(IResult result){
        Text UsetName = DialogUserName.GetComponent<Text> ();

        if (result.Error == null) {
            UsetName.text = "Hi there, " + result.ResultDictionary ["first_name"];
        } else {
            Debug.Log (result.Error);
        }
    }

    void DialogUserProfileImgCallBack(IGraphResult result){
        

        if (result.Texture != null) {
            Image ProfilePic = DialogPrifilePic.GetComponent<Image> ();
            ProfilePic.sprite = Sprite.Create (result.Texture, new Rect (0, 0, 128, 128), new Vector2 ());
        } else {
            Debug.Log (result.Error);
        }
    }


    //게임등록을 안한 유저 
    void InvitableFriendListCallBack(IResult result){
        if (result.Error != null) {
            Debug.Log ("Error : " + result.Error);
        } else {
            invitable_friendListDic =  FriendListParsing.Parsing (result.RawResult);
            SetText ( Invitable_FriendListGM , invitable_friendListDic);
        }
    }


    //게임등록을  유저 
    void FriendListCallBack(IResult result){
        if (result.Error != null) {
            Debug.Log ("Error : " + result.Error);
        } else {
            friendListDic = FriendListParsing.Parsing (result.RawResult);
            SetText (FriendListGm, friendListDic);
        }
    }


    //리스ㅌ트를 텍스트로 뿌려준다 
    void SetText(GameObject gm, Dictionary<string,string> dic){
        Text textField = gm.GetComponent<Text> ();
        foreach (KeyValuePair<string,string> pair in dic) {
            textField.text += string.Format ("{0}", pair.Key, pair.Value)+"\n";
        }
    }


        


}

파싱용 스크립트


using Facebook.MiniJSON;
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using Facebook.Unity;

public class FriendListParsing {



    //친구 리스트 파싱 
    static public Dictionary<string,string> Parsing(string text){
        Debug.Log ("parsing " + text);

        //이곳에 json이 담겨져 있ㅏ 
        Dictionary<string, object> dict = (Dictionary<string,object>)Json.Deserialize (text);

        object friendsH;
        List<object> friends = new List<object> ();
        string friendName;

        Dictionary<string,string> friend = new Dictionary<string,string> ();

        if (dict.TryGetValue ("data", out friendsH)) {
            friends = (List<object>)dict ["data"];

            if (friends.Count > 0) {

                for (int i = 0; i< friends.Count; i++) {
                    Dictionary<string,object> friendDict = ((Dictionary<string,object>)(friends [i]));

                    friend [(string)friendDict ["name"]] = (string)friendDict ["name"];

                }
            }
        }


        return friend;
    }
}



http://blog.naver.com/brane7/220712830773


'Program > Unity' 카테고리의 다른 글

google sdk  (0) 2017.02.23
[Unity3D] Application.LoadLevel(string) is obsolete 마이그레이션  (0) 2017.02.20

http://ts-endingpopup.yjmgames.net:10574/service.php?packet_id=Ending&ssn=2&bundleid=com.bundleid.nohero.ios&market=12&country=EN


http://endingpopup.yjmgames.net:10574/service.php?packet_id=Ending&ssn=2&bundleid=com.bundleid.nohero.ios&market=12&country=EN


http://ts-notice.yjmgames.net:10574/service.php?packet_id=Notice&ssn=2&bundleid=com.bundleid.nohero.ios&market=12&country=EN


http://terms.yjmgames.net:10574/service.php?packet_id=Terms&ssn=2&bundleid=com.bundleid.nohero.ios&market=12&country=EN


http://ts-terms.yjmgames.net:10574/service.php?packet_id=Terms&ssn=2&bundleid=com.bundleid.nohero.ios&market=12&country=EN


http://ts-terms.yjmgames.net:10574/service.php?packet_id=Notice&ssn=2&bundleid=com.bundleid.nohero.ios&market=12&country=EN


http://notice.yjmgames.net:10574/service.php?packet_id=Notice&ssn=2&bundleid=com.bundleid.nohero.ios&market=12&country=EN


http://ts-iws.yjmgames.net:10574/state.php?packet_id=Iws&ssn=1&bundleid=com.bundleid.nohero.aos&market=2&version=1.01



http://52.78.177.195:10574/main_server.php?packet_id=Iws&ssn=1&bundleid=com.bundleid.nohero.aos&market=2&version=1.01

+ Recent posts