Neo4j入門之中國電影票房排行淺析

什麼是Neo4j?

  Neo4j是一個高性能的NoSQL圖形數據庫(Graph Database),它將結構化數據存儲在網絡上而不是表中。它是一個嵌入式的、基於磁盤的、具備完全的事務特性的Java持久化引擎,但是它將結構化數據存儲在網絡(從數學角度叫做圖)上而不是表中。Neo4j也可以被看作是一個高性能的圖引擎,該引擎具有成熟數據庫的所有特性。
  作爲圖形數據庫,Neo4j最讓人驚喜的功能就是它可以直觀地展示圖,也就是節點與節點之間的關係,當然,它還有其它的優勢,比如:

  • 很容易表示連接的數據;
  • 檢索/遍歷/導航更多的連接數據是非常容易和快速的;
  • 能輕鬆地表示半結構化數據;
  • Neo4j CQL查詢語言命令類似於SQL,可讀性好,容易學習;
  • 使用簡單而強大的數據模型;
  • 不需要複雜的連接來檢索連接的/相關的數據。

  在本文中,筆者希望能夠通過一個簡單的例子來展示Neo4j的使用以及它的強大之處,這無疑更適合於初學者,因爲筆者也是剛入門。
  以下,筆者並不會過多地介紹Neo4j的操作,只是希望讀者能對Neo4j的功能有直觀的感受,至於教程之類的,可以參考文章最後的參考文獻。
  下面,讓我們進入本次的Neo4j之旅~

項目展示

  由於《流浪地球》的大熱以及筆者對此的欣賞,因此,此次的項目爲分析中國電影票房排行。我們的數據來自於百度百科,用爬蟲得到我們需要的數據,主要是電影的相關信息,如排名,票房,上映日期等,以及電影的主演。將數據儲存爲CSV文件,並導入至Neo4j,最後得到電影的簡單分析及可視化。
  整個項目主要是以下三步:

  • 數據獲取:利用爬蟲實現;
  • 數據導入:利用Py2neo實現;
  • 數據展示:利用Neo4j實現。

其中,Py2neo爲Neo4j的Python接口。
  整個項目的結構如下圖:

電影票房項目

  接下來,筆者將詳細地介紹每一步的操作及代碼,let's go ~

數據獲取

  數據的獲取始終是一個重要的問題,好在我們有爬蟲這個工具。爲了能夠展示中國電影票房排行中的電影信息以及演員與電影的關係,首先的重要一步就是獲取我們需要的需要。
  我們需要兩份數據。第一份數據,就是中國電影票房排行數據,網址爲:https://baike.baidu.com/item/...,頁面如下:

中國電影票房_百度百科

  我們製作爬蟲,將這個表格爬取下來,並將表格的第一行(字段名稱)作爲電影的相關信息,然後儲存爲movies.csv。整個過程的Python代碼(movie.py)如下:(因爲這是公開數據,這個爬蟲是合理的。)

# -*- coding: utf-8 -*-

import requests
import pandas as pd
from bs4 import BeautifulSoup

url = "https://baike.baidu.com/item/%E4%B8%AD%E5%9B%BD%E7%94%B5%E5%BD%B1%E7%A5%A8%E6%88%BF/4101787"
# 請求頭部
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
r = requests.get(url, headers=headers)
soup = BeautifulSoup(r.text.encode('ISO-8859-1'),'lxml')
table = soup.find('table')('tr')

movies = []
for line in table[1:]:
   movie = {'rank': int(line('td')[0].text),
                 'src': line('td')[1]('a')[0]['href'],
                 'name': line('td')[1].text,
                 'box_office': line('td')[2].text,
                 'avg_price': int(line('td')[3].text),
                 'avg_people': int(line('td')[4].text),
                 'begin_date': line('td')[5].text.strip(),
                 }
   # print(movie)
   movies.append(movie)

# print(movies)

df = pd.DataFrame({'rank': [movie['rank'] for movie in movies],
                  'src': [movie['src'] for movie in movies],
                  'name': [movie['name'] for movie in movies],
                  'box_office': [movie['box_office'] for movie in movies],
                  'avg_price': [movie['avg_price'] for movie in movies],
                  'avg_people': [movie['avg_people'] for movie in movies],
                  'begin_date': [movie['begin_date'] for movie in movies]
                  })
# print(df.head())
df.to_csv(r'./movies.csv', index=False)

  movies.csv中的數據如下:

movies.csv

  OK,第二份數據,每部電影(共20部)的主演,以《流浪地球》爲例,網址爲:https://baike.baidu.com/item/...,頁面如下:

流浪地球

我們只需要爬取每部電影的主演就夠了,也就是上圖中的紅色部分,實現的Python代碼(actor.py)如下:

# -*- coding: utf-8 -*-

import requests
import pandas as pd
from bs4 import BeautifulSoup

def get_actors(src):
    url = "https://baike.baidu.com"+src
    # 請求頭部
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
    r = requests.get(url, headers=headers)
    soup = BeautifulSoup(r.text.encode('ISO-8859-1'),'lxml')
    names = soup.find_all('dt', class_="basicInfo-item name")
    values = soup.find_all('dd', class_="basicInfo-item value")

    actors = []
    for name, value in zip(names, values):
        # print(name.text)
        if '主' in name.text and '演' in name.text:
            # print(name.text.replace('    ', ''), value.text)
            actors = value.text.strip().split(',')
            actors = [actor.strip().replace('\xa0', '').replace('\n[6]', '') for actor in actors if actor]
    # print(actors)
    return ','.join(actors)

df = pd.read_csv('./movies.csv')

actors_list = []
for name, src in zip(list(df['name']), list(df['src'])):

    actors = get_actors(src)
    # print(name, actors)
    actors_list.append(actors)

new_df = pd.DataFrame({'actors': actors_list})
new_df['name'] = df['name']
# print(new_df)
new_df.to_csv(r'./actors.csv', index=False)

  生成的actor.csv數據如下:

actor.csv

  OK,數據收集的任務就到此完成了,有了爬蟲,輕鬆搞定數據難題。

導入數據

   接着,我們需要用到剛纔儲存的movies.csv和actor.csv,利用Py2neo來將數據導入至Neo4j中。
   首先,需要確保你的電腦已安裝好Neo4j,Py2neo,並開啓了Neo4j服務,具體的安裝流程可參考網址:https://www.w3cschool.cn/neo4...
   利用Py2neo,我們就可以用Python輕鬆地實現將數據導入至Neo4j,實現的功能爲:創建電影節點以及演員節點,並創建兩者之間的關係,關係名稱爲“ACT_IN”。實現的Python代碼()如下:

# -*- coding: utf-8 -*-

import pandas as pd
from py2neo import Graph, Node, Relationship, NodeMatcher

# 讀取csv文件
movies_df = pd.read_csv(r'./movies.csv')
actors_df = pd.read_csv(r'./actors.csv')

# 連接Neo4j服務
graph = Graph(host="localhost://7474", auth=("neo4j", "jc147369"))

# 創建電影節
for i in range(movies_df.shape[0]):
    rank = str(movies_df.iloc[i, :]['rank'])
    name = movies_df.iloc[i, :]['name']
    box_office = movies_df.iloc[i, :]['box_office']
    avg_price = str(movies_df.iloc[i, :]['avg_price'])
    avg_people = str(movies_df.iloc[i, :]['avg_people'])
    begin_date = movies_df.iloc[i, :]['begin_date']
    
    node = Node("Movie", 
                name=name,
                rank=rank,
                box_office=box_office,
                avg_price=avg_price,
                avg_people=avg_people,
                begin_date=begin_date
                )
    # print(movies_df.iloc[i, :]['rank'])
    graph.create(node)

print('create movie nodes successfully!')

# 創建演員節點
all_actors = set()
for i in range(actors_df.shape[0]):
    actor_list = actors_df.iloc[i, :]['actors'].split(',')
    for actor in actor_list:
        all_actors.add(actor)
 
for actor in all_actors:
    node = Node("Actor", name=actor)
    graph.create(node)

print('create actor nodes successfully!')

# 創建演員——電影關係
for i in range(actors_df.shape[0]):
    name = actors_df.iloc[i, :]['name']
    matcher = NodeMatcher(graph)
    movie_node = matcher.match("Movie", name=name).first()
    actors = actors_df.iloc[i, :]['actors'].split(',')
    # print(name, actors)
    for actor in actors:
        actor_node = matcher.match("Actor", name=actor).first()
        relationship = Relationship(actor_node, 'ACT_IN', movie_node)
        graph.create(relationship)

print('create relationships successfully!')
print('You can check Neo4j now!')

  只要你的電腦已安裝好Neo4j,Py2neo,並開啓了Neo4j服務,不出意外,那麼你的Neo4j已經默默地儲存了這些數據,而它們將會給出帶來巨大的驚喜:天吶,這竟然是個數據庫!
  在瀏覽器中輸入“localhost:7474”,並點擊左上方的數據庫圖標,就能看到下圖:

數據儲存好了嗎?

  可以看到,在Neo4j中,我們創建了142個節點,分爲兩類:Movie和Actor,以及136對關係,關係名稱爲ACT_IN. 當然,這些都是枯燥的,那麼我們來看看數據展示這步吧,它會給我們帶來什麼驚喜?

數據展示

  好不容易到了數據展示這一步,之前的努力都不會白費!
  在Neo4j的前端頁面(也就是網址:http://localhost:7474)中的命令行中輸入命令:

MATCH (Movie)
RETURN (Movie);

運行命令後,很快就能得到整個圖(包含電影節點、演員節點以及關係)的可視化展示,由於圖像過大,不能看清細節,因此,就局部放大來看,同時得到一些簡單的分析。
  首先是圖一,吳京主演的電影。

圖一

在中國電影票房排行榜的前20名中,吳京主演了《戰狼2》與《流浪地球》,且兩者沒有其他更多的相同主演。
  接着是圖二,沈騰主演的電影。

圖二

在中國電影票房排行榜的前20名中,沈騰主演了《西虹市首富》、《瘋狂的外星人》以及《羞羞的鐵拳》,這顯示了沈騰的票房號召力(他也是筆者喜歡的喜劇演員),不過開心麻花團隊的其他成員在這三部電影的拍攝中應該見不到面。
  接着是圖三,《捉妖記》及《捉妖記2》。

圖三

捉妖記系列電影無疑是成功的,兩部電影都進了票房的前20,他們的共同主演就多了,有曾志偉,吳君如,井柏然,白百何。
  接着是圖四,主要是看看Neo4j幫我們挖掘了哪些潛在的關係。

圖四

從《唐人街探案2》到《捉妖記2》,這個不算長的鏈條給了我們一些驚喜,原來,劉昊然可以通過尚語賢再通過曾志偉認識李宇春,一個very interesting的分析。當然,這個是筆者的分析,他倆到底認不認識筆者是不知道滴~

  分析到此結束,如果讀者想看原圖,可以參看該項目的github地址:https://github.com/percent4/N...

總結

  感謝讀者不厭其煩地看到了這裏,看完了這篇“又臭又長”的文章,好在圖比較多,應該能稍微減輕點閱讀的壓力。
  再說說該項目的不足之處:那就是Neo4j的操作語法展示的比較少,約等於沒有,這主要是筆者也是初入門,不熟。如果後續對Neo4j的操作語法CQL熟練了,我們就能能到更多有趣的結果,而不是這麼一句簡單的分析(有敷衍的嫌疑)。
  最後,對此項目感興趣的讀者,可以移步該項目的github地址:https://github.com/percent4/N...

注意:不妨瞭解下筆者的微信公衆號: Python爬蟲與算法(微信號爲:easy_web_scrape), 歡迎大家關注~

參考文獻

  1. Neo4j_百度百科:https://baike.baidu.com/item/...
  2. neo4j教程:https://www.w3cschool.cn/neo4...
  3. The Py2neo v3 Handbook: https://py2neo.org/v3/index.html
  4. Neo4j簡介及Py2Neo的用法: https://cuiqingcai.com/4778.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章