3分钟将10M Stack Overflow导入Neo4j

December 17, 2023
测试
测试
测试
测试
43 分钟阅读

我想演示如何将Stack Overflow快速导入到Neo4j中。之后,您就可以通过查询图表以获取更多信息,然后可以在该数据集上构建应用程序。如果你愿意,我们有一个运行着的(只读)Neo4j服务器,其数据在这里提供。

我想先说的是:祝贺Stack Overflow,因为它非常棒和服务了许多人。他们刚刚宣布,在他们的网站上有超过一千万个编程问题被回答。(#SOreadytohelp成为了Twitter上的一个标签,类似于微博话题)

如果没有Stack Overflow,围绕Neo4j的许多问题可能永远不会被问到和回答。我很高兴我们开始摆脱Google Groups。

Stack Overflow上的Neo4j社区增长很快,问题量也很大。

图片示意
图片示意

将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 Exchange转储文件

首先,我们将Stack Overflow社区Internet归档文件(总共11 GB)下载到一个目录中:

  • 7.3G stackoverflow.com-Posts.7z
  • 576K stackoverflow.com-Tags.7z
  • 154M stackoverflow.com-Users.7z

如果需要,其他数据可以单独导入:

  • 91z stackoverflow.com-Badges.7z
  • 2.0G stackoverflow.com-Comments.7z
  • 36M stackoverflow.com-PostLinks.7z
  • 501M stackoverflow.com-Votes.7z

解压.7z文件

for i in  * . 7z ;  do 7za - y - oextracted x $i ; 完成

这将文件解压缩到

extracted

一个目录,需要20分钟,需要66GB磁盘空间。

克隆Damien的GitHub存储库

下一步是克隆Damien的GitHub:

git clone https://github.com/mdamien/stackoverflow - neo4j

注意:该命令使用Python 3,因此您必须安装

xmltodict

sudo apt - get install python3 - setuptools
easy_install3 xmltodict

运行XML-to-CSV转换程序

之后,我们进行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

然后我们使用了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配置

然后我们想要调整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进行深入了解

以下只是我们使用Cypher查询从Stack Overflow数据中收集到的一些信息:

前10名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

Jon Skeet用于提问的前5个tags

他似乎不问问题,只回答。

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

BalusC回答的前5个tags

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

我和Darin Dimirtov的关系图标

MATCH path =  allShortestPaths (
     ( u : User { name :“Darin Dimitrov” } )- [ * ] - ( me : User { name :“Michael Hunger” } )) 
RETURN path ;
Neo5j的可视化结果
Neo5j的可视化结果

马克是回答Neo4j相关问题最多的人

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
以图形呈现的前20条路径
以图形呈现的前20条路径

历史上前5名的tags

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

和JavaScript tag一起查询

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 tag回答者

感谢所有回答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连接的等效项。

在Linkurious Visualizer中呈现
在Linkurious Visualizer中呈现

关于Neo4j最多问题的人

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

Py2neo tag的最佳答案

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数据库转储为2.3-SNAPSHOT或2.2.4
  • 运行Neo4j服务器以探索数据(只读)
  • CSV文件

如果您想了解其他方式来导入或可视化Neo4j中的Stack Overflow问题,请查看以下博客文章:

  • 来自URL AS数据的LOAD JSON
  • 使用Neo4j让主数据管理变得有趣
  • 可视化Stack Overflow
  • Neo4J,R&Java关系
  • 请同时参阅Stack Overflow开发者调查。这非常有趣。

再次感谢所有发布和回答Neo4j问题的人。你是那些让Neo4j社区成长的人,如果没有你,本文的乐趣将大打折扣。

回到Stack Overflow的1000万个问题,感谢您使用关于Neo4j和Cypher的#Soreadytohelp话题。

如果你发现这个数据集的其他有趣的问题和答案。只需发送电子邮件至content#neo4j.com。

继续阅读

更多来自我们博客的帖子

如何安装 BuddyPress
由 测试 December 17, 2023
经过差不多一年的开发,BuddyPress 这个基于 WordPress Mu 的 SNS 插件正式版终于发布了。BuddyPress...
阅读更多
Filter如何工作
由 测试 December 17, 2023
在 web.xml...
阅读更多
如何理解CGAffineTransform
由 测试 December 17, 2023
CGAffineTransform A structure for holding an affine transformation matrix. ...
阅读更多