用 JavaScript 破解我的蜜月之旅🦒
想看更多类似的精彩内容吗?订阅我的新闻邮件,请访问:alec.coffee/signup
当我妻子在 Instagram 上看到这个帖子时,她立刻被迷住了:
肯尼亚蜜月即将到来,我们开始预订房间。我咨询了几年前去过肯尼亚的阿姨,她住在这里,预订起来很顺利。但当我们得知这家酒店提前一两年就订满了时,我们感到很惊讶。
突然爆红肯定有原因。稍微查了一下,发现这家店最近上了艾伦秀。
该死,艾伦。
起初,我们查看了他们的网站,看看我们去肯尼亚的日期是否有空,但没找到。之后我们给庄园发了邮件,结果还是没收到,被告知我们被列入了“候补名单”。可能是因为要和候补名单上的其他人竞争,而且我们的行程只有几个月了,我和妻子的希望变得渺茫。
寻找解决方案
他们用来显示房间可用性的网站是只读的,没有预订房间的功能。

打电话和发邮件是联系他们的唯一方式,过程缓慢而艰巨。我以为等到有空位时,他们的网站会先更新,然后才会开始联系候补名单上的人。这样,即使有人落选,他们仍然会有预订。
假设
我接下来想的是,如果我们在房间空出来那天联系他们,很可能就能绕过候补名单。但每小时都查看网站可不是什么好玩的事。
我穿上程序员的裤子,心想这应该能派上用场,一个老派的网络抓取工具,爵士乐手。我每30分钟访问一次网站,然后给我和妻子的手机发短信,这样我们就可以给他们打电话了。这个90年代的肯尼亚网站不太可能有防机器人程序的保护措施。
看起来像是一张简单的表格,结果就是一张简单的表格:
// Example of a unbooked day HTML node
<td
width="25"
unselectable="on"
ab="0"
style="border-top: none; "
name="WB15:Salas Camp:Keekorok Honeymoon
Tent-Tent 1:0*:1:11e8485f8b9898cc8de0ac1f6b165406:0"
id="WB15:07:28:2019"
darkness="0"
onmousedown="mouseDownFunction(arguments[0]);"
onmouseup="cMouseUp(arguments[0]);"
onmouseover="mouseOverFunction(arguments[0]);"
class="overbooking calIndicator0"
>
1
</td>
这就是我需要找到的,如果节点文本是1
,则它可用。
在研究了简单的 HTML 结构之后,我开始编写 Node.js 服务来废弃它。我偶然发现了一个 NPM 模块crawler,它提供了我所需的一切。
const Crawler = require("crawler");
const startCrawler = async () => {
return new Promise(resolve => {
const c = new Crawler({
maxConnections: 10,
callback: (error, res, done) => {
if (error) {
console.log(error);
throw new Error(
`Error with sending request to website! ${JSON.stringify(error)}`
);
}
const $ = res.$;
// get the table of bookings
const results = $("#tblCalendar tbody tr").slice(12, 17);
done();
// return the results
resolve(results);
}
});
// hit giraffe manors website
c.queue(
"http://thesafaricollection.resrequest.com/reservation.php?20+2019-02-08" +
"+RS12:RS14:RS16:WB656:RS2274+15:20:30:25++WB5++n/a++true+true+0+0"
);
});
};
这需要一些调试,但现在我可以使用 Giraffe Manors 网站的 HTML 来玩了。
接下来,我使用名为cheerio的 NPM 包搜索结果。
const parseResults = async () => {
let availability = false;
// get HMTL
const results = await startCrawler();
for (let x = 0; x < results.length; x++) {
// Feb 13th - Feb 20th
const validDates = cheerio(results[x]).find("td").slice(7, 14);
// See if any of the dates are not booked
for (let y = 0; y < validDates.length; y++) {
if (parseInt(validDates[y].children[0].data, 10) === 1) {
availability = true;
}
}
}
...
现在到了最有意思的部分,当房间显示可用时,我会给我妻子发短信。我用的是Twilio,不过还有很多其他服务。这需要注册一个免费账户,我知道我最多只会发几条短信。
...
// send text message if availability
if (availability) {
// Your Account Sid and Auth Token from twilio.com/console
const accountSid = process.env.ACCOUND_SID;
const authToken = process.env.AUTH_TOKEN;
const twilio = require("twilio");
const client = twilio(accountSid, authToken);
client.messages
.create({
body: "Giraffe manor is available for our dates!",
from: process.env.SMS_FROM,
to: process.env.SMS_TO
})
.then(message => console.log(`Sent a text! ${message.sid}`))
.done();
return;
}
console.log("No availability!");
}
经过几个未预订日期的测试,它成功了!现在将其设置为每5分钟运行一次(何乐而不为呢?)。
const schedule = require("node-schedule");
schedule.scheduleJob("*/5 * * * *", () => {
console.log("Running availability checker!");
try {
main();
} catch (e) {
console.log(`Error! ${JSON.stringify(e)}`);
}
});
为了托管和运行代码,我选择了Heroku,因为我有使用经验,而且知道它的免费套餐能满足我的需求。我不知道他们的免费套餐是如何支持后台服务作业的,不过不管怎样,我还是会选择 Heroku。
几周后(我居然忘了它还在运营),我妻子的手机收到了短信!我们立刻给他们打电话,然后就收到了!就像我们希望的那样,好像绕过了候补名单。她收到了一连串的短信,还用完了我在 Twilio 上的免费套餐,因为我没有在找到空房时写一个停止方法。
我特别喜欢这样做,因为在我的生活中我并不经常用代码来解决问题,但我认为对于这样的图片来说这是值得的:
这是我运用编程技能解决“现实”问题的一个例子。我很想听听你解决了什么问题,请在这里评论。
鏂囩珷鏉ユ簮锛�https://dev.to/aleccool213/hacking-my-honeymoon-with-javascript-11fg喜欢这篇文章吗?不妨请我喝杯咖啡,支持我继续写下去。
想接收季度更新邮件吗?订阅我的新闻通讯