搭配 YouTube Data API 開發 User Control:YouTube 影片讀取器
開發目標
儘管 Data API 全面開放多種互動功能,本篇內容先放在介紹讀取影片饋送資料 (Video Feeds),藉此熟悉與 Data API 互動的方式,未來有機會再考慮更進階的應用。既然如此,來寫一個 YouTube 影片讀取器,希望可以取得我們上傳的影片,由於預料到會有改版需求,因此得考慮將資料與呈現分離,一連串的動作最好包成一個 User Control,方便日後使用。準備工作
取回影片饋送資料的方式就是對 Data API 送出請求,路徑會根據回應類型有所不同,可以參考:Developer's Guide: Data API Protocol – Video Feed Types,此外 Data API 可以接受查詢參數讓使用者微調回傳的結果,說明文件在:Developer's Guide: Data API Protocol – API Query Parameters,雖然目前尚未中文化,不過有心想要研究的人以上兩篇算是必讀吧…,假如一堆文字實在無法引起你的興趣,有一段說明如何送出搜尋的影片:YouTube APIs: Search Explained,對常用的用法做了重點介紹。另外官方還提供一個網頁:Interactive YouTube API Demo Beta,類似查詢產生器的頁面,可以用設定的方式產出一組 Resulting URI(即 request URL),跟前面提到的文件對照使用可以幫助理解,比方說參考下圖設定,這是取回使用者上傳影片最簡單的方式(Step 1 - 4):
有個小建議,不管你怎麼設定最終都要點 Submit 測試一下 (Step 4),若這組 URL 可以被 Data API 接受,就會收到如下圖的回應:
基本測試
由於剛剛設定輸出格式為 RSS 2.0,標準輸出會類似這樣:<!-- 資料來源:W3C School - RSS Sample -->
<rss version="2.0">
<channel>
<title>W3Schools Home Page</title>
<link>http://www.w3schools.com</link>
<description>Free web building tutorials</description>
<item>
<title>RSS Tutorial</title>
<link>http://www.w3schools.com/rss</link>
<description>New RSS tutorial on W3Schools</description>
</item>
<item>
<title>XML Tutorial</title>
<link>http://www.w3schools.com/xml</link>
<description>New XML tutorial on W3Schools</description>
</item>
</channel>
</rss>
以 Data API 真正回傳的 XML 檔來說,影片資料是包在 <description>…</description> 節點底下,內文是一串 HTML 標記,為確保後續開發工作能順利進行,先來簡單寫個測試程式,新增一支 aspx 內容如下:
<div>
<asp:XmlDataSource
ID="XmlDataSource1"
runat="server"
DataFile="http://gdata.youtube.com/feeds/base/users/{userId}/uploads?max-results=5&alt=rss"
XPath="/rss/channel/item"></asp:XmlDataSource>
<asp:Repeater ID="Repeater1" runat="server" DataSourceID="XmlDataSource1">
<ItemTemplate>
<asp:Literal ID="content" runat="server" Text='<%# XPath("description") %>'></asp:Literal>
</ItemTemplate>
</asp:Repeater>
</div>
</form>
XmlDataSource 中的 XPath 運算式指定為 "/rss/channel/item",先篩選出所有 item 節點內容,再將 Repeater 樣板中的 Literal.Text 屬性繫結到 XPath("description") 運算式,底下為執行效果:
由測試雛型看來,我們已經可以順利跟 Data API 介接,可惜離完成還有一段距離,有幾個缺點必須改進:
- 接收到的 HTML,參雜了 Inline CSS,不利版面調整。
- 程式邏輯未元件化,缺乏重用性。
- 查詢參數若需變動,必須針對 XmlDatasource.DataFile 屬性異動,不利快速切換。
特別是第 1 點,非常不利未來版面更動,要改善勢必要對 HTML 做拆解…,此時我想到了小朱大大曾經介紹過的:HTML Agility Pack:簡單好用的快速 HTML Parser,當初拜讀完也還未曾用上,趁這次的機會用它來試試看吧!
拆解資料
看過朱大的教學文,發現這元件拆解 HTML 是用 XPath 運算式,雖然 HTML Agility Pack 本身有提供 HAP Explorer 程式輔助取得 XPath 語法,可惜不怎麼好用,如果大家有同樣的困擾,我推薦 XML Notepad 2007 這套工具,點部落另一位大大:星寂有介紹過(請看:Tools – XML Notepad);因為先天上 HTML 規範本身並不如 XML 來得嚴謹,因此借用 XML Notepad 巡覽 HTML 文件有一個小"眉角",你必須自行確認 HTML 內容所有標籤都已滿足閉合規則(起始標籤一定要伴隨成對的結束標籤,空元素要在右角括號前面加一個斜線,如 <example />),否則嘗試用 XML Notepad 開啟會收到類似這樣的錯誤:遵照閉合規則修正即可,至於詳細取得 XPath 運算式過程就不提了,請自行參考星寂大大寫的介紹文,總之目的是取得影片連結、縮圖路徑、影片標題、觀看次數、時間長度等資料,轉存到 DataTable 裡,重點程式碼如下:
{
string userId = "userId"; // 置換有效帳號
int numberOfVideos = 5; // 限定影片數量
// 載入遠端 XML
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(
String.Format(
"http://gdata.youtube.com/feeds/base/users/{0}/uploads?max-results={1}&alt=rss",
userId,
numberOfVideos));
// 取得節點清單
XmlNodeList nodeList = xmlDoc.SelectNodes("/rss/channel/item/description");
DataTable dt = new DataTable();
dt.Columns.Add("vLink"); // 影片連結
dt.Columns.Add("ImageUrl"); // 縮圖路徑
dt.Columns.Add("Title"); // 影片標題
dt.Columns.Add("Views"); // 觀看次數
dt.Columns.Add("Time"); // 時間長度
HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
string prefix = "/div/table/tbody";
// 逐一拆解資料
foreach (XmlNode node in nodeList)
{
htmlDoc.LoadHtml(node.InnerText);
string vLink =
htmlDoc.DocumentNode.SelectSingleNode(prefix + "/tr[1]/td[2]/div[1]/a").GetAttributeValue("href", "");
string imageUrl =
htmlDoc.DocumentNode.SelectSingleNode(prefix + "/tr[1]/td[1]/div/a/img").GetAttributeValue("src", "");
string title =
htmlDoc.DocumentNode.SelectSingleNode(prefix + "/tr[1]/td[2]/div[1]/a/text()").InnerText;
string views =
htmlDoc.DocumentNode.SelectSingleNode(prefix + "/tr[1]/td[3]/div[2]/text()").InnerText;
string time =
htmlDoc.DocumentNode.SelectSingleNode(prefix + "/tr[2]/td[1]/span[2]/text()").InnerText;
DataRow dataRow = dt.NewRow();
dataRow[0] = vLink;
dataRow[1] = imageUrl;
dataRow[2] = title;
dataRow[3] = views;
dataRow[4] = time;
dt.Rows.Add(dataRow);
}
RepYouTube.DataSource = dt;
RepYouTube.DataBind();
nodeList = null;
htmlDoc = null;
}
利用 HTML Agility Pack 拆解果然省事,不一會兒功夫就抓出需要的資料,再搭配資料繫結的技巧,基本上要怎麼呈現只是樣版在抽換而已。
封裝控件
前面提到的缺點目前暫時排除了第一項,為了提高可重用性及易用性,還必須做點努力。以下看圖說故事:假設呈現方式以每支影片為一個單位,每個單位還細分為三個區塊,分別為:紅 - 縮圖區塊、藍 - 標題區塊、綠 - 描述區塊,除了每個單位可以給定 CssClass 屬性控制最外層 CSS 以外,每個區塊也有相對應的 ThumbnailCssClass、TitleCssClass、DescriptionCssClass 屬性可以進一步設定包含範圍的 CSS,加上 UserId、NumberOfVideos 兩個控制 Data API 要求路徑的屬性,總共要公開的屬性可以參考底下程式碼:
private string cssClass = null;
/// <summary>
/// 取得或設定 YouTube 影片讀取器所呈現的階層樣式表 (CSS)
/// </summary>
public string CssClass
{
get { return cssClass; }
set { cssClass = value; }
}
private string thumbnailCssClass = null;
/// <summary>
/// 取得或設定值,代表 YouTube 影片縮圖所呈現的階層樣式表 (CSS)
/// </summary>
public string ThumbnailCssClass
{
get { return thumbnailCssClass; }
set { thumbnailCssClass = value; }
}
private string titleCssClass = null;
/// <summary>
/// 取得或設定值,表示 YouTube 影片標題所呈現的階層樣式表 (CSS)
/// </summary>
public string TitleCssClass
{
get { return titleCssClass; }
set { titleCssClass = value; }
}
private string descriptionCssClass = null;
/// <summary>
/// 取得或設定值,表示 YouTube 影片描述所呈現的階層樣式表 (CSS)
/// </summary>
public string DescriptionCssClass
{
get { return descriptionCssClass; }
set { descriptionCssClass = value; }
}
private string userId = "userId"; // 置換有效帳號
/// <summary>
/// 取得或設定值,表示 YouTube 影片上傳用戶 ID
/// </summary>
public string UserId
{
get
{
return userId;
}
set
{
userId = value;
}
}
private int numberOfVideos = 5;
/// <summary>
/// 取得或設定值,表示要顯示的 YouTube 影片數量
/// </summary>
public int NumberOfVideos
{
get { return numberOfVideos; }
set { numberOfVideos = value; }
}
此時原本 Page_Load 事件底下前兩行程式碼要拿掉,改用 UserId、NumberOfVideos 屬性餵值,這麼一來透過設定的方式即可快速切換內容,最後整個改寫成 User Control 之後,使用範例類似這樣:
<%@ Register Src="UserCtrls/YoutubeReader.ascx" TagName="YoutubeReader" TagPrefix="uc1" %>
<!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">
<head runat="server">
<title>YouTube 影片讀取器 Demo</title>
<style type="text/css">
.ytr
{
color: #000000;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
width: 300px;
}
.ytr-thumbnail
{
border: 3px double gray;
width: 80px;
}
.ytr-title
{
font-size: 14px;
font-weight: bold;
}
.ytr-description
{
color: #666666;
font-size: 11px;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<div>
<h3>
YoutubeReader 使用實例:
</h3>
<uc1:YoutubeReader ID="YoutubeReader1" runat="server"
UserId="{userId}"
NumberOfVideos="3"
CssClass="ytr"
TitleCssClass="ytr-title"
ThumbnailCssClass="ytr-thumbnail"
DescriptionCssClass="ytr-description" />
<hr />
</div>
</form>
</body>
</html>
呈現的效果:
結語
跨系統整合一向都不是件輕鬆的事,尤其常常得要融合多項技術才能順利完成,好比本篇文章所載內容是我在開發實際需求的完整經歷,其中耗費在解決衍生問題的時間反而比較多…,好在一分耕耘一分收穫,以往對於類似的 XML 應用其實底子很薄弱,這次之後累積了不少經驗值,算是最值得慶幸的地方!回過頭來檢視這個 YouTubeReader,個人認為還有改進空間,特別是拆解 HTML 那一段存在滿大的隱憂,由於 XPath 運算式都是寫死的,萬一 HTML 內容(結構)變更了就得回頭來修正…,雖然心裡大概有個解法,不過仍屬構想階段(事實上是很多細節仍未研究透徹XD),況且短期間內目前作法應該還能擋一陣子,就讓我苟且一下兼且賣個關子當做之後的發文素材吧!
範例下載
參考資料
- Powered by YouTube - Overview of APIs and Tools(Video)
- YouTube API Blog
- YouTube Data API - Developer's Guide
- YouTube API 個案研究