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

前言

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

篩選出我的主頁中含「明星關係」的數據,進一步爬取並解析出後續關係圖譜所需的數據。以「張國榮-我的主頁」爲例,其直接相關的明星並很少,可見數據質量不必定多高,僅供練手,故不在此處過多糾纏。
node

數據到手後,存成 csv,丟到 neo4j 裏,就能查詢出「張國榮」的關係。
git

若是想進一步查看「張國榮」擴散出去的關係,也很方便。
github

因緣際會

有沒有以爲很酷炫,很想趕忙學起來。不急,neo4j 部分很簡單的,因此先照舊講講那些「因緣際會」的事。數據庫

細數過往,已經用 Gephi 搞過好幾回關係圖譜,相對於微博轉發圖譜和知乎大V關注圖譜的中規中矩(見於:Gephi繪製微博轉發圖譜:以「@老婆孩子在天堂」爲例374名10萬+知乎大V(一):相互關注狀況),拿本身的日記進行分析就顯得別出心裁、使人眼前一亮,算得上本身蠻中意的做品,雖然技術細節很是粗糙(見於:2017,那些出如今日記中的人:簡單的文本挖掘)。不過回頭看來,這幾個的數據格式徹底能夠無縫應用到 neo4j 裏,感興趣的朋友能夠去微博轉發圖譜一文裏領取數據並實現一波。
json

而說是「新近」其實也是半年前安利的紅樓夢人物關係及事件的可視化圖譜,纔是正兒八經用到 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 等關係型數據庫不一樣,其保存的數據格式爲節點和節點之間的關係,構建和查詢關係數據很是高效便捷。bash

安裝過程可參考:Neo4j 第一篇:在Windows環境中安裝Neo4jWindows下安裝neo4j,本來想跳過這部分,但由於也遇到幾個小問題,因此簡單講下。app

  • 安裝 Java JDK。由於以前安裝 Gephi 時就弄過了,因此本次跳過。dom

  • 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;
複製代碼

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


相關文章
相關標籤/搜索