我們將構建一個簡單的應用程序,用來歸檔及顯示微推文。我們可以把它想像成更大的應用程序中的一個組件,這個應用允許用戶密切注意與其業務相關的搜索項。該示例將展示處理來自Twitter API之類數據源的JSON,以及將它轉成MongoDB文檔有多容易。如果使用關係型數據庫,就不得不事先設計一個Schema,可能還會包含多張數據表,然後還要聲明那些表。使用MongoDB的話,這些事情就都不需要了,但還能保留推文文檔豐富的結構,並且可以高效地進行查詢。
我們稱該應用為TweetArchiver,它由兩個組件組成:歸檔器和查看器,歸檔器會調用Twitter的搜索API保存相關推文,查看器用於在Web瀏覽器裡瀏覽結果。
3.3.1 配置
該應用程序會用到三個Ruby庫,可以這樣進行安裝:
gem install mongo gem install twitter gem install sinatra
有個配置文件能在歸檔器和查看器腳本之間進行共享會很有用,創建一個名為config.rb的文件,初始化如下常量:
DATABASE_NAME = \"twitter-archive\" COLLECTION_NAME = \"tweets\" TAGS = [\"mongodb\", \"ruby\"]
首先指定了應用程序中使用的數據庫和集合的名字。然後定義了一個搜索項數組,我們會把它們發給Twitter API。
接下來是編寫歸檔器腳本。先從TweetArchiver
類開始,用一個搜索項來進行實例化。然後調用TweetArchiver
實例的update
方法,這會發起一次Twitter API調用,將結果保存到MongoDB集合裡。
讓我們先從類的構造器下手:
def initialize(tag) connection = Mongo::Connection.new db = connection[DATABASE_NAME] @tweets = db[COLLECTION_NAME] @tweets.create_index([[\'id\', 1]], :unique => true) @tweets.create_index([[\'tags\', 1], [\'id\', -1]]) @tag = tag @tweets_found = 0 end
initialize
方法實例化了一個連接、一個數據庫對像和用來存儲推文的集合對象,其中還創建了兩個索引。每條推文都有一個id字段(與MongoDB的_id
字段不同),代表推文的內部Twitter ID。我們為這個字段創建了一個唯一性索引,以避免同一條推文被插入兩次。
我們還在tags
和id
字段上創建了一個組合索引,tags
升序,id
降序。索引可以指定是升序還是降序,這主要在創建組合索引時比較重要,應該總是基於自己期待的查詢模式來選擇方向。因為我們希望查詢特定的標籤,並且按時間由近及遠顯示結果,所以tags
升序、id
降序的索引既能用來過濾結果,也能用來進行排序。如你所見,可以用1表示升序、-1表示降序,以此來指明索引方向。
3.3.2 收集數據
在MongoDB中可以插入數據而無需考慮其結構。因為不用事先知道會有哪些字段,Twitter可以隨意修改API的返回值,不會給應用程序帶來什麼不良後果。一般來說,如果使用RDBMS,對Twitter API(說得更廣泛點,對數據源)的任何改動都會要求進行數據庫Schema遷移。用了MongoDB,應用程序可能需要做些修改來適應新的數據Schema,但數據庫本身可以自動處理各種文檔風格的Schema。
Ruby的Twitter庫返回的是Ruby散列,因此可以直接將其傳遞給MongoDB集合對象。在TweetArchiver
中,添加如下實例方法:
def save_tweets_for(term) Twitter::Search.new.containing(term).each do |tweet| @tweets_found += 1 tweet_with_tag = tweet.to_hash.merge!({\"tags\" => [term]}) @tweets.save(tweet_with_tag) end end
在保存每個推文文檔前,要做個小修改。為了簡化日後的查詢,將搜索項添加到tags
屬性中。然後將修改過的文檔傳遞給save
方法。代碼清單3-1中是完整的歸檔器代碼。
代碼清單3-1 抓取推文並將其歸檔在MongoDB中的類
require \'rubygems\' require \'mongo\' require \'twitter\' require File.join{File.dirname(__FILE),\'config\'; class TweetArchiver # Create a new instance of TweetArchiver def initialize(tag) connection = Mongo::Connection.new db = connection[DATABASE_NAME] @tweets = db[COLLECTION_NAME] @tweets.create_index([[\'id\', 1]], :unique => true) @tweets.create_index([[\'tags\', 1], [\'id\', -1]]) @tag = tag @tweets_found = 0 end def update puts \"Starting Twitter search for \'#{@tag}\'...\" save_tweets_for(@tag) print \"#{@tweets_found} tweets saved.nn\" end private def save_tweets_for(term) Twitter::Search.new(term).each do |tweet| @tweets_found += 1 tweet_with_tag = tweet.to_hash.merge!({\"tags\" => [term]}) @tweets.save(tweet_with_tag) end end end
剩下的就是要編寫一個腳本,為每個搜索項運行TweetArchiver
代碼。創建update.rb,包含以下代碼:
require \'config\' require \'archiver\' TAGS.each do |tag| archive = TweetArchiver.new(tag) archive.update end
然後,運行該更新腳本:
ruby update.rb
我們會看到一些狀態消息,它們指明程序找到並保存了推文。可以打開MongoDB Shell,直接查詢集合來驗證腳本是否能正常運行:
> use twitter-archive switched to db twitter-archive > db.tweets.count 30
為了保證歸檔內容始終是最新的,可以使用一個cron任務,每隔幾分鐘就運行一次更新腳本。但那是管理的細節,這裡的重點是通過寥寥幾行代碼就能保存從Twitter查到的推文。1接下來的任務是顯示結果。
1. 還可以用更少的代碼來實現這一功能,這就留給讀者作為練習了。
3.3.3 查看歸檔
我們將使用Ruby的Sinatra Web框架構建一個簡單的應用,用來顯示結果。創建一個名為viewer.rb的文件,和其他腳本放在同一目錄裡。隨後,新建views子目錄,放入一個名為tweets.erb的文件。項目結構看起來應該像下面這樣:
- config.rb - archiver.rb - update.rb - viewer.rb - /views - tweets.erb
現在編輯viewer.rb,加入以下代碼。
代碼清單3-2 一個簡單的Sinatra應用程序,用於顯示並搜索Tweet歸檔
前面幾行代碼加載了所需的庫,還有配置文件➊。接下來的配置塊中創建了一個到MongoDB的連接,並把指向tweets
集合的引用保存在常量TWEETS
裡➋。
應用程序中最重要的部分是get \'/\' do
之後的代碼,這個塊裡的代碼處理了對應用程序根URL的請求。首先,構建查詢選擇器:如果提供了URL參數tags
則創建一個查詢選擇器,將結果集限定在給定標籤裡➌;否則就創建一個空白的選擇器,查詢會返回集合中的全部文檔➍。然後發起查詢➎。現在你應該知道賦給@tweets
變量的不是結果集,而是一個游標,我們將在視圖中對該游標進行迭代。
最後一行➏呈現了視圖文件tweets.erb,完整代碼如代碼清單3-3所示。
代碼清單3-3 用於顯示推文的內嵌Ruby的HTML
<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> <html lang=\'en\' xml:lang=\'en\' xmlns=\'http://www.w3.org/1999/xhtml\'> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/> <style> body { background-color: #DBD4C2; width: 1000px; margin: 50px auto; } h2 { margin-top: 2em; } </style> </head> <body> <h1>Tweet Archive <% TAGS.each do |tag| %> <a href=\"/?tag=<%= tag %>\"><%= tag %> <% end %> <% @tweets.each do |tweet| %> <h2><%= tweet[\'text\'] %> <p> <a href=\"http://twitter.com/<%= tweet[\'from_user\'] %>\"> <%= tweet[\'from_user\'] %> </a> on <%= tweet[\'created_at\'] %> </p> <img src=\"<%= tweet[\'profile_image_url\'] %>\" /> <% end %> </body> </html>
大部分代碼只是混入了ERB的HTML,2其中的重要部分在結尾附近,有兩個迭代器。第一個迭代器遍歷了標籤列表,顯示的鏈接能將結果集限定在指定的標籤上。@tweets.each
開頭的是第二個迭代器,遍歷了每條推文,顯示推文的正文、創建日期和用戶頭像圖片。運行應用程序來查看結果:
2. ERB全稱是embedded Ruby。Sinatra應用通過一個ERB處理器來運行tweets.erb文件,並在應用程序上下文中運算<%和%>之間的Ruby代碼。
$ ruby viewer.rb
如果應用程序正常啟動,我們將看到標準的Sinatra啟動消息:
$ ruby viewer.rb == Sinatra/1.0.0 has taken the stage on 4567 for development with backup from Mongrel
我們可以打開Web瀏覽器,訪問http://localhost:4567,頁面應該會和圖3-3類似。單擊屏幕上方的鏈接可以縮小結果範圍,基於特定的標籤顯示結果。
應用程序就這樣完成了,不可否認它比較簡單,但它演示了MongoDB的易用性。我們不用事先定義Schema;能充分利用二級索引加速查詢,避免重複插入;還能相對簡單地和編程語言進行集成。
圖3-3 Web瀏覽器中呈現的推文歸檔