我想演示如何将Stack Overflow快速导入到Neo4j中。之后,您就可以通过查询图表以获取更多信息,然后可以在该数据集上构建应用程序。如果你愿意,我们有一个运行着的(只读)Neo4j服务器,其数据在这里提供。
我想先说的是:祝贺Stack Overflow,因为它非常棒和服务了许多人。他们刚刚宣布,在他们的网站上有超过一千万个编程问题被回答。(#SOreadytohelp成为了Twitter上的一个标签,类似于微博话题)
如果没有Stack Overflow,围绕Neo4j的许多问题可能永远不会被问到和回答。我很高兴我们开始摆脱Google Groups。
Stack Overflow上的Neo4j社区增长很快,问题量也很大。
将数百万Stack Overflow问题,用户,答案和意见导入Neo4j是我的一个目标。让我无法集中注意做这件事的原因是,我还要回答社区板块上8,200多个Neo4j问题。
两个星期前,Damien在Linkurious通过Slack channel联系了我。他询问了Neo4j的导入性能,以将整个Stack Exchange数据转储到Neo4j。
经过快速讨论后,我建议他使用Neo4j的CSV导入工具,因为转储只包含以XML格式的关系表,所以非常适合此任务。
所以Damien编写了一个小的Python脚本从XML中提取CSV文件,并使用必要的头文件neo4j-import工具完成了从巨大表格中创建图表的繁重工作。您可以在这里找到脚本和说明。
导入较小的Stack Exchange社区数据只需要几秒钟。令人惊讶的是,带有用户,问题和答案的完整Stack Overflow需要80分钟时间才能转为CSV,然后只需3分钟即可在带有SSD的普通笔记本电脑上导入Neo4j。
以下是我们的步骤:
首先,我们将Stack Overflow社区Internet归档文件(总共11 GB)下载到一个目录中:
如果需要,其他数据可以单独导入:
for i in * . 7z ; do 7za - y - oextracted x $i ; 完成
这将文件解压缩到
extracted
一个目录,需要20分钟,需要66GB磁盘空间。
下一步是克隆Damien的GitHub:
git clone https://github.com/mdamien/stackoverflow - neo4j
注意:该命令使用Python 3,因此您必须安装
xmltodict
sudo apt - get install python3 - setuptools
easy_install3 xmltodict
之后,我们进行XML到CSV的转换。
python3 to_csv . py extracted
转换在我的系统上运行了80分钟,9.5GB的CSV文件被压缩到3.4G。
这是导入到Neo4j中的数据结构。CSV文件的标题行显示不同的属性。
节点:
posts . csv
postId : ID ( Post ) , title , postType : INT , createdAt , score : INT , views : INT ,
answers : INT , comments : INT , favorites : INT , updatedAt , body
users . csv userId : ID ( User ) , name , reputation : INT , createdAt , accessedAt , url , location ,
views : INT , upvotes : INT , downvotes : INT , age : INT , accountId : INT
tags . csv
tagId : ID ( Tag ) , count : INT , wikiPostId : INT
关系:
posts_answers . csv : ANSWER - > : START_ID ( Post ) , : END_ID ( Post )
posts_rel . csv : PARENT_OF - > : START_ID ( Post ) , : END_ID ( Post )
tags_posts_rel . csv : HAS_TAG - > : START_ID ( Post ) , : END_ID (Tag )
users_posts_rel . csv : POSTED - > : START_ID ( User ) , : END_ID ( Post )
然后我们使用了Neo4j导入工具
neo/bin/neo4j-import
摄取文章,用户,标签及其之间的关系。
. . / neo / bin / neo4j - import \
-- into . . / neo / data / graph . db \
-- id - type string \
-- nodes : Post csvs / posts . csv \
-- nodes : User csvs / users . csv \
-- nodes : Tag csvs / tags . csv \
--relationships : PARENT_OF csvs / posts_rel . csv \
-- relationships : ANSWER csvs / posts_answers . csv \
-- relationships : HAS_TAG csvs / tags_posts_rel . csv \
-- relationships : POSTED csvs / users_posts_rel . csv
实际导入只需要3分钟,创建了一个18 GB的图形库。
IMPORT DONE in 3m 48s 579ms . Imported :
31138559 nodes
77930024 relationships
260665346 properties
然后我们想要调整Neo4j的配置
conf/neo4j.properties
增加
dbms.pagecache.memory
选项为10G。编辑
conf/neo4j-wrapper.conf
提供更多的堆空间,如4G或8G。
然后我们开始使用Neo4j服务器
../neo/bin/neo4j start
然后,我们可以选择直接在Neo4j的服务器UI或命令行中运行查询
../neo/bin/neo4j-shell
它连接到正在运行的服务器。
这里是我们在共有多少数据:
neo4j - sh ( ? ) $ match ( n ) return head ( labels ( n ) ) as label , count ( * ) ;
+ -- -- -- -- -- -- -- -- -- - +
| label | count ( * ) |
+ -- -- -- -- -- -- -- -- --- +
| "Tag" | 41719 |
| "User" | 4551115 |
| "Post" | 26545725 |
+ -- -- -- -- -- -- -- -- -- - +
3 rows
接下来,我们创建了一些索引和约束(constraints)供以后使用:
create index on : Post ( title ) ;
create index on : Post ( createdAt ) ;
create index on : Post ( score ) ;
create index on : Post ( views ) ;
create index on : Post ( favorites ) ;
create index on : Post ( answers ) ;
create index on : Post ( score ) ;
create index on : User ( name ) ;
create index on : User ( createdAt ) ;
create index on : User ( reputation ) ;
create index on : User ( age ) ;
create index on : Tag ( count ) ;
create constraint on ( t : Tag ) assert t . tagId is unique ;
create constraint on ( u : User ) assert u . userId is unique ;
create constraint on ( p : Post ) assert p . postId is unique ;
然后我们等待索引创建完成。
schema await
请注意:Neo4j作为图形数据库最初并不是为这些全局聚合查询而构建的。这就是为什么响应不是即时的。
以下只是我们使用Cypher查询从Stack Overflow数据中收集到的一些信息:
match ( u : User )
with u , size ( ( u ) - [ : POSTED ] - > ( ) ) as posts order by posts desc limit 10
return u . name , posts ;
+ -- -- -- -- -- -- -- -- -- -- -- -- -- - +
| u . name | posts |
+ -- -- -- -- -- -- -- -- -- -- -- -- -- - +
| "Jon Skeet" | 32174 |
| "Gordon Linoff" | 20989 |
| " Darin Dimitrov" | 20871 |
| "BalusC" | 16579 |
| "CommonsWare" | 15493 |
| "anubhava" | 15207 |
| "Hans Passant" | 15156 |
| "Martijn Pieters" | 14167 |
| "SLaks" | 14118 |
| "Marc Gravell" | 13400 |
+ -- -- -- -- -- -- -- -- -- -- -- -- -- - +
10 rows
7342 ms
他似乎不问问题,只回答。
match ( u : User ) - [ : POSTED ] - > ( ) - [ : HAS_TAG ] - > ( t : Tag )
where u . name = "Jon Skeet"
return t , count ( * ) as posts order by posts desc limit 5 ;
+ -- -- -- -- -- -- -- -- -- ---- -- -- -- -- -- -- -- -- -- -- -- -- -- +
| t | posts |
+ -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +
| Node [ 31096861 ] { tagId :"c#" } | 14 |
| Node [ 31096855 ] { tagId : ".net" } | 7 |
| Node [ 31101268 ] { tagId : ".net-4.0" }| 4 |
| Node [ 31118174 ] { tagId : " c#-4.0" } | 4 |
| Node [ 31096911 ] { tagId : "asp.net" } | 3 |
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +
10 rows
36 ms
match ( u : User ) - [ : POSTED ] - > ( ) - [ : HAS_TAG ] - > ( t : Tag )
where u . name = "BalusC"
return t . tagId , count ( * ) as posts order by posts desc limit 5 ;
+ -- -- -- -- -- -- -- -- -- -- -- -- +
| t . tagId | posts |
+ -- -- -- -- -- -- -- -- -- -- -- -- +
| "java" | 5 |
| "jsf" | 3 |
| "managed-bean" | 2 |
| "eclipse" | 2 |
| "cdi" | 2 |
+ -- -- -- -- -- -- -- -- -- -- -- -- +
5 rows
23 ms
MATCH path = allShortestPaths (
( u : User { name :“Darin Dimitrov” } )- [ * ] - ( me : User { name :“Michael Hunger” } ))
RETURN path ;
MATCH ( u : User ) - [ : POSTED ] - > ( answer ) < - [ : PARENT_OF ] - ( ) - [ : HAS_TAG ] - ( : Tag { tagId : "neo4j" } )
WHERE u . name like "Mark % "
RETURN u . name , u . reputation , u .location , count ( distinct answer ) AS answers
ORDER BY answers DESC ;
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- -- -- +
| u . name | u . reputation | u . location | answers |
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- -- -- +
| "Mark Needham" | 1352 | "United Kingdom" | 36 |
| "Mark Leighton Fisher" | 4065 | "Indianapolis, IN" | 3 |
| "Mark Byers" | 377313 | "Denmark" | 2 |
| "Mark Whitfield" | 899 | < null > | 1 |
| "Mark Wojciechowicz" | 1473 | < null > | 1 |
| "Mark Hughes" | 586 | "London, UK" | 1 |
| "Mark Mandel" | 859 | "Melbourne, Australia" | 1 |
| "Mark Jackson" | 56 | "Atlanta, GA" | 1 |
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +
8 rows
38 ms
match ( t : Tag )
with t order by t . count desc limit 5
return t . tagId , t . count ;
+ -- -- -- -- -- -- -- -- -- -- -- -- +
| t . tagId | t . count |
+ -- -- -- -- -- -- -- -- -- ---- -- +
| "javascript" | 917772 |
| "java" | 907289 |
| "c#" | 833458 |
| "php" | 791534 |
| "android" | 710585 |
+ -- -- -- -- - - -- -- -- -- -- -- -- +
5 rows
30 ms
match ( t : Tag { tagId : "javascript" } ) < - [ : HAS_TAG ] - ( ) - [ : HAS_TAG ] - > ( other : Tag )
WITH other , count ( * ) as freq order by freq desc limit 5
RETURN other . tagId , freq ;
+ -- -- -- -- ---- -- -- -- -- -- +
| other . tagId | freq |
+ -- -- -- -- -- -- -- -- -- -- -- +
| "jquery" | 318868 |
| "html" | 165725 |
| "css" | 76259 |
| "php" | 65615 |
| "ajax" | 52080 |
+ -- ---- -- -- -- -- -- -- -- -- +
5 rows
感谢所有回答Neo4j问题的人!
match ( t : Tag { tagId : "neo4j" } ) < - [ : HAS_TAG ] - ( )
- [ : PARENT_OF ] - > ( ) < - [ : POSTED ] - ( u : User )
WITH u , count ( * ) as freq order by freq desc limit 10
RETURN u . name ,freq ;
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +
| u . name | freq |
+ -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- - +
| "Michael Hunger" | 1352 |
| "Stefan Armbruster" | 760 |
| "Peter Neubauer" | 308 |
| "Wes Freeman" | 277 |
| "FrobberOfBits" | 277 |
| "cybersam" | 277 |
| "Luanne" | 235 |
| "Christophe Willemsen" | 190 |
| "Brian Underwood" | 169 |
| "jjaderberg" | 161 |
+ -- -- -- -- -- -- -- -- -- -- ---- -- -- -- - +
10 rows
45 ms
MATCH ( neo : Tag { tagId : "neo4j" } ) < - [ : HAS_TAG ] - ( )
- [ : PARENT_OF ] - > ( ) < - [ : POSTED ] - ( u : User )
WITH neo , u , count ( * ) as freq order by freq desc limit 10
MATCH ( u) - [ : POSTED ] - > ( ) < - [ : PARENT_OF ] - ( p ) - [ : HAS_TAG ] - > ( other : Tag )
WHERE NOT ( p ) - [ : HAS_TAG ] - > ( neo )
WITH u , other , count ( * ) as freq2 order by freq2 desc
RETURN u . name , collect ( distinct other . tagId ) [ 1 . . 5 ] as tags ;
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +
| u . name | tags |
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +
| "cybersam" | [ "java" , "javascript" ,"node.js" , "arrays" ] |
| "Luanne" | [ "spring-data-neo4j" , "java" , "cypher" , "spring" ] |
| "Wes Freeman" | [ "go" , " node.js" , "java" , "php" ] |
| "Peter Neubauer" | [ "graph" , "nosql" , "data-structures" , "java"] |
| "Brian Underwood" | [ "ruby-on-rails", "neo4j.rb" , "ruby-on-rails-3" , "activerecord" ] |
| "Michael Hunger" | [ "spring-data-neo4j" , "nosql" , "cypher" , "graph-databases" ] |
| "Christophe Willemsen" | [ "php" , "forms" , "doctrine2" , "sonata" ] |
| "Stefan Armbruster" | [ "groovy" , "intellij-idea" , "tomcat" , "grails-plugin" ] |
| "FrobberOfBits" | [ "python" , "xsd" , "xml" , "django" ] |
| "jjaderberg" | [ "vim" , "logging" , "python" , "maven"] |
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- ---- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +
10 rows
84 ms
请注意,上面的Cypher查询包含14个SQL连接的等效项。
MATCH ( t : Tag { tagId :'neo4j' } )< - [ : HAS_TAG ] - (: Post )< - [ : POSTED ] - ( u : User )
RETURN u 。名称,数(* ) 作为计数
ORDER BY count DESC LIMIT 10 ;
+ - - - - - - - - - - - - +
| c 。名称 | count |
+ - - - - - - - - - - - - +
| “LDB” | 39 |
| “deemeetree” | 39 |
| “alexanoid” | 38 |
| “MonkeyBonkey” | 35 |
| “Badmiral” | 35 |
| “Mik378” | 27 |
| “Kiran” | 25 |
| “红魔” | 24 |
| “raHul” | 23 |
| “Sovos” | 23 |
+ - - - - - - - - - - - - +
10 rows
42 ms
MATCH ( t : Tag { tagId : 'neo4j' } ) < - [ : HAS_TAG ] - ( : Post ) < - [ : POSTED ] - ( u : User )
RETURN u . name , count ( * ) as count
ORDER BY count DESC LIMIT 10 ;
+ -- -- -- -- -- -- -- -- -- -- -- -- +
| c . name | count |
+ -- -- -- -- -- -- -- -- -- -- -- -- +
| "LDB" | 39 |
| "deemeetree" | 39 |
| "alexanoid" | 38 |
| "MonkeyBonkey" | 35 |
| "Badmiral" | 35 |
| "Mik378" | 27 |
| "Kiran" | 25 |
| "red-devil" | 24 |
| "raHul" | 23 |
| "Sovos" | 23 |
+ -- -- - - -- -- -- -- -- -- -- -- -- +
10 rows
42 ms
这个全局图形查询需要一点时间,因为它关系到数据库中的2亿条路径,大约60秒后它会返回。
如果您只想在4.5M用户的子集上执行此操作,则可以添加过滤条件,例如在reputation上。
MATCH ( u : User ) WHERE u . reputation > 20000
MATCH ( u ) - [ : POSTED ] - > ( question ) - [ : ANSWER ] - > ( answer ) < - [ : POSTED ] - ( u )
WITH u , count ( distinct question ) AS questions
ORDER BY questions DESC LIMIT 5
RETURN u . name , u . reputation , questions ;
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +
| u . name | u . reputation | questions |
+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ---- -- -- -- - +
| "Stefan Kendall" | 31622 | 133 |
| "prosseek" | 31411 | 114 |
| "Cheeso" | 100779 | 107 |
| "Chase Florell" | 21207 | 99 |
| " Shimmy" | 29175 | 96 |
+ -- -- -- -- -- -- -- -- -- -- -- ---- -- -- -- -- -- -- -- -- -- - +
5 rows
10 seconds
我们很高兴为您提供Stack Overflow的图形数据库:
如果您想了解其他方式来导入或可视化Neo4j中的Stack Overflow问题,请查看以下博客文章:
再次感谢所有发布和回答Neo4j问题的人。你是那些让Neo4j社区成长的人,如果没有你,本文的乐趣将大打折扣。
回到Stack Overflow的1000万个问题,感谢您使用关于Neo4j和Cypher的#Soreadytohelp话题。
如果你发现这个数据集的其他有趣的问题和答案。只需发送电子邮件至content#neo4j.com。