針對豆瓣TOP250電影知識圖譜的構建(Python+neo4j)

數據爬取網站: https://movie.douban .com/top250?start=0.

1. 首先對網頁數據進行分析,進而確定節點和關係

在這裏插入圖片描述
我們直接分析電影點進去的詳細頁面,頁面如下:(由於豆瓣在沒有登錄的情況下頻繁對網站進行請求會被認爲惡意攻擊,導致自己的ip無法訪問該網站,所以最好先下載下來)
在這裏插入圖片描述
通過上圖,我們選擇4個結點和4種關係

4個結點分別爲:

  • 電影名稱(film_name)
  • 導演(director)
  • 演員(actor)
  • 類型(type)

4種關係分別爲:

  • acted_in(電影——>演員)
  • directed(電影——>導演)
  • belong_to(電影——>類型)
  • cooperation(導演——>演員)

2. 下載分析頁面

點擊鼠標右鍵查看源代碼,知道鏈接的模式之後我們可以採用正則表達式進行匹配,然後獲取鏈接對應頁面,最終將獲取的頁面保存在文件中,爲我們後面分析提供數據。
在這裏插入圖片描述
分析後,我們採用後面那包含class屬性的鏈接(其實和上面一種一樣,只是爲了防止多次訪問同樣頁面),最終我們採用如下正則表達式:

<a href="https://movie.douban.com/subject/\d*?/" class="">

最終形成的獲取分析頁面代碼如下(最終我存放在代碼目錄下的contents目錄下)(1_getPage.py):

import requests
import re
import time

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
}

x = range(0, 250, 25)

for i in x:
    # 請求排行榜頁面
    html = requests.get("https://movie.douban.com/top250?start=" + str(i), headers=headers)
    # 防止請求過於頻繁
    time.sleep(0.01)
    # 將獲取的內容採用utf8解碼
    cont = html.content.decode('utf8')
    # 使用正則表達式獲取電影的詳細頁面鏈接
    urlList = re.findall('<a href="https://movie.douban.com/subject/\d*?/" class="">', cont)
    # 排行榜每一頁都有25個電影,於是匹配到了25個鏈接,逐個對訪問進行請求
    for j in range(len(urlList)):
        # 獲取啊、標籤中的url
        url = urlList[j].replace('<a href="', "").replace('" class="">', "")
        # 將獲取的內容採用utf8解碼
        content = requests.get(url, headers=headers).content.decode('utf8')
        # 採用數字作爲文件名
        film_name = i + j
        # 寫入文件
        with open('contents/' + str(film_name) + '.txt', mode='w', encoding='utf8') as f:
            f.write(content)

3. 數據爬取

3.1 結點數據爬取

3.1.1 電影名稱結點獲取

在這裏插入圖片描述
首先分析頁面數據,發現電影名稱使用title標籤框住,於是可以採用如下正則表達式對電影名稱(film_name)進行提取:

<title>.*?/title>

3.1.2 導演結點獲取

接着分析導演(director)結點數據的提取:在這裏插入圖片描述
通過分析源代碼中的腳本,我們可以使用如下正則表達式對數據進行提取:

"director":.*?]
"name": ".*?"

3.1.3 演員結點獲取

同理分析下面數據,提取演員(actor)結點數據:
在這裏插入圖片描述
我們可以使用如下正則表達式進行actor的提取:

"actor":.*?]
"name": ".*?"

3.1.4 類型結點獲取

最後分析電影類別(type):
在這裏插入圖片描述
於是我們可以使用如下正則表達式進行數據提取:

<span property="v:genre">.*?</span>

3.1.5 綜上,現在列出獲取所有結點數據並且保存在csv中的代碼(2_getNode.py):

import re
import pandas

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
}


def node_save(attrCont, tag, attr, label):
    ID = []
    for i in range(len(attrCont)):
        ID.append(tag * 10000 + i)
    data = {'ID': ID, attr: attrCont, 'LABEL': label}
    dataframe = pandas.DataFrame(data)
    dataframe.to_csv('details/' + attr + '.csv', index=False, sep=',', encoding="utf_8_sig")


def save(contents):
    # save movie nodes
    film_name = re.findall('<title>.*?/title>', contents)[0]
    film_name = film_name.lstrip("<title>").rstrip("(豆瓣)</title>").replace(" ", "")
    film_names.append(film_name)

    # save director nodes
    director_cont = re.findall('"director":.*?]', contents)[0]
    director_cont = re.findall('"name": ".*?"', director_cont)
    for i in range(len(director_cont)):
        directors.append(director_cont[i].lstrip('"name": "').rstrip('"'))

    # save actors nodes
    actor_cont = re.findall('"actor":.*?]', contents)[0]
    actor_cont = re.findall('"name": ".*?"', actor_cont)
    for i in range(len(actor_cont)):
        actors.append(actor_cont[i].lstrip('"name": "').rstrip('"'))

    # save type
    type_cont = re.findall('<span property="v:genre">.*?</span>', contents)
    for i in range(len(type_cont)):
        types.append(type_cont[i].lstrip('<span property="v:genre">').rstrip('</span>'))


film_names = []
actors = []
directors = []
types = []
for i in range(250):
    with open('contents/' + str(i) + '.txt', mode='r', encoding='utf8') as f:
        contents = f.read()
    save(contents.replace("\n", ""))  # 這裏需要把讀出來的數據換行符去掉

# 去重
actors = list(set(actors))
directors = list(set(directors))
types = list(set(types))
# 保存
node_save(film_names, 0, 'film_name', 'movie')
node_save(directors, 1, 'director', 'person')
node_save(actors, 2, 'actor', 'person')
node_save(types, 3, "type", "type")
print('ok1')

3.2 結點關係數據爬取

3.2.1 考慮acted_in關係

我們分別對每個電影詳細頁面進行分析,提取電影名稱(fillm_name)和演員列表(actor),將它們分別加入列表中,分析250個電影詳細頁面文件後最終進行保存,代碼如下:

def save_acted_in(content):
    # 獲取當前電影對應ID
    film_name = re.findall('<title>.*?/title>', content)[0]
    film_name = film_name.lstrip("<title>").rstrip("(豆瓣)</title>").replace(" ", "")  # 電影名字每頁只有一個
    filmNameID = getID('film_name', film_name)

    # 獲取當前電影的演員和對應ID
    actor_cont = re.findall('"actor":.*?]', content)[0]
    actor_cont = re.findall('"name": ".*?"', actor_cont)
    for i in range(len(actor_cont)):  # 演員每頁可能多個(通常都多個)
        actor = actor_cont[i].lstrip('name": "').rstrip('"')
        start_id.append(filmNameID)
        end_id.append(getID('actor', actor))  # 查找演員名字對應ID

3.2.2 考慮directed關係

接下來,我們還是分別對每個電影詳細頁面進行分析,提取電影名稱(fillm_name)和導演列表(director),將它們分別加入列表中,分析250個電影詳細頁面文件後最終進行保存,代碼如下:

def save_directed(contnet):
    # 獲取當前電影對應ID
    film_name = re.findall('<title>.*?/title>', content)[0]
    film_name = film_name.lstrip("<title>").rstrip("(豆瓣)</title>").replace(" ", "")
    filmNameID = getID('film_name', film_name)

    #
    director_cont = re.findall('"director":.*?]', content)[0]
    director_cont = re.findall('"name": ".*?"', director_cont)
    for i in range(len(director_cont)):
        director = director_cont[i].lstrip('"name": "').rstrip('"')
        start_id.append(filmNameID)
        end_id.append(getID('director', director))

3.2.3 考慮belong_to關係

接下來,我們還是分別對每個電影詳細頁面進行分析,提取電影名稱(fillm_name)和類型列表(type),將它們分別加入列表中,分析250個電影詳細頁面文件後最終進行保存,代碼如下:

def save_belongto(content):
    # 獲取當前電影對應ID
    film_name = re.findall('<title>.*?/title>', content)[0]
    film_name = film_name.lstrip("<title>").rstrip("(豆瓣)</title>").replace(" ", "")
    filmNameID = getID('film_name', film_name)

    #
    type_cont = re.findall('<span property="v:genre">.*?</span>', content)
    for i in range(len(type_cont)):
        type = type_cont[i].lstrip('<span property="v:genre">').rstrip('</span>')
        start_id.append(filmNameID)
        end_id.append(getID('type', type))

3.2.4 考慮cooperation關係

最後,我們還是分別對每個電影詳細頁面進行分析,提取演員列表(actor)和導演列表(director),將它們分別加入列表中,分析250個電影詳細頁面文件後最終進行保存,代碼如下:

def save_cooperation(content):
    # 獲取當前電影的演員和對應ID
    actor_cont = re.findall('"actor":.*?]', content)[0]
    actor_cont = re.findall('"name": ".*?"', actor_cont)

    #
    director_cont = re.findall('"director":.*?]', content)[0]
    director_cont = re.findall('"name": ".*?"', director_cont)

    for i in range(len(actor_cont)):
        actor = actor_cont[i].lstrip('name": "').rstrip('"')
        for j in range(len(director_cont)):
            director = director_cont[j].lstrip('"name": "').rstrip('"')
            start_id.append(getID('actor', actor))
            end_id.append(getID('director', director))

以上對保存每種關係分別定義了相應的函數進行關係對應,採用2個列表進行存儲,一 一對應。

3.2.5 下面貼出獲取所以關係的整個代碼(3_getRelations.py):

(下面代碼執行可能運行時間比較久、、、、、、、、、、、、、、、、、、)

import re
import pandas


def getID(name, nameValue):
    df = pandas.read_csv('details/' + name + '.csv')
    for j in range(len(df[name])):
        if nameValue == df[name][j]:
            return df['ID'][j]


acted_in_data = pandas.DataFrame()
directed_data = pandas.DataFrame()
cooperation_data = pandas.DataFrame()
belong_to_data = pandas.DataFrame()


def save_relation(start_id, end_id, relation):
    dataframe = pandas.DataFrame({':START_ID': start_id, ':END_ID': end_id, ':relation': relation, ':TYPE': relation})
    dataframe.to_csv('details/' + relation + '.csv', index=False, sep=',', encoding="utf_8_sig")


def save_acted_in(content):
    # 獲取當前電影對應ID
    film_name = re.findall('<title>.*?/title>', content)[0]
    film_name = film_name.lstrip("<title>").rstrip("(豆瓣)</title>").replace(" ", "")  # 電影名字每頁只有一個
    filmNameID = getID('film_name', film_name)

    # 獲取當前電影的演員和對應ID
    actor_cont = re.findall('"actor":.*?]', content)[0]
    actor_cont = re.findall('"name": ".*?"', actor_cont)
    for i in range(len(actor_cont)):  # 演員每頁可能多個(通常都多個)
        actor = actor_cont[i].lstrip('name": "').rstrip('"')
        start_id.append(filmNameID)
        end_id.append(getID('actor', actor))  # 查找演員名字對應ID


def save_directed(contnet):
    # 獲取當前電影對應ID
    film_name = re.findall('<title>.*?/title>', content)[0]
    film_name = film_name.lstrip("<title>").rstrip("(豆瓣)</title>").replace(" ", "")
    filmNameID = getID('film_name', film_name)

    #
    director_cont = re.findall('"director":.*?]', content)[0]
    director_cont = re.findall('"name": ".*?"', director_cont)
    for i in range(len(director_cont)):
        director = director_cont[i].lstrip('"name": "').rstrip('"')
        start_id.append(filmNameID)
        end_id.append(getID('director', director))


def save_belongto(content):
    # 獲取當前電影對應ID
    film_name = re.findall('<title>.*?/title>', content)[0]
    film_name = film_name.lstrip("<title>").rstrip("(豆瓣)</title>").replace(" ", "")
    filmNameID = getID('film_name', film_name)

    #
    type_cont = re.findall('<span property="v:genre">.*?</span>', content)
    for i in range(len(type_cont)):
        type = type_cont[i].lstrip('<span property="v:genre">').rstrip('</span>')
        start_id.append(filmNameID)
        end_id.append(getID('type', type))


def save_cooperation(content):
    # 獲取當前電影的演員和對應ID
    actor_cont = re.findall('"actor":.*?]', content)[0]
    actor_cont = re.findall('"name": ".*?"', actor_cont)

    #
    director_cont = re.findall('"director":.*?]', content)[0]
    director_cont = re.findall('"name": ".*?"', director_cont)

    for i in range(len(actor_cont)):
        actor = actor_cont[i].lstrip('name": "').rstrip('"')
        for j in range(len(director_cont)):
            director = director_cont[j].lstrip('"name": "').rstrip('"')
            start_id.append(getID('actor', actor))
            end_id.append(getID('director', director))


# 用來存放關係節點ID的列表
start_id = []
end_id = []

# 循環查找每個頁面(即contents文件夾中下載下來的頁面),找出對應關係(acted_in)
for i in range(250):
    with open('contents/' + str(i) + '.txt', mode='r', encoding='utf8') as f:
        content = f.read().replace('\n', "")  # 要去掉換行符
    save_acted_in(content)
save_relation(start_id, end_id, 'acted_in')
print('[+] save acted_in finished!!!!!!!!!!!!!!!!!')

start_id.clear()
end_id.clear()
# 循環查找每個頁面(即contents文件夾中下載下來的頁面),找出對應關係(directed)
for i in range(250):
    with open('contents/' + str(i) + '.txt', mode='r', encoding='utf8') as f:
        content = f.read().replace('\n', "")  # 要去掉換行符
    save_directed(content)
save_relation(start_id, end_id, 'directed')
print('[+] save directed finished!!!!!!!!!!!!!!!!!')

start_id.clear()
end_id.clear()
# 循環查找每個頁面(即contents文件夾中下載下來的頁面),找出對應關係(belong_to)
for i in range(250):
    with open('contents/' + str(i) + '.txt', mode='r', encoding='utf8') as f:
        content = f.read().replace('\n', "")  # 要去掉換行符
    save_belongto(content)
save_relation(start_id, end_id, 'belong_to')
print('[+] save belong_to finished!!!!!!!!!!!!!!!!!')

start_id.clear()
end_id.clear()
# 循環查找每個頁面(即contents文件夾中下載下來的頁面),找出對應關係(cooperation)
for i in range(250):
    with open('contents/' + str(i) + '.txt', mode='r', encoding='utf8') as f:
        content = f.read().replace('\n', "")  # 要去掉換行符
    save_cooperation(content)
save_relation(start_id, end_id, 'cooperation')
print('[+] save cooperation finished!!!!!!!!!!!!!!!!!')

4. 使用neo4j創建知識圖譜

我將neo4j安裝在kail(Linux)上,WIndows下類似,使用如下命令導入我們爬取的csv文件,包括結點(使用–nodes)和關係(使用–relationships)

./neo4j-admin import --mode=csv --database=movies.db --nodes /usr/local/demo/actor.csv --nodes /usr/local/demo/director.csv --nodes /usr/local/demo/film_name.csv --nodes /usr/local/demo/type.csv --relationships /usr/local/demo/acted_in.csv --relationships /usr/local/demo/directed.csv --relationships /usr/local/demo/belong_to.csv --relationships /usr/local/demo/cooperation.csv

命令說明如下:(需要在neo4j文件夾下的bin目錄才能這樣執行)
./neo4j-admin:是導入的腳本
import :導入操作
–mode=csv:導入csv格式文件
–database=movies.db:導入的數據庫(默認graph.db),若寫成movies.db需要修改配置文件(在neo4j文件夾下的conf/neo4j.conf
在這裏插入圖片描述
-nodes /usr/local/demo/actor.csv --nodes /usr/local/demo/director.csv:導入結點,後面爲結點文件路徑
–relationships /usr/local/demo/belong_to.csv --relationships /usr/local/demo/cooperation.csv:導入關係,後面爲關係文件路徑


插入結果圖如下:
在這裏插入圖片描述
在這裏插入圖片描述


neo4j部分結果圖如下:
在這裏插入圖片描述

發佈了31 篇原創文章 · 獲贊 36 · 訪問量 177萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章