前几日在人人网上看到有位北京大学的做了一个"人人网六度空间"的Flash, 觉得很好玩, 遂向其请教一二, 自己也做了一个, 这篇就来做个梳理和总结吧, 哪些性能方面不好的希望大家能够指出并改进. 本篇没有完整的代码或程序可以下载, 更没有我获取到的数据可以下载, 数据也很大, 我用XML存储了我们学校整个人际关系用了几百兆! 切勿用文章内的思路做盗取他人隐私违法犯罪的商业应用!
"六度空间"可行性分析
六度空间”理论又称作六度分隔(Six Degrees of Separation)理论。这个理论可以通俗地阐述为:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过六个人你就能够认识任何一个陌生人。”该理论产生于20世纪60年代,由美国心理学家格伦提出。
输入一个人A, 找到另一个人B, 通过在线联网的方式(本地不保存数据)找到A和B之间的关系那当然好, 但在人人网没有提供一个能快速找到任意一个人的好友的API情况下做到这点几乎是不可能的(时间方面). 我做了一个测试, 如果一个人有1000个好友, 在120kbps的网速下找到他的所有好友大概需要8s钟! 可想而知, 你想找出A和B之间的关系, 采用双向搜索自然来得快不少, 先找A所有的好友, 再找B的好友, 看看有没有交集...没有继续找A所有的好友的好友, 找B所有的好友的好友, 看看有没有交集...到这一步需要的时间地球大概已经转了半圈了, 我昨晚找了C的好友, 再找C的好友的所有好友, 从早上1:58找到早上6:01...所以我说这么多意思是没有快速API的情况下做成在线版几乎不可能, 除非你本地保存了数据, 说着A和B是直接好友关系.
那么就下载所有人人网用户的信息?人人网有近2亿账号, 如果每个只需要5秒的话, 对于我这个带宽120kbps, 硬盘250GB的电脑显然也不可能...那么就从小范围做起吧, 就找出我们学校所有人之间的关系吧.
得到学校里每个人, 以及每个人的好友
假设学校里的每个人都注册了人人网(对于那些没有注册人人网的俺也没法找到他们, 呵呵), 我偶然想到了一个特殊的人人网用户A, 这个用户是学校一个聊天论坛在人人网里的一个账号, 好友数量已经达到2000, 我有如下假设: 任何一个学校里的人B, 一定有一个他认识的人认识A, 按照人人网里的话说叫做"A和B有共同好友", 这个假设根据我个人经验认为是正确的, 如果B不满足这个条件, 这个人一定孤陋寡闻, 不参加社交-_-!!
所以找到学校里每一个人, 可以以这个A为起点, 找到A的所有好友集合S1, 再找到S的所有好友, 那么整个学校的人的名单90%以上已经到手. 在昨天那个月黑风高的夜晚, 我整整用了4个小时把学校所有人都下载下来了(大概15W条记录).
存储人和人之间的关系
光下载到到人的名单还不行, 我并不想以A为中心建立大家之间的关系, 因为A毕竟是一个组织, 不是一个人, 以它为中心的关系几乎都是浮云, 现在已经有A的所有好友, 以及A的好友的好友集合S2{sa, sb, sc...}, 学校的所有人都包含在S2中了, 任何人之间的关系可以用图表示, 在人人网中且是无向图(没有A是B的好友, B不是A的好友这种情况), 所以还得再逆向的找出S2中的每个人的好友, 并存储这个关系!
图可以用矩阵法和邻接表表示, 因为这个关系图属于稀疏矩阵, 应该用邻接表表示更省空间(用矩阵表示也是几乎不可能的事情, 因为如果有学校有10000个人, 那么就得建立一个10000*10000的矩阵, 当然可以利用矩阵的一些运算比如找出矩阵特殊值等方法压缩这个矩阵, 但压缩之后的矩阵仍然不小...). 因此我建立了如下一个XML格式的文件存储人和人之间的关系.
........ ........
这个把全校所有人都爬虫出来的代码是这样的, 先找出A的所有好友:
public void downLoadData(){ XmlDocument doc = new XmlDocument(); doc.Load(@"C:\Users\Chen Hua\Desktop\Relation.xml"); renrenoperation renren = new renrenoperation(); Console.WriteLine("Login Success"); //获得A的好友 var peopleList = renren.Get_FriendList("A的id号码"); foreach (Person person in peopleList) { var node = doc.CreateElement("Person"); node.SetAttribute("id", person.id); node.SetAttribute("name", person.name); node.SetAttribute("school", person.school); node.SetAttribute("faceUri", person.faceUri); doc.DocumentElement.AppendChild(node); var list2 = renren.Get_FriendList(person.id); Console.WriteLine(person.name + " Write Success."); //获得A的每一个好友的好友 foreach (Person person2 in list2) { if (person2.school.Contains("某某学校") || person2.school.Contains("南京市")) { var friend = doc.CreateElement("Friend"); friend.SetAttribute("id", person2.id); friend.SetAttribute("name", person2.name); friend.SetAttribute("school", person2.school); friend.SetAttribute("faceUri", person2.faceUri); node.AppendChild(friend); } } }}
接下来就是把A的好友的好友提取出来, 找出A的好友的好友的好友...(逻辑有点那个...不知道大家有没有想通)
static void Main(string[] args){ XmlDocument doc = new XmlDocument(); doc.Load(@"C:\Users\Chen Hua\Desktop\Relation.xml"); Console.WriteLine("XML加载完毕."); var PersonList = doc.SelectNodes("//Person"); //所有的Person foreach (XmlNode EveryPersonNode in PersonList) //Person中所有的节点 { foreach (XmlNode EveryPersonChildNode in EveryPersonNode.ChildNodes) //每个Person的子节点, 朋友 { if (ContainedThisPerson(EveryPersonChildNode, PersonList)) //如果发现这个朋友已经在Person集合中 { } else //如果不存在 { var NewPersonNode = doc.CreateElement("Person");//新建一个节点 NewPersonNode.SetAttribute("id", EveryPersonChildNode.Attributes["id"].InnerText);//设置id, 名字, 学校, 头像等 NewPersonNode.SetAttribute("name", EveryPersonChildNode.Attributes["name"].InnerText); NewPersonNode.SetAttribute("school", EveryPersonChildNode.Attributes["school"].InnerText); NewPersonNode.SetAttribute("faceUri", EveryPersonChildNode.Attributes["faceUri"].InnerText); {//开始加载这个节点有哪些朋友 foreach (XmlNode AnotherPersonList in PersonList) foreach (XmlNode AnotherPersonListChildNode in AnotherPersonList.ChildNodes) if (AnotherPersonListChildNode.Attributes["id"].InnerText == NewPersonNode.Attributes["id"].InnerText) { var newFriendNode = doc.CreateElement("Friend"); newFriendNode.SetAttribute("id", AnotherPersonListChildNode.ParentNode.Attributes["id"].InnerText); newFriendNode.SetAttribute("name", AnotherPersonListChildNode.ParentNode.Attributes["name"].InnerText); newFriendNode.SetAttribute("school", AnotherPersonListChildNode.ParentNode.Attributes["school"].InnerText); newFriendNode.SetAttribute("faceUri", AnotherPersonListChildNode.ParentNode.Attributes["faceUri"].InnerText); NewPersonNode.AppendChild(newFriendNode); break; } } doc.DocumentElement.AppendChild(NewPersonNode); Console.WriteLine("Add New Node " + NewPersonNode.Attributes["name"].InnerText); } } } doc.Save(@"C:\Users\Chen Hua\Desktop\Relation.xml");}private static bool ContainedThisPerson(XmlNode node, XmlNodeList nodeList){ foreach (XmlNode nodeInList in nodeList) { if (node.Attributes["id"].InnerText == nodeInList.Attributes["id"].InnerText) return true; } return false;}
获得一个人的好友列表
上面说了那么多, 怎么获得好友列表呢?
1. 分析登陆协议, 看登陆时POST了哪些信息, 写一个登陆方法
2. 去=你需要找的id
获得第2步每一页的字符串, 用正则表达式提取出好友的信息, 正则表达式我写好了:
@"<p class=""avatar""><a href=""[a-zA-z]+://www\.renren\.com/profile\.do\?id=([0-9]*)""><img src=""([a-zA-z]+://hdn.xnimg.cn/photos/[^\s]*)""[\s]?alt=""[a-zA-z]+://hdn.xnimg.cn/photos/[^\s]*jpg[\s]?.*"">[\s]?<dl>[\s]?<dt>.*</dt>[\s]?<dd><a href=""[^\s]*"">(.*)</a>[\s]+</dd>[\s]?.*[\s]?<dt>.*</dt>[\s]?<dd>(.*)</dd>"
下图是一个获取好友列表的测试图片:
先写这么多, 程序还在哗啦啦的运行中, 下篇继续...争取多一些程序优化方面的.