使用JS和HttpWatch监控携程网低价机票信息(非广告纯代码)

生活 同时被 2 个专栏收录
1 篇文章 0 订阅
1 篇文章 0 订阅

1、 春节回家,为了订到便宜机票,需要不停的刷新携程网来查看。时间一长,感觉又费时又费力,要不写个自动程序来定时检查提示该多好啊。为达到此目的,决定使用JS和HttpWatch实现自动监控低价机票的功能。以下是实现流程。帖不了附件很麻烦啊...

2、 总体流程

------------------------------------------------------------------------------------------

开始->分析页面->模拟访问URL->抓取请求返回消息->定位结果表->转化DOM对象验证

------------------------------------------------------------------------------------------

3、 分析请求页面,查看页面页文件(完整文件请看附件),有如下信息比较关键。可以看到在Form中有flightway(选择方式,单程,双程),homecity(出发点),destcity1(到达点)和DDatePeriod1(出发时间)几个对象。

------------------------------------------------------------------------------------------
<FORM id=flightForm οnsubmit="return false;"

action=http://flights.ctrip.com/Domestic/ShowFareFirst.aspx method=post>

<DIV class=searchbox_fah>&nbsp;<DFN>自由·机+酒特惠</DFN><A id=airHotelBtn

href="javascript:void(0);">更实惠·更便捷</A></DIV>

<TABLE class=searchbox_content_fixed>

<TBODY>

<TR>

<TH>航程类型</TH>

<TD><LABEL class=index_label><INPUT type=radio CHECKED value=Single
------------------------------------------------------------------------------------------

4、对于flightway和DDatePeriod1比较容易确认内容,但对于homecity和destcity1内容是中文、汉语拼音,还是代码,只有测试后才知道。先后试了中文、汉语拼音后还是不行。再次查看源代码,发现了如下关键信息。这不就是航空城市代码吗,试了一下,可以了。然后筛出自己关心的两个代码:KMG(昆明)和NKG(南京)。附件是测试代码

-----------------------------------------------------------------

$.module.searchBox.airHotelList="BJS,SHA,CKG,DLC,TAO,NKG,HGH,XMN,CTU,SZX,CAN,KWL,KMG,LJG,CSX,SIA,WUH,TSN,HRB,KWE,URC,HAK,HET,TYN,FOC,HFE,NGB,KHN,NNG,SHE,WNZ,CGO";

$.module.pkgSearch.pkgStartCityHash={

-----------------------------------------------------------------

5、完成以上几步分析后,可以大致通过代码查询到机票信息。剩下的工作就需要使用到HttpWatch(版本6.0.14,低版本可能会导致抓取数据乱码无法解析)来完成请求返回数据的抓取。把查询请求信息先拼接为URL串(http://flights.ctrip.com/Domestic/ShowFareFirst.aspx?homecity=KMG&destcity1=NKG&DDatePeriod1=2011-02-13)。

6、通过HttpWatch查看返回的请求详细信息,分析后知道http://flights.ctrip.com/Domestic/ShowFareFirst.aspx这个请求里包含了所需的详细信息,主要的处理也针对这个URL项来处理。附件为该请求的返回消息内容。

7、开始编写测试驱动代码,使用了HttpWatch的IE控件(只需要安装HttpWatch即包含了IE控件)。编写RetryManage对象,主要包含了一些常量定义和一些用到的变量。CONST_URL是请求的URL,MATCH_URL是用于匹配HttpWatch返回请求结果列表中我们关心的URL(一个URL请求中,服务器会返回多个URL项,包括js、图片、页面信息等内容)。MATCH_BEG_MSG是在匹配到我们所关心的信息后,从信息中找到我们关注的HTML标记。MATCH_END_MSG是结束HTML标记(查找方式很多,本例仅使用indexOf方法来获取特定内容在整个字符串的开始、结束序号,用以确定子串内容,该方法并不好,随着网站的变化就会失效)。Control保存HttpWatch控制对象(只需获取一次即可),plugin保存HttpWatch的对应IE的ActiveXObject对象。isReady表示是否上下文环境是否已准备好

-----------------------------------------------------------------

function RetryManage()

{

// const url for air tickets query

this.CONST_URL = "http://flights.ctrip.com/Domestic/ShowFareFirst.aspx?homecity=KMG&destcity1=NKG&DDatePeriod1=2011-02-13";

 

// const url for match query

this.MATCH_URL = "http://flights.ctrip.com/Domestic/ShowFareFirst.aspx";

 

// const begin match tickets fee table

this.MATCH_BEG_MSG = "<table id=/"ctl00_MainContentPlaceHolder_LowestPriceWebControl_LowestPriceTable/"";

 

// const end match tickets fee table

this.MATCH_END_MSG = "</table>";

 

// inner object

this.control = null;

this.plugin = null;

this.isReady = false;

}

-----------------------------------------------------------------


8、 为RetryManage对象添加初始化方法,用于初始化上下文环境。初始化方法,获取IE控件等请参见HttpWatch接口说明(在安装HttpWatch时已自带,通过Help找到即可)。在初始化control和plugin对象成功后,就将isReady置为true,表示上下文环境可用。执行New方法后会打开一个IE窗口(测试时使用了360会不正常,请将默认程序设置为IE)。

-----------------------------------------------------------------

RetryManage.prototype.init = function()

{

try

{

// obtian httpwatch control

this.control = new ActiveXObject('HttpWatch.Controller');

 

// new IE window

this.plugin = this.control.IE.New();

 

// set ready status

this.isReady = true;

}

catch (e)

{

alert("init:" + e.message);

}

}

-----------------------------------------------------------------


9、 为RetryManage对象添加请求处理方法,除最后一步外,中间几步是HttpWatch请求的标准写法。Clear清除历史记录,Record开始记录请求历史记录,GotoURL访问指定URL(访问的请求、返回请求结果列表都会写会历史记录中),Wait等待请求完整返回后才处理(GotoURL是异步方法,如果不这样等待,则有可能会请求未完成程序已经开始处理了)。Stop是停止记录历史记录。然后就可以开始处理了,调用parserMessage方法解析请求(该方法是自定义的,下一步详述)

-----------------------------------------------------------------
RetryManage.prototype.procRequest = function()
{
// clear history log
this.plugin.clear();

// start record
this.plugin.Record();

// go to url
this.plugin.GotoURL(this.CONST_URL);

// wait server return
this.control.Wait(this.plugin, -1);

// stop record
this.plugin.Stop();

// parser message
this.parserMessage(this.plugin.Log.Entries);
}

-----------------------------------------------------------------

10、 为RetryManage对象添加请求记录解析方法。该方法处理比较简单,就是循环请求访问历史记录,找到我们关注的URL后就返回。注意不能使用==,而需要使用indexOf,应该entry.URL不仅仅是URL,还包含了URL中的查询参数等信息。用==是不能匹配上的。匹配完成后请调用parserContent方法处理返回消息内容(该方法是自定义的,下一步详述),完成后结束循环。

-----------------------------------------------------------------
RetryManage.prototype.parserMessage = function(entries)
{
// total entries(request message)
var enCount = entries.Count;
var entry = null;

for (var i = 0; i < enCount; i++)
{
entry = entries.Item(i);

if (entry.URL.indexOf(this.MATCH_URL) != -1)
{
this.parserContent(entry);
break;
}
}
}

-----------------------------------------------------------------

11、 为RetryManage对象添加请求内容解析方法。该方法处理也比较简单,就是使用indexOf方法从字符串中找到匹配,然后获取序号。var begIndex = feeMsg.indexOf(this.MATCH_BEG_MSG);获取到开始串的开始序号,然后使用var endIndex = feeMsg.indexOf(this.MATCH_END_MSG, begIndex);获取结束串的开始序号,注意多了一个begIndex参数是为了保证结束串是开始串之后第一个,否则会导致查询内容不正确。然后使用feeMsg = feeMsg.substring(begIndex, endIndex + this.MATCH_END_MSG.length);取出字串,注意endIndex只是结束串的起始位置,要获得完整子串,还需要在endIndex加上this.MATCH_END_MSG.length。完成后,就可以通过 var tab = document.getElementsByTagName("TABLE")(0); tab.outerHTML = feeMsg;把子串赋值给页面TABLE对象(从这里能看到获取到的子串是否正确)。最后调用foundLower(该方法是自定义的,下一步详述)查找低价信息,第一个参数是指定的日期,第二个参数是指定的最大金额。

-----------------------------------------------------------------
RetryManage.prototype.parserContent = function(entry)
{
var feeMsg = entry.content.data;

var begIndex = feeMsg.indexOf(this.MATCH_BEG_MSG);

if (begIndex == -1)
{
alert("Can not found fee info");
return;
}

var endIndex = feeMsg.indexOf(this.MATCH_END_MSG, begIndex);

if (endIndex == -1)
{
alert("Can not found fee info");
return;
}

feeMsg = feeMsg.substring(begIndex, endIndex + this.MATCH_END_MSG.length);
var tab = document.getElementsByTagName("TABLE")(0);
tab.outerHTML = feeMsg;

this.foundLower("02-13", 1300);
}

-----------------------------------------------------------------


12、 为RetryManage对象添加查找低价信息方法。主要处理就是将表格中数据解析到js对象列表中,然后循环和查询条件匹配。对于找到的信息(包括找到和没有找到)都写入页面对象textarea中,便于监控程序是否在正常运行。附件为解析后得到的表格HTML代码。

-----------------------------------------------------------------
RetryManage.prototype.foundLower = function(needDate, needMaxFee)
{
var tab = document.getElementsByTagName("TABLE")(0);
var fi = null;
var fd = null;
var ff = null;
var feeList = new Array();

for (var i = 1; i < tab.rows.length; i++)
{
for (var j = 0; j < tab.rows(i).cells.length; j++)
{
if (tab.rows(i).cells(j).hasChildNodes())
{
if (!tab.rows(i).cells(j).firstChild.hasChildNodes())
{
continue;
}

fd = trim(tab.rows(i).cells(j).firstChild.childNodes[0].toString());
ff = trim(tab.rows(i).cells(j).firstChild.childNodes[2].innerText);
ff = ff.substring(1);

fi = new FeeItem(fd, ff);

feeList.push(fi);
}
}
}

var hisMsg = document.getElementById("hisMsg");
var curMsg = null;

for (var i = 0; i < feeList.length; i++)
{
if (needDate == feeList[i].getDate())
{
if (new Number(needMaxFee) >= feeList[i].getFee())
{
hisMsg.innerText += "/n/rHas lower tickets fee at:" + feeList[i].getFee() + ". Date:" + new Date();
return;
}
else
{
curMsg = feeList[i].getFee();
}
}
}

hisMsg.innerText += "/n/rNo lower tickets fee below then " + needMaxFee + " Current is:" + curMsg + ". Date:" + new Date();
}

-----------------------------------------------------------------

13、 至此,所有处理方法都已编写完成,就可以编写doStart测试驱动方法。然后在处理完成后调用window.setTimeout(doStart, 30000);设置为每30秒处理一次(不包含处理时间)。将new RetryManage写在doStart方法,避免在每次执行时被重新创建,导致isReady状态丢失,造成每执行一次就打开一个IE窗口。

-----------------------------------------------------------------
RetryManage.prototype.isAllReady = function()
{
return this.isReady;
}

RetryManage.prototype.doTest = function()
{
try
{
if (!this.isAllReady())
{
this.init();
}

if (!this.isAllReady())
{
return;
}

this.procRequest();

window.setTimeout(doStart, 30000);
}
catch (e)
{
alert(e.message);
}
}

function doStart()
{
rm.doTest();
}

var rm = new RetryManage();
doStart();

-----------------------------------------------------------------

14、 到此就全部完成了,剩下的工作就是验证。以下就是源代码.

-----------------------------------------------------------------
<html>

<head>
<title>Retry</title>
<meta http-equiv="CONTENT-TYPE" content="TEXT/HTML; CHARSET=GBK"/>
<meta http-equiv="PRAGMA" content="no-cache">
<meta http-equiv="CACHE-CONTROL" content="no-cache">
<meta http-equiv="EXPIRES" content="0">
<meta NAME="AUTHOR" CONTENT="xuejun">
<meta NAME="KEYWORDS" CONTENT="JS HTTPWATCH">
<meta NAME="DESCRIPTION" CONTENT="Retry">
</head>

<body>

<form>

<table id="retMsg"></table>
<textArea id="hisMsg" rows="10" cols="100"></textArea>

</form>

</body>

</html>

<script>
<!--


function RetryManage()
{
// const url for air tickets query
this.CONST_URL = "http://flights.ctrip.com/Domestic/ShowFareFirst.aspx?homecity=KMG&destcity1=NKG&DDatePeriod1=2011-02-13";

// const url for match query
this.MATCH_URL = "http://flights.ctrip.com/Domestic/ShowFareFirst.aspx";

// const begin match tickets fee table
this.MATCH_BEG_MSG = "<table id=/"ctl00_MainContentPlaceHolder_LowestPriceWebControl_LowestPriceTable/"";

// const end match tickets fee table
this.MATCH_END_MSG = "</table>";

// inner object
this.control = null;
this.plugin = null;
this.isReady = false;
}

 

RetryManage.prototype.init = function()
{
try
{
// obtian httpwatch control
this.control = new ActiveXObject('HttpWatch.Controller');

// new IE window
this.plugin = this.control.IE.New();

// set ready status
this.isReady = true;
}
catch (e)
{
alert("init:" + e.message);
}
}


RetryManage.prototype.procRequest = function()
{
// clear history log
this.plugin.clear();

// start record
this.plugin.Record();

// go to url
this.plugin.GotoURL(this.CONST_URL);

// wait server return
this.control.Wait(this.plugin, -1);

// stop record
this.plugin.Stop();

// parser message
this.parserMessage(this.plugin.Log.Entries);
}


RetryManage.prototype.parserMessage = function(entries)
{
// total entries(request message)
var enCount = entries.Count;
var entry = null;

for (var i = 0; i < enCount; i++)
{
entry = entries.Item(i);

if (entry.URL.indexOf(this.MATCH_URL) != -1)
{
this.parserContent(entry);
break;
}
}
}


RetryManage.prototype.parserContent = function(entry)
{
var feeMsg = entry.content.data;

var begIndex = feeMsg.indexOf(this.MATCH_BEG_MSG);

if (begIndex == -1)
{
alert("Can not found fee info");
return;
}

var endIndex = feeMsg.indexOf(this.MATCH_END_MSG, begIndex);

if (endIndex == -1)
{
alert("Can not found fee info");
return;
}

feeMsg = feeMsg.substring(begIndex, endIndex + this.MATCH_END_MSG.length);
var tab = document.getElementsByTagName("TABLE")(0);
tab.outerHTML = feeMsg;

this.foundLower("02-13", 1300);
}


RetryManage.prototype.foundLower = function(needDate, needMaxFee)
{
var tab = document.getElementsByTagName("TABLE")(0);
var fi = null;
var fd = null;
var ff = null;
var feeList = new Array();

for (var i = 1; i < tab.rows.length; i++)
{
for (var j = 0; j < tab.rows(i).cells.length; j++)
{
if (tab.rows(i).cells(j).hasChildNodes())
{
if (!tab.rows(i).cells(j).firstChild.hasChildNodes())
{
continue;
}

fd = trim(tab.rows(i).cells(j).firstChild.childNodes[0].toString());
ff = trim(tab.rows(i).cells(j).firstChild.childNodes[2].innerText);
ff = ff.substring(1);

fi = new FeeItem(fd, ff);

feeList.push(fi);
}
}
}

var hisMsg = document.getElementById("hisMsg");
var curMsg = null;

for (var i = 0; i < feeList.length; i++)
{
if (needDate == feeList[i].getDate())
{
if (new Number(needMaxFee) >= feeList[i].getFee())
{
hisMsg.innerText += "/n/rHas lower tickets fee at:" + feeList[i].getFee() + ". Date:" + new Date();
return;
}
else
{
curMsg = feeList[i].getFee();
}
}
}

hisMsg.innerText += "/n/rNo lower tickets fee below then " + needMaxFee + " Current is:" + curMsg + ". Date:" + new Date();
}


RetryManage.prototype.isAllReady = function()
{
return this.isReady;
}


RetryManage.prototype.doTest = function()
{
try
{
if (!this.isAllReady())
{
this.init();
}

if (!this.isAllReady())
{
return;
}

this.procRequest();

window.setTimeout(doStart, 300000);
}
catch (e)
{
alert(e.message);
}
}


function FeeItem(fDate, fFee)
{
this.fDate = fDate;
this.fFee = fFee;
}


FeeItem.prototype.getDate = function()
{
return this.fDate;
}


FeeItem.prototype.getFee = function()
{
return new Number(this.fFee);
}


FeeItem.prototype.toString = function()
{
return this.fDate + ":" + this.fFee;
}


function trim(str)
{
if (str == null)
{
return "";
}

return str.replace(/(^/s*)|(/s*$)/g, "");
}


function doStart()
{
rm.doTest();
}


var rm = new RetryManage();
doStart();

 

//-->
</script>

-----------------------------------------------------------------

 

  • 1
    点赞
  • 4
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值