如何从Mathematica访问StackOverflow API

26

前几天我在想是否可以从Mathematica访问StackOverflow的API,后来发现确实有:"Saving plot annotations"

如何最好地将数据从StackOverflow导入Mathematica?Sjoerd使用这些信息制作了一个图表。我想在我的笔记本中保留一个停靠单元格,添加与StackOverflow相关的通知,以便我可以在不离开Mathematica的情况下知道是否有更新或响应。


1
我从这个问题和答案中学到了很多。以前从未使用过Mma来接口Web。感谢大家。 - Dr. belisarius
2个回答

27

根据广大用户的要求,使用SO API(这是一个相当不错和完整的API;有很多好东西。而且使用也很容易——请看我的代码)生成前10个SO回答者的图表代码(不包括注释)。

更新:添加了App-key,以确保代码更好地与SO-API协作(提高每日调用上限)。请仅为此应用程序使用它。

2011年4月 enter image description here

2011年8月 enter image description here

MMA 8版本!MMA7版本在下面

getRepChanges[userID_Integer] :=
 Module[{totalChanges},
  totalChanges = 
   "total" /. 
    Import["http://api.stackoverflow.com/1.1/users/" <> 
      ToString[userID] <> "/reputation?key=NgVJ4Y6vFkuF-oqI-eOvOw&fromdate=0&pagesize=1&page=1",
      "JSON"
    ];
    Join @@ 
    Table[
      "rep_changes" /. 
         Import["http://api.stackoverflow.com/1.1/users/" <> 
                ToString[userID] <> 
                "/reputation?key=NgVJ4Y6vFkuF-oqI-eOvOw&fromdate=0&pagesize=100&page=" 
                <> ToString[page], 
                "JSON"
         ],
         {page, 1, Ceiling[totalChanges/100]}
    ]
  ]

topAnswerers = 
  ({"display_name","user_id", "email_hash"} /. #) & /@ 
     ("user" /. 
      ("top_users" /. 
        Import[
          "http://api.stackoverflow.com/1.1/tags/mathematica/top-answerers/all-time",    
          "JSON"
        ]
       )
      )

topAnswerers = {#, #2, 
    Import["http://www.gravatar.com/avatar/" <> #3 <> ".jpg?s=36&d=identicon&d=identicon"]
    } & @@@ topAnswerers

repChangesTopUsers =
  Table[
    repChange = 
     ReleaseHold[
        (
         Hold[
           {
              DateList["on_date" + AbsoluteTime["January 1, 1970"]], 
             "positive_rep" - "negative_rep"
           }
         ] /. #
        ) & /@ getRepChanges[userID]
      ] // Sort;
      accRepChange = {repChange[[All, 1]],Accumulate[repChange[[All, 2]]]}\[Transpose],
      {userID, topAnswerers[[All, 2]]}
    ];

pl = DateListLogPlot[
  Tooltip @@@ 
   Take[({repChangesTopUsers, Row /@ topAnswerers[[All, {3, 1}]]}\[Transpose]), 
    10], Joined -> True, Mesh -> None, ImageSize -> 1000, 
  PlotRange -> {All, {10, All}}, 
  BaseStyle -> {FontFamily -> "Arial-Bold", FontSize -> 16}, 
  DateTicksFormat -> {"MonthNameShort", " ", "Year"}, 
  GridLines -> {True, None}, 
  FrameLabel -> (Style[#, FontSize -> 18] & /@ {"Date", "Reputation", 
      "Top-10 answerers", ""})]
编辑
请注意,您可以通过更改Take函数中的值来绘制前20个内容,包括第20个。很快会变得繁忙。
尝试改善标记代码的可读性。抱歉在复制时可能会产生一些不必要的空格。
编辑
页面大小恢复为每页100个元素==>减少API调用
请注意,对API的第一个调用是确定用户拥有的帖子数量。无论页面大小如何,这些数据都存在,因此最好选择较小的页面大小(大约10个或1个,没有检查)。然后在连续页面中获取数据,直到达到最后一页。您可以使用最大页面大小(100)进行操作。只需注意相应地调整循环计数中的最大页面数。
编辑:更好的MMA 7代码(Fri Apr 22)
MMA 7不支持JSON导入,因此我进行文本导入,然后是基本的JSON转换。我现在已经测试了这个版本(在MMA 8中),它似乎可以正常工作,没有昨天出现的错误。
getRepChanges[userID_Integer] :=
 Module[{totalChanges},
  totalChanges = 
   "total" /. 
    ImportString[
     StringReplace[(Import[
        "http://api.stackoverflow.com/1.1/users/" <> 
         ToString[userID] <> 
         "/reputation?key=NgVJ4Y6vFkuF-oqI-eOvOw&fromdate=0&pagesize=1&page=1", "Text"]), {":" ->
         "->", "[" -> "{", "]" -> "}"}], "NB"];
  Join @@ 
   Table["rep_changes" /. 
     ImportString[
      StringReplace[
       Import["http://api.stackoverflow.com/1.1/users/" <> 
         ToString[userID] <> 
         "/reputation?key=NgVJ4Y6vFkuF-oqI-eOvOw&fromdate=0&pagesize=100&page=" <> ToString[page],
         "Text"], {":" -> "->", "[" -> "{", "]" -> "}"}], 
      "NB"], {page, 1, Ceiling[totalChanges/100]}]]
topAnswerers = ({"display_name", "user_id", 
      "email_hash"} /. #) & /@ ("user" /. ("top_users" /. 
      ImportString[
       StringReplace[
        " " <> Import[
          "http://api.stackoverflow.com/1.1/tags/mathematica/top-answerers/all-time", "Text"], {":" -> "->", "[" -> "{", "]" -> "}"}], 
       "NB"]))
topAnswerers = {#, #2, 
    Import["http://www.gravatar.com/avatar/" <> #3 <> 
      ".jpg?s=36&d=identicon&d=identicon"]} & @@@ topAnswerers
repChangesTopUsers = 
  Table[repChange = 
    ReleaseHold[(Hold[{DateList[
             "on_date" + AbsoluteTime["January 1, 1970"]], 
            "positive_rep" - "negative_rep"}] /. #) & /@ 
       getRepChanges[userID]] // Sort;
   accRepChange = {repChange[[All, 1]], 
      Accumulate[repChange[[All, 2]]]}\[Transpose], {userID, 
    topAnswerers[[All, 2]]}];

DateListLogPlot[
 Tooltip @@@ 
  Take[({repChangesTopUsers, 
      Row /@ topAnswerers[[All, {3, 1}]]}\[Transpose]), 10], 
 Joined -> True, Mesh -> None, ImageSize -> 1000, 
 PlotRange -> {All, {10, All}}, 
 BaseStyle -> {FontFamily -> "Arial-Bold", FontSize -> 16}, 
 DateTicksFormat -> {"MonthNameShort", " ", "Year"}, 
 GridLines -> {True, None}, 
 FrameLabel -> (Style[#, FontSize -> 18] & /@ {"Date", "Reputation", 
     "Top-10 answerers", ""})] 

编辑:用于按帖子标签筛选的辅助函数 这些函数可用于过滤声望收益,以便仅查找特定标签的收益。 tagLookup将post_ID整数作为输入并生成特定帖子的标签。 getQuestionIDsgetAnswerIDsFrom...则相反。给定一个标签,它们会查找所有问题和答案的ID,以便可以使用MemberQ测试给定的post_ID是否属于该标签。由于需要进行许多API调用,因此tagLookup和getAnswerIDs都很慢。我无法测试最后两个函数,因为API访问要么不可用,要么我的IP已达到上限。

tagLookup[postID_Integer] :=
 Module[{im},
  im = Import["http://api.stackoverflow.com/1.1/questions/" <> ToString[postID],"JSON"];
  If[("questions" /. im) != {},
   First[("tags" /. ("questions" /. im))],
   im = Import["http://api.stackoverflow.com/1.1/answers/" <> ToString[postID],"JSON"];
   First[("tags" /. ("questions" /. Import["http://api.stackoverflow.com/1.1/questions/" <> 
          ToString[First["question_id" /. ("answers" /. im)]], "JSON"]))]
   ]
  ]

getQuestionIDs[tagName_String] := Module[{total},
  total = 
   "total" /. 
    Import["http://api.stackoverflow.com/1.1/questions?tagged=" <> 
      tagName <> "&pagesize=1", "JSON"];
  Join @@ 
   Table[("question_id" /. ("questions" /. 
        Import["http://api.stackoverflow.com/1.1/questions?key=NgVJ4Y6vFkuF-oqI-eOvOw&tagged=" <>
           tagName <> "&pagesize=100&page=" <> ToString[i], 
         "JSON"])), {i, 1, Ceiling[total/100]}]
  ]

getAnswerIDsFromQuestionID[questionID_Integer] :=
 Module[{total},
  total = 
   Import["http://api.stackoverflow.com/1.1/questions/" <> 
     ToString[questionID] <> "/answers?key=NgVJ4Y6vFkuF-oqI-eOvOw&pagesize=1", "JSON"];
  If[total === $Failed, Return[$Failed], total = "total" /. total]; 
  Join @@ Table[
    "answer_id" /. ("answers" /. 
       Import["http://api.stackoverflow.com/1.1/questions/" <> 
         ToString[questionID] <> "/answers?key=NgVJ4Y6vFkuF-oqI-eOvOw&pagesize=100&page=" <> 
         ToString[i], "JSON"]), {i, 1, Ceiling[total/100]}]
  ]

getAnswerIDsFromTag[tagName_String] :=
 Module[{},
  Join @@ (getAnswerIDsFromQuestionID /@ 
     Cases[getQuestionIDs[tagName], Except[$Failed]])
  ]

2
@Mr.Wizard JSON 导入是 Mathematica V8 中的新功能。这对于 V7 用户可能会很有用 https://dev59.com/HXE85IYBdhLWcg3wvGOm (包括代码和至少一个其他实现的链接)。 - Brett Champion
1
谢谢。不要试图猜测我的时区,这只会让你更困惑。 - Mr.Wizard
1
@Sjoerd 应用你的课程:http://meta.stackexchange.com/questions/88673/how-many-users-got-the-x-badge-before-i-did/88680#88680 :) - Dr. belisarius
1
@Mr.Wizard 我添加了一些辅助函数。看起来很简单,但我遇到了几个困难。由于似乎又被API限制了,我无法测试所有内容。新增内容与之前一样是JSON格式的。上述提到的适用于mma 7的基本JSON导入应该对您有用。 - Sjoerd C. de Vries
1
@Mr.Wizard API现在已经恢复正常运行。这些函数可以找到726个标记为Mathematica的问题和1707个答案。因此,有2433个与Mathematica相关的帖子可以用来过滤声望。由于速度较慢,这不是您经常想要做的事情。要获取答案ID,需要对API进行726个单独的调用。当您经常这样做时,这可能会触发IP限制。在我的PC上花了7分钟。 - Sjoerd C. de Vries
显示剩余18条评论

12

Brett,与SO API无关,但是你可以使用RSS提要获取最新的Mathematica标记问题。这是我的天真实现:

QuestionHyperlink[data_] := 
 Function[{name, title, link}, 
   Hyperlink[Tooltip[title, name], link]] @@ Join[
   Cases[data, 
    XMLElement[
      "author", _, {___, XMLElement["name", {}, {name_}], ___}] :> 
     name],
   Cases[data, XMLElement["title", _, {title_}] :> title],
   Cases[data, XMLElement["link", rules_, {}] :> ("href" /. rules)]]

Cases[Import[
  "http://stackoverflow.com/feeds/tag?tagnames=mathematica&sort=\
newest", "XML"], 
 XMLElement["entry", attrs_, data_] :> 
  QuestionHyperlink[data], Infinity]

输入图片说明


1
我建议使用以下URL来获取所有与Wolfram相关的问题的RSS:http://stackoverflow.com/feeds/tag/mathematica+or+wolfram+or+wolframalpha+or+mathematica-frontend+or+mathematica-8 - Alexey Popkov
@Sasha 这正是我浪费时间做的事情。:) 为自己的订阅源加油。 - telefunkenvf14
@Alexey 鉴于这里相对较少的 mma 问题涌入,以及等待着狼吞虎咽任何被抛入其中的回答者群体,我希望有一种即时解决方案,而不是基于轮询的解决方案。 - Sjoerd C. de Vries
@Sjoerd 你是什么意思?这与我的RSS链接有关吗? - Alexey Popkov
1
@Alexey 我的问题是,我在手机上有一个 RSS 阅读器,我像每个晚上一样使用它来阅读大约20个源,但在 Stack Overflow 的情况下,我需要在有新内容时立即得到通知,否则那些有价值的问题已经被深入回答了。 - Sjoerd C. de Vries
@Sjoerd 现在我明白了。Sasha的解决方案是即时的,但无法在手机上运行。它也不能突出显示新的和未观看的问题。后者可能可以通过SO API实现。 - Alexey Popkov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接