This script takes input from poker tournament pages at pokeratlas dot com, and outputs a CSV file that I can import to Google Calendar, listing each of the tournament events, start time, location, and a link back to pokeratlas for their full detail page.
I've used moment, Q, and cheerio. I learned moment specifically for parsing date/time strings here. I would use "jsdom" for parsing, because I've used it before, but it's a serious bear to install in Windows. So, this is the first time I've used anything jQuery like. I'm learning Q to use in other projects, so I thought I'd include it here, but I can't really find much of a good use for it, since there's very little that is actually asynchronous here.
I'm seeking any input anyone might have as to how to make better use of cheerio, Q, or general node.js code criticism.
var http = require('http');
var moment = require('moment');
var Q = require('q');
var cheerio = require('cheerio');
function loadPage(options) {
var content = "",
d = Q.defer();
var req = http.request(options, function(res) {
res.setEncoding("utf8");
res.on("data", function(chunk) {
content += chunk;
});
res.on("end", function() {
d.resolve(content);
});
res.on("error", function(err) {
d.reject(err);
});
});
req.end();
return d.promise;
}
function parseSection(section) {
// each day has a "section" tag, which has a child that has class "header" and it's "id" == "header-(date)" where date is the date of the events contained.
var date = section.children(".header").attr("id").substr("header-".length);
// each section has multiple divs classed "tournament-item", which give the details of each individual tournament
var tournaments = section.find(".tournament-item");
tournaments.each(function(index, tournament) {
parseTournament(date, cheerio(tournament));
});
}
function parseTournament(date, tournament) {
// TODO: perhaps a future enhancement might be to load each individual tournament's page, and parse details off it, but for now, this works.
// each tournament-item has a single anchor that links to a page with details about the specific game.
var tournamentLink = tournament.find("a").attr("href"),
// each one has a div classed "buy-in" which describes the buy-in for the game
buyin = tournament.find(".buy-in").text().replace(/[\n\r,]+/g,''),
// each one has a div classed "venue-name" which gives the readable name for the location
locationName = tournament.find(".venue-name").text(),
// locations are often of format ("Name of room", or "Name of room at (name of building or place they are inside)",
// make some short hand name to stick in the main description. This is all very arbitrary, but we tend to know
// where a place is based on it's location name, rather than the name of the room itself. ie, "Krazy Kopz at Vision Lanes"
// is not ambiguous, when an event could also be hosted by "Krazy Kopz" at "Joey's" or something else.
// Without inserting NLP :-) this works fine for my purposes here.
// So, "Momo's Poker Room" becomes just "Momo's", and "Krazy Kopz at Vision Lanes" becomes just "Vision Lanes".
// So far, the only WTF i've encountered here are games listed as at "300 Bowl". Which shows as "300". Perhaps
// adding a minimum length to number of characters before we start splitting would be helpful.
shortLocName = locationName.indexOf(" at ") > -1 ? locationName.split(" at ").pop() : locationName.split(" ").shift(),
// each one has a div classed "type" which provides the type of game being played -- NLHE for NoLimitHoldem, PLO for PotLimitOmaha, others are possible
type = tournament.find(".type").text(),
// each one has a div classed "time" which provides the start time of the game. guesstimated end times are occasionally on page, but not reliably.
time = tournament.find(".time").text(),
// each one has a div classed "structure-info" which provides some free-hand text info about the game, usually how many chips you start with, and how
// fast the game progresses. There may be nothing, or there may be other info.
structure = tournament.find(".structure-info").text().replace(/[\n\r,]+/g, ''),
// An example summary will read: "Caesars $65 NLH"
summary = shortLocName + " " + buyin + " " + type,
outStr = summary + "," + date + ",";
// time comes in format "3:30pm", but Calendar requires it in format "03:30:00 PM"
if(time) {
time = moment(date + " " + time, "YYYY-MM-DD H:mma");
time = time.format("hh:mm:ss A");
}
outStr += String(time) + ",";
outStr += '"' + structure;
outStr += "\nDetails: http://www.pokeratlas.com" + tournamentLink;
outStr += '"';
outStr += ",";
outStr += locationName;
console.log(outStr);
}
function parsePage(content) {
var $ = cheerio.load(content);
$('section').each(function(index, section) {
parseSection($(section));
});
}
// http://www.pokeratlas.com/poker-tournaments/detroit-toledo/upcoming?page=0
var options = {
host: "www.pokeratlas.com",
port: 80,
path: "/poker-tournaments/detroit-toledo/upcoming?page=0"
};
console.log("Subject,Start Date,Start Time,Description,Location");
loadPage(options)
.then(function(page) {
parsePage(page);
})
.catch(function(err) {
console.log("ERROR", err);
})
.done();