一文教你用 Neo4j 快速构建明星关系图谱

更多有趣项目及代码见于:DesertsX/gulius-projectscss

前言

本文将带你用 neo4j 快速实现一个明星关系图谱,由于拖延的缘故,正好遇上又一年的4月1日,因而将文中的几个例子顺势改为了“哥哥”张国荣。正所谓“巧妇难为无米之炊”,本次爬取娱乐圈_专业的娱乐综合门户网站下属“明星”页的“更多明星”里全部9141条数据。
html

筛选出我的主页中含“明星关系”的数据,进一步爬取并解析出后续关系图谱所需的数据。以“张国荣-我的主页”为例,其直接相关的明星并很少,可见数据质量不必定多高,仅供练手,故不在此处过多纠缠。
java

数据到手后,存成 csv,丢到 neo4j 里,就能查询出“张国荣”的关系。node


若是想进一步查看“张国荣”扩散出去的关系,也很方便。python


因缘际会

有没有以为很酷炫,很想赶忙学起来。不急,neo4j 部分很简单的,因此先照旧讲讲那些“因缘际会”的事。git

细数过往,已经用 Gephi 搞过好几回关系图谱,相对于微博转发图谱和知乎大V关注图谱的中规中矩(见于:Gephi绘制微博转发图谱:以“@老婆孩子在天堂”为例374名10万+知乎大V(一):相互关注状况),拿本身的日记进行分析就显得别出心裁、使人眼前一亮,算得上本身蛮中意的做品,虽然技术细节很是粗糙(见于:2017,那些出如今日记中的人:简单的文本挖掘)。不过回头看来,这几个的数据格式彻底能够无缝应用到 neo4j 里,感兴趣的朋友能够去微博转发图谱一文里领取数据并实现一波。
github

而说是“新近”其实也是半年前安利的红楼梦人物关系及事件的可视化图谱,才是正儿八经用到 neo4j 的,当初本身也曾兴致高昂地分析了下支撑该项目的json数据,手动写了稍显复杂的函数来提取“私通”相关的人物关系链,如今看来 neo4j 一行代码就能解决。(见于:安利一个惊艳的红楼梦可视化做品左手读红楼梦,右手写BUG,闲快活数据库

def word2id(word):
    df = edges_df[edges_df.label== word]
    from_id = df['from'].values.tolist()
    to_id = df['to'].values.tolist()
    return from_id, to_id

def id2label(ids):
    tables = []
    for ID in ids:
        tables.append(person_df[person_df['id']==ID])
    labels = pd.concat(tables)['label'].values.tolist()
    return labels

def get_relation(from_id,to_id):
    for from_label, to_label in zip(id2label(from_id), id2label(to_id)):
        print(from_label, '--> {} -->'.format(word), to_label)

word = "私通"
from_id,to_id = word2id(word)
get_relation(from_id,to_id)
############################
# 如下为输出结果
贾蔷 --> 私通 --> 龄官
贾珍 --> 私通 --> 秦可卿
贾琏 --> 私通 --> 多姑娘
薛蟠 --> 私通 --> 宝蟾
王熙凤 --> 私通 --> 贾蓉
秦可卿 --> 私通 --> 贾蔷
司棋 --> 私通 --> 潘又安
宝蟾 --> 私通 --> 薛蟠
尤三姐 --> 私通 --> 贾珍
鲍二家的 --> 私通 --> 贾琏
智能儿 --> 私通 --> 秦钟
万儿 --> 私通 --> 茗烟

Neo4j 安装

Neo4j 属于图形数据库,与更广为人知的 MySQL 等关系型数据库不一样,其保存的数据格式为节点和节点之间的关系,构建和查询关系数据很是高效便捷。json

安装过程可参考:Neo4j 第一篇:在Windows环境中安装Neo4jWindows下安装neo4j,本来想跳过这部分,但由于也遇到几个小问题,因此简单讲下。浏览器

  • 安装 Java JDK。由于以前安装 Gephi 时就弄过了,因此本次跳过。

  • Neo4j官网下载最新社区(Community)版本 ,解压到目录,E:\neo4j-file\neo4j-community-3.5.3\

  • 启动Neo4j程序:组合键Windows+R,输入cmd,打开命令行窗口,切换到主目录cd E:\neo4j-file\neo4j-community-3.5.3,以管理员身份运行命令:neo4j.bat console后,会报错。

  • 百度解决方案,在“个人电脑”-“属性”-“高级系统设置”-“环境变量”,将主路径放入系统变量中NEO4J_HOME=E:\neo4j-file\neo4j-community-3.5.3,同时将%NEO4J_HOME%\bin添加到path中,注意英文分号分隔。

  • 接着还有错误:Import-Module : 未能加载指定的模块“\Neo4j-Management.psd1”,因而更改E:\neo4j-file\neo4j-community-3.5.3\bin\neo4j.ps1文件里的Import-Module "$PSScriptRoot\Neo4j-Management.psd1"为绝对路径Import-Module "E:\neo4j-file\neo4j-community-3.5.3\bin\Neo4j-Management.psd1"

  • 保存文件后,从新启用,红色提示消失,运行Neo4j install-service命令,将Neo4j服务安装在系统上。而后运行Neo4j start命令,启动Neo4j。

  • 浏览器中输入 http://localhost:7474 ,即可进入 neo4j 界面,初始登陆名和密码均为neo4j,按照提醒修改密码后,便完成了准备工做。

Neo4j 初体验

安装完成后,在之后的岁月里,只需在命令行窗口进入E:\neo4j-file\neo4j-community-3.5.3\bin文件夹,运行neo4j start即可启动
neo4j,而后打开网址http://localhost:7474,输入初始登陆名和密码均neo4j或修改后的密码便可。

cd /d E:
cd E:\neo4j-file\neo4j-community-3.5.3\bin
neo4j start

接着即可以用 Cypher 查询语言(CQL,像Oracle数据库具备查询语言SQL,Neo4j具备CQL做为查询语言)建立节点和关系。可阅读w3cschool的教程 快速入门:Neo4j - CQL简介

下面是一些入门的语句,简单了解下,后面实现明星关系图谱就够用了。

# 建立具备带属性(name ,age)的 People 节点
create(p:People{name:"Alex", age:20});

create(p:People{name:"Tom", age:22});

# 匹配 People节点,并返回其 name 和 age 属性
match (p:People) return p.name, p.age

# 匹配全部 age 为20的 People 节点
match (p:People{age:20}) RETURN p

# 建立 Alex 和 Tom 之间单向的 Friend 关系
create(:People{name:"Alex", age:20})-[r:Friends]->(:People{name:"Tom", age:22})

# 
match p=()-[r:RELATION]->() return p LIMIT 25

# 匹配全部节点并查看其中25个
match (n) return n LIMIT 25;

# 简单粗暴删除全部节点及节点相关的关系
match (n) detach delete n

数据爬取

爬虫部分不进行过多讲解,一直翻页直到获取所有9141条明星姓名及我的主页连接便可。完整代码见于:DesertsX/gulius-projects

另外提取了明星图片连接等信息,本次没用到,能够忽略的,但若是能在关系图谱中加入人物图片,效果会更佳,只是还不知道如何实现。

import time
import random
import requests
from lxml import etree
import pandas as pd
from fake_useragent import UserAgent

ylq_all_star_ids = pd.DataFrame(columns = ['num', 'name', 'star_id', 'star_url', 'image'])
total_pages=153
for page in range(1, total_pages+1):
    ua = UserAgent()
    url = 'http://www.ylq.com/star/list-all-all-all-all-all-all-all-{}.html'
    r = requests.get(url=url.format(page), headers=headers)
    r.encoding = r.apparent_encoding
    dom = etree.HTML(r.text)
    
    # 'http://www.ylq.com/neidi/xingyufei/'
    star_urls = dom.xpath('//div[@class="fContent"]/ul/li/a/@href')
    star_ids = [star_url.split('/')[-2] for star_url in star_urls]
    star_names = dom.xpath('//div[@class="fContent"]/ul/li/a/h2/text()')
    star_images = dom.xpath('//div[@class="fContent"]/ul/li/a/img/@src')
    
    print(page, len(star_urls), len(star_ids), len(star_images), len(star_names))
    
    for i in range(len(star_ids)):
        ylq_all_star_ids = ylq_all_star_ids.append({'num':int((page-1)*60+i+1), 'name': star_names[i],
                                                    'star_id':star_ids[i], 'star_url': star_urls[i],
                                                    'image':star_images[i]},ignore_index=True)
    # if page%5 == 0:
    # time.sleep(random.randint(0,2))
print("爬虫结束!")

验收下数据,没问题。


因为并非多有明星的我的主页都含有“明星关系”的数据,全部筛选出含关系数据的1263条连接。注意这部分比较耗时,可自行优化加速,后续有空再改进。

star_has_relations = []
for num, url in enumerate(star_urls):
    ua = UserAgent()
    headers ={"User-Agent": ua.random,
              'Host': 'www.ylq.com'}
    try:
        r = requests.get(url=url, headers =headers, timeout=5)
        r.encoding = r.apparent_encoding
    
        if 'starRelation' in r.text:
            star_has_relations.append(url)
            print(num, "Bingo!", end=' ')
        if num%100==0:
            print(num, end=' ')
    except:
        print(num, star_has_relations)
# if (num+index)%50==0:
# time.sleep(random.randint(0,2))

接着有针对性的爬取这部分关系数据便可,固然爬虫部分可根据本身喜爱,合并一些步骤,好比筛选含关系连接与爬取关系数据这个一步到位也能够。

datas = []
ylq_all_star_relations = pd.DataFrame(columns = ['num', 'subject', 'relation', 'object',
                                                 'subject_url', 'object_url', 'obeject_image'])
for num, subject_url in enumerate(star_has_relations):
    ua = UserAgent()
    headers ={"User-Agent": ua.random,
              'Host': 'www.ylq.com'}
    try:
        r = requests.get(url=subject_url, headers =headers, timeout=5)
        r.encoding = r.apparent_encoding
        dom = etree.HTML(r.text)
        subject = dom.xpath('//div/div/div/h1/text()')[0]
        relations = dom.xpath('//div[@class="hd starRelation"]/ul/li/a/span/em/text()')
        objects = dom.xpath('//div[@class="hd starRelation"]/ul/li/a/p/text()')
        object_urls = dom.xpath('//div[@class="hd starRelation"]/ul/li/a/@href')
        object_images = dom.xpath('//div[@class="hd starRelation"]/ul/li/a/img/@src')
        for i in range(len(relations)):
            relation_data = {'num': int(num+1), 'subject': subject, 'relation': relations[i],
                             'object': objects[i], 'subject_url':subject_url,
                             'object_url': object_urls[i], 'obeject_image':object_images[i]}
            datas.append(relation_data)
            ylq_all_star_relations = ylq_all_star_relations.append(relation_data,
                                                                   ignore_index=True)
        print(num, subject, end=' ')
    except:
        print(num, datas)
# if num%20 == 0:
# time.sleep(random.randint(0,2))
# print(num, 'sleep a moment')

获取的明星关系数据格式以下,后面还考虑到状况,但貌似均可以删减掉,因此在此就不赘述了,完整代码见于:DesertsX/gulius-projects

构建明星关系图谱

若是你对爬虫不感兴趣,只是想知道如何导入现有的csv数据,而后用neo4j构建关系图谱,那么直接从这里开始实践便可,毕竟此次的数据也是无偿提供的。

手动去掉一些无用的列数据后,将ylq_star_nodes.csvylq_star_relations.csv 两个csv文件,放到E:\neo4j-file\neo4j-community-3.5.3\import目录下,而后分别执行下面两个命令,就完成了关系图谱的建立!是的,一秒完成,固然数据量大的话,可能会等上一小会。

LOAD CSV  WITH HEADERS FROM 'file:///ylq_star_nodes.csv' AS data CREATE (:star{starname:data.name, starid:data.id});

LOAD CSV  WITH HEADERS FROM "file:///ylq_star_relations.csv" AS relations MATCH (entity1:star{starname:relations.subject}) , (entity2:star{starname:relations.object}) CREATE (entity1)-[:rel{relation: relations.relation}]->(entity2)

以后就能够分别查询各类信息了。

# 查某人所有关系
return (:star{starname:"张国荣"})-->();
# 查某人朋友的朋友(5层关系)
match p=(n:star{starname:"张国荣"})-[*..5]->() return p limit 50;
# 查询特定关系
match p=()-[:rel{relation:"旧爱"}]->() return p LIMIT 25;
# 使用函数,查询张国荣与张卫健的最短路径
match p=shortestpath((:star{starname:"张国荣"})-[*..5]->(:star{starname:"张卫健"})) return p;

更多有趣的命令可自行学习和尝试,其余好玩的数据集也可按我的兴趣去耍耍。

相关文章
相关标签/搜索