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

古柳_Deserts_X發表於2019-04-01

前言

本文將帶你用 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)

    # '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;
複製程式碼

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


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


相關文章