本文將帶你用 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 屬於圖形數據庫,與更廣爲人知的 MySQL 等關係型數據庫不一樣,其保存的數據格式爲節點和節點之間的關係,構建和查詢關係數據很是高效便捷。bash
安裝過程可參考:Neo4j 第一篇:在Windows環境中安裝Neo4j和Windows下安裝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
,按照提醒修改密碼後,便完成了準備工做。
安裝完成後,在之後的歲月裏,只需在命令行窗口進入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.csv
和ylq_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;
複製代碼
更多有趣的命令可自行學習和嘗試,其餘好玩的數據集也可按我的興趣去耍耍。