一文教你用 Neo4j 快速構(gòu)建明星關(guān)系圖譜

更多有趣項目及代碼見于:DesertsX/gulius-projects

前言

本文將帶你用 neo4j 快速實現(xiàn)一個明星關(guān)系圖譜,因為拖延的緣故,正好趕上又一年的4月1日,于是將文中的幾個例子順勢改成了“哥哥”張國榮。正所謂“巧婦難為無米之炊”,本次爬取娛樂圈_專業(yè)的娛樂綜合門戶網(wǎng)站下屬“明星”頁的“更多明星”里所有9141條數(shù)據(jù)。

篩選出個人主頁中含“明星關(guān)系”的數(shù)據(jù),進一步爬取并解析出后續(xù)關(guān)系圖譜所需的數(shù)據(jù)。以“張國榮-個人主頁”為例,其直接相關(guān)的明星并不多,可見數(shù)據(jù)質(zhì)量不一定多高,僅供練手,故不在此處過多糾纏。

數(shù)據(jù)到手后,存成 csv,丟到 neo4j 里,就能查詢出“張國榮”的關(guān)系。


如果想進一步查看“張國榮”擴散出去的關(guān)系,也很方便。


因緣際會

有沒有覺得很酷炫,很想趕緊學(xué)起來。不急,neo4j 部分很簡單的,所以先照舊講講那些“因緣際會”的事。

細數(shù)過往,已經(jīng)用 Gephi 搞過好幾次關(guān)系圖譜,相對于微博轉(zhuǎn)發(fā)圖譜和知乎大V關(guān)注圖譜的中規(guī)中矩(見于:Gephi繪制微博轉(zhuǎn)發(fā)圖譜:以“@老婆孩子在天堂”為例、374名10萬+知乎大V(一):相互關(guān)注情況),拿自己的日記進行分析就顯得別出心裁、令人眼前一亮,算得上自己蠻中意的作品,雖然技術(shù)細節(jié)非常粗糙(見于:2017,那些出現(xiàn)在日記中的人:簡單的文本挖掘)。不過回頭看來,這幾個的數(shù)據(jù)格式完全可以無縫應(yīng)用到 neo4j 里,感興趣的朋友可以去微博轉(zhuǎn)發(fā)圖譜一文里領(lǐng)取數(shù)據(jù)并實現(xiàn)一波。

而說是“新近”其實也是半年前安利的紅樓夢人物關(guān)系及事件的可視化圖譜,才是正兒八經(jīng)用到 neo4j 的,當初自己也曾興致高昂地分析了下支撐該項目的json數(shù)據(jù),手動寫了稍顯復(fù)雜的函數(shù)來提取“私通”相關(guān)的人物關(guān)系鏈,現(xiàn)在看來 neo4j 一行代碼就能解決。(見于:安利一個驚艷的紅樓夢可視化作品、左手讀紅樓夢,右手寫B(tài)UG,閑快活

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)
############################
# 以下為輸出結(jié)果
賈薔 --> 私通 --> 齡官
賈珍 --> 私通 --> 秦可卿
賈璉 --> 私通 --> 多姑娘
薛蟠 --> 私通 --> 寶蟾
王熙鳳 --> 私通 --> 賈蓉
秦可卿 --> 私通 --> 賈薔
司棋 --> 私通 --> 潘又安
寶蟾 --> 私通 --> 薛蟠
尤三姐 --> 私通 --> 賈珍
鮑二家的 --> 私通 --> 賈璉
智能兒 --> 私通 --> 秦鐘
萬兒 --> 私通 --> 茗煙

Neo4j 安裝

Neo4j 屬于圖形數(shù)據(jù)庫,與更廣為人知的 MySQL 等關(guān)系型數(shù)據(jù)庫不同,其保存的數(shù)據(jù)格式為節(jié)點和節(jié)點之間的關(guān)系,構(gòu)建和查詢關(guān)系數(shù)據(jù)非常高效便捷。

安裝過程可參考:Neo4j 第一篇:在Windows環(huán)境中安裝Neo4jWindows下安裝neo4j,原本想跳過這部分,但因為也遇到幾個小問題,所以簡單講下。

  • 安裝 Java JDK。因為之前安裝 Gephi 時就弄過了,所以本次跳過。

  • Neo4j官網(wǎng)下載最新社區(qū)(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后,會報錯。

  • 百度解決方案,在“我的電腦”-“屬性”-“高級系統(tǒng)設(shè)置”-“環(huán)境變量”,將主路徑放入系統(tǒng)變量中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服務(wù)安裝在系統(tǒng)上。然后運行Neo4j start命令,啟動Neo4j。

  • 瀏覽器中輸入 http://localhost:7474 ,便可進入 neo4j 界面,初始登錄名和密碼均為neo4j,按照提醒修改密碼后,便完成了準備工作。

Neo4j 初體驗

安裝完成后,在以后的歲月里,只需在命令行窗口進入E:\neo4j-file\neo4j-community-3.5.3\bin文件夾,運行neo4j start便可啟動
neo4j,然后打開網(wǎng)址http://localhost:7474,輸入初始登錄名和密碼均neo4j或修改后的密碼即可。

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

接著便可以用 Cypher 查詢語言(CQL,像Oracle數(shù)據(jù)庫具有查詢語言SQL,Neo4j具有CQL作為查詢語言)創(chuàng)建節(jié)點和關(guān)系??砷喿xw3cschool的教程 快速入門:Neo4j - CQL簡介

下面是一些入門的語句,簡單了解下,后面實現(xiàn)明星關(guān)系圖譜就夠用了。

# 創(chuàng)建具有帶屬性(name ,age)的 People 節(jié)點
create(p:People{name:"Alex", age:20});

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

# 匹配 People節(jié)點,并返回其 name 和 age 屬性
match (p:People) return p.name, p.age

# 匹配所有 age 為20的 People 節(jié)點
match (p:People{age:20}) RETURN p

# 創(chuàng)建 Alex 和 Tom 之間單向的 Friend 關(guān)系
create(:People{name:"Alex", age:20})-[r:Friends]->(:People{name:"Tom", age:22})

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

# 匹配所有節(jié)點并查看其中25個
match (n) return n LIMIT 25;

# 簡單粗暴刪除所有節(jié)點及節(jié)點相關(guān)的關(guān)系
match (n) detach delete n

數(shù)據(jù)爬取

爬蟲部分不進行過多講解,一直翻頁直到獲取全部9141條明星姓名及個人主頁鏈接即可。完整代碼見于:DesertsX/gulius-projects

另外提取了明星圖片鏈接等信息,本次沒用到,可以忽略的,但如果能在關(guān)系圖譜中加入人物圖片,效果會更佳,只是還不知道如何實現(xiàn)。

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("爬蟲結(jié)束!")

驗收下數(shù)據(jù),沒問題。


由于并不是多有明星的個人主頁都含有“明星關(guān)系”的數(shù)據(jù),所有篩選出含關(guān)系數(shù)據(jù)的1263條鏈接。注意這部分比較耗時,可自行優(yōu)化加速,后續(xù)有空再改進。

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))

接著有針對性的爬取這部分關(guān)系數(shù)據(jù)即可,當然爬蟲部分可根據(jù)自己喜好,合并一些步驟,比如篩選含關(guān)系鏈接與爬取關(guān)系數(shù)據(jù)這個一步到位也可以。

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')

獲取的明星關(guān)系數(shù)據(jù)格式如下,后面還考慮到情況,但貌似都可以刪減掉,所以在此就不贅述了,完整代碼見于:DesertsX/gulius-projects

構(gòu)建明星關(guān)系圖譜

如果你對爬蟲不感興趣,只是想知道如何導(dǎo)入現(xiàn)有的csv數(shù)據(jù),然后用neo4j構(gòu)建關(guān)系圖譜,那么直接從這里開始實踐即可,畢竟這次的數(shù)據(jù)也是無償提供的。

手動去掉一些無用的列數(shù)據(jù)后,將ylq_star_nodes.csvylq_star_relations.csv 兩個csv文件,放到E:\neo4j-file\neo4j-community-3.5.3\import目錄下,然后分別執(zhí)行下面兩個命令,就完成了關(guān)系圖譜的創(chuàng)建!是的,一秒完成,當然數(shù)據(jù)量大的話,可能會等上一小會。

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)

之后就可以分別查詢各種信息了。

# 查某人全部關(guān)系
return (:star{starname:"張國榮"})-->();
# 查某人朋友的朋友(5層關(guān)系)
match p=(n:star{starname:"張國榮"})-[*..5]->() return p limit 50;
# 查詢特定關(guān)系
match p=()-[:rel{relation:"舊愛"}]->() return p LIMIT 25;
# 使用函數(shù),查詢張國榮與張衛(wèi)健的最短路徑
match p=shortestpath((:star{starname:"張國榮"})-[*..5]->(:star{starname:"張衛(wèi)健"})) return p;

更多有趣的命令可自行學(xué)習(xí)和嘗試,其他好玩的數(shù)據(jù)集也可按個人興趣去耍耍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容