跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

用 Google 代码创建棒球击球记录程序: 在 Google Gadget 中显示击球统计数据

利用 Google Gadget、Google Spreadsheet API 和 Google Chart API 创建棒球击球记录程序

Paul D. Reiners (reiners_@us.ibm.com), 软件开发人员, EMC
Paul Reiners
Paul Reiners 是 Catfish Hunter、Rollie Fingers 和 Vida Blue 时代 Oakland Athletics 棒球队的球迷。Paul 是几个开放源码程序的开发者,包括 Automatous Monk、Twisted Life 和 Leipzig。他于 1991 年 5 月在伊利诺斯大学 Urbana-Champaign 分校获得应用数学(计算理论)硕士学位,他目前住在明尼苏达州,他在业余时间喜欢演奏电子贝斯和在 TopCoder 大赛上一展身手。
(An IBM developerWorks Contributing Author)
John S. Mysak, 软件开发人员, Systems Documentation, Inc. (SDI)
John Mysak
John 以前是一个接球手,他一直使用 Google 作为其主要的搜索引擎。John 是 PowerWorship 的开发人员,他有一只世界上最聪明的小猎犬。
Paul DiCarlo, 软件开发人员, Systems Documentation, Inc. (SDI)
Paul DiCarlo
Paul DiCarlo 在 New York Yankees 棒球队做投球手的时候比任何人都喜欢球员 Catfish Hunter。他现居住于明尼苏达州,他热衷于学习夏威夷四弦琴。

简介: 本文通过一个棒球击球记录程序示例展示了几个 Google Code API 的用法。我们将创建一个用来显示美国棒球联盟击球数据的 Google Gadget。您将会接触到 Google Gadget、Google Spreadsheet API 和 Google Chart API。读完本文之后,您将对使用这些 API 创建的各种应用程序有一个很好的理解,甚至能够开始编写自己的应用程序,此外,您还知道如何获得更详细的信息。本文是 Google Code baseball hacks 系列 的第一篇文章。

查看本系列更多内容

发布日期: 2008 年 9 月 02 日
级别: 中级 其他语言版本: 英文
访问情况 : 1880 次浏览
评论: 


简介

在本文中,我们通过编写有趣的棒球击球记录程序程序展示了如何使用这三个 Google Code API。本文的编排以及应用程序代码的灵感均来自 Joseph Adler 的杰作 Baseball Hacks(参见 参考资料)。我们使用了三个 Google Code API:Google Chart API、Google Spreadsheets Data API 和 Google Gadgets API。我们不打算详尽介绍这些 API,但希望我们的介绍恰到好处,能激起您的兴趣。更详细的内容可以参考 Google 文档和教程。我们的目的是构建一个 Google Gadget 来显示美国棒球联盟球队的最新击球数据。为实现此目的,我们需要以下三个步骤。

首先,从一个棒球网站下载当前的棒球数据并将这些数据存储于以逗号分隔的文本文件。其次,使用 Google Spreadsheets Data API 将这些数据上传到 Google 电子数据表。第三,编写一个 Google Gadget 来从此电子数据表读取这些数据并利用 Google Chart API 以条形图显示这些数据。


从 Web 上获取数据

第一步是下载数据。我们的代码基于 Baseball Hacks 中的 “Hack #25: Spider Baseball Sites for Data”。我们在本文中不打算详细介绍它,因为在此书中已有很多的解释。(我们的代码存于 get_all_teams_hitting.pl Perl 程序和 GetStats.pm Perl 模块,均包括在本文的 示例代码下载 中)。但有以下几点值得一提。

首先,虽然针对比赛结果(即积分表)的 RSS 和 Atom 提要有很多,但针对实时的球员积分数据的 RSS 和 Atom 提要好像没有。如果有的话,就需要从这些 Web 提要中获取数据。然而,我们却不得不从 HTML 页面获取数据。这样做不是很好,而且不可靠。比如,如果 HTML 的格式突然变了,代码很容易崩溃。第二点是在没有得到站点主人的同意之前不要从其站点获取数据。请先查看站点的 “使用条款”。第三,如果可以从该站点获取数据,也不要太过分。在连续对页面进行单击的空隙间,请留出一两秒的停顿。这不仅是个礼貌之举,而且,如果不这么做,您很可能会被拒之站外。(我们进行了测试,没有停顿的代码会遭到拒绝)。


以编程的方式向 Google 电子数据表上传数据

我们或取 HTML 页、下载数据并将其存储在以逗号分隔的各个文本文件中,每个队一个文件,我们希望将这些数据存储在一个 Google 电子数据表中以供 Google Gadget 随后访问。Google Spreadsheets Data API 让您能够按行或按单元格添加、更改和删除 Google 电子数据表的内容。Google Spreadsheet Data API 对 Java™、.NET、PHP 和 Python 都可用。我们使用的是 Python,因为我们更喜欢它。

清单 1 中,Python 代码循环遍历这组以逗号分隔的文本文件,然后调用向电子数据表上传数据的代码。(我们还使用了另一个数据文件 MLBTeams.csv,其中包含了这些球队的列表)。


清单 1. upload_all_teams_hitting.py 的片段
				
mlb_teams = open('../../data/MLBTeams.csv')
try:
      lines = mlb_teams.readlines( );
finally:
      mlb_teams.close( )             
  
# Skip header.
for line in lines[1:]:
    line = line.rstrip();
    if line != '':
        parts = line.split(",")  
        curr_wksht_id = parts[1].rstrip()
        teamAbbrev = parts[0].rstrip()
        print "Updating: " + teamAbbrev
        infile = "../../tempData/" + teamAbbrev + "Hitting.txt"
                
        uploader = Uploader(user, pw, infile)
        if curr_key != '':
            uploader.set_spreadsheet(curr_key)
        if curr_wksht_id != '':
            uploader.set_worksheet(curr_wksht_id)
        uploader.prompt_delete_old_rows_and_add_new_rows()

以编程的方式与 Google 电子数据表交互的方式有两种:基于行的提要和基于单元格的提要。在这个应用程序中,由于我们通常都只按行删除旧数据并按行添加新数据,所以我们主要使用基于行的提要。

下面是我们使用的 gdata.spreadsheet 方法和字段:

  • SpreadsheetsListFeed:让您可以在行的级别处理电子数据表
  • SpreadsheetsList:工作表中的一行
  • SpreadsheetsCellsFeed:让您可以在单元格级别处理电子数据表
    • row_count.text:用于获得工作表内的行数
  • service.SpreadsheetsService:用来访问此电子数据表的客户 API 对象
    • email:Gmail 电子邮件地址
    • password:Gmail 密码
    • source:访问此 API 的应用程序名
    • ProgrammaticLogin():登录到 Google 帐户
    • GetCellsFeed(key, wksht_id):提供电子数据表的一个单元格提要(SpreadsheetsCellsFeed)。必须指定此电子数据表的 ID 和电子数据表中的工作表的 ID。
    • GetListFeed(key, wksht_id):提供电子数据表的一个列表提要(SpreadsheetsListFeed)。必须指定此电子数据表的 ID 和电子数据表中的工作表的 ID。
    • InsertRow(row_data, key, wksht_id):向电子数据表添加一个新行
    • DeleteRow(entry):删除 SpreadsheetsList(一行)

清单 2 所包含的代码可将我们的击球数据上传到 Google 电子数据表。首先,我们删除当前工作表(在 _delete_and_add_rows 中)的所有行,然后我们用来自 .csv 文件(在 _fill_in_rows 中)的数据添加新行。


清单 2. upload_stats.py 的片段
				
import gdata.spreadsheet.service

class Uploader(CRUD):
    """
    Deletes old contents of a Google Worksheet and adds new contents from a .csv
    file.
    """

    def __init__(self, email, password, infile):
        "Initialize attributes of an Uploader."
        self.gd_client = gdata.spreadsheet.service.SpreadsheetsService()
        self.gd_client.email = email
        self.gd_client.password = password
        self.gd_client.source = 'Upload Batting Stats'
        self.gd_client.ProgrammaticLogin()
        self.curr_key = ''
        self.curr_wksht_id = ''
        self.list_feed = None
        self.infile = infile
    
    def prompt_delete_old_rows_and_add_new_rows(self):
        """
        Delete old rows and add new ones, but prompt user for spreadsheet and 
        worksheet IDs if they're not already set.
        """
        if not self.curr_key:
            self._PromptForSpreadsheet()
        if not self.curr_wksht_id:
            self._PromptForWorksheet()

        self._delete_and_add_rows()

    def _delete_and_add_rows(self):
        """
        First, delete all the rows (except the header row) in the spreadsheet 
        and then call method to add rows.
        """
        cells_feed = self.gd_client.GetCellsFeed(self.curr_key, self.curr_wksht_id)
        row_count = int(cells_feed.row_count.text)
        while (row_count > 1):
            self._list_delete_action(0)
            cells_feed = self.gd_client.GetCellsFeed(self.curr_key, self.curr_wksht_id)
            row_count = int(cells_feed.row_count.text)
      
        self._fill_in_rows()
  
    def _list_delete_action(self, index):
        "Delete the row with the given index."
        self.list_feed = self.gd_client.GetListFeed(self.curr_key, self.curr_wksht_id)
        self.gd_client.DeleteRow(self.list_feed.entry[index])
        print 'Deleted!'
  
    def _fill_in_rows(self):
        "Create new rows with the new data."
        infile = open(self.infile)
        header_line = infile.readline().strip()
        headers = header_line.split(',')
        col_count = len(headers)
        while infile:
            line = infile.readline().strip()
            if len(line) > 0:
                row_data = {}
                parts = line.split(',')
                for col in range(col_count):
                    row_data[headers[col].lower()] = parts[col].strip()
                try:
                    self._list_insert_action(row_data)
                except (gdata.service.RequestError, SyntaxError):
                    print 'Error inserting row:', row_data
                    print 'Will skip that row.'
            else:
                break;
    
    def _list_insert_action(self, row_data):
        "Insert a new row with data."
        entry = self.gd_client.InsertRow(row_data,
            self.curr_key, self.curr_wksht_id)
        if isinstance(entry, gdata.spreadsheet.SpreadsheetsList):
            print 'Inserted!'

接下来,我们将在 Google 条形图中显示这些数据。


用 Google Chart API 创建一个 Google 条形图

Google Chart API 易于使用并可通过特定的 google.com URL 访问。指定所需的参数后,Google Chart API 就会返回定制图表。

让我们来看一个示例。图 1 展示了我们的这个击球统计数据的图表。该图表显示了平均打击率(AVG)、上垒率(OBP)和多垒安打率(SLG)。


图 1. Google 条形图示例
Google 条形图示例

生成此示例图表的 URL 如 清单 3 所示,我们稍候会分别解释它的每个组件。


清单 3. Google Chart API 的示例 URL
				
var chartURL = "http://chart.apis.google.com/chart?cht=bvg&" +
    "chd=t:0.284,0.299,0.271|0.328,0.37,0.298|0.355,0.494,0.4&chs=298x180&" +
    "chco=c6d9fd,4d89f9,8a31fb&chdl=AVG|OBP|SLG&chds=0,0.700&" + 
    "chtt=Minnesota%20Twins+Batting&chxt=y,x&" +
    "chxl=0:|0.000|0.100|0.200|0.300|0.400|0.500|0.600|0.700|" + 
    "1:|D+Young|J+Morneau|C+Gomez&chbh=15"

各参数所指定的内容如下:

  • cht:图表类型。bvg 意味着我们想要一个垂直方向的条形图,而且其中的柱形条是分组的(而非堆叠的)。
  • chd:图表数据
  • chs:图表大小(以像素为单位)
  • chco:条的颜色
  • chdl:图表数据标签
  • chds:图表比例参数
  • chtt:图表标题
  • chxt:多轴的方位
  • chxl:图表标签
  • chbh:条的高度(默认地,条是水平的,但本例中的条是垂直的,所以,这实际上是条的宽度)。

创建 Google Gadget

最后,创建我们的 Google Gadget。Google Gadget 是可以放置于 Web 页面(比如 iGoogle 页面)内的一些小部件。它们由 XML、HTML 和 JavaScript 组成。

Google Gadget 包含了我们的图表,如图 2 所示。


图 2. 击球数据 Google Gadget
击球数据 Google Gadget

正如您所见,Google Gadget 可以提示用户进行参数选择。在我们的示例中,我们让用户选择棒球队以及用户希望显示的球员的数量。


图 3. 选择参数后的 Gadget
选择参数后的 Gadget

清单 4 给出了这个 gadget 的代码。此代码主要完成三个功能:

  1. 从 Google 电子数据表获取击球数据
  2. 为 Google Chart 构造 URL
  3. 将此图表添加到 gadget 中的 HTML

我们使用 Spreadsheets Data API 检索 JSON(JavaScript Object Notation)提要以便从电子数据表获得数据。这个 JSON 提要再返回给 listStats 回调函数,然后我们在该函数内解析电子数据表数据。



清单 4. mlb-batting-stats.xml 的片段
				
<Module>
 <ModulePrefs 
  title="MLB Batting Stats"
  screenshot="http://mrsabermetrics.sourceforge.net/images/MLBBattingScreenShot.PNG"
  description="Displays up-to-date MLB batting stats."
  height="200"
  width="675"
  thumbnail="http://mrsabermetrics.sourceforge.net/images/Babe_Ruth.jpg"
  title_url="http://mrsabermetrics.sourceforge.net/"
  scrolling="true"
 >
 </ModulePrefs>
 <UserPref name="num_players" display_name="How many players to show?" 
           datatype="enum" default_value="9" required="true">
  <EnumValue value="2" display_value="2"/>
  <EnumValue value="3" display_value="3"/>
  <EnumValue value="4" display_value="4"/>
    .
    .
    .
  <EnumValue value="15" display_value="15"/>
 </UserPref>
 <UserPref name="team" 
     display_name="Team"
     datatype="enum"
     required="true"
 >
  <EnumValue value="ARI" display_value="Arizona Diamondbacks"/>
  <EnumValue value="ATL" display_value="Atlanta Braves"/>
  <EnumValue value="BAL" display_value="Baltimore Orioles"/>
    .
    .
    .
  <EnumValue value="MON" display_value="Washington Nationals"/>
 </UserPref>
 <Content type="html">
<![CDATA[
<div id="statsdiv"></div>
<script>
    function listStats(root) {
        var feed = root.feed;
        var entries = feed.entry || [];
        var html = [''];
        var avgs = [];
        var obps = [];
        var slgs = [];
        var names = [];
            
        // Construct the URL for the Google Chart.             
        var cht = 'bvg';            
        html.push(
            '<img src="http://chart.apis.google.com/chart?cht=' + cht + '&chd=t:');
        for (var i = 0; i < feed.entry.length; ++i) {
            var entry = feed.entry[i];
            var title = entry.title.$t;
            names.push(title);
            var content = entry.content.$t;
            var parts = content.split(',');

            var avg = extractStat(parts[0]);
            avgs.push(avg);
            var obp = extractStat(parts[1]);
            obps.push(obp);
            var slg = extractStat(parts[2]);
            slgs.push(slg);
        }
        
        var numPlayersPref = getStringPref("num_players");

        // Show highest AB players
        var numPlayers = parseInt(numPlayersPref);
        if (numPlayers > names.length) {
            numPlayers = names.length;
        }
        
        for (var i = 0; i < numPlayers; ++i) {
            html.push(avgs[i]);
            if (i < numPlayers - 1) {
                html.push(",");
            }
        }
        html.push("|");
        
        for (var i = 0; i < numPlayers; ++i) {
            html.push(obps[i]);
            if (i < numPlayers - 1) {
                html.push(",");
            }
        }
        html.push("|");
        
        for (var i = 0; i < numPlayers; ++i) {
            html.push(slgs[i]);
            if (i < numPlayers - 1) {
                html.push(",");
            }
        }
        var width = 1000;
        if (numPlayers <= 9) {
            var width = 58 * numPlayers + 124;
        }
        var chs = width + 'x180';
        var chds = '0,0.700';
        html.push(
            "&chs=" + chs + 
                "&chco=c6d9fd,4d89f9,8a31fb&chdl=AVG|OBP|SLG&chds=" + chds);

        var teamAbbrevToName = new Object();
        for (var i = 0; i < teamInfoArray.length; i += 3) {
            teamAbbrevToName[teamInfoArray[i]] = teamInfoArray[i + 2];
        }  
        var teamName = teamAbbrevToName[getStringPref("team")]; 
        var chxt = 'y,x';
        html.push('&chtt=' + teamName + '+Batting&chxt=' + chxt + 
            '&chxl=0:|0.000|0.100|0.200|0.300|0.400|0.500|0.600|0.700|1:');
        
        for (var i = 0; i < numPlayers; ++i) {
            var parts = names[i].split(' ');
            name = parts.join("+");
            html.push('|' + name)
        }
        
        html.push('&chbh=15"/>');
        
        var generatedHTML = html.join("");
    
        // Add the chart to the HTML in the gadget.         
        document.getElementById("statsdiv").innerHTML = generatedHTML;
    }
    
    function extractStat(nameAndStat) {
        var parts = nameAndStat.split(":");
        var stat = parts[1];
        var fStat = parseFloat(stat);
        
        return fStat;
    }
  
    function getStringPref(prefName) {
        var prefs = new _IG_Prefs();
        var stringPref = prefs.getString(prefName);

        return stringPref;
    }
  
    var teamInfoArray = 
        new Array('MIN', 'od6', "Minnesota Twins", 'ANA', 'od7', "Los Angeles Angels", 
                  'ARI', 'od4', "Arizona Diamondbacks", 'ATL', 'od5', "Atlanta Braves", 
                  'BAL', 'oda', "Baltimore Orioles", 'BOS', 'odb', "Boston Red Sox", 
                    .
                    .
                    .
                  'TEX', 'ocm', "Texas Rangers", 'TOR', 'ocn', "Toronto Blue Jays");  
</script>
  
<script language="javascript">
    var teamToWkshtID = new Object(  );
    for (var i = 0; i < teamInfoArray.length; i += 3) {
        teamToWkshtID[teamInfoArray[i]] = teamInfoArray[i + 1];
    }  
    var wkshtID = teamToWkshtID[getStringPref("team")];
    
    // Get the batting stats from the Google spreadsheet.
    // Retrieve a JSON feed using the Spreadsheets Data API
    var parmsrc = 
        "http://spreadsheets.google.com/feeds/list/" + 
            "o15075496074116042190.8006901556347913051/" + wkshtID + 
            "/public/basic?alt=json-in-script&callback=listStats"; 
    scriptNode = document.createElement('script');
    scriptNode.src = parmsrc;
    scriptNode.type = 'text/javascript';
    document.getElementsByTagName('head')[0].appendChild(scriptNode);
</script>
]]>

 </Content>
</Module>
        


解密这个击球记录程序

对这个 gadget 还可以进一步改进。以下是几个改进建议:

  • 使它能显示各条所代表的实际数值。比如,当鼠标箭头在其中一个条上悬浮时,实际的数值就会出现(正如工具提示那样)。
  • 使它能比较球队的 AVG/OBP/SLG 统计数据。
  • 在 Gadget 代码中有多列球队信息,我们希望将它们作为数据保存在其他文本文件中,而不是保存在代码本身。另一方面,这些数据不太可能经常更改(虽然 Devil Rays 球队的确将其球队名称缩短为 “Rays”),所以将数据保存在代码中也没有太大的问题。

您可能会认为我们根本没必要采用电子数据表这一中间步骤,而是直接从棒球网站上获得 gadget 代码中的统计数据。我们采用电子数据表这一中间步骤是有原因的。首先,这些统计数据每天只改变一次,而且对每个用户都是一样的。所以,没有必要重复打扰获取数据的网站。第二,本文的主旨是展示 Google Code API 中可用的东西,而这是一个很妙的 Google Spreadsheet API 示例。

您能创建棒球 Google Gadget 的数量是没有限制的。如能创建多个 gadget 组成一个 “指示板” 来协助您管理棒球队,那会很有趣。

Google Gadget 也不是惟一的小部件类型。Mac OS X Dashboard Widget 是另一种流行的小部件类型。


结束语

在本文中,您对使用 Google Gadget、Google Spreadsheet API 和 Google Chart API 构建的 Web 应用程序类型有了一些了解。这个代码示例为使用这些 Google Code API 编写自己的应用程序提供了足够的参考。在 参考资料 部分,您会看到关于详细信息的链接。我们还建议您阅读 Joseph Adler 的杰作 Baseball Hacks。即使您觉得自己不喜欢棒球,也会发现这本书的确很有趣。


下载

注意:

  1. 在本文中构建的 Google Gadget。

参考资料

学习

讨论

作者简介

Paul Reiners developerWorks 投稿作者

Paul Reiners 是 Catfish Hunter、Rollie Fingers 和 Vida Blue 时代 Oakland Athletics 棒球队的球迷。Paul 是几个开放源码程序的开发者,包括 Automatous Monk、Twisted Life 和 Leipzig。他于 1991 年 5 月在伊利诺斯大学 Urbana-Champaign 分校获得应用数学(计算理论)硕士学位,他目前住在明尼苏达州,他在业余时间喜欢演奏电子贝斯和在 TopCoder 大赛上一展身手。

John Mysak

John 以前是一个接球手,他一直使用 Google 作为其主要的搜索引擎。John 是 PowerWorship 的开发人员,他有一只世界上最聪明的小猎犬。

Paul DiCarlo

Paul DiCarlo 在 New York Yankees 棒球队做投球手的时候比任何人都喜欢球员 Catfish Hunter。他现居住于明尼苏达州,他热衷于学习夏威夷四弦琴。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=333635
ArticleTitle=用 Google 代码创建棒球击球记录程序: 在 Google Gadget 中显示击球统计数据
publish-date=09022008
author1-email=reiners_@us.ibm.com
author1-email-cc=
author2-email=mysak_@us.ibm.com
author2-email-cc=
author3-email=pdicarlo_@us.ibm.com
author3-email-cc=

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。