一文教你用 Neo4j 快速構建明星關系圖譜


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

前言

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

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

數據到手后,存成 csv,丟到 neo4j 里,就能查詢出“張國榮”的關系。


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


因緣際會

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

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

而說是“新近”其實也是半年前安利的紅樓夢人物關系及事件的可視化圖譜,才是正兒八經用到 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 等關系型數據庫不同,其保存的數據格式為節點和節點之間的關系,構建和查詢關系數據非常高效便捷。

安裝過程可參考: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)

<span class="hljs-comment"># 'http://www.ylq.com/neidi/xingyufei/'</span>
star_urls = dom.xpath(<span class="hljs-string">'//div[@class="fContent"]/ul/li/a/@href'</span>)
star_ids = [star_url.split(<span class="hljs-string">'/'</span>)[<span class="hljs-number">-2</span>] <span class="hljs-keyword">for</span> star_url <span class="hljs-keyword">in</span> star_urls]
star_names = dom.xpath(<span class="hljs-string">'//div[@class="fContent"]/ul/li/a/h2/text()'</span>)
star_images = dom.xpath(<span class="hljs-string">'//div[@class="fContent"]/ul/li/a/img/@src'</span>)

print(page, len(star_urls), len(star_ids), len(star_images), len(star_names))

<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(star_ids)):
    ylq_all_star_ids = ylq_all_star_ids.append({<span class="hljs-string">'num'</span>:int((page<span class="hljs-number">-1</span>)*<span class="hljs-number">60</span>+i+<span class="hljs-number">1</span>), <span class="hljs-string">'name'</span>: star_names[i],
                                                <span class="hljs-string">'star_id'</span>:star_ids[i], <span class="hljs-string">'star_url'</span>: star_urls[i],
                                                <span class="hljs-string">'image'</span>:star_images[i]},ignore_index=<span class="hljs-keyword">True</span>)
<span class="hljs-comment"># if page%5 == 0:</span>
<span class="hljs-comment">#    time.sleep(random.randint(0,2))</span>

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
    <span class="hljs-keyword">if</span> <span class="hljs-string">'starRelation'</span> <span class="hljs-keyword">in</span> r.text:
        star_has_relations.append(url)
        print(num, <span class="hljs-string">"Bingo!"</span>, end=<span class="hljs-string">' '</span>)
    <span class="hljs-keyword">if</span> num%<span class="hljs-number">100</span>==<span class="hljs-number">0</span>:
        print(num, end=<span class="hljs-string">' '</span>)
<span class="hljs-keyword">except</span>:
    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;

更多有趣的命令可自行學習和嘗試,其他好玩的數據集也可按個人興趣去耍耍。

      </div>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM