结合使用 YouTube API 和 PHP

使用 PHP 的 SimpleXML 扩展处理 YouTube 数据并将其集成到 PHP 应用程序中

YouTube 视频共享站点允许 Web 开发人员通过其基于 REST 的开发人员 API 访问公共内容。PHP 的 SimpleXML 扩展非常适合处理由这种 API 生成的 XML 提要,并使用它们构建定制的 PHP 应用程序。本文将向您介绍 YouTube Data API,演示如何使用它浏览用户生成的视频内容,访问视频元数据、评论和回复,并执行关键字搜索。

Vikram Vaswani, 创始人, Melonfire

Vikram Vaswani 是 Melonfire 的创始人和 CEO,该公司是一家专门研究开源工具和技术的咨询服务公司。他还著有 PHP Programming SolutionsHow to do Everything with PHP and MySQL 等著作。



2008 年 5 月 15 日

简介

常用缩写词

  • API:应用程序编程接口(application programming interface)
  • HTTP:超文本传输协议(Hypertext Transfer Protocol)
  • PEAR:PHP 扩展和应用库(PHP Extension and Application Repository)
  • PHP:超文本预处理语言(PHP Hypertext Preprocessor)
  • REST:具象状态传输(Representational state transfer)
  • RSS:真正简单的连锁(Really Simple Syndication
  • URL:统一资源定位符(Uniform Resource Locator
  • XML:可扩展标记语言(Extensible Markup Language)

谈到在线视频共享,目前最流行的站点非 YouTube 莫属,该站点每天会增加几十亿个页面视图和数十万个视频文件。它不仅仅提供家庭录像:如今,YouTube 还提供音乐节目录像、电视节目剪辑、即将上映的影片片花、动漫剪辑等大量内容。它还允许用户使用关键字为视频添加标记,在任何时刻都可以观看最受欢迎的视频文件。

YouTube 的一个出色特性就是它的 YouTube Data API,该特性允许开发人员通过基于 REST 的 API 访问和搜索 YouTube 视频数据,并将数据集成到自己的支持 XML 的应用程序中。这个过程实现起来并不困难 — 编写应用程序级别的代码发送 REST 请求、解析和解码响应,然后将结果数据集成到应用程序接口。如果使用 PHP,那么您可以通过 YouTube 的 PHP Client Library 执行这些任务,也可以手动解析 REST 请求的 XML 响应。

本文将讨论后一种方法,展示如何通过 YouTube API 访问公共内容并使用 SimpleXML 将这些内容集成到您的 PHP 应用程序中。本文提供的示例将检索特定类别中的视频清单;根据关键字搜索视频;检索视频元数据,包括缩略图和统计信息;并访问用户个人信息。


了解 YouTube 数据格式

在深入研究 PHP 代码之前,首先简单介绍一下 YouTube Data API。与所有基于 REST 的服务一样,首先从一个发送到指定资源的 HTTP 请求开始。这个 HTTP 请求包含一个查询,具有一个或多个输入参数;服务器使用 Atom 或 RSS 格式的响应回复该查询,该响应适合在支持 XML 的任何客户机内进行解析。

要了解工作原理,请在您喜爱的 Web 浏览器中访问 URL http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed。这个 REST 方法返回 YouTube 上当前查看次数最多的视频列表。该方法的原始 XML 响应(您可以通过结果页面的源代码查看)包含关于这些视频的详细信息,内容类似 清单 1 所示:

清单 1. YouTube API 生成的示例提要
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' 
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' 
xmlns:gml='http://www.opengis.net/gml' xmlns:georss='http://www.georss.org/georss' 
xmlns:media='http://search.yahoo.com/mrss/' 
xmlns:yt='http://gdata.youtube.com/schemas/2007' 
xmlns:gd='http://schemas.google.com/g/2005'>
  <id>http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed</id>
  <updated>2008-03-06T14:43:27.000-08:00</updated>
  <category scheme='http://schemas.google.com/g/2005#kind' 
  term='http://gdata.youtube.com/schemas/2007#video'/>
  <title type='text'>Most Viewed</title>
  <logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo>
  <link rel='alternate' type='text/html' href='http://www.youtube.com/browse?s=mp'/>
  <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed'/>
  <link rel='self' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed
  ?start-index=1&max-results=5'/>
  <link rel='next' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed
  ?start-index=6&max-results=5'/>
  <author>
    <name>YouTube</name>
    <uri>http://www.youtube.com/</uri>
  </author>
  <generator version='beta' uri='http://gdata.youtube.com/'>
  YouTube data API</generator>
  <openSearch:totalResults>94</openSearch:totalResults>
  <openSearch:startIndex>1</openSearch:startIndex>
  <openSearch:itemsPerPage>5</openSearch:itemsPerPage>
  <entry>
    <id>http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg</id>
    <published>2006-04-06T14:30:53.000-07:00</published>
    <updated>2008-03-12T00:22:25.000-07:00</updated>
    <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' 
    term='Dancing'/>
    <category scheme='http://schemas.google.com/g/2005#kind' 
    term='http://gdata.youtube.com/schemas/2007#video'/>
    <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' 
    term='comedy'/>
    <category scheme='http://gdata.youtube.com/schemas/2007/categories.cat' 
    term='Comedy' label='Comedy'/>
    <title type='text'>Evolution of Dance</title>
    <content type='text'>The funniest 6 minutes you will ever see! 
    Remember how many of these you have done!
Judson Laipply is dancing -
http://www.evolutionofdance.com -
for more info including song list!</content>
    <link rel='alternate' type='text/html' 
    href='http://www.youtube.com/watch?v=dMH0bHeiRNg'/>
    <link rel='http://gdata.youtube.com/schemas/2007#video.responses' 
    type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/responses'/>
    <link rel='http://gdata.youtube.com/schemas/2007#video.related' 
    type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/related'/>
    <link rel='self' type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed/dMH0bHeiRNg'/>
    <author>
      <name>judsonlaipply</name>
      <uri>http://gdata.youtube.com/feeds/api/users/judsonlaipply</uri>
    </author>
    <media:group>
      <media:title type='plain'>Evolution of Dance</media:title>
      <media:description type='plain'>The funniest 6 minutes you will ever see!
       Remember how many of these you have done!
Judson Laipply is dancing -
http://www.evolutionofdance.com -
for more info including song list!</media:description>
      <media:keywords>comedy, Dancing</media:keywords>
      <yt:duration seconds='360'/>
      <media:category label='Comedy' 
      scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>Comedy
      </media:category>
      <media:content 
      url='http://www.youtube.com/v/dMH0bHeiRNg' type='application/x-shockwave-flash' 
      medium='video' isDefault='true' expression='full' duration='360' yt:format='5'/>
      <media:content 
      url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQnYRKJ3bPTBdBMYDSANFEgGDA==
      /0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='360'
       yt:format='1'/>
      <media:content 
      url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQnYRKJ3bPTBdBMYESARFEgGDA==
      /0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='360'
       yt:format='6'/>
      <media:player url='http://www.youtube.com/watch?v=dMH0bHeiRNg'/>
      <media:thumbnail 
      url='http://img.youtube.com/vi/dMH0bHeiRNg/2.jpg' height='97' width='130' 
      time='00:03:00'/>
      <media:thumbnail 
      url='http://img.youtube.com/vi/dMH0bHeiRNg/1.jpg' height='97' width='130' 
      time='00:01:30'/>
      <media:thumbnail 
      url='http://img.youtube.com/vi/dMH0bHeiRNg/3.jpg' height='97' width='130' 
      time='00:04:30'/>
      <media:thumbnail 
      url='http://img.youtube.com/vi/dMH0bHeiRNg/0.jpg' height='240' width='320' 
      time='00:03:00'/>
    </media:group>
    <yt:statistics viewCount='78060679' favoriteCount='400468'/>
    <gd:rating min='1' max='5' numRaters='276123' average='4.65'/>
    <gd:comments>
      <gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg
      /comments' countHint='124130'/>
    </gd:comments>
  </entry>
  <entry>
    <id>http://gdata.youtube.com/feeds/api/videos/cQ25-glGRzI</id>
    <published>2007-02-27T15:08:01.000-08:00</published>
    <updated>2008-03-12T00:46:25.000-07:00</updated>
    ...
  </entry>
</feed>

快速查看这些输出,熟悉其中的主要元素:

  • YouTube Data API 对 REST 请求的响应是一个包含所请求数据的提要。提要的内容多种多样:视频提要、视频评论提要、用户播放列表提要、用户联系方式提要,等等。因此,在大多数情况下,XML 响应包含一个 <feed> 元素作为根元素。<feed> 元素包含 <link> 元素,后者包含当前、上一个、下一个结果集页面的 URL,以及一个包含搜索的摘要统计数据的 <openSearch:> 元素。
  • 最外层的 <feed> 元素封装了一个或多个 <entry> 元素,分别代表一个与查询匹配的视频。每个 <entry> 包含它所表示的视频的更多信息,包括类别、标题、描述、发布日期、作者和持续时间。每个 <entry> 中还包含 <link> 元素,通过其中的链接可以查看视频、视频回复和 YouTube Web 站点中其他相关的视频。
  • 每个 <entry> 中的 <media:group> 元素包含关于视频的详细信息:标题、描述、各种格式的可用性、缩略图链接和视频播放器链接。
  • 每个 <entry> 中的 <yt:statistics> 元素提供视频观看者的统计信息,而 <gd:rating> 元素提供该视频的平均得分,以及对该视频进行打分的用户总数。

这无疑包含了大量信息 — 可能超出了您的想象。正是这些丰富的信息库使 YouTube Data API 变得引人注目,因为它允许开发人员自由地构建创新性的应用程序。

并不是所有的 YouTube Data API 功能都可以通过这种方式进行公开访问。某些功能 — 尤其是修改站点数据的功能,包括上传视频、编辑内容或用户个人信息、添加评论或为视频打分 — 只能通过开发锁(developer key)和身份验证令牌访问。要访问这些 API 功能,需要在 YouTube 注册为一名开发人员(参加 参考资料 中的链接)。本文讨论的内容仅限于那些不需要开发锁的 API 功能;您不久将看到,这些功能足以让您应接不暇!


检索视频清单

现在让我们查看一个使用 PHP 处理 YouTube Data API 提要的示例。清单 2清单 1 获取提要并使用 SimpleXML 从中提取有关的数据片段,然后将它格式化到一个 Web 页面:

清单 2. 使用一个 PHP 脚本列出观看次数最多的 YouTube 视频
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing most viewed videos</title>
    <style type="text/css">
    div.item {
      border-top: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    span.thumbnail {
      float: left;
      margin-right: 20px;
      padding: 2px;
      border: solid silver 1px;  
      font-size: x-small; 
      text-align: center
    }    
    span.attr {
      font-weight: bolder;  
    }
    span.title {
      font-weight: bolder;  
      font-size: x-large
    }
    img {
      border: 0px;  
    }    
    a {
      color: brown; 
      text-decoration: none;  
    }
    </style>
  </head>
  <body>
    <?php
    // set feed URL
    $feedURL = 'http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed';
    
    // read feed into SimpleXML object
    $sxml = simplexml_load_file($feedURL);
    ?>
      <h1><?php echo $sxml->title; ?></h1>
    <?php
    // iterate over entries in feed
    foreach ($sxml->entry as $entry) {
      // get nodes in media: namespace for media information
      $media = $entry->children('http://search.yahoo.com/mrss/');
      
      // get video player URL
      $attrs = $media->group->player->attributes();
      $watch = $attrs['url']; 
      
      // get video thumbnail
      $attrs = $media->group->thumbnail[0]->attributes();
      $thumbnail = $attrs['url']; 
            
      // get <yt:duration> node for video length
      $yt = $media->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->duration->attributes();
      $length = $attrs['seconds']; 
      
      // get <yt:stats> node for viewer statistics
      $yt = $entry->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->statistics->attributes();
      $viewCount = $attrs['viewCount']; 
      
      // get <gd:rating> node for video ratings
      $gd = $entry->children('http://schemas.google.com/g/2005'); 
      if ($gd->rating) {
        $attrs = $gd->rating->attributes();
        $rating = $attrs['average']; 
      } else {
        $rating = 0; 
      } 
      ?>
      <div class="item">
        <span class="title">
          <a href="<?php echo $watch; ?>"><?php echo $media->group->title; ?></a>
        </span>
        <p><?php echo $media->group->description; ?></p>
        <p>
          <span class="thumbnail">
            <a href="<?php echo $watch; ?>"><img src="<?php echo $thumbnail;?>" /></a>
            <br/>click to view
          </span> 
          <span class="attr">By:</span> <?php echo $entry->author->name; ?> <br/>
          <span class="attr">Duration:</span> <?php printf('%0.2f', $length/60); ?> 
          min. <br/>
          <span class="attr">Views:</span> <?php echo $viewCount; ?> <br/>
          <span class="attr">Rating:</span> <?php echo $rating; ?> 
        </p>
      </div>      
    <?php
    }
    ?>
  </body>
</html>

图 1 演示了可能看到的输出:

图 1. 观看最频繁的视频列表
观看最频繁的视频列表

清单 2 首先使用 simplexml_load_file() 对象向提要 URL 发送一个请求并将响应转换为一个 SimpleXML 对象。然后遍历响应中的 <entry> 元素,使用 foreach() 循环处理每个元素并检索如 图 1 所示的信息。下面介绍了每个值是如何获取的:

  • 视频元数据保存在 <media:group> 节点集合中,位于每个 <entry> 节点下面。SimpleXML 的 children() 方法结合 media: 名称空间以 $media 形式返回节点集合,随后分别以 $media->group->title$media->group->description 的形式访问视频标题和描述。
  • 各种 <media:thumbnail> 元素的 url 属性保存视频缩略图的 URL。要访问这个值,需要调用 $media->group->thumbnail[0]->attributes(),并访问结果属性数组的 url 键。
  • <media:player> 元素的 url 属性保存视频播放器的 URL。要访问这个值,调用 $media->group->player->attributes() 并访问结果属性数组的 url 键。
  • <media:group> 元素下的 <yt:duration> 元素保存视频的长度信息,单位为秒。$media->children() 方法联合 yt: 名称空间以 SimpleXML 对象的形式返回这个节点,该对象的 attributes() 方法检索 seconds 属性的值。要获得以分钟计算的视频长度信息,则使用这个属性值除以 60 即可。
  • 类似地,视频观看者统计数据可以通过 <yt:stats> 元素获得。同样,通过名称空间访问这个元素,结果对象的 attributes() 方法检索 viewCount 属性的值。
  • 用户对视频的评分信息可以通过每个 <entry> 元素下的 <gd:ratings> 元素找到。要访问这个元素,使用它的名称空间调用 SimpleXML 的 children() 方法,并访问对象的 average 属性来获取平均评分值。

获得了所有信息之后,进行格式化并在 Web 页面上显示就非常简单了;清单 2 顶部的简单 CSS 规则负责完成这些任务。

注意,清单 2 中使用的提要 URL 是 YouTube 提供给开发人员的各种标准提要之一。其他常用的类型包括:

  • 评分最高的视频:http://gdata.youtube.com/feeds/api/standardfeeds/top_rated
  • 最近添加的视频:http://gdata.youtube.com/feeds/api/standardfeeds/most_recent
  • 链接最多的视频:http://gdata.youtube.com/feeds/api/standardfeeds/most_linked

要访问标准提要的完整列表,请参考 参考资料 中提供的 YouTube Data API 开发者指南。


处理视频类别

除了 前一小节 讨论的标准提要以外,YouTube 还允许开发人员按照类别检索视频。方法是将类别名称附加到视频提要的 URL 之后,例如 http://gdata.youtube.com/feeds/api/videos/-/Travel/http://gdata.youtube.com/feeds/api/videos/-/Tech/

清单 3 演示了如何检索并列出出 Travel 中的视频:

清单 3. 使用一个 PHP 脚本列出属于 Travel 类别的 YouTube 视频
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing videos in a category</title>
  </head>
  <body>
    <?php
    // set feed URL
    // add category
    $feedURL = 'http://gdata.youtube.com/feeds/api/videos/-/Travel/';
    
    // read feed into SimpleXML object
    $sxml = simplexml_load_file($feedURL);
    
    // get summary counts from opensearch: namespace
    $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
    $total = $counts->totalResults;  
    ?>
    <h1><?php echo $sxml->title; ?></h1>
    <?php echo $total; ?> items found.
    <p/>
    <ol>
    <?php    
    // iterate over entries in category
    // print each entry's details
    foreach ($sxml->entry as $entry) {
      // get nodes in media: namespace for media information
      $media = $entry->children('http://search.yahoo.com/mrss/');
      
      // get video player URL
      $attrs = $media->group->player->attributes();
      $watch = $attrs['url']; 
      
      // get <yt:duration> node for video length
      $yt = $media->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->duration->attributes();
      $length = $attrs['seconds']; 
      
      // get <gd:rating> node for video ratings
      $gd = $entry->children('http://schemas.google.com/g/2005'); 
      if ($gd->rating) {
        $attrs = $gd->rating->attributes();
        $rating = $attrs['average']; 
      } else {
        $rating = 0; 
      } 
            
      // print record
      echo "<li>\n";
      echo "<a href=\"{$watch}\">{$media->group->title}</a>
      <br/>\n";
      echo sprintf("%0.2f", $length/60) . " min. | {$rating} user rating
      <br/>\n";
      echo "{$media->group->description}<p/>\n";
      echo "<p/></li>\n";
    }
    ?>
    </ol>
  </body>
</html>

清单 2 类似,清单 3 首先向 Travel 类别的提要 URL 发送一个 GET 请求,然后将响应转换为一个 SimpleXML 对象。随后遍历集合中的 <entry> 元素以检索标题、描述、持续时间和用户评分,最后将信息格式化为一个有序列表。清单 3 还添加了一项新特性:查看 XML 响应中 <openSearch:> 元素包含的摘要数据,输出类别中找到的视频总数。

图 2 演示了输出的外观:

图 2. Travel 类别的视频列表
Travel 类别的视频列表

您可以从 URL http://gdata.youtube.com/schemas/2007/categories.cat 获得完整的 YouTube 类别列表。清单 4 演示了这个文件的样例:

清单 4. 一个示例 YouTube 类别列表
<?xml version='1.0' encoding='UTF-8'?>
<app:categories xmlns:app='http://www.w3.org/2007/app' 
xmlns:atom='http://www.w3.org/2005/Atom' 
xmlns:yt='http://gdata.youtube.com/schemas/2007' fixed='yes' 
scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>
  <atom:category term='Film' label='Film & Animation' xml:lang='en-US'>
    <yt:browsable/>
    <yt:assignable/>
  </atom:category>
  <atom:category term='Autos' label='Autos & Vehicles' xml:lang='en-US'>
    <yt:browsable/>
    <yt:assignable/>
  </atom:category>
  ...
</app:categories>

编写一个 PHP 脚本,使用它检索和解析文件,然后使用 YouTube API 检索每个类别中观看次数最多的前五个视频,这个任务并不困难。清单 5 演示了这种脚本:

清单 5. 使用一个 PHP 脚本列出各个类别中观看最多的前五个 YouTube 视频
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing videos in different categories</title>
  </head>
  <body>
    <?php
    // set URL for XML feed containing category list
    $catURL = 'http://gdata.youtube.com/schemas/2007/categories.cat';
    
    // retrieve category list using atom: namespace
    // note: you can cache this list to improve performance, 
    // as it doesn't change very often!
    $cxml = simplexml_load_file($catURL);
    $cxml->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
    $categories = $cxml->xpath('//atom:category');
    
    // iterate over category list
    foreach ($categories as $c) {
      // for each category    
      // set feed URL
      $feedURL = "http://gdata.youtube.com/feeds/api/videos/-/{$c['term']}
      ?max-results=5&orderby=viewCount";
      
      // read feed into SimpleXML object
      $sxml = simplexml_load_file($feedURL);
      
      // get summary counts from opensearch: namespace
      $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
      $total = $counts->totalResults; 
      ?>
      
      <h1><?php echo $c['label']; ?></h1>
      <?php echo $total; ?> items found.
      <p/>
      
      <?php    
      echo "<ol>\n";
      // iterate over entries in category
      // print each entry's details
      foreach ($sxml->entry as $entry) {
        // get nodes in media: namespace for media information
        $media = $entry->children('http://search.yahoo.com/mrss/');
        
        // get video player URL
        $attrs = $media->group->player->attributes();
        $watch = $attrs['url']; 
        
        // get <yt:duration> node for video length
        $yt = $media->children('http://gdata.youtube.com/schemas/2007');
        $attrs = $yt->duration->attributes();
        $length = $attrs['seconds']; 
        
        // get <gd:rating> node for video ratings
        $gd = $entry->children('http://schemas.google.com/g/2005'); 
        if ($gd->rating) {
          $attrs = $gd->rating->attributes();
          $rating = $attrs['average']; 
        } else {
          $rating = 0; 
        }

        // print record
        echo "<li>\n";
        echo "<a href=\"{$watch}\">{$media->group->title}</a>
        <br/>\n";
        echo sprintf("%0.2f", $length/60) . " min. | {$rating} user rating
        <br/>\n";
        echo substr($media->group->description,0,50) . "...<p/>\n";
        echo "<p/></li>\n";
      }
      echo "</ol>\n";
    }
    ?>
  </body>
</html>

清单 5 首先检索 http://gdata.youtube.com/schemas/2007/categories.cat 中使用 XML 编码的类别列表。然后使用 SimpleXML 的 xpath() 函数从列表中检索 YouTube 类别名称,随后通过为每个类别动态创建提要 URL,执行 foreach 循环来检索每个类别中观看次数最多的前五个视频。现在,您应该不会对 foreach() 循环内部的操作感到惊讶,因为它与 清单 3 中的步骤几乎完全相同;然而,需要指出的是,有两个额外的参数被添加到了提要 URL 中:

  • max-results 参数指定将在提要中返回的结果的数量。
  • orderby 参数指定如何对这些结果进行排序:根据受欢迎程度(viewCount)、语言、用户评分(rating)或发布日期(published)。

现在,看看 图 3,它包含由 清单 5 生成的样例输出:

图 3. 显示来自各个类别的视频的 Web 页面
显示来自各个类别的视频的 Web 页面

执行关键字搜索

YouTube 允许用户使用关键字为视频添加标记,从而简化视频的识别。YouTube API 还允许开发人员根据关键字搜索视频,使用与类别相似的方式。例如,要搜索使用关键字 “boat” 标记的视频,则向 http://gdata.youtube.com/feeds/api/videos/-/boat 发送一个 GET 请求。类似地,要查找使用关键字 ‘westminster’ 和 ‘london’ 标记的所有视频,向 http://gdata.youtube.com/feeds/api/videos/-/westminster/london 发送一个 GET 请求。这些请求的 XML 响应类似于 清单 1 所示:一个 <feed> 元素包含多个 <entry> 元素,其中每个 <entry> 元素代表一个匹配的视频。

无疑,这大大简化了使用 PHP 构建简单的搜索引擎的过程,使用这个搜索引擎可以挖掘 YouTube 数据库,搜索与用户提供的关键字匹配的视频。清单 6 展示了这样一种搜索引擎:

清单 6. 一个简单的针对 YouTube 视频的关键字搜索引擎
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Searching for videos by keyword</title>
    <style>
    img {
      padding: 2px; 
      margin-bottom: 15px;
      border: solid 1px silver; 
    }
    td {
      vertical-align: top;
    }
    td.line {
      border-bottom: solid 1px black;  
    }
    </style>
  </head>
  <body>
    <?php
    // if form not submitted
    // display search box
    if (!isset($_POST['submit'])) {
    ?>
    <h1>Keyword search</h1>  
    <form method="post" action="<?php echo 
      htmlentities($_SERVER['PHP_SELF']); ?>">
      Keywords: <br/>
      <input type="text" name="q" />
      <p/>
      Items to display: <br/>
      <select name="i">
        <option value="10">10</option>
        <option value="25">25</option>
        <option value="50">50</option>
        <option value="100">100</option>
      </select>
      <p/>
      <input type="submit" name="submit" value="Search"/>  
    </form>
    <?php      
    // if form submitted
    } else {
      // check for search keywords
      // trim whitespace
      // separate multiple keywords with /
      if (!isset($_POST['q']) || empty($_POST['q'])) {
        die ('ERROR: Please enter one or more search keywords');
      } else {
        $q = $_POST['q'];
        $q = ereg_replace('[[:space:]]+', '/', trim($q));
      }
      
      // set max results
      if (!isset($_POST['i']) || empty($_POST['i'])) {
        $i = 25;
      } else {
        $i = $_POST['i'];
      }
      
      // generate feed URL
      $feedURL = "http://gdata.youtube.com/feeds/api/videos/-/{$q}
      ?orderby=viewCount&max-results={$i}";
      
      // read feed into SimpleXML object
      $sxml = simplexml_load_file($feedURL);
      
      // get summary counts from opensearch: namespace
      $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
      $total = $counts->totalResults; 
      $startOffset = $counts->startIndex; 
      $endOffset = ($startOffset-1) + $counts->itemsPerPage;       
      ?>
      
      <h1>Search results</h1>
      <?php echo $total; ?> items found. Showing items 
      <?php echo $startOffset; ?> to <?php echo $endOffset; ?>:
      <p/>
      
      <table>
      <?php    
      // iterate over entries in resultset
      // print each entry's details
      foreach ($sxml->entry as $entry) {
        // get nodes in media: namespace for media information
        $media = $entry->children('http://search.yahoo.com/mrss/');
        
        // get video player URL
        $attrs = $media->group->player->attributes();
        $watch = $attrs['url']; 
        
        // get video thumbnail
        $attrs = $media->group->thumbnail[0]->attributes();
        $thumbnail = $attrs['url']; 
        
        // get <yt:duration> node for video length
        $yt = $media->children('http://gdata.youtube.com/schemas/2007');
        $attrs = $yt->duration->attributes();
        $length = $attrs['seconds']; 
        
        // get <gd:rating> node for video ratings
        $gd = $entry->children('http://schemas.google.com/g/2005'); 
        if ($gd->rating) {
          $attrs = $gd->rating->attributes();
          $rating = $attrs['average']; 
        } else {
          $rating = 0; 
        }

        // print record
        echo "<tr><td colspan=\"2\" class=\"line\"></td>
        </tr>\n";
        echo "<tr>\n";
        echo "<td><a href=\"{$watch}\"><img src=\"$thumbnail\"/></a></td>\n";
        echo "<td><a href=\"{$watch}\">
        {$media->group->title}</a><br/>\n";
        echo sprintf("%0.2f", $length/60) . " min. | {$rating} user 
        rating<br/>\n";
        echo $media->group->description . "</td>\n";
        echo "</tr>\n";
      }
    }
    ?>
    </table>
  </body>
</html>

清单 6 包含两个部分,一个表单和它的结果页面,使用一个条件检验分隔。第一部分非常简单。它所做的就是生成一个表单(参见 图 4)以供用户输入搜索关键字并指定要在输出页面中显示的结果数。

图 4. 一个关键字搜索表单
一个关键字搜索表单

当用户提交该表单后,脚本首先检查是否输入了一个或多个搜索关键字,如果一个也没有输入,则中断脚本处理并显示一条错误消息。如果给出了搜索关键字,脚本将把关键字内插到一个 YouTube 提要 URL 中并生成一个对 YouTube API 的 REST 请求。然后将产生的 XML 文档转换为一个 SimpleXML 对象,并使用前面的清单 235 中介绍的技术进行遍历以生成一个结果页面。

清单 6 中有两点值得注意:

  • 用户输入到搜索表单中的关键字被重新格式化,以便应用于提要 URL。实现此操作的方法为:首先使用 trim() 删除字符串中所有的空白,然后通过 PHP 的 ereg_replace() 函数使用斜杠(/)压缩并替换关键字之间的空白。
  • 除了显示搜索到的结果总数外,清单 6 还使用 <openSearch:startIndex> 和 <openSearch:itemsPerPage> 元素表示所显示的结果集的开始和结束偏移量。

图 5 演示了 清单 6 生成的示例搜索结果页面:

图 5. 搜索结果页面
搜索结果页面

使用额外的搜索参数

要进行更精确的搜索,考虑向您的搜索查询添加下面这些参数:

  • start-index 参数指定提要的开始偏移量
  • vq 参数指定一个 URL 编码的搜索条件
  • format 参数将视频提要限制为一种特定格式
  • lr 参数将视频提要限制为使用特定语言描述
  • time 参数将提要(只针对一些标准提要)过滤为只包含某一时间范围内发布的视频

清单 7 使用这些额外的参数对 清单 6 中的搜索引擎脚本进行了增强,并使用 PEAR 中的 Pager 类添加了分页支持:

清单 7. 针对 YouTube 视频的增强的关键字搜索引擎
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Searching for videos by keyword</title>
    <style>
    img {
      padding: 2px; 
      margin-bottom: 15px;
      border: solid 1px silver; 
    }
    td {
      vertical-align: top;
    }
    td.line {
      border-bottom: solid 1px black;  
    }
    </style>
  </head>
  <body>
    <?php
    // if form not submitted
    // display search box
    if (!isset($_GET['submit'])) {
    ?>
    <h1>Keyword search</h1>  
    <form method="get" action="<?php echo 
     htmlentities($_SERVER['PHP_SELF']); ?>">
      Keywords: <br/>
      <input type="text" name="vq" />
      <p/>
      Items sorted by: <br/>
      <select name="s">
        <option value="viewCount">User views</option>
        <option value="rating">User rating</option>
        <option value="published">Publication time</option>
      </select>
      <p/>
      Items per page: <br/>
      <select name="i">
        <option value="10">10</option>
        <option value="25">25</option>
        <option value="50">50</option>
        <option value="100">100</option>
      </select>
      <p/>
      <input type="submit" name="submit" value="Search"/>  
    </form>
    <?php      
    // if form submitted
    } else {
      // check for search keywords
      // trim whitespace
      // encode search string
      if (!isset($_GET['vq']) || empty($_GET['vq'])) {
        die ('ERROR: Please enter one or more search keywords');
      } else {
        $vq = $_GET['vq'];
        $vq = ereg_replace('[[:space:]]+', ' ', trim($vq));
        $vq = urlencode($vq);
      }
      
      // set max results per page
      if (!isset($_GET['i']) || empty($_GET['i'])) {
        $i = 25;
      } else {
        $i = htmlentities($_GET['i']);
      }
      
      // set sort critera
      if (!isset($_GET['s']) || empty($_GET['s'])) {
        $s = 'viewCount';
      } else {
        $s = htmlentities($_GET['s']);
      }
      
      // set start index
      if (!isset($_GET['pageID']) || $_GET['pageID'] <= 0) {
        $o = 1;  
      } else {        
        $pageID = htmlentities($_GET['pageID']);
        $o = (($pageID-1) * $i)+1;  
      }
      
      // generate feed URL
      $feedURL = "http://gdata.youtube.com/feeds/api/videos
      ?vq={$vq}&orderby={$s}&max-results={$i}&start-index={$o}";
      
      // read feed into SimpleXML object
      $sxml = simplexml_load_file($feedURL);
      
      // get summary counts from opensearch: namespace
      $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
      $total = $counts->totalResults; 
      $startOffset = $counts->startIndex; 
      $endOffset = ($startOffset-1) + $counts->itemsPerPage;       
      
      // include Pager class
      require_once 'Pager/Pager.php';
      $params = array(
          'mode'       => 'Jumping',
          'perPage'    => $i,
          'delta'      => 5,
          'totalItems' => $total,
      );
      $pager = & Pager::factory($params);
      $links = $pager->getLinks();     
      ?>
      
      <h1>Search results</h1>
      <?php echo $total; ?> items found. 
      Showing items <?php echo $startOffset; ?> to 
      <?php echo $endOffset; ?>:
      <p/>
      
      <?php
      // print page links
      echo $links['all'];
      ?>
      
      <table>
      <?php    
      // iterate over entries in resultset
      // print each entry's details
      foreach ($sxml->entry as $entry) {
        // get nodes in media: namespace for media information
        $media = $entry->children('http://search.yahoo.com/mrss/');
        
        // get video player URL
        $attrs = $media->group->player->attributes();
        $watch = $attrs['url']; 
        
        // get video thumbnail
        $attrs = $media->group->thumbnail[0]->attributes();
        $thumbnail = $attrs['url']; 
        
        // get <yt:duration> node for video length
        $yt = $media->children('http://gdata.youtube.com/schemas/2007');
        $attrs = $yt->duration->attributes();
        $length = $attrs['seconds']; 
        
        // get <yt:stats> node for viewer statistics
        $yt = $entry->children('http://gdata.youtube.com/schemas/2007');
        $attrs = $yt->statistics->attributes();
        $viewCount = $attrs['viewCount']; 
      
        // get <gd:rating> node for video ratings
        $gd = $entry->children('http://schemas.google.com/g/2005'); 
        if ($gd->rating) {
          $attrs = $gd->rating->attributes();
          $rating = $attrs['average']; 
        } else {
          $rating = 0; 
        }

        // get video ID
        $arr = explode('/',$entry->id);
        $id = $arr[count($arr)-1];
             
        // print record
        echo "<tr><td colspan=\"2\" class=\"line\"></td></tr>\n";
        echo "<tr>\n";
        echo "<td><a href=\"{$watch}\">
        <img src=\"$thumbnail\"/></a></td>\n";
        echo "<td><a href=\"{$watch}\">
        {$media->group->title}</a><br/>\n";
        echo sprintf("%0.2f", $length/60) . " min. | {$rating} user rating | 
        {$viewCount} views<br/>\n";
        echo $media->group->description . "<br/>\n";
        echo "<a href=\"details.php?id=$id\">More information</a>
        </td>\n"; 
        echo "</tr>\n";
      }
    }
    ?>
    </table>
  </body>
</html>

经过增强的表单现在包含额外的排序参数,如 图 6 所示:

图 6. 增强后的关键字搜索表单
增强后的关键字搜索表单

用户提交表单时,用户输入的参数将被内插到一个提要 URL 中,并且处理和显示 XML 结果集。分页功能由 PEAR 的 Pager 类完成,该类使用 <openSearch:totalResults> 元素和 $_GET['pageID'] 变量的值自动生成到前一个结果集页面和下一个结果集页面的链接。当用户选择这些页面链接时,将使用 $_GET['pageID'] 变量的值重新计算起始索引,然后将起始索引重新插入到提要 URL 中以生成下一个结果集。

图 7 演示了修改后的输出,其中显示了页面链接:

图 7. 搜索结果页面
搜索结果页面

注意每个条目旁边的 More information 链接。该链接指向另一个 PHP 脚本,该脚本显示关于该视频的额外信息,包括它的评论、视频回复、作者信息和相关视频。下一节将讨论如何获取这些信息。


处理评论、视频回复和相关视频

如果仔细查看 清单 1 中的样例视频条目,将会发现除了视频元数据外,每个视频 <entry> 还包含针对评论、视频回复和相关视频的提要的链接。让我们具体讨论一下每一种情况。

评论是其他 YouTube 用户针对视频发布的反馈。您可以通过视频的评论提要访问视频的评论。这种提要的 URL 位于 <gd:comments> 元素中。下面是一个例子:

<gd:comments>
  <gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments' 
  countHint='124130'/>
</gd:comments>

通过一个 GET 请求检索这个链接将生成一个新的 XML 提要。在 清单 8 中,您可以观察到这种提要,其中包含由用户发布的视频评论。

清单 8. 评论提要示例
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' 
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'>
  <id>http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments</id>
  <updated>2008-03-13T11:58:12.729Z</updated>
  <category scheme='http://schemas.google.com/g/2005#kind' 
  term='http://gdata.youtube.com/schemas/2007#comment'/>
  <title type='text'>Comments on 'Evolution of Dance'</title>
  <logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo>
  <link rel='related' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg'/>
  <link rel='alternate' type='text/html' 
  href='http://www.youtube.com/watch?v=dMH0bHeiRNg'/>
  <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments'/>
  <link rel='self' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/
  comments?start-index=1&max-results=25'/>
  <link rel='next' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/
  comments?start-index=26&max-results=25'/>
  <author>
    <name>YouTube</name>
    <uri>http://www.youtube.com/</uri>
  </author>
  <generator version='beta' uri='http://gdata.youtube.com/'>
  YouTube data API</generator>
  <openSearch:totalResults>124353</openSearch:totalResults>
  <openSearch:startIndex>1</openSearch:startIndex>
  <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
  <entry>
    <id>http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments/
    206D45A8B1191817</id>
    <published>2008-03-13T04:57:22.000-07:00</published>
    <updated>2008-03-13T04:57:22.000-07:00</updated>
    <category scheme='http://schemas.google.com/g/2005#kind' 
    term='http://gdata.youtube.com/schemas/2007#comment'/>
    <title type='text'>how can you say ...</title>
    <content type='text'>how can you say that????!??!!!</content>
    <link rel='related' type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg'/>
    <link rel='http://gdata.youtube.com/schemas/2007#in-reply-to' 
    type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments/
    1DA6D946967C23A6'/>
    <link rel='alternate' type='text/html' 
    href='http://www.youtube.com/watch?v=dMH0bHeiRNg'/>
    <link rel='self' type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments/
    206D45A8B1191817'/>
    <author>
      <name>stez407</name>
      <uri>http://gdata.youtube.com/feeds/api/users/stez407</uri>
    </author>
  </entry>
  <entry>
  ...
  </entry>
</feed>

视频回复是 YouTube 用户为了响应另一个视频而发布的视频。您可以通过 <link rel='http://gdata.youtube.com/schemas/2007#video.responses' ...> 元素的 href 属性访问视频回复。下面是一个例子:

<link rel='http://gdata.youtube.com/schemas/2007#video.responses' 
type='application/atom+xml' 
href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/responses'/>

类似地,您可以通过 <link rel='http://gdata.youtube.com/schemas/2007#video.related' ...> 元素的 href 属性访问相关视频的列表。下面是一个例子:

<link rel='http://gdata.youtube.com/schemas/2007#video.related' 
type='application/atom+xml' 
href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/related'/>

准备好所有这些信息之后,可以轻松地创建一个更详细的视频信息 Web 页面。如 清单 9 所示:

清单 9. 检索视频详细信息的 PHP 脚本
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Retrieving video details</title>
    <style>
    img {
      padding: 2px; 
      margin-bottom: 15px;
      border: solid 1px silver; 
    }
    td {
      vertical-align: top;
    }
    </style>
  </head>
  <body>
    <?php
    // function to parse a video <entry>
    function parseVideoEntry($entry) {      
      $obj= new stdClass;
      
      // get nodes in media: namespace for media information
      $media = $entry->children('http://search.yahoo.com/mrss/');
      $obj->title = $media->group->title;
      $obj->description = $media->group->description;
      
      // get video player URL
      $attrs = $media->group->player->attributes();
      $obj->watchURL = $attrs['url']; 
      
      // get video thumbnail
      $attrs = $media->group->thumbnail[0]->attributes();
      $obj->thumbnailURL = $attrs['url']; 
            
      // get <yt:duration> node for video length
      $yt = $media->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->duration->attributes();
      $obj->length = $attrs['seconds']; 
      
      // get <yt:stats> node for viewer statistics
      $yt = $entry->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->statistics->attributes();
      $obj->viewCount = $attrs['viewCount']; 
      
      // get <gd:rating> node for video ratings
      $gd = $entry->children('http://schemas.google.com/g/2005'); 
      if ($gd->rating) { 
        $attrs = $gd->rating->attributes();
        $obj->rating = $attrs['average']; 
      } else {
        $obj->rating = 0;         
      }
        
      // get <gd:comments> node for video comments
      $gd = $entry->children('http://schemas.google.com/g/2005');
      if ($gd->comments->feedLink) { 
        $attrs = $gd->comments->feedLink->attributes();
        $obj->commentsURL = $attrs['href']; 
        $obj->commentsCount = $attrs['countHint']; 
      }
      
      // get feed URL for video responses
      $entry->registerXPathNamespace('feed', 'http://www.w3.org/2005/Atom');
      $nodeset = $entry->xpath("feed:link[@rel='http://gdata.youtube.com/schemas/
      2007#video.responses']"); 
      if (count($nodeset) > 0) {
        $obj->responsesURL = $nodeset[0]['href'];      
      }
         
      // get feed URL for related videos
      $entry->registerXPathNamespace('feed', 'http://www.w3.org/2005/Atom');
      $nodeset = $entry->xpath("feed:link[@rel='http://gdata.youtube.com/schemas/
      2007#video.related']"); 
      if (count($nodeset) > 0) {
        $obj->relatedURL = $nodeset[0]['href'];      
      }
    
      // return object to caller  
      return $obj;      
    }   
    
    // get video ID from $_GET 
    if (!isset($_GET['id'])) {
      die ('ERROR: Missing video ID');  
    } else {
      $vid = $_GET['id'];
    }
    
    // set video data feed URL
    $feedURL = 'http://gdata.youtube.com/feeds/api/videos/' . $vid;

    // read feed into SimpleXML object
    $entry = simplexml_load_file($feedURL);
    
    // parse video entry
    $video = parseVideoEntry($entry);
       
    // display main video record
    echo "<table>\n";
    echo "<tr>\n";
    echo "<td><a href=\"{$video->watchURL}\">
    <img src=\"$video->thumbnailURL\"/></a></td>\n";
    echo "<td><a href=\"{$video->watchURL}\">{$video->title}</a>
    <br/>\n";
    echo sprintf("%0.2f", $video->length/60) . " min. | {$video->rating} 
    user rating | {$video->viewCount} views<br/>\n";
    echo $video->description . "</td>\n";
    echo "</tr>\n";
    
    // read 'video comments' feed into SimpleXML object
    // parse and display each comment
    if ($video->commentsURL && $video->commentsCount > 0) {
      $commentsFeed = simplexml_load_file($video->commentsURL);    
      echo "<tr><td colspan=\"2\"><h3>" . $commentsFeed->title . 
      "</h3></td></tr>\n";
      echo "<tr><td colspan=\"2\"><ol>\n";
      foreach ($commentsFeed->entry as $comment) {
        echo "<li>" . $comment->content . "</li>\n";
      }
      echo "</ol></td></tr>\n";
    }
    
    // read 'video responses' feed into SimpleXML object
    // parse and display each video entry
    if ($video->responsesURL) {
      $responseFeed = simplexml_load_file($video->responsesURL);    
      echo "<tr><td colspan=\"2\"><h3>" . 
      $responseFeed->title . "</h3></td></tr>\n";
      foreach ($responseFeed->entry as $response) {
        $responseVideo = parseVideoEntry($response);
        echo "<tr>\n";
        echo "<td><a href=\"{$responseVideo->watchURL}\">
        <img src=\"$responseVideo->thumbnailURL\"/></a></td>\n";
        echo "<td><a href=\"{$responseVideo->watchURL}\"
        >{$responseVideo->title}</a><br/>\n";
        echo sprintf("%0.2f", $responseVideo->length/60) . " min. | 
        {$responseVideo->rating} user rating | {$responseVideo->viewCount} 
        views<br/>\n";
        echo $responseVideo->description . "</td>\n";
        echo "</tr>\n";      
      }
    }
    
    // read 'related videos' feed into SimpleXML object
    // parse and display each video entry
    if ($video->relatedURL) {
      $relatedFeed = simplexml_load_file($video->relatedURL);    
      echo "<tr><td colspan=\"2\"><h3>" . 
      $relatedFeed->title . "</h3></td></tr>\n";
      foreach ($relatedFeed->entry as $related) {
        $relatedVideo = parseVideoEntry($related);
        echo "<tr>\n";
        echo "<td><a href=\"{$relatedVideo->watchURL}\">
        <img src=\"$relatedVideo->thumbnailURL\"/></a></td>\n";
        echo "<td><a href=\"{$relatedVideo->watchURL}\">
        {$relatedVideo->title}</a><br/>\n";
        echo sprintf("%0.2f", $relatedVideo->length/60) . " min. | 
        {$relatedVideo->rating} user rating | {$relatedVideo->viewCount} 
        views<br/>\n";
        echo $relatedVideo->description . "</td>\n";
        echo "</tr>\n";      
      }
    }
    echo "</table>\n";    
    ?>
  </body>
</html>

清单 9 看上去可能有一点复杂,实际上并非如此。首先,它定义一个新的 parseVideoEntry() 函数,该函数接受一个 SimpleXML <entry> 对象作为输入,从该对象中提取数据(使用清单 236 中介绍的技术)并返回一个新的 stdClass 对象,对象属性与视频的关键属性相对应。将这些代码封装到一个函数中,便于理解和重用,并且使脚本更加易于阅读。

代码首先发送一个获取视频提要的 GET 请求,使用视频 ID 作为键(视频 ID 是 清单 7 中的 $_GET 数组通过 More information 链接获得的)。结果提要中的 <entry> 元素随后被传递给 parseVideoEntry(),后者返回一个包含视频基本信息的 stdClass 对象。

这还不算全部完成。除了检索诸如缩略图、标题和描述这样的基本视频信息,parseVideoEntry() 还提取针对视频评论、回复和相关视频的提要 URL。脚本随后为每种提要生成 GET 请求,解析生成的 <entry> 元素,然后将结果信息(包括评论、缩略图、相关视频的元数据和视频回复)插入到输出页面中。图 8 演示了输出页面的外观:

图 8. 显示视频评论、回复和相关视频的 Web 页面
显示视频评论、回复和相关视频的 Web 页面

访问作者信息

YouTube API 还允许开发人员访问上传特定视频的用户的个人信息。仔细查看 清单 1,您会发现 <author> 元素。该元素提供了视频所有者的用户名,以及用户个人信息提要的 URL。下面是一个例子:

<author>
  <name>judsonlaipply</name>
  <uri>http://gdata.youtube.com/feeds/api/users/judsonlaipply</uri>
</author>

对这个 URL 的 GET 请求将生成类似 清单 10 所示的 XML 提要:

清单 10. 作者提要示例
<?xml version='1.0' encoding='UTF-8'?>
<entry xmlns='http://www.w3.org/2005/Atom' 
xmlns:media='http://search.yahoo.com/mrss/' 
xmlns:yt='http://gdata.youtube.com/schemas/2007' 
xmlns:gd='http://schemas.google.com/g/2005'>
  <id>http://gdata.youtube.com/feeds/api/users/judsonlaipply</id>
  <published>2006-03-23T07:20:45.000-08:00</published>
  <updated>2008-03-13T09:33:36.000-07:00</updated>
  <category scheme='http://gdata.youtube.com/schemas/2007/channeltypes.cat' 
  term='Standard'/>
  <category scheme='http://schemas.google.com/g/2005#kind' 
  term='http://gdata.youtube.com/schemas/2007#userProfile'/>
  <title type='text'>judsonlaipply Channel</title>
  <link rel='http://gdata.youtube.com/schemas/2007#featured-video' 
  type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/
  videos/61heClTFc5w'/>
  <link rel='alternate' type='text/html' 
  href='http://www.youtube.com/profile?user=judsonlaipply'/>
  <link rel='self' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply'/>
  <author>
    <name>judsonlaipply</name>
    <uri>http://gdata.youtube.com/feeds/api/users/judsonlaipply</uri>
  </author>
  <yt:age>31</yt:age>
  <yt:username>judsonlaipply</yt:username>
  <yt:gender>m</yt:gender>
  <yt:location>US</yt:location>
  <media:thumbnail url='http://i.ytimg.com/vi/61heClTFc5w/default.jpg'/>
  <yt:statistics viewCount='1120502' videoWatchCount='858' 
  subscriberCount='21800' lastWebAccess='2008-03-11T15:36:18.000-07:00'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.favorites' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/favorites' 
  countHint='0'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.contacts' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/contacts' 
  countHint='2331'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.inbox' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/inbox' 
  countHint='20143'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.playlists' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/playlists'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.subscriptions' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/subscriptions' 
  countHint='1'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.uploads' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/uploads' countHint='2'/>
</entry>

如您所见,清单 10 提供了一份用户基本个人信息 — 用户姓名、位置、年龄和性别 — 以及各种 URL,包括用户最喜爱的视频、订阅、联系方式、收件箱(要求身份验证),以及上传的视频列表。因此可以很轻松地解析 清单 10 中的数据并将一些信息添加到 清单 9 生成的页面中。Listing 11 展示了这一变化:

清单 11. 检索详细作者信息的 PHP 脚本
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Retrieving video details</title>
    <style>
    img {
      padding: 2px; 
      margin-bottom: 15px;
      border: solid 1px silver; 
    }
    td {
      vertical-align: top;
    }
    </style>
  </head>
  <body>
    <?php
    // function to parse a video <entry>
    function parseVideoEntry($entry) {      
      $obj= new stdClass;
      
      // get author name and feed URL
      $obj->author = $entry->author->name;
      $obj->authorURL = $entry->author->uri;
      
      // get nodes in media: namespace for media information
      $media = $entry->children('http://search.yahoo.com/mrss/');
      $obj->title = $media->group->title;
      $obj->description = $media->group->description;
      
      // get video player URL
      $attrs = $media->group->player->attributes();
      $obj->watchURL = $attrs['url']; 
      
      // get video thumbnail
      $attrs = $media->group->thumbnail[0]->attributes();
      $obj->thumbnailURL = $attrs['url']; 
            
      // get <yt:duration> node for video length
      $yt = $media->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->duration->attributes();
      $obj->length = $attrs['seconds']; 
      
      // get <yt:stats> node for viewer statistics
      $yt = $entry->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->statistics->attributes();
      $obj->viewCount = $attrs['viewCount']; 
      
      // get <gd:rating> node for video ratings
      $gd = $entry->children('http://schemas.google.com/g/2005'); 
      if ($gd->rating) { 
        $attrs = $gd->rating->attributes();
        $obj->rating = $attrs['average']; 
      } else {
        $obj->rating = 0;         
      }
        
      // get <gd:comments> node for video comments
      $gd = $entry->children('http://schemas.google.com/g/2005');
      if ($gd->comments->feedLink) { 
        $attrs = $gd->comments->feedLink->attributes();
        $obj->commentsURL = $attrs['href']; 
        $obj->commentsCount = $attrs['countHint']; 
      }
      
      // get feed URL for video responses
      $entry->registerXPathNamespace('feed', 'http://www.w3.org/2005/Atom');
      $nodeset = $entry->xpath("feed:link[@rel='http://gdata.youtube.com/
      schemas/2007#video.responses']"); 
      if (count($nodeset) > 0) {
        $obj->responsesURL = $nodeset[0]['href'];      
      }
         
      // get feed URL for related videos
      $entry->registerXPathNamespace('feed', 'http://www.w3.org/2005/Atom');
      $nodeset = $entry->xpath("feed:link[@rel='http://gdata.youtube.com/
      schemas/2007#video.related']"); 
      if (count($nodeset) > 0) {
        $obj->relatedURL = $nodeset[0]['href'];      
      }
    
      // return object to caller  
      return $obj;      
    }   
    
    // get video ID from $_GET 
    if (!isset($_GET['id'])) {
      die ('ERROR: Missing video ID');  
    } else {
      $vid = $_GET['id'];
    }

    // set video data feed URL
    $feedURL = 'http://gdata.youtube.com/feeds/api/videos/' . $vid;

    // read feed into SimpleXML object
    $entry = simplexml_load_file($feedURL);
    
    // parse video entry
    $video = parseVideoEntry($entry);
       
    // display main video record
    echo "<table>\n";
    echo "<tr>\n";
    echo "<td><a href=\"{$video->watchURL}\">
    <img src=\"$video->thumbnailURL\"/></a></td>\n";
    echo "<td><a href=\"{$video->watchURL}\">{$video->title}</a>
    <br/>\n";
    echo sprintf("%0.2f", $video->length/60) . " min. 
    | {$video->rating} user rating | {$video->viewCount} views<br/>\n";
    echo $video->description . "</td>\n";
    echo "</tr>\n";
    
    // read 'author profile feed' into SimpleXML object
    // parse and display author bio
    $authorFeed = simplexml_load_file($video->authorURL);    
    echo "<tr><td colspan=\"2\"><h3>Author information</h3>
    </td></tr>\n";
    $authorData = $authorFeed->children('http://gdata.youtube.com/schemas/2007');
    echo "<tr><td>Username:</td><td>" . $video->author . 
    "</td></tr>\n";
    echo "<tr><td>Age:</td><td>" . $authorData->age . 
    "</td></tr>\n";
    echo "<tr><td>Gender:</td><td>" . 
    strtoupper($authorData->gender) . "</td></tr>\n";
    echo "<tr><td>Location:</td><td>" . $authorData->location
     . "</td></tr>\n";    
    
    // read 'video comments' feed into SimpleXML object
    // parse and display each comment
    if ($video->commentsURL && $video->commentsCount > 0) {
      $commentsFeed = simplexml_load_file($video->commentsURL);    
      echo "<tr><td colspan=\"2\"><h3>" . 
      $commentsFeed->title . "</h3></td></tr>\n";
      echo "<tr><td colspan=\"2\"><ol>\n";
      foreach ($commentsFeed->entry as $comment) {
        echo "<li>" . $comment->content . "</li>\n";
      }
      echo "</ol></td></tr>\n";
    }
    
    // read 'video responses' feed into SimpleXML object
    // parse and display each video entry
    if ($video->responsesURL) {
      $responseFeed = simplexml_load_file($video->responsesURL);    
      echo "<tr><td colspan=\"2\"><h3>" . 
      $responseFeed->title . "</h3></td></tr>\n";
      foreach ($responseFeed->entry as $response) {
        $responseVideo = parseVideoEntry($response);
        echo "<tr>\n";
        echo "<td><a href=\"{$responseVideo->watchURL}\">
        <img src=\"$responseVideo->thumbnailURL\"/></a></td>\n";
        echo "<td><a href=\"{$responseVideo->watchURL}\">
        {$responseVideo->title}</a><br/>\n";
        echo sprintf("%0.2f", $responseVideo->length/60) . " min. |
         {$responseVideo->rating} user rating | {$responseVideo->viewCount} 
         views<br/>\n";
        echo $responseVideo->description . "</td>\n";
        echo "</tr>\n";      
      }
    }
    
    // read 'related videos' feed into SimpleXML object
    // parse and display each video entry
    if ($video->relatedURL) {
      $relatedFeed = simplexml_load_file($video->relatedURL);    
      echo "<tr><td colspan=\"2\"><h3>" . 
      $relatedFeed->title . "</h3></td></tr>\n";
      foreach ($relatedFeed->entry as $related) {
        $relatedVideo = parseVideoEntry($related);
        echo "<tr>\n";
        echo "<td><a href=\"{$relatedVideo->watchURL}\">
        <img src=\"$relatedVideo->thumbnailURL\"/></a></td>\n";
        echo "<td><a href=\"{$relatedVideo->watchURL}\">
        {$relatedVideo->title}</a><br/>\n";
        echo sprintf("%0.2f", $relatedVideo->length/60) . " min. | 
        {$relatedVideo->rating} user rating | {$relatedVideo->viewCount} 
        views<br/>\n";
        echo $relatedVideo->description . "</td>\n";
        echo "</tr>\n";      
      }
    }
    echo "</table>\n";    
    ?>
  </body>
</html>

清单 11 中,parseVideoEntry() 函数被修改为在输出对象中包含作者姓名和作者提要 URL。在主脚本中,附加的脚本代码块发送一个获取提要 URL 的 GET 请求,并解析响应来提取和显示视频所有者的用户名、年龄、位置和性别。虽然这个脚本并没有检索有关用户最爱视频、联系方式和订阅的信息,但是执行起来并不困难,可以使用主作者提要中的 <link> 来实现。

图 9 显示了修改后的输出:

图 9. 显示作者信息的 Web 页面
显示作者信息的 Web 页面

结束语

本文介绍了如何使用 SimpleXML 将 YouTube 服务中的数据集成到 PHP 应用程序中。本文中的示例演示了以下内容:

  • 介绍 YouTube 的提要
  • 展示如何按照类别和关键字搜索视频
  • 解释如何提取视频的元数据
  • 演示如何获取辅助信息,例如评论、相关视频、视频回复和作者个人信息

这些示例证明,YouTube REST API 为开发人员提供了极好的灵活性,使他们可以自由创建新的 Web 应用程序。如果尝试将 YouTube 数据与来自其他 Web 服务的数据聚合,或者仅仅是想为 YouTube 社区构建一个定制的接口,那么 YouTube REST API 将非常有用。


下载

描述名字大小
本文的样例代码x-youtubeapi-code.zip8KB

参考资料

学习

获得产品和技术

  • PEAR Pager 包:下载此工具,将数组数据作为输入添加并指定将数据分配给页面的参数。
  • IBM 试用软件:使用 IBM 试用软件构建您的下一个开发项目,可直接从 developerWorks 下载获得。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


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


忘记密码?
更改您的密码

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

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

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

选择您的昵称



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

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

标有星(*)号的字段是必填字段。

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

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

 


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


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source
ArticleID=307939
ArticleTitle=结合使用 YouTube API 和 PHP
publish-date=05152008