I write a lot of Perl. Currently, I'm spending two days a week working for a client - maintaining an old Perl codebase that drives a successful financial business. On the other days of the week, I'm writing a large Dancer application that I hope to turn into a business in the future. But sometimes, it's fun to use Perl to solve simpler problems too. Here's how I spent fifteen minutes or so this morning.
I listen to music while I work. One of the joys of working from home is that I can fill the room with music and don't have to wear headphones. I tend to listen to a single artist for a couple of hours at a time. I'll just say "Hey Google, play music by [someone]" and listen until I fancy something different.
But it's easy to get into a bit of a rut. I've noticed that I've spent a lot of time listening to four or five artists and, therefore, ignoring all of the other artists I enjoy listening to. This morning I had an idea of how I could work round that.
I've been scrobbling most of my listening for about fifteen years. For those who don't know, "scrobbling" is the act of logging what you're listening to using an external service so that, over time, you build up an accurate picture of who you listen to. I use Last.FM and you can see what I've been listening to on their site.
Another page on the site shows my scrobbles by artist for all time. From there, it's easy to spot the artists who have been receiving too much attention from me recently.
So I decided, I'd write a Perl program to solve this problem. This was the basic design:
- Scrape my "artists scrobbled" page to get a list of artists and the number of scrobbles
- Ignore anything with over a thousand scrobbles (as that's the artists I listen to too much)
- Ignore anything with fewer than five hundred scrobbles (as I'm not sure how much I like those artists)
- Pick an artist at random from the remaining list
It didn't take long to write. The code is on GitHub and I've reproduced it below.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use HTML::TreeBuilder;
my $username = shift || 'davorg';
my $url = "https://www.last.fm/user/$username/library/artists";
my $tree = HTML::TreeBuilder->new_from_url($url);
my ($table) = $tree->look_down(
class => qr/\bchartlist\b/,
);
my @artists;
my @rows = $table->look_down(_tag => 'tr', class => qr/chartlist-row/);
for (@rows) {
my $name = $_->look_down(_tag => 'td', class => qr/chartlist-name/);
my $count = $_->look_down(_tag => 'span', class => qw/chartlist-count-bar-value/);
my $x = $count->content->[0] =~ s/,//gr;
next if $x >= 1000 or $x <= 500;
push @artists, $name->look_down(_tag => 'a')->content->[0];
}
say $artists[ rand @artists ];
I used HTML::TreeBuilder because I had been reminded of its existence earlier in the week but, honestly, I'm not convinced it was the best choice. I might have a go at rewriting it using something like Web::Scraper at some point.
And as I'm writing this, I'm thinking "Doesn't Last.FM have an API? Shouldn't I have used that?" (Answer: yes it does and yes you should - so that's another fix I should make in the future.)
But anyway, that's what I threw together in fifteen minutes this morning. It works, so I don't really have much incentive to do much to it (Oh, who am I trying to kid? Of course, I'm desperate to spend the next two weeks "improving" it!)
I didn't expect it to be useful to anyone else, so I didn't even bother tweeting about it. But before long I got a tweet from @wantarray saying he'd taken the basic logic and used it as the basic of a program which uses Audio::MPD to actually play the music.
So I thought I'd blog about it because a) it turns out to be more useful than I thought and b) I wanted to share the joy of using a powerful programming language to solve a trivial problem in my life.
How do you use Perl to make your life better?
Update: I couldn't resist rewriting it to use the Last.FM API. It looks much nicer now.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Net::LastFM;
my $username = shift || 'davorg';
my $method = 'user.getTopArtists';
my $lastfm = Net::LastFM->new(
api_key => $ENV{LASTFM_API_KEY},
api_secret => $ENV{LASTFM_SECRET},
);
my $data = $lastfm->request_signed(
method => $method,
user => $username,
);
my @artists;
for (@{$data->{topartists}{artist}}) {
next if $_->{playcount} >= 1000;
next if $_->{playcount} <= 500;
push @artists, $_->{name};
}
say $artists[ rand @artists ];
Of course, you now need to register for a Last.FM API key.