宋小宝通过哪几个人可以找到詹妮弗劳伦斯——利用Web Scraping测试“六度分隔假说”

来源:互联网 发布:阿里云售后自助服务 编辑:程序博客网 时间:2024/04/29 08:48

六度分隔假说:你和任何一个陌生人之间所间隔的人不会超过六个

这条假说意味着大多数人有认识地球上任何一个人的可能性。然而在现实中,即使两个陌生人之间存在着某种联系,但信息的不对称性往往使得这些联系极难被挖掘。于此不同,网络上储存了公共人物大量公开的信息数据,利用这些数据,可以很轻松地找到两个名人之间的人物关系链。

那么我们的问题是:

1.宋小宝同学通过哪几个人可以找到大表姐詹妮弗劳伦斯?

项目 项目

2.这些人又是怎么认识的?

本文将利用Python Web Scraping解决这两个问题。


Web Scraping介绍

Web Scraping(网络爬虫),是一种按照一定的规则,自动地抓取网络信息的程序或者脚本。简单地说,Web Scraping就是从网站抽取信息, 利用程序来模拟人浏览网页的过程,发送http请求,从http响应中获得结果。

Python提供了便利的Web Scraping基础,有很多第三方库。加上其语言的简便性,Web Scraping目前常常与Python绑定在一起。

本文所涉及的库有:

  • Requests:http://www.python-requests.org/en/master/

  • BeautifulSoup:http://www.crummy.com/software/BeautifulSoup/

  • Pymysql:https://github.com/PyMySQL/PyMySQL

数据爬取

数据来源于百度百科,其HTML页面结构较为稳定,因此并未使用框架进行爬取。

首先做一些初始化准备工作。

import requestsimport pymysqlfrom bs4 import BeautifulSoup# 与Mysql数据库交互conn = pymysql.connect(    host='127.0.0.1',    port=3306,    user='******',    passwd='******',    charset='utf8',    db='deepsearch',)cur = conn.cursor()def insertName(name):    cur.execute("select * from searched_name where name = %s", name)    if cur.rowcount == 0:        cur.execute("insert into searched_name (name) values(%s)", name)        conn.commit()def insertLinks(fromname, toname):    cur.execute(        "select * from names_link where fromname = %s and toname = %s", (fromname, toname))    if cur.rowcount == 0:        cur.execute(            "insert into names_link (fromname,toname) values (%s,%s)", (fromname, toname))        conn.commit()

在正式爬取数据之前,数据库中已建立好了searched_name,names_link两个表格,用来存储已爬取的名字,名字的对应关系。大致结构如下:

searched_name

id name 1 宋小宝 … … 1024 吴彦祖 id fromname toname 1 宋小宝 赵本山 2 宋小宝 沈春阳 … … …

*原始数据表为空,此处数据仅为了方便理解而编造。

接下来设置一些辅助函数。
(此处代码写得较为冗余,不感兴趣的可直接跳过,后面有对代码的解释)

def getFilms(name):     filmList = []    try:        r = requests.get("http://baike.baidu.com/item/" + name)    except HTTPError as e:        print(e)        return filmList    r.encoding = 'utf-8'    bs = BeautifulSoup(r.text, "lxml")    for i in bs.findAll("b", {"class": "title"}):        if i.a != None:            if ('href' in i.a.attrs) and (i.a.attrs['href'] not in filmList):                film = findCorrectFilm(name, i.a.attrs['href'])                filmList.append(film)    return filmListdef findCorrectFilm(name, film):    actors = []    samefilms = []    r = requests.get("http://baike.baidu.com/" + film)    r.encoding = 'utf-8'    bs = BeautifulSoup(r.text, 'lxml')    same_films_table = bs.find(        'ul', {'class': ['polysemantList-wrapper', 'cmn-clearfix']})    if same_films_table != None:        same_films_list = same_films_table.findAll('li')        if same_films_list:            for j in same_films_list:                j = j.a                if j != None:                    if 'href' in j.attrs:                        samefilms.append(j.attrs['href'])            for i in bs.findAll('dl', {'class': 'info'}):                if i.a != None:                    tempname = i.a.get_text()                    if tempname not in actors:                        actors.append(tempname)            if name not in actors:                for t in range(0, len(samefilms)):                    r = requests.get("http://baike.baidu.com/" + samefilms[t])                    r.encoding = 'utf-8'                    bs = BeautifulSoup(r.text, 'lxml')                    for i in bs.findAll('dl', {'class': 'info'}):                        if i.a != None:                            tempname = i.a.get_text()                            if tempname not in actors:                                actors.append(tempname)                    if name in actors:                        return samefilms[t]            else:                return film    else:        return film

这两个函数体现了此次数据爬取的思路,即如何在百度百科上找到指定演员所认识的人。我的做法是

  • 1.先爬取该指定演员出演过的全部电影

  • 2.再对这些电影进行遍历,爬取每部电影对应的所有演员、职员

而这些演员、职员即是该指定演员所认识的人。

在上述代码中getFilms函数返回了指定演员出演的所有作品,findCorrectFilm函数则对同名电影进行了过滤,找到属于指定演员真正出演的电影。

最后对人物关系进行爬取和存储。

SEARCHEDNAME = set()def getFriends(name, deep):    global SEARCHEDNAME    if name not in SEARCHEDNAME:        SEARCHEDNAME.add(name)        if deep > 5:             return        insertName(name)         filmlist = getFilms(name)          friendlist = []        for film in filmlist:            if film != None:                try:                    r = requests.get("http://baike.baidu.com" + film)                except HTTPError as e:                    return                r.encoding = 'utf-8'                bs = BeautifulSoup(r.text, 'lxml')                for i in bs.findAll('dl', {'class': 'info'}):                    if i.a != None:                        toname = i.a.get_text()                        if toname not in friendlist:                            insertLinks(name, toname)                            friendlist.append(toname)            for newname in friendlist:                getFriends(newname, deep + 1)    else:        returntry:    getFriends("宋小宝", 0)finally:    cur.close()    conn.close()

此处代码完成了上述爬取思路中的第二步,即爬取指定演员所演电影对应的所有演员、职员的名字,并把这些对应关系存入数据库。

在此过程中采用了递归调用,并设置了变量deep来跟跟踪函数递归的次数,当deep值为6时,函数会自动返回,防止数据太大导致内存堆栈溢出。

因为无法预估此程序运行时间,我将其放置在云服务器上运行,最后程序运行时间为4118.0s。


数据分析

此时,数据已经安安静静地躺在数据库中。

那么问题来了:

宋小宝通过哪几个人可以找到詹妮弗劳伦斯?

不考虑性能,用获得的数据,本程序实现了一个深度优先,找到即返回的搜索算法,代码如下:

class Found(RuntimeError):    def __init__(self, message):        self.message = messagedef getNames(fromname):    cur.execute(        "select toname from names_link where fromname = %s ", fromname)    names = []    for i in cur.fetchall():        if i[0] != fromname:            names.append(i[0])    return namessearched_name = set()key_name = []def DeepSearch(fromname, toname, depth):    global searched_name    global key_name    if fromname not in searched_name:        searched_name.add(fromname)    if depth > 5:        return    names = getNames(fromname)    if(names):        if toname in names:            print("找到:" + toname)            key_name.append(toname)            key_name.append(fromname)            raise Found("来自:" + fromname)        for name in names:            if name not in searched_name:                try:                    DeepSearch(name, toname, depth + 1)                except Found as f:                    print(f.message)                    key_name.append(fromname)                    raise Found("来自:" + fromname)    else:        returntry:    DeepSearch("宋小宝", "詹妮弗·劳伦斯", 0)    print("找不到两人之间的关系")except Found as f:    print(f.message)

这里的getNames函数是辅助函数,用来从数据库中获取指定名字所认识的名字列表。

程序运行规则如下:

1.如果递归达到次数限制,停止搜索。

2.如果函数获取的名字列表不为空,则在该列表上进行搜索。如果名字列表为空,则返回上一层进行搜索。

3.如果当前列表包含要搜索的目标名字,就将该名字打印,并抛出异常,显示目标名字已经搜索到。递归过程中的每个栈都会打印当前名字,然后抛出异常显示目标名字已经搜索到,最终在屏幕上打印完整的名字路径。

4.如果在当前列表未找到目标名字,把递归次数加1,调用函数搜索下一层名字列表。

程序运行结果如下:
运行结果

至此,人物关系链已经找到。然而我们还需解决另一个问题:

这些人是怎么认识的?

在上述代码中,变量key_name已将人物关系链中的名字进行了储存。在此基础上,我对其中每一个名字进行重新爬取,找到他们的共同电影。代码如下:

def getFilms(name):    filmList = []    try:        r = requests.get("http://baike.baidu.com/item/" + name)    except HTTPError as e:        print(e)        return filmList    r.encoding = 'utf-8'    bs = BeautifulSoup(r.text, "lxml")    for i in bs.findAll("b", {"class": "title"}):        if i.a != None:            filmList.append(i.a.get_text())    return filmListdef findCommonFilm(filmlist1, filmlist2):    for film in filmlist1:        if film in filmlist2:            return filmdef relationShip():    common_film = []    chain = ""    for i in range(0, len(key_name) - 1):        filmlist1 = getFilms(key_name[i])        filmlist2 = getFilms(key_name[i + 1])        common_film.append(findCommonFilm(filmlist1, filmlist2))    for i in range(1, len(common_film) + 1):        chain += str(key_name[-i]) + "因电影<<" + str(common_film[-i]) + ">>" + "认识了" + str(key_name[-i - 1]) + "," + "\n"    return chainchain = relationShip()print("人物关系:" + chain )

程序运行结果如下:
运行结果

出于好奇,我对其它一些名字进行了测试。
运行结果

这里写图片描述

总结

通过这个小程序我们找到了宋小宝和大表姐的关系,他们之间间隔了4个人,分别是赵本山、张洪杰、张豆豆、贝尔。在此过程中,通过总结发现了以下一些问题:

1.数据存在局限性。由于数据来源于百度百科,考虑到爬取的难度,目前获取的数据都集中在影视圈,对于跨圈的人际关系无法得到很好的验证。

2.实现的搜索算法较为简陋。只找了众多路径中的一条,且并未解决“A指向B,B指向C,但同时A又直接指向C“的问题,这导致了人物关系链的增长。

3.我和吴彦祖一定是有交集的。

0 0
原创粉丝点击