Python小练习:可视化人人好友关系
可视化人人好友关系
受R分析人人网好友推荐系统和用python进行人人好友分析启发,完全用python的模块和方式实现了一遍,结果搞得好像一点也不Pythonic,倒好像有点继承了之前在lisp下养成的函数式风格……
作为菜鸟深知代码写得不怎么样,写在这里,希望没什么基础的人都能体会到其中我所感受到的乐趣/Happy hacking/,也欢迎各路高手大牛不吝赐教。
完整代码见github/reverland/scripts/renren.py
必要条件
For Reader:
读者需要有一定python基础,如果没有,不妨花半个小时看看Python简明教程。
For Computer:
我在gentoo linux下完成所有的编写测试,也推荐想尝试的朋友选择linux环境。不过只是推荐,python作为著名的跨平台语言,其代码可以没什么差别的运行在各个平台上,但你需要以下一些必备的东西:
- python 2.7 也许2.5也行,cookielib之前好像不在标准库中,而python3中则有改动。
- networkx 一个分析,操作,绘制网络的python模块。
- matplotlib 经常用来绘图的python模块
怎么安装请自行参照官方网站说明。对后两个模块,建议使用pip安装,这货就相当于个包管理器(一条命令完成搜索下载安装所有操作并自动处理所有依赖)。
最后,还有可选的开发环境:ipython,该程序提供一个功能强大的交互环境,很方便做测试调试探索各种 一次性 工作。
我们要做些什么
模拟登录
人人的模拟登录还是比较简单的。模拟登录最困难的部分就是对要登录网站登录过程的分析。通常办法是通过抓包,用wireshark总有种杀鸡用牛刀的感觉,而且当你像作者一样天天用socks代理时会发现什么也抓不到……所以IE/Chrome/Firefox的开发工具可能更合适。这里用firebug,你可以在火狐扩展中心找到并安装它。
然后在抓包过程中找到用户名2和登录时请求的服务器。
不过,之前有很多人已经分析过人人的登录过程(一般不会要求验证码,除非登录过于频繁)。你所必须要做的基本上只有两件事:
- 将用户名和密码POST到服务器
- 处理cookie
模拟登录的工具使用python的标准库中的=urllib=,=urllib2=和=cookielib=即可
import urllib import urllib2 import cookielib
如果对这三个标准库不熟悉,建议花时间看看下面两篇教程。不过也许无所谓,代码可以自己解释自己:p。
- urllib2 the missing manual
- cookielib
当浏览器使用POST方法请求服务器时,它将参数经过编码附加到url后传递过去:
http://www.renren.com/ajaxLogin/login&email=username&password=blablabla
登录成功后,还要获取人人中用来作为用户唯一标识额uid(打开人人主页注意url就看到了)并返回,以供将来使用。将来所有的抓取都通过独一无二的uid而非可能重名的姓名。
使用正则抓去uid
import re
我们先写登录函数:
def login(username, password): """log in and return uid""" logpage = "http://www.renren.com/ajaxLogin/login" data = {'email': username, 'password': password} login_data = urllib.urlencode(data) cj = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) res = opener.open(logpage, login_data) print "Login now ..." html = res.read() #print html # Get uid print "Getting user id of you now" res = urllib2.urlopen("http://www.renren.com/home") html = res.read() # print html uid = re.search("'ruid':'(\d+)'", html).group(1) # print uid print "Login and got uid successfully" return uid
不妨在ipython中先测试下。
抓取数据
每个人的好友都可以从页面=http://friend.renren.com/GetFriendList.do?curpage=0&id=uid=%E8%8E%B7%E5%8F%96%EF%BC%8C%E8%99%BD%E7%84%B6%E4%BA%BA%E4%BA%BA%E5%B7%B2%E7%BB%8F%E6%94%B9%E7%89%88%EF%BC%8C%E4%BD%86%E8%BF%99%E4%B8%AA%E9%A1%B5%E9%9D%A2%E8%BF%98%E8%83%BD%E7%94%A8%E3%80%82%E5%85%B6%E4%B8%ADcurpage%E5%8F%82%E6%95%B0%E7%9A%84%E5%80%BC%E6%98%AF%E9%A1%B5%E7%A0%81%EF%BC%8Cid%E5%8F%82%E6%95%B0%E7%9A%84%E5%80%BC%E6%98%AF%E6%8B%9F%E6%8A%93%E5%8F%96%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%94%A8%E6%88%B7ID%E3%80%82%E9%80%9A%E8%BF%87%E5%BE%AA%E7%8E%AF%E6%8A%93%E5%8F%96%E6%89%80%E6%9C%89%E5%A5%BD%E5%8F%8B%E5%B9%B6%E4%BB%A5%E7%94%A8%E6%88%B7id%E4%B8%BA%E9%94%AE%E5%A7%93%E5%90%8D%E4%B8%BA%E5%80%BC%E4%BF%9D%E5%AD%98%E4%B8%BA%E5%AD%97%E5%85%B8。
def getfriends(uid): """Get the uid's friends and return the dict with uid as key,name as value.""" print "Get %s 's friend list" % str(uid) pagenum = 0 dict1 = {} while True: targetpage = "http://friend.renren.com/GetFriendList.do?curpage=" + str(pagenum) + "&id=" + str(uid) res = urllib2.urlopen(targetpage) html = res.read() pattern = '<a href="http://www\.renren\.com/profile\.do\?id=(\d+)"><img src="[\S]*" alt="[\S]*[\s]\((.*)\)" />' m = re.findall(pattern, html) #print len(m) if len(m) == 0: break for i in range(0, len(m)): no = m[i][0] uname = m[i][1] #print uname, no dict1[no] = uname pagenum += 1 print "Got %s 's friends list successfully." % str(uid) return dict1
我们再写个获取好友关系字典的函数,为了避免我们每次为了获取字典都要登录抓取。
def getdict(uid): """cache dict of uid in the disk.""" try: with open(str(uid) + '.txt', 'r') as f: dict_uid = p.load(f) except: with open(str(uid) + '.txt', 'w') as f: p.dump(getfriends(uid), f) dict_uid = getdict(uid) return dict_uid
我们还需要一个用来判断两个人关系的函数,来判断我们好友之间的关系。
def getrelations(uid1, uid2): """receive two user id, If they are friends, return 1, otherwise 0.""" dict_uid1 = getdict(uid1) if uid2 in dict_uid1: return 1 else: return 0
绘制图像
利用以上函数判断好友关系并通过networkx创建一个相应的网络。
def getgraph(username, password): """Get the Graph Object and return it. You must specify a Chinese font such as `SimHei` in ~/.matplotlib/matplotlibrc""" uid = login(username, password) dict_root = getdict(uid) # Get root tree G = nx.Graph() # Create a Graph object for uid1, uname1 in dict_root.items(): # Encode Chinese characters for matplotlib **IMPORTANT** # if you want to draw Chinese labels, uname1 = unicode(uname1, 'utf8') G.add_node(uname1) for uid2, uname2 in dict_root.items(): uname2 = unicode(uname2, 'utf8') # Not necessary for networkx if uid2 == uid1: continue if getrelations(uid1, uid2): G.add_edge(uname1, uname2) return G
最后是绘图函数,有很多控制图像输出的参数,可能多次调整才会得到想要的图像。在matplotlib画出的图像在窗口中也可以放大缩小选取适当范围。
def draw_graph(username, password, filename='graph.txt', label_flag=True, remove_isolated=True, different_size=True, iso_level=10, node_size=40): """Reading data from file and draw the graph.If not exists, create the file and re-scratch data from net""" print "Generating graph..." try: with open(filename, 'r') as f: G = p.load(f) except: G = getgraph(username, password) with open(filename, 'w') as f: p.dump(G, f) #nx.draw(G) # Judge whether remove the isolated point from graph if remove_isolated is True: H = nx.empty_graph() for SG in nx.connected_component_subgraphs(G): if SG.number_of_nodes() > iso_level: H = nx.union(SG, H) G = H # Ajust graph for better presentation if different_size is True: L = nx.degree(G) G.dot_size = {} for k, v in L.items(): G.dot_size[k] = v node_size = [G.dot_size[v] * 10 for v in G] pos = nx.spring_layout(G, iterations=50) nx.draw_networkx_edges(G, pos, alpha=0.2) nx.draw_networkx_nodes(G, pos, node_size=node_size, node_color='r', alpha=0.3) # Judge whether shows label if label_flag is True: nx.draw_networkx_labels(G, pos, alpha=0.5) #nx.draw_graphviz(G) plt.show() return G
把以上函数写进一个文件比如说renren.py,在ipython中导入。
In[1]: from renren import * In[2]: username = yourusername In[3]: password = yourpassword In[4]: draw_graph(username, password)
总结
通过图像你会发现。这些绘图软件的算法相当不错的,你会发现很明显的聚类,这一片是大学同学、这片是小学初中同学,旁边与之联系紧密的是高中同学,这一片孤立的是网友等等。
也许你还会发现你的某些好友竟然相互认识。
抓取下来的数据还可以留待其它研究
你也许会发现有的好友和你的共同好友多得超乎他人,也许发现共同好友分布比较均匀
就这么多这么简单。
希望你也能体会到这个乐趣横生的过程,对我来说,探索和学习的过程是相当意趣盎然的,折腾出来还相当有成就感呢。
What's more
如果你想让matplotlib显示中文,你需要修改matplotlibrc更改字体。但有一种更通用的办法可以不用修改配置文件。自行google。
ps:这回开高亮了,没感觉和不高亮有啥大区别。感觉还是vim中的高亮漂亮啊,哪天不用pygments直接用vim converto html = =