文本分析了4000万条Stack Overflow讨论帖,这些是程序员最推荐的编程书(附代码)

大数据 数据分析 前端
程序员们都看什么书?他们会向别人推荐哪些书?本文作者分析了Stack Overflow上的4000万条问答,找出了程序员们最常讨论的书,同时非常慷慨地公开了数据分析代码。让我们来看看作者是怎么说的吧。

程序员们都看什么书?他们会向别人推荐哪些书?

本文作者分析了Stack Overflow上的4000万条问答,找出了程序员们最常讨论的书,同时非常慷慨地公开了数据分析代码。让我们来看看作者是怎么说的吧。

寻找下一本值得读的编程书是一件很难,而且有风险的事情。

作为一个开发者,你的时间是很宝贵的,而看书会花费大量的时间。这时间其实你本可以用来去编程,或者是去休息,但你却决定将其用来读书以提高自己的能力。

所以,你应该选择读哪本书呢?我和同事们经常讨论看书的问题,我发现我们对于书的看法相差很远。

幸运的是,Stack Exchange(程序员最常用的IT技术问答网站Stack Overflow的母公司)发布了他们的问答数据。用这些数据,我找出了Stack Overflow上4000万条问答里,被讨论最多的编程书籍,一共5720本。

在这篇文章里,我将详细介绍数据获取及分析过程,附有代码。

文本分析了4000万条Stack Overflow讨论帖,这些是程序员最推荐的编程书(附代码)

我开发了dev-books.com来展示书籍推荐排序

文本分析了4000万条Stack Overflow讨论帖,这些是程序员最推荐的编程书(附代码)

让我们放大看看这些最受欢迎的书

 

  • “被推荐次数最多的书是Working Effectively with Legacy Code《修改代码的艺术》,其次是Design Pattern: Elements of Reusable Object-Oriented Software《设计模式:可复用面向对象软件的基础》。
  • 虽然它们的名字听起来枯燥无味,但内容的质量还是很高的。你可以在每种标签下将这些书依据推荐量排序,如JavaScript, C, Graphics等等。这显然不是书籍推荐的终极方案,但是如果你准备开始编程或者提升你的知识,这是一个很好的开端。”

——来自Lifehacker.com的评论

获取和输入数据

我从archive.org抓取了Stack Exchange的数据。(https://archive.org/details/stackexchange)

从最开始我就意识到用最常用的方式(如 myxml := pg_read_file(‘path/to/my_file.xml’))输入48GB的XML文件到一个新建立的数据库(PostgreSQL)是不可能的,因为我没有48GB的RAM在我的服务器上,所以我决定用SAX程序。

所有的值都被储存在这个标签之间,我用Python来提取这些值:

  1. def startElement(self, name, attributes):  
  2.  if name == ‘row’:  
  3.  self.cur.execute(“INSERT INTO posts (Id, Post_Type_Id, Parent_Id, Accepted_Answer_Id, Creation_Date, Score, View_Count, Body, Owner_User_Id, Last_Editor_User_Id, Last_Editor_Display_Name, Last_Edit_Date, Last_Activity_Date, Community_Owned_Date, Closed_Date, Title, Tags, Answer_Count, Comment_Count, Favorite_Count) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)”,  
  4.  (  
  5.  (attributes[‘Id’] if ‘Id’ in attributes else None),  
  6.  (attributes[‘PostTypeId’] if ‘PostTypeId’ in attributes else None),  
  7.  (attributes[‘ParentID’] if ‘ParentID’ in attributes else None),  
  8.  (attributes[‘AcceptedAnswerId’] if ‘AcceptedAnswerId’ in attributes else None),  
  9.  (attributes[‘CreationDate’] if ‘CreationDate’ in attributes else None),  
  10.  (attributes[‘Score’] if ‘Score’ in attributes else None),  
  11.  (attributes[‘ViewCount’] if ‘ViewCount’ in attributes else None),  
  12.  (attributes[‘Body’] if ‘Body’ in attributes else None),  
  13.  (attributes[‘OwnerUserId’] if ‘OwnerUserId’ in attributes else None),  
  14.  (attributes[‘LastEditorUserId’] if ‘LastEditorUserId’ in attributes else None),  
  15.  (attributes[‘LastEditorDisplayName’] if ‘LastEditorDisplayName’ in attributes else None),  
  16.  (attributes[‘LastEditDate’] if ‘LastEditDate’ in attributes else None),  
  17.  (attributes[‘LastActivityDate’] if ‘LastActivityDate’ in attributes else None),  
  18.  (attributes[‘CommunityOwnedDate’] if ‘CommunityOwnedDate’ in attributes else None),  
  19.  (attributes[‘ClosedDate’] if ‘ClosedDate’ in attributes else None),  
  20.  (attributes[‘Title’] if ‘Title’ in attributes else None),  
  21.  (attributes[‘Tags’] if ‘Tags’ in attributes else None),  
  22.  (attributes[‘AnswerCount’] if ‘AnswerCount’ in attributes else None),  
  23.  (attributes[‘CommentCount’] if ‘CommentCount’ in attributes else None),  
  24.  (attributes[‘FavoriteCount’] if ‘FavoriteCount’ in attributes else None)  
  25.  )  
  26.  ); 

在数据输入进行了三天之后(有将近一半的XML在这段时间内已经被导入了),我发现我犯了一个错误:我把“ParentId”写成了“ParentID”。

但这个时候,我不想再多等一周,所以把处理器从AMD E-350 (2 x 1.35GHz)换成了Intel G2020 (2 x 2.90GHz),但这并没能加速进度。

下一个决定——批量输入:

  1. class docHandler(xml.sax.ContentHandler):  
  2.  def __init__(self, cusor):  
  3.  self.cusor = cusor;  
  4.  self.queue = 0;  
  5.  self.output = StringIO();  
  6.    def startElement(self, name, attributes):  
  7.  if name == ‘row’:  
  8.  self.output.write(  
  9.  attributes[‘Id’] + '\t` +   
  10.  (attributes[‘PostTypeId’] if ‘PostTypeId’ in attributes else '\\N') + '\t' +   
  11.  (attributes[‘ParentId’] if ‘ParentId’ in attributes else '\\N') + '\t' +   
  12.  (attributes[‘AcceptedAnswerId’] if ‘AcceptedAnswerId’ in attributes else '\\N') + '\t' +   
  13.  (attributes[‘CreationDate’] if ‘CreationDate’ in attributes else '\\N') + '\t' +   
  14.  (attributes[‘Score’] if ‘Score’ in attributes else '\\N') + '\t' +   
  15.  (attributes[‘ViewCount’] if ‘ViewCount’ in attributes else '\\N') + '\t' +   
  16.  (attributes[‘Body’].replace('\\', '\\\\').replace('\n', '\\\n').replace('\r', '\\\r').replace('\t', '\\\t') if ‘Body’ in attributes else '\\N') + '\t' +   
  17.  (attributes[‘OwnerUserId’] if ‘OwnerUserId’ in attributes else '\\N') + '\t' +   
  18.  (attributes[‘LastEditorUserId’] if ‘LastEditorUserId’ in attributes else '\\N') + '\t' +   
  19.  (attributes[‘LastEditorDisplayName’].replace('\n''\\n') if ‘LastEditorDisplayName’ in attributes else '\\N') + '\t' +   
  20.  (attributes[‘LastEditDate’] if ‘LastEditDate’ in attributes else '\\N') + '\t' +   
  21.  (attributes[‘LastActivityDate’] if ‘LastActivityDate’ in attributes else '\\N') + '\t' +   
  22.  (attributes[‘CommunityOwnedDate’] if ‘CommunityOwnedDate’ in attributes else '\\N') + '\t' +   
  23.  (attributes[‘ClosedDate’] if ‘ClosedDate’ in attributes else '\\N') + '\t' +   
  24.  (attributes[‘Title’].replace('\\', '\\\\').replace('\n', '\\\n').replace('\r', '\\\r').replace('\t', '\\\t') if ‘Title’ in attributes else '\\N') + '\t' +   
  25.  (attributes[‘Tags’].replace('\n''\\n') if ‘Tags’ in attributes else '\\N') + '\t' +   
  26.  (attributes[‘AnswerCount’] if ‘AnswerCount’ in attributes else '\\N') + '\t' +   
  27.  (attributes[‘CommentCount’] if ‘CommentCount’ in attributes else '\\N') + '\t' +   
  28.  (attributes[‘FavoriteCount’] if ‘FavoriteCount’ in attributes else '\\N') + '\n'  
  29.  );  
  30.  self. 

StringIO让你可以用一个文件作为变量来执行copy_from这个函数,这个函数可以执行COPY(复制)命令。用这个方法,执行所有的输入过程只需要一个晚上。

好,是时候创建索引了。理论上,GiST Indexes会比GIN慢,但它占用更少的空间,所以我决定用GiST。又过了一天,我得到了70GB的加了索引的数据。

在试了一些测试语句后,我发现处理它们会花费大量的时间。至于原因,是因为Disk IO需要等待。使用SSD GOODRAM C40 120Gb会有很大提升,尽管它并不是目前最快的SSD。

我创建了一组新的PostgreSQL族群:

  1. initdb -D /media/ssd/postgresq/data 

然后确认改变路径到我的config服务器(我之前用Manjaro OS):

  1. vim /usr/lib/systemd/system/postgresql.service  
  2. Environment=PGROOT=/media/ssd/postgres 
  3. PIDFile=/media/ssd/postgres/data/postmaster.pid 

重新加载config并且启动postgreSQL:

  1. systemctl daemon-reload postgresql systemctl start 
  2. postgresql 

这次输入数据用了几个小时,但我用了GIN(来添加索引)。索引在SSD上占用了20GB的空间,但是简单的查询仅花费不到一分钟的时间。

从数据库提取书籍

数据全部输入之后,我开始查找提到这些书的帖子,然后通过SQL把它们复制到另一张表:

  1. CREATE TABLE books_posts AS SELECT * FROM posts WHERE body LIKE ‘%book%’”; 

下一步是找的对应帖子的连接:

  1. CREATE TABLE http_books AS SELECT * posts WHERE body LIKE ‘%http%’”; 

但这时候我发现StakOverflow代理的所有链接都如下所示:

  1. rads.stackowerflow.com/[$isbn]/ 

于是,我建立了另一个表来保存这些连接和帖子:

  1. CREATE TABLE rads_posts AS SELECT * FROM posts WHERE body LIKE ‘%http://rads.stackowerflow.com%'"; 

我使用常用的方式来提取所有的ISBN(国际标准书号),并通过下图方式提取StackOverflow的标签到另外一个表:

  1. regexp_split_to_table 

当我有了最受欢迎的标签并且做了统计后,我发现不同标签的前20本提及次数最多的书都比较相似。

我的下一步:改善标签。

方法是:在找到每个标签对应的前20本提及次数最多的书之后,排除掉之前已经处理过的书。因为这是一次性工作,我决定用PostgreSQL数组,编程语言如下:

  1. SELECT *  
  2.  , ARRAY(SELECT UNNEST(isbns) EXCEPT SELECT UNNEST(to_exclude ))  
  3.  , ARRAY_UPPER(ARRAY(SELECT UNNEST(isbns) EXCEPT SELECT UNNEST(to_exclude )), 1)   
  4.  FROM (  
  5.  SELECT *  
  6.  , ARRAY[‘isbn1’, ‘isbn2’, ‘isbn3’] AS to_exclude   
  7.  FROM (  
  8.  SELECT   
  9.  tag  
  10.  , ARRAY_AGG(DISTINCT isbn) AS isbns  
  11.  , COUNT(DISTINCT isbn)   
  12.  FROM (  
  13.  SELECT *   
  14.  FROM (  
  15.  SELECT   
  16.  it.*  
  17.  , t.popularity   
  18.  FROM isbn_tags AS it   
  19.  LEFT OUTER JOIN isbns AS i on i.isbn = it.isbn   
  20.  LEFT OUTER JOIN tags AS t on t.tag = it.tag   
  21.  WHERE it.tag in (  
  22.  SELECT tag   
  23.  FROM tags   
  24.  ORDER BY popularity DESC   
  25.  LIMIT 1 OFFSET 0  
  26.  )   
  27.  ORDER BY post_count DESC LIMIT 20  
  28.  ) AS t1   
  29.  UNION ALL  
  30.  SELECT *   
  31.  FROM (  
  32.  SELECT   
  33.  it.*  
  34.  , t.popularity   
  35.  FROM isbn_tags AS it   
  36.  LEFT OUTER JOIN isbns AS i on i.isbn = it.isbn   
  37.  LEFT OUTER JOIN tags AS t on t.tag = it.tag   
  38.  WHERE it.tag in (  
  39.  SELECT tag   
  40.  FROM tags   
  41.  ORDER BY popularity DESC   
  42.  LIMIT 1 OFFSET 1  
  43.  )   
  44.  ORDER BY post_count   
  45.  DESC LIMIT 20  
  46.  ) AS t2   
  47.  UNION ALL  
  48.  SELECT *   
  49.  FROM (  
  50.  SELECT   
  51.  it.*  
  52.  , t.popularity   
  53.  FROM isbn_tags AS it   
  54.  LEFT OUTER JOIN isbns AS i on i.isbn = it.isbn   
  55.  LEFT OUTER JOIN tags AS t on t.tag = it.tag   
  56.  WHERE it.tag in (  
  57.  SELECT tag   
  58.  FROM tags   
  59.  ORDER BY popularity DESC   
  60.  LIMIT 1 OFFSET 2  
  61.  )   
  62.  ORDER BY post_count DESC   
  63.  LIMIT 20  
  64.  ) AS t3   
  65.  ...  
  66.  UNION ALL  
  67.    SELECT *   
  68.  FROM (  
  69.  SELECT   
  70.  it.*  
  71.  , t.popularity   
  72.  FROM isbn_tags AS it   
  73.  LEFT OUTER JOIN isbns AS i on i.isbn = it.isbn   
  74.  LEFT OUTER JOIN tags AS t on t.tag = it.tag   
  75.  WHERE it.tag in (  
  76.  SELECT tag   
  77.  FROM tags   
  78.  ORDER BY popularity DESC   
  79.  LIMIT 1 OFFSET 78  
  80.  )   
  81.  ORDER BY post_count DESC   
  82.  LIMIT 20  
  83.  ) AS t79  
  84.  ) AS tt   
  85.  GROUP BY tag   
  86.  ORDER BY max(popularity) DESC   
  87.  ) AS ttt  
  88.  ) AS tttt   
  89.  ORDER BY ARRAY_upper(ARRAY(SELECT UNNEST(arr) EXCEPT SELECT UNNEST(la)), 1) DESC

既然已经有了所需要的数据,我开始着手建立网站。

建立网站

因为我不是一个网页开发人员,更不是一个网络用户界面专家,所以我决定创建一个基于默认主题的十分简单的单页面app。

我创建了“标签查找”的选项,然后提取最受欢迎的标签,使每次查找都可以点击相应选项来搜索。

我用长条图来可视化搜索结果。尝试了Hightcharts和D3(分别为两个JavaScript数据可视化图表库),但是他们只能起到展示作用,在用户响应方面还存在一些问题,而且配置起来很复杂。所以我决定用SVG创建自己的响应式图表,为了使图表可响应,必须针对不同的屏幕旋转方向对其进行重绘。

 

  1. var w = $('#plot').width();  
  2.  var bars = "";var imgs = "";  
  3.  var texts = "";  
  4.  var rx = 10;  
  5.  var tx = 25;  
  6.  var max = Math.floor(w / 60);  
  7.  var maxPop = 0;  
  8.  for(var i =0; i < max; i ++){  
  9.  if(i > books.length - 1 ){  
  10.  break;  
  11.  }  
  12.  obj = books[i];  
  13.  if(maxPop < Number(obj.pop)) {  
  14.  maxPop = Number(obj.pop);  
  15.  }  
  16.  }  
  17.    for(var i =0; i < max; i ++){  
  18.  if(i > books.length - 1){  
  19.  break;  
  20.  }  
  21.  obj = books[i];  
  22.  h = Math.floor((180 / maxPop ) * obj.pop);  
  23.  dt = 0;  
  24.    if(('' + obj.pop + '').length == 1){  
  25.  dt = 5;  
  26.  }  
  27.    if(('' + obj.pop + '').length == 3){  
  28.  dt = -3;  
  29.  }  
  30.    var scrollTo = 'onclick="scrollTo(\''+ obj.id +'\'); return false;" "';  
  31.  bars += '<rect id="rect'+ obj.id +'" class="cla" x="'+ rx +'" y="' + (180 - h + 30) + '" width="50" height="' + h + '" ' + scrollTo + '>';  
  32.    bars += '<title>' + obj.name'</title>';  
  33.  bars += '</rect>';  
  34.    imgs += '<image height="70" x="'+ rx +'" y="220" href="img/ol/jpeg/' + obj.id + '.jpeg" onmouseout="unhoverbar('+ obj.id +');" onmouseover="hoverbar('+ obj.id +');" width="50" ' + scrollTo + '>';  
  35.  imgs += '<title>' + obj.name'</title>';  
  36.  imgs += '</image>';  
  37.    texts += '<text x="'+ (tx + dt) +'" y="'+ (180 - h + 20) +'" class="bar-label" style="font-size: 16px;" ' + scrollTo + '>' + obj.pop + '</text>';  
  38.  rx += 60;  
  39.  tx += 60;  
  40.  }  
  41.    $('#plot').html(  
  42.  ' <svg width="100%" height="300" aria-labelledby="title desc" role="img">'  
  43.  + ' <defs> '  
  44.  + ' <style type="text/css"><![CDATA['  
  45.  + ' .cla {'  
  46.  + ' fill: #337ab7;'  
  47.  + ' }'  
  48.  + ' .cla:hover {'  
  49.  + ' fill: #5bc0de;'  
  50.  + ' }'  
  51.  + ' ]]></style>'  
  52.  + ' </defs>'  
  53.  + ' <g class="bar">'  
  54.  + bars  
  55.  + ' </g>'  
  56.  + ' <g class="bar-images">'  
  57.  + imgs  
  58.  + ' </g>'  
  59.  + ' <g class="bar-text">'  
  60.  + texts  
  61.  + ' </g>'  
  62.  + '</svg>'); 

网页服务失败

 

文本分析了4000万条Stack Overflow讨论帖,这些是程序员最推荐的编程书(附代码)

Nginx 还是 Apache?

 

当我发布了 dev-books.com这个网站之后,它有了大量的点击。而Apache却不能让超过500个访问者同时访问网站,于是我迅速部署并将网站服务器调整为Nginx。说实在的,我对于能有800个访问者同时访问这个网站感到非常惊喜!

责任编辑:未丽燕 来源: 大数据文摘
相关推荐

2020-08-28 09:50:12

Java程序员语言

2015-07-07 10:55:05

个人信息个人信息安全信息安全

2019-11-07 13:48:02

程序员技能开发者

2015-04-13 14:14:18

程序员开发语言调查

2014-07-16 09:34:44

2018-11-15 16:11:10

2020-08-04 08:48:34

数据弹屏技术

2019-03-25 07:14:57

程序员工程师职业

2012-07-18 10:35:22

GitHub程序员代码

2015-04-16 13:02:50

程序员编程选择编程技术书

2014-11-18 15:27:17

编程

2022-07-05 12:00:18

编程语言JavascriptPython

2015-02-03 02:40:33

程序员盲人程序员

2020-10-27 11:43:29

低代码开发工具开发

2017-12-04 23:25:24

2015-11-12 10:23:26

老程序员编程策略

2019-12-03 10:04:18

程序员招聘开发

2015-07-01 09:10:20

2020-11-19 15:12:56

程序员技能开发者

2020-05-06 08:21:37

程序员年薪能力
点赞
收藏

51CTO技术栈公众号