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/%E4%B8%AD%E5%9B%BD%E7%94%B5%E5%BD%B1%E7%A5%A8%E6%88%BF/4101787?fr=aladdin,页面如下:

中国电影票房_百度百科

  我们制作爬虫,将这个表格爬取下来,并将表格的第一行(字段名称)作为电影的相关信息,然后储存为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/%E6%B5%81%E6%B5%AA%E5%9C%B0%E7%90%83/16278407,页面如下:

流浪地球

我们只需要爬取每部电影的主演就够了,也就是上图中的红色部分,实现的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/neo4j/neo4j_features_advantages.html
   利用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/Neo4j_movie_demo

总结

  感谢读者不厌其烦地看到了这里,看完了这篇“又臭又长”的文章,好在图比较多,应该能稍微减轻点阅读的压力。
  再说说该项目的不足之处:那就是Neo4j的操作语法展示的比较少,约等于没有,这主要是笔者也是初入门,不熟。如果后续对Neo4j的操作语法CQL熟练了,我们就能能到更多有趣的结果,而不是这么一句简单的分析(有敷衍的嫌疑)。
  最后,对此项目感兴趣的读者,可以移步该项目的github地址:https://github.com/percent4/Neo4j_movie_demo

注意:不妨了解下笔者的微信公众号: Python爬虫与算法(微信号为:easy_web_scrape), 欢迎大家关注~

参考文献

  1. Neo4j_百度百科:https://baike.baidu.com/item/Neo4j/9952114?fr=aladdin
  2. neo4j教程:https://www.w3cschool.cn/neo4j/neo4j_features_advantages.html
  3. The Py2neo v3 Handbook: https://py2neo.org/v3/index.html
  4. Neo4j简介及Py2Neo的用法: https://cuiqingcai.com/4778.html
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容