<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>Coderholic</title>
 <link href="http://www.coderholic.com/atom.xml" rel="self"/>
 <link href="http://www.coderholic.com.com/"/>
 <updated>2013-05-31T17:44:16-07:00</updated>
 <id>http://www.coderholic.com/</id>
 <author>
   <name>Ben Dowling</name>
 </author>

 
 <entry>
   <title>More invaluable command line tools for web developers</title>
   <link href="http://www.coderholic.com/more-invaluable-command-line-tools-for-web-developers"/>
   <updated>2012-11-15T00:00:00-08:00</updated>
   <id>http://www.coderholic.com/more-invaluable-command-line-tools-for-web-developers</id>
   <content type="html">&lt;p&gt;This article is a follow up to &lt;a href=&quot;http://www.coderholic.com/invaluable-command-line-tools-for-web-developers/&quot;&gt;Invaluable command line tools for web developers&lt;/a&gt;, and covers some more great tools that can make your life as a developer that little bit easier.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post first appeared, combined with the first command line tools post, on &lt;a href=&quot;http://coding.smashingmagazine.com/2012/10/29/powerful-command-line-tools-developers/&quot;&gt;Smashing Magazine&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;netcat&lt;/h2&gt;

&lt;p&gt;Netcat, or &lt;code&gt;nc&lt;/code&gt;, is a self described networking swiss-army-knife. It's an very simple but also very powerful and versatile application that allows you to create arbitrary network connections. Here we see it being used as a port scanner:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;nc -z example.com 20-100
Connection to example.com 22 port &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;tcp/ssh&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; succeeded!
Connection to example.com 80 port &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;tcp/http&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; succeeded!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In addition to creating arbitrary connections netcat can also listen for incoming connections. Here we use this feature of nc, combined with tar, to very quickly and efficiently copy files between servers. On the server run:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;nc -l 9090 | tar -xzf -
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And on the client:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;tar -czf dir/ | nc server 9090
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can use netcat to expose any application over the network. Here we expose a shell over port 8080:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mkfifo backpipe
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;nc -l 8080  0&amp;lt;backpipe | /bin/bash &amp;gt; backpipe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You can now access the server from any client:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;nc example.com 8080
uname -a
Linux li228-162 2.6.39.1-linode34 &lt;span class=&quot;c&quot;&gt;##1 SMP Tue Jun 21 10:29:24 EDT 2011 i686 GNU/Linux&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;While the last two examples are slightly contrived (in reality you'd be more likely to use tools such as rsync to copy files, and ssh to remotely access a server) they do show the power and flexibility of netcat, and hint at all of the different things you can achieve by combining netcat with other applications.&lt;/p&gt;

&lt;h2&gt;sshuttle&lt;/h2&gt;

&lt;p&gt;Sshuttle allows you to securely tunnel your traffic via any server you have SSH access to. It's extremely easy to setup and use, not requiring you to install any software on the server, or change any local proxy settings.&lt;/p&gt;

&lt;p&gt;By tunnelling your traffic over SSH you secure yourself against tools like &lt;a href=&quot;http://codebutler.com/firesheep/&quot;&gt;firesheep&lt;/a&gt; and &lt;a href=&quot;http://monkey.org/~dugsong/dsniff/&quot;&gt;dsniff&lt;/a&gt; when on unsecured public wifi or other untrusted networks. All network communication, including DNS requests, can be sent via your SSH server:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;sshuttle -r &amp;lt;server&amp;gt; --dns 0/0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you provide the &lt;code&gt;--daemon&lt;/code&gt; argument sshuttle will run in the background as a daemon. Combined with some other options you can make aliases to simply and quickly start and stop tunnelling your traffic:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;tunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;sshuttle --D --pidfile=/tmp/sshuttle.pid -r &amp;lt;server&amp;gt; --dns 0/0&amp;#39;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;stoptunnel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;[[ -f /tmp/sshuttle.pid ]] &amp;amp;&amp;amp; kill `cat /tmp/sshuttle.pid`&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You can also use sshuttle is to get around the IP-based-geolocation filters that are now used by many services, such as BBC's iPlayer which requires you to be in the UK, and turntable.fm requires you to be in the US. You'll need access to a server in the target country. Amazon has a &lt;a href=&quot;http://aws.amazon.com/free/&quot;&gt;free tier&lt;/a&gt; of EC2 micro instances that are available in many countries, or you can find a cheap VPS in almost &lt;a href=&quot;http://www.thevpslist.com/countries&quot;&gt;any country in the world&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this scenario rather than tunnelling all of your traffic you might want to send only that for the service you are targeting. Unfortunately sshuttle only accepts IP address arguments rather than hostnames, so we need to make use of &lt;code&gt;dig&lt;/code&gt; to first resolve the hostname:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;sshuttle -r &amp;lt;server&amp;gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;dig +short &amp;lt;hostname&amp;gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;mitmproxy&lt;/h2&gt;

&lt;p&gt;mitmproxy is an SSL-capable man-in-the-middle HTTP proxy that allows you to inspect both HTTP and HTTPS traffic, and rewrite requests on the fly. The application has been behind quite a few different iOS app privacy scandals, including &lt;a href=&quot;http://mclov.in/2012/02/08/path-uploads-your-entire-address-book-to-their-servers.html&quot;&gt;Path's address book upload&lt;/a&gt; one. It's ability to rewrite requests on the fly has also been used to target iOS, including setting a &lt;a href=&quot;http://mitmproxy.org/doc/tutorials/gamecenter.html&quot;&gt;fake a high score in GameCenter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Far from only being useful to see what mobile apps are sending over the wire or for faking high scores, mitmproxy can help out with a whole range of web development tasks. For example, instead of constantly hitting F5 or clearing your cache to make sure you're seeing the latest content you can run&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mitmproxy --anticache
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;which will automatically strip all cache control headers and make sure you always get fresh content. Unfortunately it doesn't automatically setup forwarding for you like sshuttle, so after starting mitmproxy you still need to change your system wide, or browser specific proxy settings.&lt;/p&gt;

&lt;p&gt;Another extremely handy feature of mitmproxy is the ability to record and replay HTTP interactions. The official documentation gives an example of &lt;a href=&quot;http://mitmproxy.org/doc/tutorials/30second.html&quot;&gt;a wireless network login&lt;/a&gt;. Exactly the same technique can we used as a basic web testing framework. For example, to confirm that your user signup flow is works you can start recording the session:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mitmdump -w user-signup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then go through the user signup process, which at this point should work as expected. Stop recording the session with Ctrl-c. At any point we can then replay what was recorded and check for the 200 status code:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mitmdump -c user-signup | tail -n1 | grep 200 &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;OK&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;FAIL&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If the signup flow gets broken at any point we'll see a FAIL message, rather than an OK. You could create a whole suite of these tests and run them regularly to make sure you get notified if you ever accidentally break anything on your site.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2 Years of #HNLondon</title>
   <link href="http://www.coderholic.com/2-years-of-hnlondon"/>
   <updated>2012-08-15T00:00:00-07:00</updated>
   <id>http://www.coderholic.com/2-years-of-hnlondon</id>
   <content type="html">&lt;p&gt;It's hard to believe that it's been 2 years since Dmitri and I started the &lt;a href=&quot;http://www.meetup.com/HNLondon/events/77825522/?a=ea1_grp&amp;amp;rv=ea1&quot;&gt;Hacker News London meetup&lt;/a&gt;. We saw some amazing growth in the &lt;a href=&quot;http://www.coderholic.com/hacker-news-london-meetup-a-year-on/&quot;&gt;first year&lt;/a&gt;, going from 40 to almost 200 attendees.&lt;/p&gt;

&lt;p&gt;Things didn't slow down from there. In the past year we've  moved to a bigger venue and grown to around 500 regular attendees, making it one of the largest tech meetups in Europe! We've had some great sponsors, allowing us to ensure that everyone is well fed on Pizza, and suitably drunk on beer (although, sadly, we did once run out of beer!).&lt;/p&gt;

&lt;p&gt;We've bagged some fantastic speakers too, including Joel Spolsky, Harjeet Taggar (partner at YC), Eben Upton of Raspberry Pi and others. You can view their talks, and all of the others on the &lt;a href=&quot;http://vimeo.com/hnlondon&quot;&gt;HNLondon vimeo page&lt;/a&gt;. One of my personal favourites has been Dr Marc Warner's talk &lt;em&gt;Quantum Computing for Hackers&lt;/em&gt;&lt;/p&gt;

&lt;iframe src=&quot;http://player.vimeo.com/video/41325239&quot; width=&quot;500&quot; height=&quot;281&quot; frameborder=&quot;0&quot; webkitAllowFullScreen mozallowfullscreen allowFullScreen&gt;&lt;/iframe&gt;


&lt;p&gt;Sadly June's HNLondon event was the last time I'll personally be inolved with the event. With the recent &lt;a href=&quot;http://techcrunch.com/2012/05/15/facebook-lightbox/&quot;&gt;acquisition of Lightbox&lt;/a&gt; I'll be moving to the US later this summer to work from the Facebook HQ in Menlo Park. I'm hugely excited about the opportunity, but I'll definitley miss the HNLondon meetups and all of the great people I've met as a result of them.&lt;/p&gt;

&lt;p&gt;I'm sure HNLondon will continue to go from strength to strength in Dmitri and Steve's hands, and I look forward to watching the videos online!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>EasyDB: Simple SQL Persistence for Python</title>
   <link href="http://www.coderholic.com/easydb-simple-sql-persistence-for-python"/>
   <updated>2012-03-10T00:00:00-08:00</updated>
   <id>http://www.coderholic.com/easydb-simple-sql-persistence-for-python</id>
   <content type="html">&lt;p&gt;Python provides some great tools for simple data persistence. For key value storage there's &lt;a href=&quot;http://docs.python.org/library/anydbm.html&quot;&gt;anydb&lt;/a&gt; for when you have only string values, and &lt;a href=&quot;http://docs.python.org/library/shelve.html&quot;&gt;shelve&lt;/a&gt; for when you've got more complex values (eg. lists or objects).&lt;/p&gt;

&lt;p&gt;Sometimes key value persistence doesn't cut it. For example, you might want to sort, filter or query your data efficiently. In those situations you can always use Python's built in support for SQLite - a serverless self-contained SQL database engine. With SQLite you've got to worry about database schemas, connections and transactions though. A far cry from &lt;em&gt;simple persistence&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;&lt;a href=&quot;https://github.com/coderholic/easydb&quot;&gt;EasyDB&lt;/a&gt;&lt;/strong&gt; - essentially a really simple SQLite wrapper for that saves you from having to worry about creating tables,  managing connections or transactions. Here's a quick example:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;easydb&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EasyDB&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EasyDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;filename.db&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;users&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;username text&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;comments text&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]})&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;INSERT INTO users (username, comments) VALUES (?, ?)&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;ben&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Python coder&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;SELECT * FROM users&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
    
&lt;span class=&quot;c&quot;&gt;# =&amp;gt; (&amp;#39;ben&amp;#39;, &amp;#39;Python coder&amp;#39;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If the filename being passed into the EasyDB constructor already exists it'll get reused. Otherwise it'll be created with the provided database structure. Notice that you don't need to worry about the correct SQL syntax for creating tables, you just provide the details as a Python dictionary in the following format (see the &lt;a href=&quot;http://sqlite.org/datatype3.html&quot;&gt;SQLite documentation&lt;/a&gt; for a list of available types):&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;#39;table_name&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;column_name column_type&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;…&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;#39;table_name&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;column_name column_type&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;…&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;EasyDB can also be used on existing SQLite database by simply passing in the database filename:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;easydb&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EasyDB&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EasyDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;filename.db&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;SELECT * FROM mytable&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fetchall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;EasyDB is available on GitHub at &lt;a href=&quot;https://github.com/coderholic/easydb&quot;&gt;https://github.com/coderholic/easydb&lt;/a&gt;, and can also be installed via pip.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>My Startup Failed, But It's OK</title>
   <link href="http://www.coderholic.com/my-startup-failed-but-its-ok"/>
   <updated>2012-01-28T00:00:00-08:00</updated>
   <id>http://www.coderholic.com/my-startup-failed-but-its-ok</id>
   <content type="html">&lt;p&gt;It's now been more than 18 months since I wrote about &lt;a href=&quot;http://www.coderholic.com/taking-the-leap-how-i-became-a-full-time-entrepreneur/&quot;&gt;taking the leap&lt;/a&gt; from being an employee to working full time on my  startup, Geomium. At the time I was full of mixed emotions. I was hugely excited about the possibilities that lay ahead, and also hugely apprehensive about leaving behind the safety net of employment to go all out own my own. Fame and fortune would &lt;em&gt;obviously&lt;/em&gt; await me if it all went right, but what would happen if it all went wrong? I had a wife a daughter to support, I couldn't let them down! You've got to feel the fear and do it anyway though, right?&lt;/p&gt;

&lt;p&gt;Well, we did fail. Geomium had been in a state of limbo since April last year, but I finally pulled the plug in November when I emailed Geomium's 50,000 users to tell them that we were  discontinuing the service. The website will live on in a different form, but the Geomium that we'd envisaged back when I wrote the post, the Geomium we'd been trying to achieve for all that time, the Geomium that we hoped we could create is dead.&lt;/p&gt;

&lt;p&gt;Things didn't work out for a whole variety of reasons. I'm sure we didn't fail in any particularly new or exciting way, and probably made &lt;a href=&quot;http://klinger.io/post/16308736248/the-things-first-time-founders-do&quot;&gt;mistakes that have been made thousands of times before&lt;/a&gt;, so I'm not going to dwell on what went wrong or what we could have done differently. What I am going to focus on is two of the biggest takeaways...&lt;/p&gt;

&lt;h2&gt;Sometimes you have to learn things first hand&lt;/h2&gt;

&lt;p&gt;Not only were none of the mistakes we made novel, a lot of them were mistakes I'd read about before and was sure I'd avoid. I then went on to make them anyway. I think the reason is that when you read about mistakes that other people have made, or the right way to do things, it's often in an abstract way that won't apply directly to your own situation. It can therefore be difficult to recognise the advice you've read. However, once you've made the mistake yourself it's a lot more concrete, and more easily recognised in the future.&lt;/p&gt;

&lt;p&gt;As valuable as reading all the books, reading all the blogs, and doing all your research is, sometimes it's just a case of getting hard earned experience. The only way to do that is to jump in, and get your hands dirty.&lt;/p&gt;

&lt;p&gt;I've definitely got many more mistakes to make, and many more lessons to learn, but hopefully there'll be a few that I'll manage to avoid next time.&lt;/p&gt;

&lt;h2&gt;The worst case scenario isn't as bad as you think&lt;/h2&gt;

&lt;p&gt;By far the biggest lesson for me was that failure isn't anywhere near as bad as you think it might be. Even when everything goes wrong, it's actually OK.&lt;/p&gt;

&lt;p&gt;Instead of ending up destitute and homeless a load of new opportunities came out of the experience. It was at Seedcamp, where Geomium were finalists in January 2011, that I met one of my partners on &lt;a href=&quot;http://busmapper.co.uk&quot;&gt;BusMapper&lt;/a&gt;, which is now a successful side project. Another opportunity came out of a VC pitch. We pitched Geomium to a lot of the top VC firms in London, and it was when pitching Index Ventures that I met Thai Tran. I'm now working full time for his startup &lt;a href=&quot;http://lightbox.com&quot;&gt;Lightbox&lt;/a&gt;. Had I not taken the leap I'd have missed these opportunities, and others.&lt;/p&gt;

&lt;h2&gt;Keep On Failing, Keep On Trying&lt;/h2&gt;

&lt;p&gt;I'm still really disappointed that we couldn't make Geomium a success, but I'm pleased I tried, and I learnt a lot along the way that I'll be able to apply to other projects in the future.&lt;/p&gt;

&lt;p&gt;If you're thinking of taking the leap, but worried about what happens if it all goes wrong, don't. Chances are it will go wrong, but you'll learn a lot along the way and expose yourself to even more opportunities. Once you're not afraid to fail there's no stopping you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt; If you enjoyed this post please vote for it on on &lt;a href=&quot;http://news.ycombinator.com/item?id=3522946&quot;&gt;Hacker News&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Goodbye Wordpress, Hello Jekyll!</title>
   <link href="http://www.coderholic.com/goodbye-wordpress-hello-jekyll"/>
   <updated>2012-01-20T00:00:00-08:00</updated>
   <id>http://www.coderholic.com/goodbye-wordpress-hello-jekyll</id>
   <content type="html">&lt;p&gt;I've finally given up on Wordpress, and moved this blog over to &lt;a href=&quot;https://github.com/mojombo/jekyll&quot;&gt;Jekyll&lt;/a&gt;, the &quot;blog aware static site generator&quot;. My blog is now 100% static HTML. No database, no dynamic code, and hopefully no downtime the next time one of my posts makes it to the front page of Hacker News!&lt;/p&gt;

&lt;p&gt;My primary motivation for the move was the fact that wordpress would die every time my blog got a decent amount of traffic, even with WP-SuperCache enabled. That's far from the only benefit though. I don't have to worry about keeping wordpress updated anymore, or it getting hacked (which has happened a few times). Tom Preston-Werner, GitHub co-founder and Jekyll author, talks about even more benefits in his article &lt;a href=&quot;http://tom.preston-werner.com/2008/11/17/blogging-like-a-hacker.html&quot;&gt;Blogging like a Hacker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before settling on Jekyll I evaluated a few different static site generators, including &lt;a href=&quot;https://github.com/mitsuhiko/rstblog&quot;&gt;rstblog&lt;/a&gt; and &lt;a href=&quot;https://github.com/lakshmivyas/hyde&quot;&gt;Hyde&lt;/a&gt;. What tipped it in jekyll's favour was the integration with GitHub. Thanks to &lt;a href=&quot;http://pages.github.com&quot;&gt;GitHub pages&lt;/a&gt; this blog is now served directly from GitHub, so I don't even need to worry about a server!&lt;/p&gt;

&lt;h2&gt;Migrating from Wordpress&lt;/h2&gt;

&lt;p&gt;Paul Stamatiou did a &lt;a href=&quot;http://paulstamatiou.com/how-to-wordpress-to-jekyll&quot;&gt;write up&lt;/a&gt; of his own experience of moving from wordpress to Jekyll. I took a slightly different approach. Rather than installing Ruby on my server and running the normal migration script I instead took an XML export of my posts from wordpress, and then ran a &lt;a href=&quot;https://github.com/davidwinter/wordpress-to-jekyll&quot;&gt;php script&lt;/a&gt; on my local machine.&lt;/p&gt;

&lt;p&gt;The script took care of creating files for each of the posts, but those posts still had a load of links to images and scripts that would need to be updated. For me this is where the benefits of having your posts locally as flat files became really clear - with a little bit of command line magic I could update all my links, and download all the linked files!&lt;/p&gt;

&lt;p&gt;As I was using wordpress I knew all of the links that would need updating would contain &quot;wp-content&quot;, so I could pull them out with grep:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd _posts
$ grep -Eho &quot;[^\&quot;']*?/wp-content/[^\&quot;']*&quot; *
http://www.coderholic.com/wp-content/uploads/google-index.png
http://www.coderholic.com/wp-content/uploads/twitter.png
http://www.coderholic.com/wp-content/uploads/technorati.png
…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I outputted these to a file, and the downloaded them all with wget:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ mkdir files
$ cd files
$ cat files | xargs wget
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next I updated the URLs, coverting something like &lt;code&gt;http://www.coderholic.com/wp-content/uploads/google-index.png&lt;/code&gt; to &lt;code&gt;/files/google-index.png&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;find . -type f -exec sed -i -e &quot;s/[^\&quot;']*\/wp-content\/[^\&quot;']*\/\([^\&quot;']*\)/\/files\/\1/g&quot; {} \;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;All that was left to do then was to push it to GitHub, and let them take care of the rest!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>My OSX Setup</title>
   <link href="http://www.coderholic.com/my-osx-setup"/>
   <updated>2011-10-09T00:00:00-07:00</updated>
   <id>http://www.coderholic.com/my-osx-setup</id>
   <content type="html">&lt;p&gt;After over 10 years of Linux I recently switched to OSX on a MacBook Air. I can't say enough good things about the Air hardware, but it's taken me  a few months to get completely confortable with the operating system. Here are the apps I've settled on, which I think make for a fairly awesome OSX setup:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.alfredapp.com/&quot;&gt;Alfred&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;Alfred is a powerful launcher, which allows you to start any application simply by pressing Alt-Space and then typing the application name. Alfred also has an inbuilt calculator, which is really handy, and can be used to quickly search google too.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/files/alfred.jpg&quot;&gt;&lt;img class=&quot;alignnone size-full wp-image-1251&quot; title=&quot;alfred&quot; src=&quot;/files/alfred.jpg&quot; alt=&quot;&quot; width=&quot;663&quot; height=&quot;210&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.google.com/chrome/intl/en-GB/landing_tv.html&quot;&gt;Chrome&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I did try Safari for a while but came back to Chrome, my browser of choice. It's fast, has a clean UI, and more and more great extensions are available all the time. My current extensions: &lt;a href=&quot;https://chrome.google.com/webstore/detail/hihakjfhbmlmjdnnhegiciffjplmdhin&quot;&gt;Rapportive&lt;/a&gt;, &lt;a href=&quot;https://chrome.google.com/webstore/detail/piekbefgpgdecckjcpffhnacjflfoddg&quot;&gt;Pretty Beautiful Javascript&lt;/a&gt;, &lt;a href=&quot;https://chrome.google.com/webstore/detail/ninejjcohidippngpapiilnmkgllmakh&quot;&gt;YSlow&lt;/a&gt;, &lt;a href=&quot;https://chrome.google.com/webstore/detail/lliahakgdhnopceclokooacmcgimcdoo&quot;&gt;GitHub Inbox&lt;/a&gt;, and &lt;a href=&quot;https://chrome.google.com/webstore/detail/cpngackimfmofbokmjmljamhdncknpmg&quot;&gt;Screen Capture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://cyberduck.ch/&quot;&gt;Cyberduck&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;Cyberduck is a handy little file transfer app that supports FTP, SCP, WebDAV and a load of cloud storage services such as Amazon's S3. It integrates well with Finder, so you can simply drag and drop files between computers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://fluidapp.com/&quot;&gt;Fluid&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A lot of the applications that I use these days are actually web apps. For the ones I use most frequently (Google Calendar and &lt;a href=&quot;http://www.workflowy.com&quot;&gt;WorkFlowy&lt;/a&gt;) I use Fluid to turn them into desktop-like apps in their own window, which makes them easier to access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://mxcl.github.com/homebrew/&quot;&gt;Homebrew&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A package manager for OSX that makes it as easy as &lt;code&gt;brew install &amp;lt;package&amp;gt;&lt;/code&gt; to install almost any UNIX package you can think of. For those it doesn't support it's relatively easy to add support yourself. Here are the apps installed with brew so far:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew list
ack             fortune         jpeg            ngrep           pv              sqlite
android-sdk     gdbm            libevent        nmap            readline        unrar
cmake           git             libmemcached    pidof           redis           watch
ctags           graphviz        macvim          pil             siege           wget
curl            htop            memcached       pkg-config      solr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.iterm2.com/&quot;&gt;iTerm2&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;OSX ships with a terminal application, but iTerm comes with a whole host of advanced features that it became my terminal app of choice. Some of those features include 256 colour support and allowing scrollback within screen. The killer feature for me though is the system wide hotkey. I've set it up so that whenever I press F12 iTerm drops down from the top of the screen, quake style, and I'm good to go.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/files/iterm.jpg&quot;&gt;&lt;img class=&quot;alignnone size-full wp-image-1344&quot; title=&quot;iterm&quot; src=&quot;/files/iterm.jpg&quot; alt=&quot;&quot; width=&quot;650&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://code.google.com/p/macvim/&quot;&gt;MacVim&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On Linux I used Vim within a terminal, but I'm really liking MacVim on OSX. It integrates really nicely with the system clipboard and is customisable enough that you get get most of the default GUI elements out of the way. I had to make some minor adjustments to my existing &lt;a href=&quot;https://github.com/coderholic/config/blob/master/.vimrc&quot;&gt;vimrc&lt;/a&gt; but almost everything I had working on vim under linux, including all of the plugins, just worked.&lt;/p&gt;

&lt;p&gt;&lt;a style=&quot;font-weight: bold;&quot; href=&quot;http://www.videolan.org&quot;&gt;VLC&lt;/a&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;/strong&gt; I find iTunes a bit too heavy weight for most of my media needs, and it also crashes a lot! I used VLC a lot on Linux, and the Mac client is equally as good. I've yet to find an audio or video format it doesn't support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://support.apple.com/kb/ht1624&quot;&gt;Spaces&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Initially the hardest part of the transition to OSX was the lack of a good virtual desktop manager. With Linux I'd constantly be changing virtual desktops, and moving windows around with the various keyboard shortcuts. OSX's manager, spaces, though required me to move windows around with the mouse! It wasn't until I discovered the &quot;always open this app on this desktop&quot; feature that I really began to like spaces. I have chrome always open on it's own desktop, vim on another, workflowy on another, and everything else on another one again. I haven't upgraded to Lion yet, and I hear that Spaces has been replaced. Hopefully I'll get on with the replacement just as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What am I missing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let me know what great OSX apps I'm missing out on!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How I got the Turntable.fm Gorilla in less than 48 hours</title>
   <link href="http://www.coderholic.com/how-i-got-the-turntable-fm-gorilla-in-less-than-48-hours"/>
   <updated>2011-09-13T00:00:00-07:00</updated>
   <id>http://www.coderholic.com/how-i-got-the-turntable-fm-gorilla-in-less-than-48-hours</id>
   <content type="html">&lt;p&gt;&lt;img style=&quot;float: left; padding-right: 10px; width: 499px; height: 414px;&quot; src=&quot;/files/ttgorilla.jpg&quot; alt=&quot;&quot;  /&gt;&lt;/p&gt;

&lt;p&gt;Turntable.fm has taken off in a big way. Launching in May, over &lt;a href=&quot;http://www.betabeat.com/2011/06/22/how-many-users-does-turntable-fm-have-2011-06-22/&quot;&gt;140,000 users&lt;/a&gt; signed up in the first month. Celebrities regularly hang out there, and &lt;a href=&quot;http://www.businessinsider.com/kanye-west-and-lady-gaga-have-invested-in-turntablefm-2011-7?op=1&quot;&gt;Lady GaGa and Kanye West are even investing in the site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For those that haven't tried it out yet (or perhaps can't, more on that shortly) turntable is somewhere that you can listen to great music and discover new artists and songs that you might otherwise not have otherwise come across.&lt;/p&gt;

&lt;p&gt;The site features a number of rooms, each with a different theme (eg. chillout, dubstep, indie) and in each room there are up to 5 DJs. Every one in a room can &quot;awesome&quot; or &quot;lame&quot; each song that is played.&lt;/p&gt;

&lt;p&gt;If you're the DJ you get a point for everyone awesome you receive. The more points you get the better the avatar you can select. The most prized avatar on the site, a gorilla, requires 1000 points. Currently that's something that has only been achieved by 4000 of the sites users, and something that has taken most of them weeks or even months of effort. Here's how I got the gorilla in less than 48 hours...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting in&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Due to some licensing issues &lt;a href=&quot;http://thenextweb.com/insider/2011/06/25/turntable-fm-blocks-access-to-everyone-outside-the-us/&quot;&gt;Turntable has been unavailable to anyone outside the US&lt;/a&gt; since the end of June. I'm based in the UK, so the first challenge I faced was simply getting into the site. I'd need to make it appear as though I was in the US. The &lt;a href=&quot;https://github.com/apenwarr/sshuttle&quot;&gt;sshuttle&lt;/a&gt; app makes extremely easy to do exactly that. You just need a host in the target country (fortunately this blog is hosted in the US, so that's what I used), and the IP address of the target site. It gets a little more complicated if the target site has several IP addresses, but a quick check with &lt;code&gt;dig&lt;/code&gt; shows that turntable currently has just the one:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;dig -tA +short turntable.fm
50.16.229.9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Tunnelling all traffic to this IP via my US based server is as simple as running this command:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;sshuttle -r coderholic.com 50.16.229.9/32
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It'll now appear to turntable that I'm in the US, and can login into the site using my Facebook account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting 1000 points&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In popular rooms it can be really difficult to get a DJ spot. Even if you manage to get one it's not easy to pick songs that get a lot of points, and it can be hard to keep your spot. That's why it usually takes weeks if not months of effort to get the 1000 points required for the gorilla. My plan was to automate the process as much as possible.&lt;/p&gt;

&lt;p&gt;There are already lots of scripts and plugins available for Turntable. I started digging into the code of frankielaguna's &lt;a href=&quot;https://github.com/frankielaguna/Turntable-Bookmarklets/blob/master/autoawesome.js&quot;&gt;Auto-Awesome bookmarket&lt;/a&gt; to see what was going on under the hood. The code starts with this:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;//Attempt to find the room manager object&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;prop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hasOwnProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;roommanager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt; 
        &lt;span class=&quot;nx&quot;&gt;ttObj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &quot;room manager object&quot; sounded very interesting! Pasting the above code into the Chrome javascript console showed right away how much interesting stuff there really is in this object:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/tt.png&quot; style=&quot;border: 1px solid #AAA;&quot;/&gt;&lt;/p&gt;

&lt;p&gt;It contains details of who's DJing, how many DJ slots there are, callbacks for when you get points, and lots lots more. Certainly everything I would need was there.&lt;/p&gt;

&lt;p&gt;My plan was to create a new room and login with 2 accounts, one my actual account, and one fake account. I'd have both the accounts DJ and automatically awesome each other. To speed the process up I made some changes to the auto-awesome code so that it would &quot;awesome&quot; every 5 seconds, and so that the current DJ would skip the rest of their song as soon as they received a point. I noticed a set_dj_points method in the room manager object, and overrode it like so:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Override the set_dj_points function, so we skip to the next DJ as soon as we get a point&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;set_dj_points&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ttObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;set_dj_points&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;ttObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;set_dj_points&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ttObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;myuserid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ttObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;current_dj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;I&amp;#39;ve got more points:&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// We&amp;#39;re done - skip to the next DJ&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;ttObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;stop_song&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;set_dj_points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After settings things in motion I discovered that turntable require a variable amount of the song to be played before the an awesome is actually counted, so it would usually take longer than 5 seconds for the current DJ to get a point. Not a huge problem, but it meant things would take longer than expected.&lt;/p&gt;

&lt;p&gt;The next problem I ran into was a little more serious. After each DJ had played 40 songs they got kicked off. I could manually make them a DJ again, but that'd require me to check the site every so often. Instead I updated the script so that every 5 second iteration we check to see if we're the DJ, and if not then become one:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Check to see if we&amp;#39;re in the DJ queue or not&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ttObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;myuserid&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ttObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;djs_uid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// We&amp;#39;re not!! Become a DJ&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;ttObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;become_dj&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The next problem that I ran into was that turntable limits the number of awesomes you can get from a single user to 50! Therefore I had to signup for more fake facebook accounts and get them in on the act. Rather than signing up for one new account at a time I instead created severeal accounts and got them all into the room at the same time. I created a slightly different script for these other accounts, so that they'd just awesome the song rather than DJ. I also modified the DJ script to DJ for a fixed amount of time, rather than give up DJing after the first awesome. That way I could collect a few points for each song play.&lt;/p&gt;

&lt;p&gt;Less than 48 hours and a load of fake facebook accounts later I'd managed to get the required 1000 points. The gorilla was mine! The complete code that I used is up on GitHub: &lt;a href=&quot;https://github.com/coderholic/turntable.fm&quot;&gt;https://github.com/coderholic/turntable.fm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preventing it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I went through this process mostly out of curiosity. It's clear that not many people are employing the same kind of tactics, with only around 4000 users of the site having obtained the gorilla. It'd certainly be a problem for turntable if everyone started doing this though, so what could they do to prevent it?&lt;/p&gt;

&lt;p&gt;It seems as though turntable are already doing quite a bit to make it difficult , by limiting the number of awesomes received from each profile, requiring the song to be played for a certain amount of time, and booting DJs off after they've played a certain number of songs. There's certainly more that they could do. For example, they could make it harder to get hold of the roommanger object. Within the room manager object itself they could ignore any actions unless the browser has focus.&lt;/p&gt;

&lt;p&gt;Ultimately the client code must communicate with the Turntable server though, so any client side changes would only make things harder. They wouldn't actually prevent anything. In fact Alain Gilbert has put together a &lt;a href=&quot;https://github.com/alaingilbert/Turntable-API&quot;&gt;node.js based Turntable client&lt;/a&gt; that does exactly that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comment or vote at &lt;a href=&quot;http://news.ycombinator.com/item?id=2990486&quot;&gt;Hacker News&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Invaluable command line tools for web developers</title>
   <link href="http://www.coderholic.com/invaluable-command-line-tools-for-web-developers"/>
   <updated>2011-08-13T00:00:00-07:00</updated>
   <id>http://www.coderholic.com/invaluable-command-line-tools-for-web-developers</id>
   <content type="html">&lt;p&gt;Life as a web developer can be hard when things start going wrong. The problem could be in any number of places. Is there a problem with the request your sending, is the problem with the response, is there a problem with a request in a third party library you're using, is an external API failing? There are lots of different tools that can make our life a little bit easier. Here are some command line tools that I've found to be invaluable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Curl&lt;/strong&gt;
Curl is a network transfer tool that's very similar to wget, the main difference being that by default wget saves to file, and curl outputs to the command line. This makes is really simple to see the contents of a website. Here, for example, we can get our current IP from the &lt;a href=&quot;http://ipinfo.io&quot;&gt;ipinfo.io&lt;/a&gt; website:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl ipinfo.io/ip
93.96.141.93
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Curl's &lt;code&gt;-i&lt;/code&gt; (show headers) and &lt;code&gt;-I&lt;/code&gt; (show only headers) option make it a great tool for debugging HTTP responses and finding out exactly what a server is sending to you:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl -I news.ycombinator.com
HTTP/1.1 200 OK
Content-Type: text/html; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Cache-Control: private
Connection: close
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;-L&lt;/code&gt; option is handy, and makes Curl automatically follow redirects. Curl has support for HTTP Basic Auth, cookies, manually settings headers, and much much more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Siege&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Siege is a HTTP benchmarking tool. In addition to the load testing features it has a handy &lt;code&gt;-g&lt;/code&gt; option that is very similar to &lt;code&gt;curl -iL&lt;/code&gt; except it also shows you the request headers. Here's an example with www.google.com (I've removed some headers for brevity):&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;siege -g www.google.com
GET / HTTP/1.1
Host: www.google.com
User-Agent: JoeDog/1.00 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;en&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X11; I; Siege 2.70&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Connection: close

HTTP/1.1 302 Found
Location: http://www.google.co.uk/
Content-Type: text/html; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UTF-8
Server: gws
Content-Length: 221
Connection: close

GET / HTTP/1.1
Host: www.google.co.uk
User-Agent: JoeDog/1.00 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;en&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X11; I; Siege 2.70&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Connection: close

HTTP/1.1 200 OK
Content-Type: text/html; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ISO-8859-1
X-XSS-Protection: 1; &lt;span class=&quot;nv&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;block
Connection: close
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;What siege is really great at is server load testing. Just like &lt;code&gt;ab&lt;/code&gt; (apache benchmark tool) you can send a number of concurrent requests to a site, and see how it handles the traffic. With the following command we test google with 20 concurrent connections for 30 seconds, and then get a nice report at the end:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;siege -c20 www.google.co.uk -b -t30s
...
Lifting the server siege...      &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;.
Transactions:                    1400 hits
Availability:                 100.00 %
Elapsed &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;:                  29.22 secs
Data transferred:              13.32 MB
Response &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;:                  0.41 secs
Transaction rate:              47.91 trans/sec
Throughput:                     0.46 MB/sec
Concurrency:                   19.53
Successful transactions:        1400
Failed transactions:               0
Longest transaction:            4.08
Shortest transaction:           0.08
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;One of the most useful features of siege is that it can take a url file as input, and hit those urls rather than just a single page. This is great for load testing, because you can replay real traffic against your site and see how it performs, rather than just hitting the same URL again and again. Here's how you would use siege to replay your apache logs against another server to load test it with:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;cut -d &lt;span class=&quot;s1&quot;&gt;&amp;#39; &amp;#39;&lt;/span&gt; -f7 /var/log/apache2/access.log &amp;gt; urls.txt
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;siege -c&amp;lt;concurreny rate&amp;gt; -b -f urls.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Ngrep&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For serious network packet analysis there's &lt;a href=&quot;http://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt;, with it's thousands of settings, filters and different configuration options. There's also a command line version, tshark. For simple tasks I find wireshark can be overkill, so unless I need something more powerful, ngrep is my tool of choice. It allows you to do with network packets what grep does with files.&lt;/p&gt;

&lt;p&gt;For web traffic you almost always want the &lt;code&gt;-W byline&lt;/code&gt; option which preserves linebreaks, and &lt;code&gt;-q&lt;/code&gt; is a useful argument which supresses some additional output about non-matching packets. Here's an example that captures all packets that contain GET or POST:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;ngrep -q -W byline &lt;span class=&quot;s2&quot;&gt;&amp;quot;^(GET|POST) .*&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You can also pass in additional packet filter options, such as limiting the matched packets to a certain host, IP or port. Here we filter all traffic going to or coming from google.com, port 80, and that contains the term &quot;search&quot;.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;ngrep -q -W byline &lt;span class=&quot;s2&quot;&gt;&amp;quot;search&amp;quot;&lt;/span&gt; host www.google.com and port 80
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



</content>
 </entry>
 
 <entry>
   <title>Hacker News London Meetup: A Year On</title>
   <link href="http://www.coderholic.com/hacker-news-london-meetup-a-year-on"/>
   <updated>2011-05-29T00:00:00-07:00</updated>
   <id>http://www.coderholic.com/hacker-news-london-meetup-a-year-on</id>
   <content type="html">&lt;p&gt;Almost exactly a year ago I &lt;a href=&quot;http://news.ycombinator.com/item?id=1377664&quot;&gt;posted a comment&lt;/a&gt; to Hacker News about organizing a London meetup. Soon after that I met up with &lt;a href=&quot;http://twitter.com/dmitrigrabov&quot;&gt;Dmitri&lt;/a&gt;, and over a beer we came up with a plan for the Hacker News London meetup. The first event was a lot of fun, with around 40 London based hackers turning up to a pub to drink beer and chat about what they were all hacking on.&lt;/p&gt;

&lt;p&gt;We've had 7 more meetups since then, and in that time we've grown from 40 hackers to almost 200! We've changed the format slightly, starting the evening with 8 short 5 minute talks, and we've managed to bag a few sponsors who make sure everyone who attends is well fed with pizza, and never short of a beer. We're still toying with the format a bit, and at the most recent meetup we had a panel of YC alumni (Pete Smith and Phil Cowans from SongKick, Josh Buckley from MinoMonstors, and Colin Beattie from Tuxebo - see the picture below) that I think worked well.&lt;/p&gt;

&lt;p&gt;The meetups are a great opportunity to meet like-minded people, discover new opportunities, and relax over a few beers! Our next event is going to be on June 23rd, so if you're a Hacker News reader, hacker, or simply interested in technology and startups and not too far from London then signup on our &lt;a href=&quot;http://www.meetup.com/HNLondon/&quot;&gt;meetup page&lt;/a&gt; and come along! If you're not in London then see if there's a &lt;a href=&quot;https://spreadsheets.google.com/ccc?key=0AmQExXr67OcTdDBZZl93MXZwaE4tWlQwTENVMnVQalE&amp;hl=en#gid=0&quot;&gt;HN meetup near you&lt;/a&gt;, and if not start one!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/files/IMG_2955.jpg&quot;&gt;&lt;img src=&quot;/files/IMG_2955.jpg&quot; alt=&quot;YC alumni panel&quot; title=&quot;Back Camera&quot; width=&quot;600&quot; height=&quot;448&quot; class=&quot;size-full wp-image-1180&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Scraping the web with Node.io</title>
   <link href="http://www.coderholic.com/scraping-the-web-with-node-io"/>
   <updated>2011-04-15T00:00:00-07:00</updated>
   <id>http://www.coderholic.com/scraping-the-web-with-node-io</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://node.io/&quot;&gt;Node.io&lt;/a&gt; is a relatively new screen scraping framework that allows you to easily scrape data from websites using Javascript, a language that I think is perfectly suited to the task. It's built on top of &lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt;, but you don't need to know any Node.js to get started, and can run your node.io jobs straight from the command line.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/chriso/node.io/wiki&quot;&gt;existing documentation&lt;/a&gt; is pretty good, and includes a few detailed examples, such as the one below that returns the number of google search results for some given keywords:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeio&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;node.io&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;job&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;hello&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;foobar&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;weather&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;keyword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getHtml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;http://www.google.com/search?q=&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;keyword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;#resultStats&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;keyword&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39; has &amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Running this from the command line gives you the following output:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;node.io google.js 
hello has about 878,000,000 results
foobar has about 2,630,000 results
weather has about 719,000,000 results
OK: Job &lt;span class=&quot;nb&quot;&gt;complete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Scraping Multiple Pages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately some of the documentation simply says &lt;em&gt;coming soon&lt;/em&gt;, so you're left to guess the best way to put together more advanced scraping workflows. For example, I wanted to scrape the search results from GitHub. If you search for &quot;&lt;a href=&quot;https://github.com/search?type=Repositories&amp;language=&amp;q=django&amp;repo=&amp;langOverride=&amp;x=24&amp;y=16&amp;start_value=1&quot;&gt;django&lt;/a&gt;&quot; then you (currently) get 6067 results spread over 203 pages.&lt;/p&gt;

&lt;p&gt;What I could figure out from the documentation is that a node.io job passes through several stages: input, run, reduce, and output. The documentation also mentions that multiple invocations of the run method can be run in parallel, so the logical thing to do seems to be to pass in the page number to &lt;code&gt;run&lt;/code&gt;, and have it scrape the results from a single page. You can then scrape lots of different pages in parallel.&lt;/p&gt;

&lt;p&gt;To calculate the total number of pages, and pass the page numbers to the &lt;code&gt;run&lt;/code&gt; method, I implemented an &lt;code&gt;input&lt;/code&gt; method. There's not much documentation on this, but the key thing is to make sure it returns &lt;code&gt;false&lt;/code&gt; once you're done, otherwise it'll keep getting called again and again. The other key thing is that you need to pass your data to the run method via the callback function, and it needs to be wrapped in an array. Here's the complete GitHub search results scraper:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeio&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;node.io&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;job&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// We only want the input method to run once&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getHtml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;https://github.com/search?type=Repositories&amp;amp;language=python&amp;amp;q=django&amp;amp;repo=&amp;amp;langOverride=&amp;amp;x=0&amp;amp;y=0&amp;amp;start_value=1&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;total_pages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;.pager_link&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;total_pages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// The page number will be passed to the run method&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; 
    &lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;page_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getHtml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;https://github.com/search?type=Repositories&amp;amp;language=python&amp;amp;q=django&amp;amp;repo=&amp;amp;langOverride=&amp;amp;x=0&amp;amp;y=0&amp;amp;start_value=&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;page_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;ERROR&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;retry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;.result&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;project&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
                    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;h2 a&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;listing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fulltext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;author&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot; / &amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot; / &amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;https://github.com&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;h2 a&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;listing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attribs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
                    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;language&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;.language&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;listing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fulltext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;language&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;language&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;language&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Strip of leading and trailing brackets&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;.description&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;listing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fulltext&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;While my solution works I'm sure it's not optimal. By implementing an &lt;code&gt;input&lt;/code&gt; method there's no way to specify a search term from the command line, which is far from ideal. Hopefully I'll be able to improve the scraper once some additional documentation is written, or after I've dug through the node.io code some more.&lt;/p&gt;

&lt;p&gt;There's lots more than node.io can do. It has built in functions to do things like calculate the pagerank of a domain, resolving domain names to IPs, and lots of other useful utilities. Like Node.js it also has full support for &lt;a href=&quot;http://jashkenas.github.com/coffee-script/&quot;&gt;coffeescript&lt;/a&gt;. It's a fantastic tool to have in your toolbox!&lt;/p&gt;
</content>
 </entry>
 
 
</feed>
