12.5 电视节目时间表
电视节目时间表是一项非常实用的信息,本例的目标就是向用户提供电视节目时间表的在线查询服务。电视节目时间表是一种需要经常更新的信息,这里的最终数据源是TVMAO网站提供的。
12.5.1 概述
打开页面http://www.tvmao.com/ext/demo_tv.html,可以看到如图12.5所示的页面。这个界面就是TVMAO网站向其他站点提供的查询调用界面。如果作为一个查询项目,这个界面占用的空间就有点太大了。并且,查询项目一般更倾向于使用文本框或者下拉框进行输入。本例的目标就是把这个查询界面改造成如图12.6所示。

图12.5 TVMAO的数据调用演示界面图

图12.6 改良后的查询界面效果图
图12.7所示是单击【查询】按钮,查询结果界面。

图12.7 得到查询结果后的效果图
仔细观察上面的几个运行效果图,可以发现,与电视节目时间表查询相关的数据有两类。一类是相对固定的数据,如各省的数据、电台数据、电台各频道的数据。这些数据在短时间内基本没什么变化,为了使用方便,最好能把它收集到自己的服务器数据库中。另一类属于及时性的数据,数据内容和时间有密切关系,如各电台频道的电视节目时间表。这些数据经常更新,无法在本地固定存储,只能随时从TVMAO获得。电视节目时间表查询项目可以分为两部分:固定数据的获取和在线查询。
12.5.2 固定数据的获取
通过以上的分析可以发现,固定数据可以在一个表中进行存储。新建一个SQL数据库,在数据库中添加一个表,命名为TV。TV表用来记录每个电视台频道的数据,包括所属省市、所属电台以及频道的简写(也是TVMAO上本频道电视节目的url)。表TV的详细字段设计如表12-2所示。
表12-2 表TV的字段设计
|
字 段 名 |
字 段 类 型 |
字 段 说 明 |
|
Id |
int |
关键字,电视台的标志 |
|
Prov |
Nvarcahar(200) |
省市 |
|
Classname |
Nvarcahar(300) |
大类的名称 |
|
Channel |
Nvarcahar(300) |
电视台名称 |
|
url |
Nvarchar(500) |
频道的简写 |
如何获取这些数据信息呢?当然只能从TVMAO的演示界面中获取。查看如图12.5所示效果图的源文件,其包括如下代码:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> TVMAO 节目表风格定制示例 </title>
<meta name="Author" content="">
<meta name="Keywords" content="">
<meta name="Description" content="">
<script language="JavaScript">
var W_Height=530;//整个窗口的高度
var W_Width=580;//整个窗口的宽度
var Top_Height=60;//顶部导航的高度
var Left_Width=200;//左侧频道列表的宽度
var Area_ID="FJTV";//设置缺省的地区ID
</script>
</head>
<body>
<script type="text/JavaScript" src="http://www.tvmao.com/ext/tvmao.js"></script>
</body>
</html>
这个页面文件的头部定义了一些变量,而页面主体是http://www.tvmao.com/ext/tvmao.js这个脚本。下载这个脚本,发现脚本文件中有如下代码:
var DEF_W_HEIGHT="700";
DEF_W_WIDTH="600";
DEF_TOP_HEIGHT="60";
DEF_LEFT_WIDTH="200";
DEF_MAIN_WIDTH="400";
try{void (W_Height);void (W_Width);void (Top_Height);void (Left_Width);void (Main_Width);}catch(e){
W_Height=DEF_W_HEIGHT;W_Width=DEF_W_WIDTH;Top_Height=DEF_TOP_HEIGHT;Left_Width= DEF_LEFT_WIDTH;Main_Width=DEF_MAIN_WIDTH;}
if(isNaN(W_Height))
{W_Height=DEF_W_HEIGHT;}
if(isNaN(W_Width))
{W_Width=DEF_W_WIDTH;}if(isNaN(Top_Height)) {Top_Height=DEF_TOP_HEIGHT;}
if(isNaN(Left_Width))
{Left_Width=DEF_LEFT_WIDTH;}if(isNaN(Main_Width)) {Main_Width=DEF_MAIN_WIDTH;}
var Main_Width=W_Width-Left_Width; //主窗口节目表的宽度
//custom radio
var DEF_AREA_ID="CCTV";
try{void (Area_ID)}catch(e){Area_ID=DEF_AREA_ID;}
var st = window.location.protocol + "//" + window.location.hostname;
var s = '<table border=0 height="'+W_Height+'" width="'+W_Width+'" cellspacing=0 cellpadding=0><tr>';
s += '<td colspan=2 height='+Top_Height+'><iframe width="100%" style="margin-top: 0px;margin-left:0px;padding-left:2px;" height="100%" marginheight=0 frameborder=0 scrolling=no id=a_frame name=a_frame src="http://www.tvmao.com/ext/a.html?st='+st+'"> </iframe></td>';
s += '</tr><tr>';
s += '<td width='+Left_Width+' valign=top><iframe width="99%" height="100%" margin height=0 frameborder=0 scrolling=auto id=r_frame name=r_frame src="http://www.tvmao. com/ext/r.jsp?a='+Area_ID+'&st='+st+'"></iframe></td>';
s += '<td width='+Main_Width+' valign=top><iframe width="100%" height="100%" margin height=0 frameborder=0 scrolling=auto id=p_frame name=main_frame src="http://www. tvmao.com/ext/show_tv.jsp?a='+Area_ID+'&st='+st+'"></iframe></td>';
s += '</tr></table>';
document.write(s);
由上面的代码可以看出,TVMAO的演示界面包括了3个页面:http://www.tvmao.com/ ext/a.html用来显示所有省的信息、http://www.tvmao.com/ext/show_tv.jsp用来显示某个电台频道的节目时间表、http://www.tvmao.com/ext/r.jsp用来显示各省的所有电台和电台频道,这正是系统所需要的数据。以香港为例,打开http://www.tvmao.com/ext/r.jsp?a=HK这个页面,可以看到如下代码:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title></title>
<script type="text/javascript" src="qqmenu.js"></script>
<link href="http://www.tvmao.com/default_tv.css" rel="stylesheet" type="text/css">
<link id="custom_tvmao_tv" href="" rel="stylesheet" type="text/css">
<script type="text/javascript" src="http://www.tvmao.com/ext/chg_css.js"></script>
<BASE target="main_frame">
</head>
<body>
<table border="0" cellspacing="0" cellpadding="0" id="menuTab">
<tr>
<td id="menuTD">
<script type="text/javascript">
p = new qqMenu('p');
p.add(0,-1,'TVB','','menu');
p.add(1,0,'TVB翡翠台','http://www.tvmao.com/ext/show_tv.jsp?c=TVB1');
p.add(2,0,'TVB Pearl','http://www.tvmao.com/ext/show_tv.jsp?c=TVB2');
p.add(3,0,'TVB无线剧集台','http://www.tvmao.com/ext/show_tv.jsp?c=TVB3');
p.add(4,0,'TVB无线经典台','http://www.tvmao.com/ext/show_tv.jsp?c=TVB4');
p.add(5,0,'TVB无线音乐台','http://www.tvmao.com/ext/show_tv.jsp?c=TVB5');
p.add(6,0,'TVB无线新闻台','http://www.tvmao.com/ext/show_tv.jsp?c=TVB6');
p.add(7,0,'TVB无线健康台','http://www.tvmao.com/ext/show_tv.jsp?c=TVB7');
p.add(8,0,'TVB无线儿童台','http://www.tvmao.com/ext/show_tv.jsp?c=TVB8');
p.add(9,0,'TVB星河频道','http://www.tvmao.com/ext/show_tv.jsp?c=TVB9');
p.add(10,0,'TVB8','http://www.tvmao.com/ext/show_tv.jsp?c=TVB10');
p.add(11,-1,'华娱卫视','','menu');
p.add(12,11,'华娱卫视','http://www.tvmao.com/ext/show_tv.jsp?c=HUAYU1');
p.add(13,-1,'星空卫视','','menu');
p.add(14,13,'星空卫视','http://www.tvmao.com/ext/show_tv.jsp?c=XINGKONG1');
p.add(15,13,'星空卫视音乐频道','http://www.tvmao.com/ext/show_tv.jsp?c=STARTV1');
p.add(16,13,'星空卫视国际电影频道','http://www.tvmao.com/ext/show_tv.jsp?c=STARTV2');
p.add(17,13,'国家地理频道','http://www.tvmao.com/ext/show_tv.jsp?c=STARTV3');
p.add(18,13,'星空卫视英文节目表','http://www.tvmao.com/ext/show_tv.jsp?c=STARTV4');
p.add(19,-1,'凤凰卫视','','menu');
p.add(20,19,'凤凰卫视中文台','http://www.tvmao.com/ext/show_tv.jsp?c=PHOENIX1');
p.add(21,19,'凤凰卫视资讯台','http://www.tvmao.com/ext/show_tv.jsp?c=PHOENIX2');
p.add(22,19,'凤凰卫视欧洲台','http://www.tvmao.com/ext/show_tv.jsp?c=PHOENIX3');
p.add(23,19,'凤凰卫视美州台','http://www.tvmao.com/ext/show_tv.jsp?c=PHOENIX4');
p.add(24,19,'凤凰卫视电影台','http://www.tvmao.com/ext/show_tv.jsp?c=PHOENIX5');
p.add(25,-1,'香港亚洲电视','','menu');
p.add(26,25,'香港亚州电视本港台','http://www.tvmao.com/ext/show_tv.jsp?c=HKATV1');
p.add(27,25,'香港亚州电视国际台','http://www.tvmao.com/ext/show_tv.jsp?c=HKATV2');
p.add(28,25,'香港亚州电视美洲版东岸','http://www.tvmao.com/ext/show_tv.jsp?c=HKATV3');
p.add(29,25,'香港亚州电视美洲版西岸','http://www.tvmao.com/ext/show_tv.jsp?c=HKATV4');
p.add(30,-1,'阳光卫视','','menu');
p.add(31,30,'阳光卫视','http://www.tvmao.com/ext/show_tv.jsp?c=CHINASUN1');
p.add(32,-1,'香港美亚影视台','','menu');
p.add(33,32,'香港美亚影视台','http://www.tvmao.com/ext/show_tv.jsp?c=MATV1');
p.add(34,32,'香港电影台','http://www.tvmao.com/ext/show_tv.jsp?c=MATV2');
p.add(35,32,'香港美亚电视剧台','http://www.tvmao.com/ext/show_tv.jsp?c=MATV3');
p.add(36,32,'美亚电影台(香港)','http://www.tvmao.com/ext/show_tv.jsp?c=MATV4');
p.add(37,32,'美亚电影台(亚洲)','http://www.tvmao.com/ext/show_tv.jsp?c=MATV5');
p.add(38,-1,'东风卫视','','menu');
p.add(39,38,'东风卫视','http://www.tvmao.com/ext/show_tv.jsp?c=AZIOTV1');
p.add(40,-1,'天映频道','','menu');
p.add(41,40,'天映电影频道','http://www.tvmao.com/ext/show_tv.jsp?c=CELETV1');
document.write(p);
//p.itemStatus(1,0);
p.o(0);
</script> </td>
</tr>
</table>
</body>
</html>
系统所需要的固定数据全在这个页面。用Thief类获取这个页面,然后再用正则表达式获取每条记录,插入数据库即可。限于篇幅,具体过程读者可自己实现。
12.5.3 节目查询页面Tv.aspx
在线查询的界面比较简单,其设计时期效果如图12.8所示。
整个界面主要由3个下拉框组成,分别用来显示省份、电视台和频道。其中红色字体部分是异步加载数据时的信息提示区,最底部的黑色文字区域是用来显示查询结果的。Tv.aspx的详细实现代码如下:

图12.8 查询界面的设计时期图
<%@ Page Language="C#" %>
<!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>电视节目查询</title>
<script type="text/javascript" src="xmlhttp.js"></script>
</head>
<body>
<form id="form1" runat="server">
<div align="center">
<h2>
电视节目查询</h2>
<table border="0" cellpadding="0" cellspacing="0" width="500px">
<tr>
<td align="center" height="30">
省份:
<select
id="tvprov" onchange="gettvarray(this);changestv
(1,true);">
<option value="安徽">安徽</option>
<option value="澳门">澳门</option>
<option value="北京">北京</option>
<option value="福建">福建</option>
<option value="甘肃">甘肃</option>
<option value="广东">广东</option>
<option value="广西">广西</option>
<option value="贵州">贵州</option>
<option value="海南">海南</option>
<option value="海外">海外</option>
<option value="河北">河北</option>
<option value="河南">河南</option>
<option value="黑龙江">黑龙江</option>
<option value="湖北">湖北</option>
<option value="湖南">湖南</option>
<option value="吉林">吉林</option>
<option value="江苏">江苏</option>
<option value="江西">江西</option>
<option value="辽宁">辽宁</option>
<option value="内蒙古">内蒙古</option>
<option value="宁夏">宁夏</option>
<option value="山东">山东</option>
<option value="山西">山西</option>
<option value="陕西">陕西</option>
<option value="上海">上海</option>
<option value="四川">四川</option>
<option value="台湾">台湾</option>
<option value="天津">天津</option>
<option value="香港">香港</option>
<option value="新疆">新疆</option>
<option value="云南">云南</option>
<option value="浙江">浙江</option>
<option
selected="selected" value="中央台">中央台
</option>
<option value="重庆">重庆</option>
</select>
电视台:<select id="tv_ct"
onchange="changestv(1,true)"
style="width: 95px"></select>
</td>
</tr>
<tr>
<td height="30">
频道列表:<select id="tv_s3" onchange="gettvset()" style="width: 214px"></select></td>
</tr>
<tr>
<td align="center" height="25">
<input onclick="gettvset();" type="button" value="查 询" />
</td>
</tr>
<tr>
<td style="text-align: left">
<div style="display: none; color: Red; text-align: center" id="loadingflag">
正在查找您选择的电台的节目表</div>
<span id="resulttv" style="text-align: left">选择省份,频道列表,电视台即可查询相应电台近一周的节目。</span>
</td>
</tr>
</table>
<script type="text/javascript" src="tv.js"></script>
<br />
<br />
</div>
</form>
</body>
</html>
12.5.4 脚本文件Tv.js
与Tv.aspx页面文件相关的脚本文件是Tv.js。它负责向服务器发送异步请求,然后处理服务器的返回结果。在Tv.js中,定义了一个数组tvarray,用来存储各电视台频道。严格来说,tvarray是一个数组的数组,它里面的每个元素都是一个4维数组,分别表示省、电台、频道和频道编号。Tv.aspx页面初始化时,首先获取一个省的电台频道信息,存储在tvarray数组中。如果选择了其他省,则首先遍历tvarray数组,如果存在这个省的数据,直接取出。若不存在,则重新从服务器异步获取,把获取数据存储在数组中。
至于对某个电视频道节目时间表的查询,则是异步请求Tv.ashx,然后把返回结果合理显示。本章中对XMLHTTP的封装,仍采用的是前面章节中的XMLHTTP.js这个脚本。Tv.js的实现代码如下:
//和电视节目预告相关的脚本
var tvarray=new Array();//电视台频道数组
//更改频道下拉框数据或更改电台下拉框数据;index:省下拉框的选择项;isurl:;
//flag:为非空表示更改电台列表数据,为空表示更改频道列表数据
function changestv(index,isurl,flag)
{
var obj=$("tv_ct"); //电台下拉框
var obj2=$("tv_s3"); //频道下拉框
if(flag) //是要求更改频道下拉框的数据
{
obj=$("tvprov");
obj2=$("tv_ct");
}
obj2.options.length=0; //清空频道下拉框的数据
for(var i=0;i<tvarray.length;i++) //遍历频道数组
{
if(tvarray[i][index]==obj.value) //属于本电台
{
var tvx;
if(isurl) //有编码
{
tvx=new Option(tvarray[i][index+1],tvarray[i][3]);
}
else
{
tvx=new Option(tvarray[i][index+1],tvarray[i][index+1]);
}
var isexists=false; //此频道是否已经添加上
//遍历频道下拉框,查找是否已经添加了这个频道
for(var x=0;x<obj2.options.length;x++)
{
if(obj2.options[x].value==tvarray[i][index+1])
isexists=true;
}
if(!isexists)
obj2.options[obj2.length]=tvx; //在频道下拉框中添加一个选项
}
}
}
//从服务器得到某省电台列表后的回调函数
function rectvprov(req,data)
{
var xml=req.responseXML; //获得数据
if(xml==null)return;
var root=xml.documentElement;
if(root) //如果存在
{
//遍历每条数据,添加到频道数组中
for(var i=0;i<root.childNodes.length;i++)
{
var tvp_1=root.childNodes[i].selectSingleNode("prov").text;
//获取省信息
var tvc_1=root.childNodes[i].selectSingleNode("classname").text;
//获取电台信息
var tvch_1=root.childNodes[i].selectSingleNode("channel").text;
//获取频道信息
var tvu_1=root.childNodes[i].selectSingleNode("url").text; //频道编号
tvarray[tvarray.length]=new Array(tvp_1,tvc_1,tvch_1,tvu_1);//添加信息
}
}
changestv(0,false,1); //更改电台下拉框的数据
changestv(1,true); //更改频道下拉框的数据
}
//取得频道数组
function gettvarray(obj)
{
obj2 = $("tv_ct"); //电台下拉框
var tvp=obj.value; //省下拉框的值
if(tvp=="")return ;
obj2.options.length=0; //清空电台下拉框
var z=false; //本省的数据是否已经从服务器得到了
//遍历频道数组,查找本省数据是否已经获得
for(var i=0;i<tvarray.length;i++)
{
if(tvarray[i][0]==tvp)//找到
{
z=true;
break;
}
}
changestv(0,false,1); //更改频道下拉框的数据
if(!z) //本省数据没有,从服务器获得
var post="p="+escape(tvp); //构建省信息字符串
Request.sendGET("TV.ashx?type=0&"+post,rectvprov,null,null,null);
//发出获得数组的请求
}
}
//获取某天的电视节目
function gettvset(url,day)
{
if(!day)//如果没有日期,获取当前日期
{
var wday = new Date();
day = wday.getDay ();
}
//发送请求
Request.sendGET("TV.ashx?type=1&url="+$("tv_s3").value.split("-")[1]+"&day
="+day,rectv, null,null,faltv);
}
//成功接收服务器回调函数
function rectv(req,data)
{
if(req.responseText=="") //返回空
$("resulttv").innerHTML = "没有数据,请重新查询!";
else
$("resulttv").innerHTML = req.responseText;
}
//出现错误后的回调函数
function faltv(req,dat)
{
alert("查询失败,请重试!!");
}
//初始化下拉列表框
gettvarray($("tvprov"));
12.5.5 服务器端处理模块Tv.ashx
由上面的脚本代码可知,服务器端需要完成两个任务:一是返回给客户端某个省的电台频道列表;另一个是返回给客户端某个电视台频道的节目时间表。第一个任务直接查询数据库即可完成。第二个任务则需要调用Thief类,远程获取节目表,然后再对节目表进行过滤。Tv.ashx是以type类型来区别两类任务的。Tv.ashx的实现代码如下:
<%@ WebHandler Language="C#" Class="TV" %>
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Text.RegularExpressions;
using System.Xml;
using System.Web;
using System.Xml.Xsl;
using System.IO;
//电视节目处理handler
public class TV : IHttpHandler
{
public string TVList(string url, string day)
{
//获得某天的电视节目页面
string str = Thief.Get("http://www.tvmao.com/ext/show_tv.jsp?c=" + url + "&day=" + day);
//过滤数据
str = str.Substring(str.IndexOf("<table id=\"main_body\">") + "<table id=\"main_body\">".Length);
str = " <table cellspacing=0 cellpadding=0 style=\"font-size:12px\">" + str;
string first = ""; //日期导航
if (day != "7") //是周日
first = str.Substring(0, str.IndexOf("星期日</a> </td>") + "星期日</a> </td>".Length);
else
first = str.Substring(0, str.IndexOf("id=\"curWeek\">星期日</span> </a> </td>") + "id=\"curWeek\">星期日</span></a> </td>".Length);
first = Regex.Replace(first, "(?<=href=)(.*?=)([^&]*)(&day=)(\\d*)", "'javascript:gettvset(\"$2\",\"$4\");'", RegexOptions.IgnoreCase);
if (day != "7") //是周日
str = str.Substring(str.IndexOf("星期日</a> </td>")
+ "星期日</a>
</td>".Length);
else
str = str.Substring(str.IndexOf("id=\"curWeek\">星期日</span></a> </td>") + "id=\"curWeek\">星期日</span></a> </td>".Length);
str = str.Substring(0, str.IndexOf("</table>") + 8);
str = Tools.DelLink(str); //删除超链接
str = first + str;//合并
str = Tools.delOption(str); //删除控制符
if (str.IndexOf("TVMAO") != -1) //替换当没内容时的提示
str = str.Replace("TVMAO", "QuCha.Net");
return str;
}
//获得某省的电台数据
public XmlDocument TVArray(string p)
{
Sql s = new Sql();
//获得数据
string
str = s.GetDS("select prov, classname,channel,url from tv where
prov='" + p + "'").GetXml();
XmlDocument xml = new XmlDocument();
xml.LoadXml(str); //转化成xml
return xml;
}
public void ProcessRequest(HttpContext context)
{
if (context.Request["type"] == "0") //获取某省的电台数据
{
context.Response.ContentType = "text/xml";
context.Response.Write(TVArray(context.Request["p"]).OuterXml);
}
else //获取某电台特定时间的电视节目时间表
{
context.Response.ContentType = "text/plain";
context.Response.Write(TVList(context.Request["url"],
context.Request
["day"]));
}
}
public bool IsReusable //不重用
{
get
{
return false;
}
}
}







