<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Corunet. El Blog &#187; Programación</title>
	<atom:link href="http://blog.corunet.com/category/programacion/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.corunet.com</link>
	<description>Web development, usability and more</description>
	<lastBuildDate>Fri, 23 Oct 2009 15:33:27 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Twitter alerts: using twitter streaming API for fun and profit</title>
		<link>http://blog.corunet.com/twitter-alerts-using-twitter-streaming-api/</link>
		<comments>http://blog.corunet.com/twitter-alerts-using-twitter-streaming-api/#comments</comments>
		<pubDate>Fri, 23 Oct 2009 12:25:31 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[Twitter]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://blog.corunet.com/?p=39</guid>
		<description><![CDATA[
Twitter is a wonderful service, but, until now, you have to subscribe to some websites to be alerted when a selected word (maybe your trademark) is tweeted. We&#8217;ll try to develop a service that filters the tweeter api, stores the interesting ones in our database, and show them in the browser in real time.
If you [...]]]></description>
			<content:encoded><![CDATA[<h2><img class="alignleft size-thumbnail wp-image-74" title="twitter_alerts" src="http://blog.corunet.com/wp-content/uploads/2009/10/twitter_alerts-150x150.png" alt="twitter_alerts" width="150" height="150" /></h2>
<p>Twitter is a wonderful service, but, until now, you have to subscribe to some websites to be alerted when a selected word (maybe your trademark) is tweeted. We&#8217;ll try to develop a service that filters the tweeter api, stores the interesting ones in our database, and show them in the browser in real time.</p>
<p>If you want to try it, <a href="twitter-alerts-using-twitter-streaming-api/#video">watch it in action</a>, grab<a href="/twitter-alerts-using-twitter-streaming-api/#thecode"> the code</a> or read on&#8230;</p>
<p><span id="more-39"></span></p>
<h2>What?</h2>
<p>We&#8217;ll take twitter real time results for a given word (or words) and visualize them in a browser window, like monitter.com, but on our own servers and a bit more automatic. This is going to be useful to get our own alerts and work with them.</p>
<h2>How?</h2>
<p>We are going to use the twitter stream API to get the comments containing a given word. Since the results are given in Json format, we&#8217;ll need to filter the data, take the interesting bits and store them. From the other side, we&#8217;re going to write a bit of javascript to read the data from the server into a web page using ajax. We could use other technologies, like comet, to push the data to the browser, but, while it would be a much cleaner implementation, I think I&#8217;m not ready to write about that yet (check this space <img src='http://blog.corunet.com/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> ). We need to decouple the reading of the stream from twitter and the serving to our clients because we can only have a single active  instance of the twitter streaming api at a given time, and we&#8217;re going to leave the listening proccess on for a long time using a daemon/service approach. On top of that, we want to store the tweets in a database for later perusal and data mining.</p>
<h2>The tools</h2>
<p>We are trying to make a useful system.  To do this, we&#8217;ll need some tools:</p>
<ul>
<li>HTTP server:I&#8217;m currently using <a href="http://apache.org" target="_blank" onclick="pageTracker._trackPageview('/outgoing/apache.org?referer=');">Apache</a>, but any one would be OK</li>
<li>Server side programming language: This time, PHP. While probably not as elegant or fast as Python or as cool as Ruby, gets the work done and it&#8217;s available in almost all web hosting plans. And the documentation is pretty extensive.</li>
<li>A database to store all the info</li>
<li>Client side programming: We are going to use HTML, javascript and the jQuery javascript library to simplify AJA(X) programming and for the effects.</li>
<li>A twitter acount: Yours own is OK but maybe you want to create a new one for this kind of tasks. Write down your user and password, we are going to need them soon.</li>
<li>The twitter API. Twitter people are so kind they have developed a restful API free for everyone to use. And it&#8217;s sub-zero cool.</li>
</ul>
<h2>The Twitter streaming API</h2>
<p>Twitter has published a Streaming API that&#8217;s described as “The Twitter Streaming API allows near-realtime access to various subsets of Twitter public statuses”. In fact, this is jus what we need. You can read all the documentation at <a href="https://twitterapi.pbworks.com/Streaming-API-Documentation" onclick="pageTracker._trackPageview('/outgoing/twitterapi.pbworks.com/Streaming-API-Documentation?referer=');">https://twitterapi.pbworks.com/Streaming-API-Documentation</a>, but I&#8217;ll try to take the interesting parts for this project so you don&#8217;t need to yet.</p>
<p>We are going to use just one method (status/filter) to get results including one or several words. This method can return a stream of data in xml or json formats, has to be called using POST and can get some parameters. You can use it from the command line if you have access to some kind of unix in the following way:</p>
<p><code>curl -d 'track=google' http://stream.twitter.com/1/statuses/filter.json -uuser:password</code></p>
<p>Where user and password are your twitter credentials.</p>
<p>It should return something like:</p>
<p class="toggle_code"><a href="/uploads/code/response.json" onclick="jQuery('#code_0').toggle('slow');return false">response.json</a></p>
<div  style="display:none" id="code_0">
<pre class="brush: js">{&quot;text&quot;:&quot;RT @gillesguillemin: Google FINALLY Releases AS3 Player for YouTube http://bit.ly/yKgaz&quot;,
&quot;favorited&quot;:false,&quot;in_reply_to_user_id&quot;:null,&quot;in_reply_to_screen_name&quot;:null,&quot;source&quot;:&quot;web&quot;,&quot;geo&quot;:null,
&quot;in_reply_to_status_id&quot;:null,&quot;user&quot;:{&quot;friends_count&quot;:52,&quot;screen_name&quot;:&quot;imrahil&quot;,&quot;verified&quot;:false,
&quot;profile_background_color&quot;:&quot;9ae4e8&quot;,&quot;favourites_count&quot;:8,&quot;notifications&quot;:null,&quot;profile_text_color&quot;:&quot;000000&quot;,
&quot;description&quot;:&quot;&quot;,&quot;location&quot;:&quot;Poland&quot;,&quot;time_zone&quot;:&quot;Warsaw&quot;,&quot;profile_link_color&quot;:&quot;0000ff&quot;,&quot;following&quot;:null,
&quot;profile_background_image_url&quot;:&quot;http://s.twimg.com/a/1255558003/images/themes/theme1/bg.png&quot;,
&quot;profile_sidebar_fill_color&quot;:&quot;e0ff92&quot;,&quot;protected&quot;:false,&quot;url&quot;:&quot;http://flex.imrahil.com&quot;,
&quot;geo_enabled&quot;:false,&quot;profile_background_tile&quot;:false,&quot;name&quot;:&quot;Jarek&quot;,&quot;profile_sidebar_border_color&quot;:&quot;87bc44&quot;,
&quot;profile_image_url&quot;:&quot;http://a3.twimg.com/profile_images/76229939/avatar5_normal.jpg&quot;,&quot;id&quot;:5393872,
&quot;statuses_count&quot;:48,&quot;utc_offset&quot;:3600,&quot;created_at&quot;:&quot;Sun Apr 22 07:02:54 +0000 2007&quot;,&quot;followers_count&quot;:31},
&quot;id&quot;:4887052323,&quot;truncated&quot;:false,&quot;created_at&quot;:&quot;Thu Oct 15 12:10:57 +0000 2009&quot;}

{&quot;text&quot;:&quot;Google Wave\u2019s Little Secret: It Already Works On The iPhone&quot;,&quot;favorited&quot;:false,
&quot;in_reply_to_user_id&quot;:null,&quot;in_reply_to_screen_name&quot;:null,
&quot;source&quot;:&quot;&lt;a href=\&quot;http://www.atebits.com/\&quot; rel=\&quot;nofollow\&quot;&gt;Tweetie&lt;/a&gt;&quot;,&quot;geo&quot;:null,
&quot;in_reply_to_status_id&quot;:null,&quot;user&quot;:{&quot;friends_count&quot;:52,&quot;screen_name&quot;:&quot;Jwizzman&quot;,
&quot;verified&quot;:false,&quot;profile_background_color&quot;:&quot;000000&quot;,&quot;favourites_count&quot;:1,&quot;notifications&quot;:null,
&quot;profile_text_color&quot;:&quot;663B12&quot;,&quot;description&quot;:&quot;&quot;,&quot;location&quot;:&quot;Amsterdam, The Netherlands&quot;,
&quot;time_zone&quot;:&quot;Amsterdam&quot;,&quot;profile_link_color&quot;:&quot;1F98C7&quot;,&quot;following&quot;:null,
&quot;profile_background_image_url&quot;:&quot;http://a3.twimg.com/profile_background_images/20711299/background.png&quot;,
&quot;profile_sidebar_fill_color&quot;:&quot;DAECF4&quot;,&quot;protected&quot;:false,&quot;url&quot;:&quot;http://www.julianprofas.com&quot;,
&quot;geo_enabled&quot;:false,&quot;profile_background_tile&quot;:false,&quot;name&quot;:&quot;Julian Profas \uf8ff&quot;,
&quot;profile_sidebar_border_color&quot;:&quot;C6E2EE&quot;,
&quot;profile_image_url&quot;:&quot;http://a3.twimg.com/profile_images/458268969/foto_avatar_2_normal.png&quot;,
&quot;id&quot;:15727906,&quot;statuses_count&quot;:645,&quot;utc_offset&quot;:3600,&quot;created_at&quot;:&quot;Mon Aug 04 21:08:08 +0000 2008&quot;,
&quot;followers_count&quot;:79},&quot;id&quot;:4887052409,&quot;truncated&quot;:false,&quot;created_at&quot;:&quot;Thu Oct 15 12:10:58 +0000 2009&quot;}
</pre>
</div>
<p>Until you exit it (with CTRL+C) . This is a Json stream and can be read and parsed by several means. In fact, it&#8217;s eval-uable javascript code that we could read from the browser. But right now, we&#8217;re going to use a server-side language to read it and work with the interesting parts.</p>
<h2>Reading Twitter stream</h2>
<p>As I told you in the tools section, we are going to use PHP as our server side language. For the first part, the reading of the twitter stream, we don&#8217;t even need a web server, since we can run it from the command line. And if we run it from the command line, we can convert it to a kind of daemon/service and leave it on for a long time. But first, some code:</p>
<p class="toggle_code"><a href="/uploads/code/basicstreaming.php" onclick="jQuery('#code_1').toggle('slow');return false">basicstreaming.php</a></p>
<div  style="display:none" id="code_1">
<pre class="brush: php">&lt;?php
	$opts = array(
		&#039;http&#039;=&gt;array(
			&#039;method&#039;	=&gt;	&quot;POST&quot;,
			&#039;content&#039;	=&gt;	&#039;track=flickr&#039;,
		)
	);
	$context = stream_context_create($opts);
	$instream = fopen(&#039;http://USERNAME:PASSWORD@stream.twitter.com/1/statuses/filter.json&#039;,&#039;r&#039; ,false, $context);
	while(! feof($instream)) {
		if(! ($line = stream_get_line($instream, 20000, &quot;\n&quot;))) {
			continue;
		}else{
			//print_r(json_decode($line));
			$tweet = json_decode($line);
			print $tweet-&gt;{&#039;text&#039;}.&quot;\n&quot;;
			flush();
		}
	}
?&gt;
</pre>
</div>
<p>This is (almost) the simplest code that delivers what we want. A php formatted stream of tweets that include our marked word. If you run it from your command line, it&#8217;ll show something like:</p>
<p class="toggle_code"><a href="/uploads/code/sample_tweets.php" onclick="jQuery('#code_2').toggle('slow');return false">sample_tweets.php</a></p>
<div  style="display:none" id="code_2">
<pre class="brush: php">stdClass Object
(
    [favorited] =&gt;
    [in_reply_to_user_id] =&gt;
    [text] =&gt; Multiplexing and switching - sci.electronics.design | Google Groups: Multiplexing and switc.. http://bit.ly/m4nIq http://bit.ly/o0gO4
    [created_at] =&gt; Tue Oct 20 09:32:08 +0000 2009
    [geo] =&gt;
    [in_reply_to_screen_name] =&gt;
    [source] =&gt; &lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;
    [in_reply_to_status_id] =&gt;
    [user] =&gt; stdClass Object
        (
            [notifications] =&gt;
            [profile_text_color] =&gt; 1c0a1c
            [url] =&gt; http://www.managedserviceproviders.biz
            [time_zone] =&gt; Arizona
            [created_at] =&gt; Thu Aug 20 20:54:47 +0000 2009
            [profile_link_color] =&gt; 29ccf5
            [profile_background_image_url] =&gt; http://s.twimg.com/a/1255997062/images/themes/theme9/bg.gif
            [description] =&gt; Telecom Professional who enjoys helping businesses set up and manage their communications including voice, data and social media.
            [profile_sidebar_fill_color] =&gt; 361da3
            [profile_background_tile] =&gt;
            [following] =&gt;
            [profile_sidebar_border_color] =&gt; 0d0f12
            [geo_enabled] =&gt;
            [statuses_count] =&gt; 737
            [followers_count] =&gt; 237
            [protected] =&gt;
            [profile_image_url] =&gt; http://a3.twimg.com/profile_images/424262573/istock_000008181854xsmall-150x150_1__normal.jpg
            [location] =&gt; Phoenix, AZ
            [friends_count] =&gt; 236
            [name] =&gt; Jennifer Springston
            [verified] =&gt;
            [profile_background_color] =&gt; 1A1B1F
            [screen_name] =&gt; inTELEgentMSP
            [id] =&gt; 67421923
            [utc_offset] =&gt; -25200
            [favourites_count] =&gt; 0
        )

    [truncated] =&gt;
    [id] =&gt; 5015099767
)
stdClass Object
(
    [text] =&gt; 逛了一下午的教务处、鼓浪听涛、豆瓣和Google，寻找厦大比较能说的老师。结果发现大部分老师都不在漳州校区开课。
    [created_at] =&gt; Tue Oct 20 09:32:12 +0000 2009
    [in_reply_to_status_id] =&gt;
    [source] =&gt; &lt;a href=&quot;http://www.hellotxt.com/&quot; rel=&quot;nofollow&quot;&gt;HelloTxt&lt;/a&gt;
    [truncated] =&gt;
    [favorited] =&gt;
    [in_reply_to_user_id] =&gt;
    [user] =&gt; stdClass Object
        (
            [profile_background_image_url] =&gt; http://s.twimg.com/a/1255724203/images/themes/theme3/bg.gif
            [url] =&gt; http://www.jiakon.com
            [created_at] =&gt; Sat Jun 02 07:59:20 +0000 2007
            [profile_sidebar_fill_color] =&gt; E3E2DE
            [profile_background_tile] =&gt;
            [profile_sidebar_border_color] =&gt; D3D2CF
            [description] =&gt; Education,Student,Mathematics,Edubuntu
            [geo_enabled] =&gt;
            [statuses_count] =&gt; 2379
            [followers_count] =&gt; 186
            [friends_count] =&gt; 168
            [following] =&gt;
            [verified] =&gt;
            [profile_background_color] =&gt; EDECE9
            [favourites_count] =&gt; 8
            [protected] =&gt;
            [profile_image_url] =&gt; http://a1.twimg.com/profile_images/26783462/IMG0017A_normal.jpg
            [location] =&gt; Xiamen China
            [notifications] =&gt;
            [profile_text_color] =&gt; 634047
            [name] =&gt;  Xu Tom
            [time_zone] =&gt; Beijing
            [screen_name] =&gt; jiakon
            [id] =&gt; 6518512
            [utc_offset] =&gt; 28800
            [profile_link_color] =&gt; 088253
        )

    [id] =&gt; 5015100624
    [geo] =&gt;
    [in_reply_to_screen_name] =&gt;
)
stdClass Object
(
    [favorited] =&gt;
    [in_reply_to_user_id] =&gt;
    [text] =&gt; Google Wave&#039;s Best Use Cases - Wave - Lifehacker: http://bit.ly/1nCqlM
    [created_at] =&gt; Tue Oct 20 09:32:12 +0000 2009
    [geo] =&gt;
    [in_reply_to_screen_name] =&gt;
    [source] =&gt; &lt;a href=&quot;http://apiwiki.twitter.com/&quot; rel=&quot;nofollow&quot;&gt;API&lt;/a&gt;
    [in_reply_to_status_id] =&gt;
    [user] =&gt; stdClass Object
        (
            [notifications] =&gt;
            [profile_text_color] =&gt; 3C3940
            [url] =&gt;
            [time_zone] =&gt; Alaska
            [created_at] =&gt; Sun Apr 19 05:22:52 +0000 2009
            [profile_link_color] =&gt; 0099B9
            [profile_background_image_url] =&gt; http://s.twimg.com/a/1255980505/images/themes/theme4/bg.gif
            [description] =&gt; Enrich you websurfing experience! Discover interesting and amazing websites now.
            [profile_sidebar_fill_color] =&gt; 95E8EC
            [profile_background_tile] =&gt;
            [following] =&gt;
            [profile_sidebar_border_color] =&gt; 5ED4DC
            [geo_enabled] =&gt;
            [statuses_count] =&gt; 4233
            [followers_count] =&gt; 5997
            [protected] =&gt;
            [profile_image_url] =&gt; http://a1.twimg.com/profile_images/146406726/webdiscover_normal.jpg
            [location] =&gt; Webverse
            [friends_count] =&gt; 5735
            [name] =&gt; Web Explorer
            [verified] =&gt;
            [profile_background_color] =&gt; 0099B9
            [screen_name] =&gt; Web2Discover
            [id] =&gt; 33139096
            [utc_offset] =&gt; -32400
            [favourites_count] =&gt; 0
        )

    [truncated] =&gt;
    [id] =&gt; 5015100766
)
</pre>
</div>
<p>Let&#8217;s look at the code:</p>
<p>We create an $opts array (in fact an array of arrays) that contain the parameters. In this particular case, we&#8217;re using two, the POST method and the search line (track=google). Then, we can treat the twitter stream as a file, using <em>stream_context_create</em> and <em>fopen</em> and just start reading lines. Each line is going to be a JSON encoded tweet, similar to what we&#8217;ve seen when we called the API from command line. Since we want to use the contents as easily as possible, we&#8217;ll need the <em>json_decode</em> function to parse it into PHP objects, print them and call <em>flush</em> just in case we&#8217;re calling the script from a browser.</p>
<h2>Storing the results</h2>
<p>The best way to store the results for later perusal is a database. I&#8217;m using MySQL but any other database should be OK. To be able to store the data, we need to create a database with a single table.</p>
<p>We are only going to store the following data:</p>
<ul>
<li>Text: This is the twetter status. 140 chars max.</li>
<li>User screen name: The screen name of the poster. This is needed to create the link to twitter</li>
<li>Id: A unique id for the tweet. It&#8217;s a sequential number, so, we can order the tweets acording to this, and use it along with the user screen name to built a link back to twitter, and use it as our primary key.</li>
<li>Followers count: The number of people that are going to receive the tweet in their inboxes. We are using it to style the real-time viewer. Since my primal intention is to watch a trademark, I care about the number of people that are watching the messages.</li>
<li>The time of the tweet: basically for filtering purposes. We are going to store our server time to avoid lengthy conversions.</li>
</ul>
<p>We could store several other fields, and a complete solution should probably take into account that you can have some different tweet types, but for the time, these four fields should suffice.</p>
<p>To create the table, we can run the following SQL script from the server:</p>
<p class="toggle_code"><a href="/uploads/code/createtable.sql" onclick="jQuery('#code_3').toggle('slow');return false">createtable.sql</a></p>
<div  style="display:none" id="code_3">
<pre class="brush: sql">CREATE DATABASE twitter_alerts;

CREATE TABLE `twitter_alerts`.`tweets` (
`id` BIGINT UNSIGNED NOT NULL ,
`text` VARCHAR( 150 ) NOT NULL ,
`screen_name` VARCHAR( 255 ) NOT NULL ,
`followers_count` INT NOT NULL ,
`created_at` DATETIME NOT NULL ,
PRIMARY KEY ( `id` )
) ENGINE = InnoDB;

GRANT ALL PRIVILEGES ON twitter_alerts.tweets TO &#039;twitter_alerts&#039;@&#039;localhost&#039; IDENTIFIED BY &#039;somepasword&#039;;
</pre>
</div>
<p>After that, we will have a single table database waiting for us to fill it with tweets&#8230; Let&#8217;s go:</p>
<p class="toggle_code"><a href="/uploads/code/storing_tweets_in_the_database.php" onclick="jQuery('#code_4').toggle('slow');return false">storing_tweets_in_the_database.php</a></p>
<div  style="display:none" id="code_4">
<pre class="brush: php">&lt;?php

	$opts = array(
		&#039;http&#039;=&gt;array(
			&#039;method&#039;	=&gt;	&quot;POST&quot;,
			&#039;content&#039;	=&gt;	&#039;track=twitter&#039;,
		)
	);
	//We&#039;re going to store the data in the database, so, let&#039;s open a connection:
	$db = mysql_connect(&#039;localhost&#039;, &#039;twitter_alerts&#039;, &#039;somepasword&#039;);
	mysql_select_db(&#039;twitter_alerts&#039;, $db);

	$context = stream_context_create($opts);
	while (1){
		$instream = fopen(&#039;http://USERNAME:PASSWORD@stream.twitter.com/1/statuses/filter.json&#039;,&#039;r&#039; ,false, $context);
		while(! feof($instream)) {
			if(! ($line = stream_get_line($instream, 20000, &quot;\n&quot;))) {
				continue;
			}else{
				$tweet = json_decode($line);
				//Clean the inputs before storing
				$id = mysql_real_escape_string($tweet-&gt;{&#039;id&#039;});
				$text = mysql_real_escape_string($tweet-&gt;{&#039;text&#039;});
				$screen_name = mysql_real_escape_string($tweet-&gt;{&#039;user&#039;}-&gt;{&#039;screen_name&#039;});
				$followers_count = mysql_real_escape_string($tweet-&gt;{&#039;user&#039;}-&gt;{&#039;followers_count&#039;});
				//We store the new post in the database, to be able to read it later
				$ok = mysql_query(&quot;INSERT INTO tweets (id ,text ,screen_name ,followers_count, created_at) VALUES (&#039;$id&#039;, &#039;$text&#039;, &#039;$screen_name&#039;, &#039;$followers_count&#039;, NOW())&quot;);
				if (!$ok) {echo &quot;Mysql Error: &quot;.mysql_error();}
				flush();
			}
		}
	}
?&gt;
</pre>
</div>
<p>It&#8217;s as ugly as sin but it works. If you run it from the command line, it should start storing tweets in your database and keep on until you stop it. So, our database is starting to fill with tweets concerning our desired word. Now we need to be able to navigate them&#8230;</p>
<h2>Creating the code from the server side</h2>
<p>Now we need to publish the tweets in our browser. To do that, we need a small PHP script that returns the tweets when called. If we call it with a parameter <em>start</em> it&#8217;ll return all the tweets with an id bigger than that. Otherwise, it&#8217;ll return the last ten tweets stored in our database. To do that, we will use two different queries, the first one to return the last ten results, and the second one to return all results since the given <em>id</em>. We use subqueries (SELECT from SELECT) to get the results in our wished order.</p>
<p class="toggle_code"><a href="/uploads/code/server.php" onclick="jQuery('#code_5').toggle('slow');return false">server.php</a></p>
<div  style="display:none" id="code_5">
<pre class="brush: php">&lt;?php
	//We are going to need a database connection:
	$db = mysql_connect(&#039;localhost&#039;, &#039;twitter_alerts&#039;, &#039;somepasword&#039;);
	mysql_select_db(&#039;twitter_alerts&#039;, $db);
	//Now, two possibilities: if we don&#039;t have a start parameter, we print the last ten tweets.
	//Otherwise, we print all the tweets with IDs bigger than start, if any
	$start = mysql_real_escape_string($_GET[&#039;start&#039;]);
	if(! $start){
		$query = &quot;SELECT * FROM (SELECT * FROM tweets ORDER BY id DESC LIMIT 0,10) AS last_ten ORDER BY id ASC&quot;;
	}else{
		$query = &quot;SELECT * FROM (SELECT * FROM tweets WHERE id&gt;&quot;.$start.&quot; ORDER BY id DESC LIMIT 0,10) AS new_tweets ORDER BY id ASC&quot;;
	}

	$result = mysql_query($query);
	$data = array(); //Initializing the results array

	while ($row = mysql_fetch_assoc($result)){
		array_push($data, $row);
	}
	$json = json_encode($data);
	print $json;
?&gt;</pre>
</div>
<p>We are going to poll this code every ten seconds and refresh the tweets list to show the most recent ones, using javascript, and the output format will be JSON.</p>
<h2>Writing a front-end</h2>
<p>Since, as <a href="http://wall.org" target="_blank" onclick="pageTracker._trackPageview('/outgoing/wall.org?referer=');">Larry Wall</a> said, one of the cardinal virtues of a programmer is lazyness, we are going to use jQuery  to construct the interface and the business logic. And we&#8217;re not even serving it, but linking from the Google CDN, as <a href="http://encosia.com/2009/10/11/do-you-know-about-this-undocumented-google-cdn-feature/" target="_blank" onclick="pageTracker._trackPageview('/outgoing/encosia.com/2009/10/11/do-you-know-about-this-undocumented-google-cdn-feature/?referer=');">Dave Ward posted</a> in his wondeful blog.</p>
<p>So, let&#8217;s start with the HTML:</p>
<p class="toggle_code"><a href="/uploads/code/barebones.html" onclick="jQuery('#code_6').toggle('slow');return false">barebones.html</a></p>
<div  style="display:none" id="code_6">
<pre class="brush: html">&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;gl&quot; lang=&quot;gl&quot;&gt;
&lt;head&gt;
		&lt;title&gt;Twitter alert viewer&lt;/title&gt;
		&lt;meta http-equiv=&quot;Content-type&quot; content=&quot;text/html;charset=UTF-8&quot; /&gt;
		&lt;script src=&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js&quot;&gt;&lt;/script&gt;
		&lt;style type=&quot;text/css&quot;&gt;
				body{
						background-color:#ccccff;
						font-family: georgia, serif;
				}
				.hidden{
						height:0px;
						overflow:hidden;
				}
				#tweets{
						margin:0 auto;
						width:400px;
				}
				#tweets div{
						border:1px solid silver;
						margin:5px;
						padding:5px;
						background-color:#fff;
				}
		&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
		&lt;div id=&quot;tweets&quot;&gt;
				&lt;h1&gt;Twitter alert viewer&lt;/h1&gt;
				&lt;a href=&quot;#&quot; onclick=&quot;clearTimeout(timeOut)&quot;&gt;Pause&lt;/a&gt;
				&lt;a href=&quot;#&quot; onclick=&quot;poll()&quot;&gt;Run&lt;/a&gt;
		&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
</div>
<p>Just some styles, one script link and a body containing one div, two links and a header. Simple, eh?</p>
<p>We need to add some javascript in the middle of the code to connect to the server:</p>
<p class="toggle_code"><a href="/uploads/code/logic.js" onclick="jQuery('#code_7').toggle('slow');return false">logic.js</a></p>
<div  style="display:none" id="code_7">
<pre class="brush: js">
var last = &#039;&#039;;
var timeOut;

function getTweets(id){
		$.getJSON(&quot;server.php?start=&quot;+id,
		function(data){
				$.each(data, function(count,item){
						addNew(item);
						last = item.id;
				});
		});
}

function addNew(item){
		if($(&#039;#tweets div.tweet&#039;).length&gt;9){ //If we have more than nine tweets
				$(&#039;#tweets div.tweet:first&#039;).toggle(300);//remove it form the screen
				$(&#039;#tweets div.tweet:first&#039;).removeClass(&#039;tweet&#039;);//and it&#039;s class
				$(&quot;#tweets div:hidden&quot;).remove(); //sweeps the already hidden elements
		}
		$(&#039;#tweets&#039;).append(renderTweet(item, &#039;hidden&#039;));
}

function renderTweet(item){
		importanceColor=getImportanceColor(item.followers_count);
		return &#039;&lt;div class=&quot;tweet&quot; id=&quot;&#039;+item.id+&#039;&quot;&gt;&#039;+
		&#039;&lt;strong&gt;&lt;a href=&quot;http://twitter.com/&#039;+item.screen_name+&#039;&quot; style=&quot;color:&#039;+importanceColor+&#039;&quot;&gt;&#039;+
		item.screen_name+&#039;&lt;/a&gt;&lt;/strong&gt;&lt;span class=&quot;text&quot;&gt;&#039;+
		item.text
		+&#039;&lt;/span&gt;&lt;span class=&quot;created_at&quot;&gt;&lt;br /&gt;&lt;a href=&quot;http://twitter.com/&#039;+
		item.screen_name+&#039;/status/&#039;+item.id+&#039;&quot;&gt;&#039;+
		item.created_at+&#039;&lt;/span&gt;&lt;/div&gt;&#039;;
}

function getImportanceColor(number){
		rgb = 255-Math.floor(16*(Math.log(number+1)+1)); //should return about 0 for 0 followers and 255 for 4million (Ashton Kutchner? Obama?)
		return &#039;rgb(&#039;+rgb+&#039;,0,0)&#039;;
}

function poll(){
		timeOut = setTimeout(&#039;poll()&#039;, 200);//It calls itself every 200ms
		getTweets(last);
}

$(document).ready(function() {
		poll();
});</pre>
</div>
<p>Let&#8217;s see&#8230; This is probably the most complex part of the article, so, I&#8217;ll try to go slow and explain every function:</p>
<h3>getTweets(id)</h3>
<p>This function calls the server using the getJSON jQuery method. Then, it takes each response line and calls addNew with it. If we call it with an <em>id</em> parameter, it&#8217;ll ask the server for all tweets with <em>id</em>s greater than that. Otherwise, it will grab the last ten tweets.</p>
<h3>addNew(item)</h3>
<p>It takes an item (a tweet) as input. It hides the first tweet, remove it&#8217;s &#8216;tweet&#8217; class, appends a new tweet at the bottom and shows it. It calls the renderTweet function to get the tweet in HTML format.</p>
<h3>renderTweet(item)</h3>
<p>Just one line to call getImportanceColor() and a return with the HTML code. It&#8217;s a bit long but that&#8217;s because we&#8217;re adding a couple of links to the tweet to be able to visit the original one.</p>
<h3>getImportanceColor(number)</h3>
<p>It takes a number of followers and returns a rgb color that will be between total black, for people without followers, and total red, for Ashton Kutchner. It uses logarithms to scale between the two extremes, because there are 6 orders of magnitude between the extremes. We will use it to paint (it) black the twitters with few followers and red the twitter stars.</p>
<h3>poll()</h3>
<p>This is the timeout function that calls itself every 200ms and gets the new tweets.</p>
<p>The last block just starts the polling as soon as the document is loaded.</p>
<h2>The Result</h2>
<p><a name="video" ></a><object type="application/x-shockwave-flash" width="425" height="344" data="http://www.youtube.com/v/96JEBQk8vig&fs=1&rel=0&hd=1&showinfo=0"><param name="movie" value="http://www.youtube.com/v/96JEBQk8vig&fs=1&rel=0&hd=1&showinfo=0"></param><param name="allowFullScreen" value="true"></param><param name="wmode" value="transparent" /></object></p>
<p>This is a small screen capture of my browser visiting the HTML/javascript page while running storing_tweets_in_the_database.php. It&#8217;s watching the word &#8216;twitter&#8217; and, as you can see, it&#8217;s running too fast for the human eye -at least mine -, but since we are keeping all the data in our database, it&#8217;s not lost forever  <img src='http://blog.corunet.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<h2>Limits</h2>
<p>Right now, because of the Twitter API limits, just one instance of the watching process can be run at once. Anyhow, you can write several words, separated by commas, and Twitter will return results for all of them.This code should not be used in production, since there are almost no security checks to avoid missuse. If you want to use it in a machine open to the public, you should check -twice- every input for missbehaviour.</p>
<h2>The code</h2>
<p><a name="thecode"></a></p>
<ol>
<li><a href="/uploads/twitter_watch/twitter_watch.zip">Download the code</a> and unzip it into a folder in your local webserver</li>
<li>Edit config.php to add your twitter login data and the words you want to watch</li>
<li>Create the database and the table with the SQL code above</li>
<li>Run watch.php and leave it running for as long as you wish.</li>
<li>Visit http://localhost/thefolderwhereyouunzippedthecode/ and watch the tweets coming.</li>
</ol>
<h2>Further work</h2>
<p>Obviously, this is just a sample. It can be made much better looking, and we could even analyze the tweets and tweet back a response to any questions concerning our keywords. The watch module should be daemonized or converted to a service to be left unatended. The HTML page could be able to filter between two dates and so on. Keep on watching. We&#8217;ll try to keep on posting this kind of contents.</p>
<h2>Shameless plug</h2>
<p>I&#8217;m part of <a href="http://coru.net/" onclick="pageTracker._trackPageview('/outgoing/coru.net/?referer=');">Corunet</a>, a web agency in Spain, that can deliver consistent good results in all kind of internet projects. You can visit our website <a href="http://coru.net/" onclick="pageTracker._trackPageview('/outgoing/coru.net/?referer=');">http://coru.net/</a> or contact me at david@corunet.com if you have any special needs <img src='http://blog.corunet.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>You can follow me on twitter as<a href="http://twitter.com/dei_biz/" onclick="pageTracker._trackPageview('/outgoing/twitter.com/dei_biz/?referer=');"> @dei_biz</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/twitter-alerts-using-twitter-streaming-api/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Eye tracking para pobres. Parte 2</title>
		<link>http://blog.corunet.com/eye-tracking-para-pobres-parte-2/</link>
		<comments>http://blog.corunet.com/eye-tracking-para-pobres-parte-2/#comments</comments>
		<pubDate>Mon, 24 Jul 2006 11:46:34 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[Programación]]></category>
		<category><![CDATA[Usabilidad]]></category>

		<guid isPermaLink="false">http://blog.corunet.com/usabilidad/eye-tracking-para-pobres-parte-2</guid>
		<description><![CDATA[En la primera parte del artículo escribimos un pequeño script para analizar la posición de los clicks en una página web. Despues de unas cuantas pruebas más, ya tenemos la información necesaria para hacer un pequeño análisis.
He probado a implantar los scripts en el sitio web de corunet y he hecho algunas pruebas de clicks [...]]]></description>
			<content:encoded><![CDATA[<p>En la <a href="/usabilidad/eye-tracking-para-pobres">primera parte del artículo </a>escribimos un pequeño script para analizar la posición de los clicks en una página web. Despues de unas cuantas pruebas más, ya tenemos la información necesaria para hacer un pequeño análisis.<span id="more-7"></span><br />
He probado a implantar los scripts en el sitio web de <a href="http://www.corunet.com" onclick="pageTracker._trackPageview('/outgoing/www.corunet.com?referer=');">corunet</a> y he hecho algunas pruebas de clicks en la pantalla a una resolución de 1280*1024. Al cabo de 65 clicks, tengo un log como este:</p>
<p><code><br />
x332y288	http://corunet.com/	192.168.0.10<br />
x399y288	http://corunet.com/	192.168.0.10<br />
x489y294	http://corunet.com/	192.168.0.10<br />
x655y346	http://corunet.com/	192.168.0.10<br />
x709y351	http://corunet.com/	192.168.0.10<br />
x350y348	http://corunet.com/	192.168.0.10<br />
x384y305	http://corunet.com/	192.168.0.10<br />
...<br />
</code></p>
<p>De aquí, me  interesan las coordenadas x e y de cada click para situarlas sobre la página. Para ello, el siguiente código me va a ayudar bastante:</p>
<p><code><br />
<span style="color:#399;font-style:italic;">#!/usr/bin/perl</span><br />
<span style="color:#001;">open</span> <span style="color:#000;">(</span><span style="color:#3A3;">DATOS</span><span style="color:#000;">,</span><span style="color:#00a;">"</span><span style="color:#00a;">log.txt</span><span style="color:#00a;">"</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#f70;">@datos</span><span style="color:#000;">=</span><span style="color:#000;">&lt;</span><span style="color:#3A3;">DATOS</span><span style="color:#000;">&gt;</span><span style="color:#000;">;</span><br />
<span style="color:#001;">close</span> <span style="color:#000;">(</span><span style="color:#3A3;">DATOS</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#080;">$i</span><span style="color:#000;">;</span><br />
<span style="color:#000;">foreach</span> <span style="color:#000;">my</span> <span style="color:#080;">$dato</span><span style="color:#000;">(</span><span style="color:#f70;">@datos</span><span style="color:#000;">)</span><span style="color:#000;">{</span><br />
	<span style="color:#000;">if</span> <span style="color:#000;">(</span><span style="color:#080;">$dato</span><span style="color:#000;">=~</span><span style="color:#00a;">/</span><span style="color:#00a;"><span style="color:#800;">\.</span>com<span style="color:#800;">\/</span><span style="color:#800;">\t</span></span><span style="color:#00a;">/</span><span style="color:#000;">)</span><span style="color:#000;">{</span><br />
		<span style="color:#000;">my</span> <span style="color:#f70;">@posicion</span><span style="color:#000;">=</span><span style="color:#001;">split</span><span style="color:#000;">(</span><span style="color:#00a;">"</span><span style="color:#00a;"><span style="color:#800;">\t</span></span><span style="color:#00a;">"</span><span style="color:#000;">,</span><span style="color:#080;">$dato</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
		<span style="color:#f70;">$posicion</span><span style="color:#000;">[</span><span style="color:#f0f;">0</span><span style="color:#000;">]</span><span style="color:#000;">=~</span><span style="color:#00a;">s/</span><span style="color:#00a;">x</span><span style="color:#00a;">/</span><span style="color:#00a;"></span><span style="color:#00a;">/</span><span style="color:#000;">;</span><br />
		<span style="color:#000;">my</span> <span style="color:#000;">(</span><span style="color:#080;">$x</span><span style="color:#000;">,</span><span style="color:#080;">$y</span><span style="color:#000;">)</span><span style="color:#000;">=</span><span style="color:#001;">split</span><span style="color:#000;">(</span><span style="color:#00a;">"</span><span style="color:#00a;">y</span><span style="color:#00a;">"</span><span style="color:#000;">,</span><span style="color:#f70;">$posicion</span><span style="color:#000;">[</span><span style="color:#f0f;">0</span><span style="color:#000;">]</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
		<span style="color:#080;">$i</span><span style="color:#000;">++</span><span style="color:#000;">;</span><br />
		<span style="color:#300;">print</span> <span style="color:#00a;">'</span><span style="color:#00a;">&lt;img src="/i/pelotilla.png" </span><br />
<span style="color:#00a;">		style="z-index:</span><span style="color:#00a;">'</span><span style="color:#000;">.</span><span style="color:#080;">$i</span><span style="color:#000;">.</span><span style="color:#00a;">'</span><span style="color:#00a;">;position:absolute;</span><br />
<span style="color:#00a;">		left:</span><span style="color:#00a;">'</span><span style="color:#000;">.</span><span style="color:#080;">$x</span><span style="color:#000;">.</span><span style="color:#00a;">'</span><span style="color:#00a;">px;top:</span><span style="color:#00a;">'</span><span style="color:#000;">.</span><span style="color:#080;">$y</span><span style="color:#000;">.</span><span style="color:#00a;">'</span><span style="color:#00a;">px;</span><br />
<span style="color:#00a;">		height:16px;width:16px;"&gt;</span><span style="color:#00a;">'</span><span style="color:#000;">.</span><span style="color:#00a;">"</span><span style="color:#00a;"><span style="color:#800;">\n</span></span><span style="color:#00a;">"</span><span style="color:#000;">;</span><br />
	<span style="color:#000;">}</span><br />
<span style="color:#000;">}</span><br />
</code></p>
<p>Este programilla (en perl) imprime un tag IMG para cada línea del log que conincide con la página raíz (termina en .com/), líneas que luego añadiremos a una copia de la página original. Además, será necesario tener una imagen en formato PNG para marcar los puntos. Yo he utilizado pelotilla.png, una imagen de 16&#215;16px creada en photoshop.</p>
<h2>El resultado</h2>
<p>El programa imprime en pantalla un trocito de código que se puede pegar directamente bajo la etiqueta &lt;body&gt; de una copia de la página en cuestión:</p>
<p><code><br />
...<br />
&lt;img src="/i/pelotilla.png"<br />
                style="z-index:51;position:absolute;<br />
                left:605px;top:773px;<br />
                height:16px;width:16px;"><br />
&lt;img src="/i/pelotilla.png"<br />
                style="z-index:52;position:absolute;<br />
                left:778px;top:792px;<br />
                height:16px;width:16px;"><br />
&lt;img src="/i/pelotilla.png"<br />
                style="z-index:53;position:absolute;<br />
                left:799px;top:810px;<br />
                height:16px;width:16px;"><br />
...<br />
</code></p>
<p> El resultado es este, utilizando las indicaciones de <a href="http://webfx.eae.net/dhtml/pngbehavior/pngbehavior.html" onclick="pageTracker._trackPageview('/outgoing/webfx.eae.net/dhtml/pngbehavior/pngbehavior.html?referer=');">webfx </a>para conseguir transparencia en PNGs en Internet explorer:</p>
<p><img src="/uploads/captura.jpg" alt="Captura de pantalla con clicks" /></p>
<p>Cada uno de los puntos rojos se refiere a un click en la página, con lo que podemos empezar a hacernos una idea de las necesidades del usuario. Por ejemplo, en la sección de las noticias de la derecha, los usuarios han hacho click en el cuerpo de la noticia, buscando más información. Será conveniente agregar un icono de leer más, o convertir el texto en un enlace. En un análisis real, sería interesante tener más puntos, más usuarios y obtener más conclusiones&#8230;</p>
<h2>Problemas y mejoras</h2>
<li>De momento, el sistema no está preparado para cambios de resolución. En un entorno no controlado, habrá que retocar el script para que refiera las coordenadas a un punto conocido de la página.</li>
<li>El evento onclick no soporta flash. En el caso de que se use para colocar menús, será necesario editar manualmente los archivos .FLA y reemplazar los SWF para que también obtengan el punto donde se hace click</li>
<p>Partiendo de este sistema, podríamos capturar, por ejemplo, la posición del ratón cada, por ejemplo,  50 milisegundos, guardarla en un array y almacenarla en un log en el servidor, con lo que sería posible seguir los gestos del usuario tal como navega en una página web. En el futuro, más cosas.</p>
<h2>Agradecimientos</h2>
<p>He obtenido la inspiración para el artículo del post <a href="http://blog.outer-court.com/click/" onclick="pageTracker._trackPageview('/outgoing/blog.outer-court.com/click/?referer=');">&#8220;Click Survey&#8221;</a> que permite hacer click en una imagen determinada y analiza luego la posición de los clicks.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/eye-tracking-para-pobres-parte-2/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Eye tracking para pobres</title>
		<link>http://blog.corunet.com/eye-tracking-para-pobres/</link>
		<comments>http://blog.corunet.com/eye-tracking-para-pobres/#comments</comments>
		<pubDate>Mon, 17 Jul 2006 12:55:39 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[Programación]]></category>
		<category><![CDATA[Usabilidad]]></category>

		<guid isPermaLink="false">http://blog.corunet.cor/general/eye-tracking-para-pobres</guid>
		<description><![CDATA[Los dispositivos de eye tracking son caros. Dan un rendimiento excepcional para analizar adónde miran los visitantes de un sitio web, pero a veces es dificil justificar la inversión. Si consigues convencer a tus testers para que hagan click en el punto al que miran, puedes sacar conclusiones&#8230;

Con un poco de ingenuidad, es posible implantar [...]]]></description>
			<content:encoded><![CDATA[<p>Los dispositivos de eye tracking son caros. Dan un rendimiento excepcional para analizar adónde miran los visitantes de un sitio web, pero a veces es dificil justificar la inversión. Si consigues convencer a tus testers para que hagan click en el punto al que miran, puedes sacar conclusiones&#8230;<span id="more-6"></span></p>
<p class="fotocentro"><img src="/uploads/566532_eye.jpg" alt="Ojo de usuario" /></p>
<p>Con un poco de ingenuidad, es posible implantar en una página web un sistema parecido, basado en javascript y que siga los puntos en donde la gente hace click con el ratón. Para ello, son necesarios los siguientes pasos:</p>
<ol>
<li>Crear una función que siga el movimiento del ratón, y que se lo pase a un logger cada vez que se hace click.</li>
<li>Hacer una llamada a un método asíncrono (HttpRequest o similar) que mande, de forma transparente para el usuario, las coordenadas del punto en el que se ha clickado.</li>
<li>Que el logger escriba en un archivo, base de datos o similar la información pertinente</li>
</ol>
<h2>Capturar  la posición del ratón </h2>
<p>Antes de nada, es necesario encontrar las coordenadas del ratón. Lo mejor para esto es hacer, al final del ducumento html, un enlace a un fichero javascrip parecido a este:</p>
<p><code>/*detectar navegador y declaraciones*/<br />
var IE = document.all?true:false<br />
if (!IE) document.captureEvents(Event.MOUSEMOVE)<br />
var tempX = 0<br />
var tempY = 0/*llamada a la funcion con un handler*/<br />
document.onclick = getMouseXY;<br />
function getMouseXY(e) { //hace todo el trabajo<br />
  if (IE) {<br />
    tempX = event.clientX + document.body.scrollLeft<br />
    tempY = event.clientY + document.body.scrollTop<br />
  } else {<br />
    tempX = e.pageX<br />
    tempY = e.pageY<br />
  }<br />
  var url="/perl/guardacoordenadas.pl?x="+tempX+"&#038;y="+tempY;<br />
  guardar(url);<br />
  return true<br />
}<br />
if( document.getElementsByTagName ){<br />
 var links = document.getElementsByTagName( 'a' );<br />
 for( var i=0; i < links.length; i++ ){<br />
  links[i].onclick = function(){<br />
   return getMouseXY(this);<br />
  }<br />
 }<br />
}<br />
</code></p>
<p> Lo que hace, más o menos, es detectar que navegador está utilizando el usuario, inicializar el controlador de clicks, asignar a cada enlace una función en tiempo de ejecución y definir una función getMouseXY. Ésta devuelve true despues de llamar a una función externa que se define en el segundo paso:</p>
<h2>HttpRequest</h2>
<p>Permite hacer una llamada al servidor fuera del flujo de la navegación. En este caso, queremos que,  cuando el usuario haga click, el servidor guarde la posición del ratón. Para esto, el código es el siguiente:</p>
<p><code>var xmlDoc = null ;<br />
if (typeof window.ActiveXObject != 'undefined' ) {<br />
 xmlDoc = new ActiveXObject("Microsoft.XMLHTTP");<br />
}else {<br />
 xmlDoc = new XMLHttpRequest();<br />
}<br />
function guardar(url){<br />
 xmlDoc.open( "GET", url, true );<br />
 xmlDoc.send( null );<br />
}</code></p>
<p>Al igual que antes, diferenciamos entre el código de Internet Explorer y el resto. Luego, utilizamos el objeto xmlDoc para hacer la petición. De esta forma, llamamos al servidor sin que el cliente note nada, y podemos guardar la posición del cursor.</p>
<h2>El lado del servidor</h2>
<p>Desde el lado del servidor, la petición se ve como cualquier otra solicitud, pero no es necesario devolver nada por salida estándar. Lo único que haría falta es imprimir en un archivo la <em>query string</em> que le hemos pasado (con las coordenadas x e y), el referer y, si queremos, la dirección IP. Como prueba de concepto, serviría este código (CGI en perl, fácil de entender o de implementar en otro lenguaje)</p>
<p><code>#!/usr/bin/perl<br />
use CGI;<br />
use strict;<br />
my $q=new CGI; #crea una nueva instancia del objeto CGI<br />
print $q->header(); #imprime cabeceras<br />
open (LOG,">>log.txt") ||die; #abre el archivo log<br />
print LOG $q->Vars; #e imprime todos los datos<br />
print LOG "\t";<br />
print LOG $ENV{'HTTP_REFERER'};<br />
print LOG "\t";<br />
print LOG $ENV{'REMOTE_ADDR'};<br />
print LOG "\n";<br />
close (LOG);</code></p>
<p>Con esto, obtendremos un archivo log.txt que nos permitirá saber los puntos donde el usuario ha clickado.</p>
<h2>Más cosas</h2>
<p>Ahora sería el momento de programar un visualizador para toda esta información, superponiendo a la web los puntos en los que se ha hecho click. <del datetime="2006-07-24T12:07:01+00:00">En breve escribiré los resultados de mi primer experimento público y como analizar todos los datos.</del>  <a href="/usabilidad/eye-tracking-para-pobres-parte-2">Ya puedes leer como analizar los logs generados en la parte 2 del artículo</a></p>
<p>Con este mismo sistema es posible hacer muchísimas cosas. Por ejemplo, llevar un log de los enlaces internos y externos que se siguen en la página, guardar los movimientos del ratón para seguir la navegación... La imaginación es la que marca el límite.</p>
<p> </p>
<p> </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/eye-tracking-para-pobres/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
