2013년 5월 21일 화요일

[python] networkx 를 활용해 그래프 그리기


파이썬을 이용해 그래프를 구현하는 방법에는 igraph, networkx 등이 있지만 그 중에서

networkx 모듈을 활용해 그래프를 그리는 예제를 공유하고자 한다.

그래프를 시각화 하기 위해서는matplotlib 이 필요하다.
import networkx as nx
import matplotlib.pyplot as plt

def draw_graph(graph):

    # graph 객체에서 노드들을 뽑아 nodes 에 저장한다
    nodes = set([n1 for n1, n2 in graph] + [n2 for n1, n2 in graph])

    # 그래프 객체를 하나 만든다
    G=nx.Graph()

    # add_node() 를 통해 그래프에 노드를 입력한다
    for node in nodes:
        G.add_node(node)

    # add_edges() 를 통해 노드간의 엣지를 지정해 준다
    for edge in graph:
        G.add_edge(edge[0], edge[1])

    # 그리려는 그래프의 속성을 설정해 준다
    nx.draw_networkx_nodes(G,pos,node_size=5)
    nx.draw_networkx_edges(G,pos,width=1)
    nx.draw_networkx_labels(G,pos,font_size=10,font_family='sans-serif')

    # 그래프 그림을 파일로 저장한다
    plt.axis('off')
    plt.savefig("graph.png", dpi=1000) # 그림 파일 크기 지정
    plt.savefig("weighted_graph.png") # png 파일로 저장

    # 그래프 레이아웃을 저장한다
    pos = nx.shell_layout(G)
    nx.draw(G, pos)

    # matplotlib 모듈을 이용해 그래프를 시각화 한다.
    plt.show()

# 그리고자 하는 그래프 예제
graph = [(20, 21),(21, 22),(22, 23), (23, 24),(24, 25), (25, 20)]
draw_graph(graph)


참고 : https://www.udacity.com/wiki/creating%20network%20graphs%20with%20python

2013년 5월 20일 월요일

[python] facebook 친구 리스트 파징


페이스북에서 특정 계정의 친구 리스트를 얻는 방법엔 여러가지가 있을 수 있다.

하지만 가장 쉬운 방법은 아래의 url 을 사용해서 json 데이터를 파징하는 방법일 것이다.

graph api를 사용할 필요가 없고, access token 이 필요 없기 때문에 정말 쉽게 리스트를 얻을 수 있다.

페이스북의 경우 로그인을 하지 않으면 특정 계정의 친구 리스트를 볼 수 없는데,

아래의 방법을 사용할 경우 계정의 uid 만 있으면 쉽게 리스트를 파징할 수 있다.

여기서 uid란 유저의 id가 아닌 facebook이 계정마다 정해주는 숫자를 의미한다.

이 숫자는 http://graph.facebook.com/유저프로필url 에서 알아낼 수 있다.

페이스북에 관한 연구를 진행할 때 소스를 참고하면 데이터를 모으는데 많은 도움이 될 듯 하다.

하지만 무단으로 개인정보를 수집하는 용도로 사용하지 않기를 바란다.

# -*- coding:utf-8 -*-
# author = 'carpedm20'

import urllib
from urllib2 import urlopen
from json import loads

def get_friend_list(f_num):
    f = urllib.urlopen('https://www.facebook.com/ajax/typeahead_friends.php?u=' + f_num  + '&__a=1')

    s = f.read()
    f.close()

    json = s.replace('for (;;);{"__ar":1,"payload":','').replace(',"bootloadable":{},"ixData":[]}','')
    try:
        json = loads(json)
    except:
        return []

    f_list=[]

    for f in json['friends']:
        f_list.append(str(f['i']))

    print " # of json : " + str(len(f_list))

    return f_list

if __name__ == "__main__" :
    f_num = "페이스북 계정 uid"
    friend_list = get_friend_list(f_num)

간단하게 7명의 친구 리스트를 파징해서 networkx 모듈을 사용해 그래프를 그리면 아래와 같이 나온다.

더욱 많은 데이터를 수집하고나서 그려보면 페이스북에서의 소셜 네트워크가 뚜렷하게 나타날 것이다.


2013년 5월 18일 토요일

[python] facebook bot 구현, access token 자동 갱신



트위터와는 달리 페이스북 봇에 대한 정보는 인터넷에 거의 없다.

트위터와 페이스북의 성격이 다르다는 점이 주된 이유겠지만

주변에 페이북 유저가 트위터 유저보다 훨씬 많아서 페이스 봇을 만들고자 했다.

먼저 페이스북에 글을 올리기 위해서는


1. Request를 직접 보내는 방법

2. Facebook API를 이용하는 방법


이렇게 두가지 방법이 가능하다.

페이스북은 플래시, 파이썬, 루비, 자바 등의 다양한 언어에 대해 API를 제공하고 있다.

파이썬에서는 Graph API 가 제공되며, 페이스북에서 데이터를 주고 받을때 주로 사용되는 API 이다.


python graph api 링크 : https://github.com/pythonforfacebook/facebook-sdk/

API 공식 reference : https://developers.facebook.com/docs/reference/api/


API를 이용하는 경우 데이터를 올리고 받을때 사용할 계정을 developer로 등록해야 하며

어플리케이션을 하나 만들어서 앱 ID를 가지고 있어야 한다

그리고 나선 어플리케이션이 할 수 있는 행동들의 범위를 지정해 주고 그에 대한 oauth_access_token를 받아야 한다

graph = facebook.GraphAPI(oauth_access_token)
profile = graph.get_object("me")
friends = graph.get_connections("me", "friends")
graph.put_object("me", "feed", message="I am writing on my wall!")

tags = json.dumps([{'x':50, 'y':50, 'tag_uid':12345}, {'x':10, 'y':60, 'tag_text':'a turtle'}])
graph.put_photo(open('img.jpg'), 'Look at this cool photo!', album_id_or_None, tags=tags)


파이썬 기본 예제에서 볼 수 있듯이 oauth_access_token 가 있어야 글을 얻거나 사진을 올릴 수 있다.

하지만 봇을 만들때 문제는 이 access_token이 시간이 지나면 expire 된다는 것이다.


나는 이 문제를 해결하기 위해 access_token을 자동으로 갱신받는 방법을 찾아 봤으며

브라우저를 이용하는 방법 post request를 사용하는 방법 등 여러가지를 시도해봤지만 실패했다.

하지만 방법을 결국 찾았는데 웹 사이트에서 페이스북 api를 사용하고자 할 때

그 웹 사이트에서 api를 사용할 수 있도록 만들어 주는 url을 만들 수 있으며

그 url 로 접속해서 접근하고자 하는 유저로 로그인을 하고 나면 redirect 되는 url에 oauth_access_token 이 주어진다.


아래의 소스는 위의 방법을 구현한 python 소스다.

분명 이 방법보다 쉬운 방법이 있을 수 있으나 내 검색 능력에서는 이게 최선이었다.

혹시라도 나와 같은 어려움을 겪고 있는 사람들을 위해 방법을 공유하고자 이렇게 글을 올린다.

이를 이용하면 재미있는 어플리케이션을 많이 만들 수 있을 것이다.

# author = 'carpedm20'

import mechanize
import facebook

while 1:
        br_mech = mechanize.Browser() # mechanize 를 이용해 브라우징을 한다
        br_mech.set_handle_robots(False) # 페이스북의 로봇 필터에 걸리지 않게 한다

        link='https://www.facebook.com/dialog/oauth?scope=사용하고자 하는 auth 범위
        &redirect_uri=리다이렉트 할 사이트 주소
        &response_type=token&client_id=어플리케이션 ID'

       # link='https://www.facebook.com/dialog/oauth?scope=publish_stream,publish_actions,
       # &redirect_uri=http://carpedm20.blogspot.kr
       # &response_type=token&client_id=어플리케이션 ID'

        #print '[1] open link'
        br_mech.open(link)

        #print '[2] current url : ' + br_mech.geturl()

        br_mech.form = list(br_mech.forms())[0]
        control = br_mech.form.find_control("email")
        control.value='로그인 할 계정 이메일'
        control = br_mech.form.find_control("pass")
        control.value='로그인 할 계정 비밀번호'

        #print '[3] submit'
        br_mech.submit() # 로그인

        #print '[4] current url : ' + br_mech.geturl()

        app_access = br_mech.geturl().split('token=')[1].split('&expires')[0]
        print '[5] access token : ' + app_access # access token 을 파징해서 출력한다
ps. 포탈봇 : https://www.facebook.com/hexa.portal
ps2. 컴공아 일하자 : https://www.facebook.com/comgong.job

2013년 5월 10일 금요일

[python] BeautifulSoup 웹 파징 예제



파징하고자 하는 사이트는 여기 며 궁극적으로 사이트에서 추천하는 샌드위치 상점들의 정보를 파징하는 예제입니다.


from bs4 import BeautifulSoup # 파징을 위한 BeautifulSoup 모듈 임포트
from urllib2 import urlopen # url 처리를 위한 BeautifulSoup 모듈 임포트
import csv # 추출한 정보를 저장하기 위한 csv 모듈 임포트

base_url = ("http://www.chicagomag.com/Chicago-Magazine/"
            "November-2012/Best-Sandwiches-Chicago/")

soup = BeautifulSoup(urlopen(base_url).read())

# div 중 class 가 sammy 인 element 찾기
sammies = soup.find_all("div", "sammy") 

#sammies 안에 있는 div 에 대해서 div 하위의 a 를 찾아 href 값을 저
sammy_urls = [div.a["href"] for div in sammies]



fieldnames = ("rank", "sandwich", "restaurant", "description", "price",
                "address", "phone", "website")

for url in sammy_urls:
    url = url.replace("http://www.chicagomag.com", "")  # inconsistent URL

    # 특별히 하는 이유는 모르겠으나 format 함수를 이용해 스트링 수정 가능
    # 위의 예제에서 파징했던 sammy_urls 링크로 이동
    page = urlopen("http://www.chicagomag.com{0}".format(url))  

    # div 중 id 가 sandwich 인 element 찾아 soup 객체에 저장
    soup = BeautifulSoup(page.read()).find("div", {"id": "sandwich"})

    # soup 객체 안의 div 중 id 가 sandRank 인 element 찾아 soup 객체에 저장
    # strip() : string 의 좌우 공백 제거
    rank = soup.find("div", {"id": "sandRank"}).encode_contents().strip()

    # soup 에서 h1 객체를 "br/" 로 split 한 후 인덱스가 0 인 리스트 저장
    sandwich = soup.h1.encode_contents().strip().split("br/")[0]

    restaurant = soup.h1.span.encode_contents()
    description = soup.p.encode_contents().strip()

    addy = soup.find("p", "addy").em.encode_contents().split(",")[0].strip()

    # partition(seperator) : seperator 로 string 을 
    # 3부분(seperator 전, sperator 자체, seperator 후)으로 나누고 리스트 리턴
    price = addy.partition(" ")[0].strip()
    address = addy.partition(" ")[2].strip()

    phone = soup.find("p", "addy").em.encode_contents().split(",")[1].strip()

    if soup.find("p", "addy").em.a:
        website = soup.find("p", "addy").em.a.encode_contents()
    else:
        website = ""


2013년 5월 9일 목요일

[python] BaseHTTPServer 웹 서버의 구현



웹 서버는 작동 원리를 이해하기 어렵지 않다:

  • 클라이언트(브라우저)가 웹서버와 접속을 하고 HTTP GET 혹은 POST 방식으로 request 를 전송한다 (요청하고자 하는 url(경로)와 쿠키 등 의 정보가 request에 포함된다)
  • 서버측에서는 전송된 request를 파징한다. (경로 (예. /some/file), 쿠키 등.) 그리고 HTTP 코드로 respond 를 전송하며 (404 코드 : 찾을 수 없음, 200 코드 : 완료) 웹 페이지의 내용도 전송한다 (html 소스, 이미지...)
브라우저
(HTTP 클라이언트)
GET /path/hello.html HTTP/1.1
Host: www.myserver.com
서버
(HTTP 서버)
--------->
HTTP/1.1 200 OK
Content-Type: text/html
<html><body>Hello, world !</body></html>
<---------

파이썬의 BaseHTTPServer 모듈을 사용하면 위의 프로세스를 쉽게 작성할 수 있다.
아래의 웹서버 소스는 http://localhost:8088/ 로 접속시 "Hello, world !" 를 출력하는 예제다.


#!/usr/bin/python
import BaseHTTPServer

class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)            # 200 코드 : request 를 잘 받았다는 의미        
        self.send_header('Content-type','text/html')   # respond 헤더 추가
        self.end_headers()                          # respond 헤더의 끝을 추가 ( "\n\r" )
        self.wfile.write('<html><body>Hello, world !</body></html>')
        return

print "Listening on port 8088..."
server = BaseHTTPServer.HTTPServer(('', 8088), MyHandler)
server.serve_forever()
  • MyHandler  가 우리가 만들고자 하는 웹서버 응답 핸들러이며, 정해진 포트로  request가 들어온 경우 그에 대한 응답을 돌려 준다.
  • GET 방식의 request 만 처리한다 (do_GET).
  • 코드 200의 의미는 request를 잘 받았다는 뜻이다. (self.send_response(200)).
  • 헤더를 추가해서 브라우저에 request 를 전송한다고 알려준다 (self.send_header('Content-type','text/html')).
  • 그리고 HTML 소스 자체를 출력한다 (self.wfile.write(...))

2013년 5월 3일 금요일

[python] 파이썬에서 bash 명령어 실행 subprocess, pexpect


subprocess 모듈은 파이썬에서 새로운 프로세스를 만들고 인풋, 아웃풋, 에러 파이프를 연결 시켜준다.

pexpect 모듈을 사용해서도 쉘과 상호작용할 수 있습니다.

# subprocess  모듈 사용
import subprocess

subprocess.call(['ls', '-l'])  # 명령어 실행
subprocess.check_call(['ls', '-l'])  # 명령어 실행 확인

subprocess.check_output(['ls', '-l'])  # 아웃풋 string 으로 출력

# pexpect 모듈 사용
import pexpect
child = pexpect.spawn ('ftp ftp.openbsd.org')

# ftp 실행 시 Name (ftp.openbsd.org:carpedm20): 로 입력 받음 -> 정규표현식 Name.* 사용
# 단순하게 child.expect(".*") 로도 사용 가능
child.expect ('Name .*: ')

# 인풋값 입력
child.sendline ('anonymous')

child.expect ('Password:')
child.sendline ('noah@example.com')
child.expect ('ftp> ')
child.sendline ('cd pub')
child.expect('ftp> ')
child.sendline ('get ls-lR.gz')
child.expect('ftp> ')
child.sendline ('bye')

print child.before   # 이전 명령어 실행으로 나온 아웃풋 출력

try:child.interact()    # 유저에게 컨트롤을 넘겨줌
except:pass