joe di castrohttp://joedicastro.com2011-12-03T00:00:00+01:00Convertir ficheros djvu a pdf en Linux2011-12-03T00:00:00+01:00joe di castrohttp://joedicastro.com/convertir-ficheros-djvu-a-pdf-en-linux.html<p>Tengo por costumbre almacenar mis documentos escaneados en el formato
<a href="http://es.wikipedia.org/wiki/DjVu">djvu</a>, que fue expresamente creado para esa tarea y que otorga la mejor
calidad posible en el menor espacio. Es el formato perfecto para documentos
complejos sobre los que no se va a realizar un <a href="http://es.wikipedia.org/wiki/Reconocimiento_%C3%B3ptico_de_caracteres">OCR</a> (aunque también lo soporta). Además es un formato abierto, por lo que nos garantiza que podrá seguir
empleándose en un futuro. Pero a veces necesito compartir estos ficheros con
otros y para evitarme problemas suelo convertirlos a un formato más conocido y
difundido como <a href="http://es.wikipedia.org/wiki/Pdf">PDF</a>.</p>
<p>Para realizar esta conversión empleo desde hace años (la primera versión es del
2009) un sencillo script en python. Ahora que he necesitado una conversión
masiva de documentos de un formato al otro, he modificado el script para hacer
esto más sencillo y he decidido compartirlo con cualquiera que pueda necesitarlo. </p>
<h2 id="los_requisitos_previos">Los requisitos previos</h2>
<p>Está diseñado para funcionar en Linux y necesita de la instalación de dos
pequeños programas que son los que realmente realizan la conversión. Estos dos
programas son <code>ddjvu</code> y <code>tiff2pdf</code>. Además de tener instalado <strong>Python</strong> en una
versión <em>2.7</em> o superior. Estos dos programas vienen en los repositorios de
prácticamente todas las distribuciones importantes dentro de los paquetes
<a href="http://djvu.sourceforge.net/">djvulibre</a> y <a href="http://libtiff.maptools.org">libtiff</a>.</p>
<p>En el caso de no tenerlos instalados, la instalación de los mismos es muy
sencilla, para distribuciones basadas en Debian/Ubuntu:</p>
<div class="codehilite"><pre><span class="gp">$</span> apt-get install djvulibre-bin libtiff-tools
</pre></div>
<p><code>ddjvu</code> nos extrae las páginas que conforman el documento <em>.djvu</em> a un archivo intermedio en formato <em>.tiff</em> y <code>tiff2pdf</code> nos lo convierte en <em>.pdf</em>.</p>
<h2 id="modo_de_empleo">Modo de empleo</h2>
<p>Emplearlo es muy sencillo, como se puede ver en la ayuda del mismo:</p>
<div class="codehilite"><pre><span class="gp">$</span> djvu2pdf -h
<span class="go">usage: djvu2pdf [-h] [-d | -z] [-v] file [file ...]</span>
<span class="go">Converts a djvu file into a pdf file</span>
<span class="go">positional arguments:</span>
<span class="go"> file The djvu file</span>
<span class="go">optional arguments:</span>
<span class="go"> -h, --help show this help message and exit</span>
<span class="go"> -d no compression. Best quality but big files.</span>
<span class="go"> -z zip compression. More quality, more size.</span>
<span class="go"> -v, --version show program's version number and exit</span>
</pre></div>
<p>Básicamente llamandalo desde python y poniendo a continuación el nombre del
fichero/s es lo único que necesitamos para ejecutarlo, por ejemplo:</p>
<div class="codehilite"><pre><span class="gp">$</span> ls
<span class="go">documento.djvu documento_2.djvu</span>
<span class="gp">$</span> python djvu2pdf.py documento.djvu documento_2.djvu
<span class="gp">$</span> ls
<span class="go">documento.djvu documento.pdf documento_2.djvu documento_2.pdf</span>
</pre></div>
<p>Opcionalmente tenemos las opciones <code>-d</code> y <code>-z</code>, que nos sirven para especificar
si queremos no emplear compresión en el <em>.pdf</em> (por defecto emplea compresión
<a href="http://es.wikipedia.org/wiki/Jpeg">jpeg</a>) o emplear compresión <a href="http://es.wikipedia.org/wiki/Formato_de_compresi%C3%B3n_ZIP">zip</a>, respectivamente. Si no empleamos compresión, la calidad final será la mejor posible, pero los archivos serán muy grandes. En cambio, empleando <em>zip</em>, tenemos unos ficheros ligeramente mayores a cambio de una calidad muy buena. Aunque la compresión <em>zip</em> puede dar problemas
con algunos visores y lectores de ebooks.</p>
<h2 id="el_script_djvu2pdfpy">El script, djvu2pdf.py</h2>
<p>El contenido del scipt es el que sigue. Este está disponible como el fichero
<code>djvu2pdf.py</code> dentro del repositorio <em>Python Recipes</em> que está alojado en
<a href="http://github.com/joedicastro/python-recipes">github</a> y actualizado siempre a la última versión.</p>
<div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span>
<span class="c"># -*- coding: utf8 -*-</span>
<span class="sd">"""</span>
<span class="sd"> djvu2pdf.py: Converts a .djvu file into a .pdf file</span>
<span class="sd">"""</span>
<span class="c">#==============================================================================</span>
<span class="c"># This Script does exactly as the description above says.</span>
<span class="c">#==============================================================================</span>
<span class="c">#==============================================================================</span>
<span class="c"># Copyright 2011 joe di castro <joe@joedicastro.com></span>
<span class="c">#</span>
<span class="c"># This program is free software: you can redistribute it and/or modify</span>
<span class="c"># it under the terms of the GNU General Public License as published by</span>
<span class="c"># the Free Software Foundation, either version 3 of the License, or</span>
<span class="c"># (at your option) any later version.</span>
<span class="c">#</span>
<span class="c"># This program is distributed in the hope that it will be useful,</span>
<span class="c"># but WITHOUT ANY WARRANTY; without even the implied warranty of</span>
<span class="c"># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span>
<span class="c"># GNU General Public License for more details.</span>
<span class="c">#</span>
<span class="c"># You should have received a copy of the GNU General Public License</span>
<span class="c"># along with this program. If not, see <http://www.gnu.org/licenses/>.</span>
<span class="c">#==============================================================================</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">"joe di castro <joe@joedicastro.com>"</span>
<span class="n">__license__</span> <span class="o">=</span> <span class="s">"GNU General Public License version 3"</span>
<span class="n">__date__</span> <span class="o">=</span> <span class="s">"03/12/2011"</span>
<span class="n">__version__</span> <span class="o">=</span> <span class="s">"0.3"</span>
<span class="k">try</span><span class="p">:</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">from</span> <span class="nn">argparse</span> <span class="kn">import</span> <span class="n">ArgumentParser</span>
<span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">Popen</span><span class="p">,</span> <span class="n">PIPE</span>
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
<span class="c"># Checks the installation of the necessary python modules</span>
<span class="k">print</span><span class="p">((</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">"An error found importing one module:"</span><span class="p">,</span>
<span class="nb">str</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">exc_info</span><span class="p">()[</span><span class="mi">1</span><span class="p">]),</span> <span class="s">"You need to install it"</span><span class="p">,</span> <span class="s">"Stopping..."</span><span class="p">]))</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">check_execs</span><span class="p">(</span><span class="o">*</span><span class="n">progs</span><span class="p">):</span>
<span class="sd">"""Check if the programs are installed, if not exit and report."""</span>
<span class="k">for</span> <span class="n">prog</span> <span class="ow">in</span> <span class="n">progs</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">Popen</span><span class="p">([</span><span class="n">prog</span><span class="p">,</span> <span class="s">'--help'</span><span class="p">],</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">OSError</span><span class="p">:</span>
<span class="n">msg</span> <span class="o">=</span> <span class="s">'The {0} program is necessary to run the script'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">prog</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
<span class="k">return</span>
<span class="k">def</span> <span class="nf">arguments</span><span class="p">():</span>
<span class="sd">"""Defines the command line arguments for the script."""</span>
<span class="n">main_desc</span> <span class="o">=</span> <span class="s">"""Converts a djvu file into a pdf file"""</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="n">main_desc</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">"file"</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s">"+"</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">"The djvu file"</span><span class="p">)</span>
<span class="n">group</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_mutually_exclusive_group</span><span class="p">()</span>
<span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">"-d"</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s">"qlty"</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s">"store_const"</span><span class="p">,</span> <span class="n">const</span><span class="o">=</span><span class="s">"-d"</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s">"no compression. Best quality but big files."</span><span class="p">)</span>
<span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">"-z"</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s">"qlty"</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s">"store_const"</span><span class="p">,</span> <span class="n">const</span><span class="o">=</span><span class="s">"-z"</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s">"zip compression. More quality, more size."</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">"-v"</span><span class="p">,</span> <span class="s">"--version"</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s">"version"</span><span class="p">,</span>
<span class="n">version</span><span class="o">=</span><span class="s">"</span><span class="si">%(prog)s</span><span class="s"> {0}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">__version__</span><span class="p">),</span>
<span class="n">help</span><span class="o">=</span><span class="s">"show program's version number and exit"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">parser</span>
<span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">fname</span><span class="p">):</span>
<span class="sd">"""Process the external commands and report the errors."""</span>
<span class="n">errors</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span><span class="o">.</span><span class="n">stderr</span><span class="o">.</span><span class="n">readlines</span><span class="p">()</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">errors</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"{0}: {1}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">fname</span><span class="o">.</span><span class="n">upper</span><span class="p">(),</span> <span class="n">line</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)))</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="sd">"""Main section."""</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">arguments</span><span class="p">()</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="n">djvu_files</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">file</span>
<span class="k">for</span> <span class="n">djvu</span> <span class="ow">in</span> <span class="n">djvu_files</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">djvu</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="s">"ERROR: cannot open '{0}' (No such file)"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">djvu</span><span class="p">))</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">djvu_filename</span> <span class="o">=</span> <span class="n">djvu</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">".djvu"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">tiff</span> <span class="o">=</span> <span class="s">'{0}.tif'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">djvu_filename</span><span class="p">)</span>
<span class="n">pdf</span> <span class="o">=</span> <span class="s">'{0}.pdf'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">djvu_filename</span><span class="p">)</span>
<span class="n">process</span><span class="p">([</span><span class="s">'ddjvu'</span><span class="p">,</span> <span class="s">'-format=tiff'</span><span class="p">,</span> <span class="n">djvu</span><span class="p">,</span> <span class="n">tiff</span><span class="p">],</span> <span class="n">tiff</span><span class="p">)</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">tiff</span><span class="p">):</span>
<span class="n">quality</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">qlty</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">qlty</span> <span class="k">else</span> <span class="s">"-j"</span>
<span class="n">process</span><span class="p">([</span><span class="s">'tiff2pdf'</span><span class="p">,</span> <span class="n">quality</span><span class="p">,</span> <span class="s">'-o'</span><span class="p">,</span> <span class="n">pdf</span><span class="p">,</span> <span class="n">tiff</span><span class="p">],</span> <span class="n">pdf</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">tiff</span><span class="p">)</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">check_execs</span><span class="p">(</span><span class="s">'ddjvu'</span><span class="p">,</span> <span class="s">'tiff2pdf'</span><span class="p">)</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>Monitorizar los cambios de tamaño en un directorio2011-05-17T21:52:00+02:00joe di castrohttp://joedicastro.com/monitorizar-los-cambios-de-tamano-en-un-directorio.html<p>Cuando administramos varias maquinas <abbr title="Linux, Unix, Solaris, BSD, etc">UN*X</abbr> nos puede interesar el tener
controlados los cambios de tamaño en algunos directorios determinados, para
poder observar pautas de comportamiento o ver cambios inesperados, para
solucionar los problemas cuando o antes de que se produzcan. Y aunque hay varias
formas de realizar esto, incluso con demonios que monitorizan los cambios en
tiempo real (incrond, inotify, dnotify, gamin, watch, ...), puede que una
solución más sencilla nos sea suficiente para directorios no críticos. Para
instalaciones no complejas nos puede servir, por ejemplo, para no tener que
lidiar con las <a href="http://en.wikipedia.org/wiki/Disk_quota">quotas de disco</a> (no siempre es una buena opción). O para
evitar que por ejemplo una mala configuración en la rotación de los logs de una
maquina acaben agotando el espacio disponible para la partición de sistema (caso
real que me he encontrado más de una vez). </p>
<p>Para poder monitorizar los cambios de tamaño de un directorio (y subdirectorios)
he creado un sencillo script <strong>Python</strong> que registra los cambios en la ruta que
le proporcionemos y luego envía un informe por correo al buzón del usuario
local. Los datos de los directorios los registra en un fichero oculto (su
nombre empieza por un <code>.</code>) binario de tipo <a href="http://docs.python.org/library/pickle.html#module-pickle">pickle</a> y el informe se guarda
a su vez en un archivo de texto con el mismo nombre que el script, pero
terminado en <code>.log</code>. </p>
<p>El informe que genera nos muestra por un lado tantos los nuevos directorios,
como los directorios que se han eliminado con la cifra del espacio en disco que
ocupan (o liberan). Por otro lado también nos informa de los directorios que han
cambiado de tamaño, mostrándonos en que porcentaje se han
incrementado/decrementado y la cantidad de espacio que ha variado. Aquí podemos
ver un ejemplo de uno de estos informes.</p>
<div class="codehilite"><pre>SCRIPT =========================================================================
dir_size_monitor (ver. 0.2)
http://joedicastro.com
Changes in size of directories for .. on yourmachine
================================================================================
START TIME =====================================================================
Tuesday 05/17/11, 21:10:48
================================================================================
NEW DIRECTORIES ________________________________________________________________
799.72 KiB ./src/test/bibendum
1.14 MiB ./src/test/condimentum
2.31 MiB ./src/test/laoreet
204.28 KiB ./src/test/risus
2.90 MiB ./src/test/torquent
DELETED DIRECTORIES ____________________________________________________________
383.79 KiB ./src/test/adipiscing
5.38 MiB ./src/test/consequat
847.72 KiB ./src/test/etiam
938.93 KiB ./src/test/maecenas
3.55 MiB ./src/test/tincidunt
2.33 MiB ./src/test/viverra
CHANGED DIRECTORIES ____________________________________________________________
34.79 % 55.5 MiB ./src
34.82 % 55.5 MiB ./src/test
-99.97 % 15.3 MiB ./src/test/odio
-99.97 % 15.6 MiB ./src/test/tellus
THRESHOLD VALUES _______________________________________________________________
The directories whose size differences are less than any of these values are ignored:
Percentage: 10 %
Size: 10.00 MiB
.. STATISTICS __________________________________________________________________
78 directories
215.04 MiB
END TIME =======================================================================
Tuesday 05/17/11, 21:10:48
================================================================================
</pre></div>
<p>Opcionalmente podemos establecer dos valores de umbral para que se ignoren todos
los cambios que estén por debajo de estas dos cifras. Para desactivarlos
simplemente hay que dejarlos a cero. Estas cifras se refieren por un lado al
porcentaje de diferencia mínimo que deseamos establecer para que se nos informe
y por otro a la cantidad mínima (expresada en bytes) de espacio en disco que se
ha incrementado/decrementado en ese directorio. Pueden funcionar los dos a la
vez, por lo que se han de cumplir las dos condiciones, o solamente uno dejando
el otro a cero.</p>
<p>Si programamos este script para que se ejecute cada cierto tiempo, podemos tener
una idea aproximada de los cambios producidos en el. Y digo aproximada porque
este nos muestra únicamente los cambios registrados entre dos <em>instantáneas</em>
tomadas, una en la ejecución anterior y otra en la ejecución actual. Y por lo
tanto no esperemos obtener la relación de todos los cambios producidas entre
ellas. Para conocer algo a ese nivel de detalle es mejor emplear uno de los
servicios en tiempo real que mencionaba al principio. Dicho esto, es evidente
que en la primera ejecución no tiene sentido informar de nada y de hecho hasta
la segunda ejecución no empezara a generar informes.</p>
<p>Este es el contenido de <a href="https://github.com/joedicastro/python-recipes/blob/master/dir_size_monitor.py">dir_size_monitor.py</a> es el siguiente:</p>
<div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span>
<span class="c"># -*- coding: utf8 -*-</span>
<span class="sd">"""</span>
<span class="sd"> dir_size_monitor.py: Monitors changes in the size of dirs for a given path</span>
<span class="sd">"""</span>
<span class="c">#===============================================================================</span>
<span class="c"># This Script monitors the changes in disk size for the directories included in</span>
<span class="c"># a given path. It reports what directories are new or deleted. Also reports the</span>
<span class="c"># directories in which their size increases or decreases above threshold values.</span>
<span class="c"># These threshold values refer to the amount in difference of size of the </span>
<span class="c"># directory or/and the percentage difference. These values can be overrided by </span>
<span class="c"># setting them to zero.</span>
<span class="c">#</span>
<span class="c"># The final report is sended via email to the local user. This script is </span>
<span class="c"># intended to run periodically (e.g. via cron) </span>
<span class="c">#===============================================================================</span>
<span class="c">#===============================================================================</span>
<span class="c"># Copyright 2011 joe di castro <joe@joedicastro.com></span>
<span class="c">#</span>
<span class="c"># This program is free software: you can redistribute it and/or modify</span>
<span class="c"># it under the terms of the GNU General Public License as published by</span>
<span class="c"># the Free Software Foundation, either version 3 of the License, or</span>
<span class="c"># (at your option) any later version.</span>
<span class="c">#</span>
<span class="c"># This program is distributed in the hope that it will be useful,</span>
<span class="c"># but WITHOUT ANY WARRANTY; without even the implied warranty of</span>
<span class="c"># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span>
<span class="c"># GNU General Public License for more details.</span>
<span class="c">#</span>
<span class="c"># You should have received a copy of the GNU General Public License</span>
<span class="c"># along with this program. If not, see <http://www.gnu.org/licenses/>.</span>
<span class="c">#===============================================================================</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">"joe di castro <joe@joedicastro.com>"</span>
<span class="n">__license__</span> <span class="o">=</span> <span class="s">"GNU General Public License version 3"</span>
<span class="n">__date__</span> <span class="o">=</span> <span class="s">"17/05/2011"</span>
<span class="n">__version__</span> <span class="o">=</span> <span class="s">"0.2"</span>
<span class="k">try</span><span class="p">:</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">platform</span>
<span class="kn">import</span> <span class="nn">pickle</span>
<span class="kn">import</span> <span class="nn">logger</span>
<span class="kn">from</span> <span class="nn">get_size</span> <span class="kn">import</span> <span class="n">best_unit_size</span><span class="p">,</span> <span class="n">get_size_fast</span>
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
<span class="c"># Checks the installation of the necessary python modules </span>
<span class="k">print</span><span class="p">((</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">"An error found importing one module:"</span><span class="p">,</span>
<span class="nb">str</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">exc_info</span><span class="p">()[</span><span class="mi">1</span><span class="p">]),</span> <span class="s">"You need to install it"</span><span class="p">,</span> <span class="s">"Stopping..."</span><span class="p">]))</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">list4log</span><span class="p">(</span><span class="n">dirs_size_dict</span><span class="p">,</span> <span class="n">wpath</span><span class="p">,</span> <span class="n">dirs</span><span class="p">):</span>
<span class="sd">"""Create a list of new or deleted directories for the log."""</span>
<span class="n">llst</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">ldir</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dirs</span><span class="p">):</span>
<span class="n">dsz</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">dirs_size_dict</span><span class="p">[</span><span class="n">ldir</span><span class="p">])</span>
<span class="n">llst</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s">" {0:8.2f} {1} ./{2}"</span><span class="o">.</span>
<span class="n">format</span><span class="p">(</span><span class="n">dsz</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">dsz</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">relpath</span><span class="p">(</span><span class="n">ldir</span><span class="p">,</span> <span class="n">wpath</span><span class="p">)))</span>
<span class="k">return</span> <span class="n">llst</span>
<span class="k">def</span> <span class="nf">diff4log</span><span class="p">(</span><span class="n">before</span><span class="p">,</span> <span class="n">current</span><span class="p">,</span> <span class="n">wpath</span><span class="p">,</span> <span class="n">dirs</span><span class="p">,</span> <span class="n">threshold_pct</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">threshold_sz</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
<span class="sd">"""Create a list of the directories that had size changes for the log."""</span>
<span class="n">llst</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">ddir</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dirs</span><span class="p">):</span>
<span class="n">pct</span> <span class="o">=</span> <span class="p">(((</span><span class="n">current</span><span class="p">[</span><span class="n">ddir</span><span class="p">]</span> <span class="o">-</span> <span class="nb">float</span><span class="p">(</span><span class="n">before</span><span class="p">[</span><span class="n">ddir</span><span class="p">]))</span> <span class="o">/</span> <span class="n">before</span><span class="p">[</span><span class="n">ddir</span><span class="p">])</span> <span class="o">*</span> <span class="mf">100.0</span><span class="p">)</span>
<span class="n">diff</span> <span class="o">=</span> <span class="n">current</span><span class="p">[</span><span class="n">ddir</span><span class="p">]</span> <span class="o">-</span> <span class="n">before</span><span class="p">[</span><span class="n">ddir</span><span class="p">]</span>
<span class="k">if</span> <span class="nb">abs</span><span class="p">(</span><span class="n">pct</span><span class="p">)</span> <span class="o">>=</span> <span class="n">threshold_pct</span> <span class="ow">and</span> <span class="nb">abs</span><span class="p">(</span><span class="n">diff</span><span class="p">)</span> <span class="o">></span> <span class="n">threshold_sz</span><span class="p">:</span>
<span class="n">dsz</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">diff</span><span class="p">)</span>
<span class="n">llst</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s">" {0:8.2f} % {1:8.1f} {2} ./{3}"</span><span class="o">.</span>
<span class="n">format</span><span class="p">(</span><span class="n">pct</span><span class="p">,</span> <span class="n">dsz</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">dsz</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">relpath</span><span class="p">(</span><span class="n">ddir</span><span class="p">,</span>
<span class="n">wpath</span><span class="p">)))</span>
<span class="k">return</span> <span class="n">llst</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">first_exec</span><span class="o">=</span><span class="bp">False</span><span class="p">):</span>
<span class="sd">"""Main section"""</span>
<span class="c"># The path to monitor changes in directories dir_size</span>
<span class="n">mon_pth</span> <span class="o">=</span> <span class="s">"/your/path/to/monitor"</span>
<span class="c"># Ignore all directories that are below these percentage or absolute value </span>
<span class="c"># of size difference. There are optional, set to zero to override them.</span>
<span class="n">thld_pct</span> <span class="o">=</span> <span class="mi">20</span> <span class="c"># In percentage of difference in size for a directory</span>
<span class="n">thld_sz</span> <span class="o">=</span> <span class="mf">10.486E6</span> <span class="c"># In bytes of absolute value of directory size difference</span>
<span class="c"># Prepare the log</span>
<span class="n">log</span> <span class="o">=</span> <span class="n">logger</span><span class="o">.</span><span class="n">Logger</span><span class="p">()</span>
<span class="n">url</span> <span class="o">=</span> <span class="s">"http://joedicastro.com"</span>
<span class="n">head</span> <span class="o">=</span> <span class="p">(</span><span class="s">"Changes in size of directories for {0} on {1}"</span><span class="o">.</span>
<span class="n">format</span><span class="p">(</span><span class="n">mon_pth</span><span class="p">,</span> <span class="n">platform</span><span class="o">.</span><span class="n">node</span><span class="p">()))</span>
<span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">head</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">"START TIME"</span><span class="p">)</span>
<span class="c"># Load the last dictionary of directories/sizes if exists</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'.dir_sizes.pkl'</span><span class="p">,</span> <span class="s">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">input_file</span><span class="p">:</span>
<span class="n">bfr_dir</span> <span class="o">=</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">input_file</span><span class="p">)</span>
<span class="k">except</span> <span class="p">(</span><span class="ne">EOFError</span><span class="p">,</span> <span class="ne">IOError</span><span class="p">,</span> <span class="n">pickle</span><span class="o">.</span><span class="n">PickleError</span><span class="p">):</span>
<span class="n">bfr_dir</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">first_exec</span> <span class="o">=</span> <span class="bp">True</span>
<span class="c"># Get the current dictionary of directories/sizes</span>
<span class="n">crr_dir</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">dirs</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">mon_pth</span><span class="p">):</span>
<span class="k">for</span> <span class="n">directory</span> <span class="ow">in</span> <span class="n">dirs</span><span class="p">:</span>
<span class="n">dir_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
<span class="n">dir_size</span> <span class="o">=</span> <span class="n">get_size_fast</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
<span class="n">crr_dir</span><span class="p">[</span><span class="n">dir_path</span><span class="p">]</span> <span class="o">=</span> <span class="n">dir_size</span>
<span class="c"># First, Save the current dirs/sizes</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">".dir_sizes.pkl"</span><span class="p">,</span> <span class="s">"wb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">output_file</span><span class="p">:</span>
<span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">crr_dir</span><span class="p">,</span> <span class="n">output_file</span><span class="p">)</span>
<span class="c"># Create the list depending the status of directories</span>
<span class="n">deleted</span> <span class="o">=</span> <span class="p">[</span><span class="n">d</span> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">bfr_dir</span> <span class="k">if</span> <span class="n">d</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">crr_dir</span><span class="p">]</span>
<span class="n">added</span> <span class="o">=</span> <span class="p">[</span><span class="n">d</span> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">crr_dir</span> <span class="k">if</span> <span class="n">d</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">bfr_dir</span><span class="p">]</span>
<span class="n">changed</span> <span class="o">=</span> <span class="p">[</span><span class="n">d</span> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">crr_dir</span> <span class="k">if</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">bfr_dir</span> <span class="k">if</span> <span class="n">crr_dir</span><span class="p">[</span><span class="n">d</span><span class="p">]</span> <span class="o">!=</span> <span class="n">bfr_dir</span><span class="p">[</span><span class="n">d</span><span class="p">]]</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">"Deleted directories"</span><span class="p">,</span> <span class="n">list4log</span><span class="p">(</span><span class="n">bfr_dir</span><span class="p">,</span> <span class="n">mon_pth</span><span class="p">,</span> <span class="n">deleted</span><span class="p">))</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">"New directories"</span><span class="p">,</span> <span class="n">list4log</span><span class="p">(</span><span class="n">crr_dir</span><span class="p">,</span> <span class="n">mon_pth</span><span class="p">,</span> <span class="n">added</span><span class="p">))</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">"Changed directories"</span><span class="p">,</span> <span class="n">diff4log</span><span class="p">(</span><span class="n">bfr_dir</span><span class="p">,</span> <span class="n">crr_dir</span><span class="p">,</span> <span class="n">mon_pth</span><span class="p">,</span> <span class="n">changed</span><span class="p">,</span>
<span class="n">thld_pct</span><span class="p">,</span> <span class="n">thld_sz</span><span class="p">))</span>
<span class="c"># If thresholds are nonzero, then report the values </span>
<span class="k">if</span> <span class="n">thld_pct</span> <span class="ow">or</span> <span class="n">thld_sz</span><span class="p">:</span>
<span class="n">tsz</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">thld_sz</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">"Threshold Values"</span><span class="p">,</span>
<span class="p">[</span><span class="s">"The directories whose size differences are less than any of "</span>
<span class="s">"these values are ignored:"</span><span class="p">,</span> <span class="s">""</span><span class="p">,</span>
<span class="s">"Percentage: {0:6} %"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">thld_pct</span><span class="p">),</span>
<span class="s">"Size: {0:6.2f} {1}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">tsz</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">tsz</span><span class="p">[</span><span class="s">'u'</span><span class="p">])])</span>
<span class="c"># Show some statistics for the analyzed path</span>
<span class="n">mon_pth_sz</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">get_size_fast</span><span class="p">(</span><span class="n">mon_pth</span><span class="p">))</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">"{0} Statistics"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">mon_pth</span><span class="p">),</span>
<span class="p">[</span><span class="s">"{0:8} directories"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">crr_dir</span><span class="p">)),</span>
<span class="s">"{0:8.2f} {1}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">mon_pth_sz</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">mon_pth_sz</span><span class="p">[</span><span class="s">'u'</span><span class="p">])])</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">"END TIME"</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">first_exec</span><span class="p">:</span>
<span class="n">log</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">"Changes in size of directories"</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">write</span><span class="p">()</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>Este script necesita los módulos <a href="http://joedicastro.com/conocer_el_tamano_de_un_directorio_con_python">get_size</a> y <a href="http://joedicastro.com/logger_informes_legibles_para_tus_scripts_python">logger</a> para poder
funcionar. Solo es necesario descargar los archivos y guardarlos en el mismo
directorio donde se aloje este script. La versión más actualizada de este script
se puede encontrar en el repositorio <em>Python Recipes</em> alojado en <a href="http://github.com/joedicastro/python-recipes">github</a>.</p>Conocer el tamaño de un directorio con Python2011-05-16T21:05:00+02:00joe di castrohttp://joedicastro.com/conocer-el-tamano-de-un-directorio-con-python.html<p>Aunque conocer el tamaño de un directorio en sistemas como Linux es algo
trivial, solo es necesario emplear el comando <code>du</code>, si queremos hacer lo mismo
con <strong>Python</strong> -sin hacer uso de este comando- la cosa ya no es tan sencilla.
Sobre todo si lo que queremos es una solución que nos devuelva tanto el tamaño
de un fichero como el de un directorio. Cuando me encontré con esta necesidad lo
primero que hice fue buscar en Internet para conocer alguna solución previa
(reinventar la rueda no siempre es lo mejor) y me encontré con esto:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">get_dir_size</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="sd">"""Get size of a directory tree in bytes."""</span>
<span class="n">path_size</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">dirs</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="k">for</span> <span class="n">fil</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">fil</span><span class="p">)</span>
<span class="n">path_size</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">getsize</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
<span class="k">return</span> <span class="n">path_size</span>
</pre></div>
<p>Esta solución nos daría el tamaño en bytes de un directorio. Pero esta solución,
que encontré en varios sitios, presentaba dos problemas:</p>
<ul>
<li>
<p><strong>No da un tamaño exacto</strong>. Esto se debe a que no tiene en cuenta las carpetas
y ficheros ocultos (los que empiezan con un <code>.</code> en Linux) y los ficheros
especiales <code>..</code> (que apuntan al directorio superior). Además tampoco tiene en
cuenta los enlaces simbólicos. Por está razón la salida de esta función no
coincide con el espacio que nos reporta el comando <abbr title="Linux, Unix, Solaris, BSD, etc">UN*X</abbr> <code>du -bs</code></p>
</li>
<li>
<p><strong>No funciona para un solo fichero</strong>. Solo trabaja cuando lo ejecutamos sobre
un directorio, al hacerlo sobre un solo fichero nos dará como resultado siempre 0.</p>
</li>
</ul>
<p>Teniendo en cuenta este punto de partida, elaboré una función que solucionara
estos dos problemas y que devolviera el tamaño exacto de un directorio o
fichero. Esta es la <strong>función que nos da el resultado correcto</strong>:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">get_size</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="sd">"""Get size of a directory tree or a file in bytes."""</span>
<span class="n">path_size</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">directories</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
<span class="n">path_size</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">lstat</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">))</span><span class="o">.</span><span class="n">st_size</span>
<span class="k">for</span> <span class="n">directory</span> <span class="ow">in</span> <span class="n">directories</span><span class="p">:</span>
<span class="n">path_size</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">lstat</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">directory</span><span class="p">))</span><span class="o">.</span><span class="n">st_size</span>
<span class="n">path_size</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">getsize</span><span class="p">(</span><span class="n">the_path</span><span class="p">)</span>
<span class="k">return</span> <span class="n">path_size</span>
</pre></div>
<p>El resultado de esta función es el mismo que el que nos devuelve el comando
Linux <code>du -bs</code>. Además tiene en cuenta los enlaces simbólicos y no los sigue.
Luego buscando una <strong>solución ligeramente más rápida</strong> (aunque menos elegante y
<em>pythonica</em>) y que siguiera dando resultados precisos, cree una variante basada
en el empleo de generadores. </p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">get_size_fast</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="sd">"""Get size of a directory tree or a file in bytes."""</span>
<span class="k">def</span> <span class="nf">get_sizes</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="sd">"""Make a generator of individual file & directory sizes."""</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">islink</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="k">for</span> <span class="n">file_or_dir</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">the_path</span><span class="p">,</span> <span class="n">file_or_dir</span><span class="p">)</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
<span class="k">yield</span> <span class="n">os</span><span class="o">.</span><span class="n">lstat</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">st_size</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">for</span> <span class="n">size</span> <span class="ow">in</span> <span class="n">get_sizes</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
<span class="k">yield</span> <span class="n">size</span>
<span class="k">yield</span> <span class="n">os</span><span class="o">.</span><span class="n">lstat</span><span class="p">(</span><span class="n">the_path</span><span class="p">)</span><span class="o">.</span><span class="n">st_size</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">os</span><span class="o">.</span><span class="n">lstat</span><span class="p">(</span><span class="n">the_path</span><span class="p">)</span><span class="o">.</span><span class="n">st_size</span>
<span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">get_sizes</span><span class="p">(</span><span class="n">the_path</span><span class="p">))</span>
</pre></div>
<h2 id="obtener_el_tama+o_del_directorio_en_la_mejor_unidad_posible">Obtener el tamaño del directorio en la mejor unidad posible</h2>
<p>Estas funciones proporcionan el resultado que deseamos, pero lo entregan en una
unidad difícilmente legible, en bytes. ¿Que ocurre si queremos verlo en
<a href="http://es.wikipedia.org/wiki/Prefijo_binario">Mebibytes, GibiBytes</a>, ... y que además sea siempre la más adecuada para una
mejor visualización? Para responder a esta pregunta desarrolle una función que
nos hace precisamente esto, tomar un tamaño en bytes y devolvernos el valor
correcto en la <a href="http://physics.nist.gov/cuu/Units/binary.html">unidad binaria IEC</a> más adecuada:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">best_unit_size</span><span class="p">(</span><span class="n">bytes_size</span><span class="p">):</span>
<span class="sd">"""Get a size in bytes & convert it to the best IEC prefix for readability.</span>
<span class="sd"> Return a dictionary with three pair of keys/values:</span>
<span class="sd"> "s" -- (float) Size of path converted to the best unit for easy read</span>
<span class="sd"> "u" -- (str) The prefix (IEC) for s (from bytes(2^0) to YiB(2^80))</span>
<span class="sd"> "b" -- (int / long) The original size in bytes</span>
<span class="sd"> """</span>
<span class="k">for</span> <span class="n">exp</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">90</span> <span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
<span class="n">bu_size</span> <span class="o">=</span> <span class="nb">abs</span><span class="p">(</span><span class="n">bytes_size</span><span class="p">)</span> <span class="o">/</span> <span class="nb">pow</span><span class="p">(</span><span class="mf">2.0</span><span class="p">,</span> <span class="n">exp</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">bu_size</span><span class="p">)</span> <span class="o"><</span> <span class="mi">2</span> <span class="o">**</span> <span class="mi">10</span><span class="p">:</span>
<span class="n">unit</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">:</span><span class="s">"bytes"</span><span class="p">,</span> <span class="mi">10</span><span class="p">:</span><span class="s">"KiB"</span><span class="p">,</span> <span class="mi">20</span><span class="p">:</span><span class="s">"MiB"</span><span class="p">,</span> <span class="mi">30</span><span class="p">:</span><span class="s">"GiB"</span><span class="p">,</span> <span class="mi">40</span><span class="p">:</span><span class="s">"TiB"</span><span class="p">,</span> <span class="mi">50</span><span class="p">:</span><span class="s">"PiB"</span><span class="p">,</span>
<span class="mi">60</span><span class="p">:</span><span class="s">"EiB"</span><span class="p">,</span> <span class="mi">70</span><span class="p">:</span><span class="s">"ZiB"</span><span class="p">,</span> <span class="mi">80</span><span class="p">:</span><span class="s">"YiB"</span><span class="p">}[</span><span class="n">exp</span><span class="p">]</span>
<span class="k">break</span>
<span class="k">return</span> <span class="p">{</span><span class="s">"s"</span><span class="p">:</span><span class="n">bu_size</span><span class="p">,</span> <span class="s">"u"</span><span class="p">:</span><span class="n">unit</span><span class="p">,</span> <span class="s">"b"</span><span class="p">:</span><span class="n">bytes_size</span><span class="p">}</span>
</pre></div>
<p>Esta función nos devuelve un diccionario con tres claves:</p>
<ul>
<li><code>'s'</code>: Es el tamaño convertido a la mejor unidad IEC posible en términos de
legibilidad.</li>
<li><code>'u'</code>: Es el prefijo IEC para el tamaño anterior.</li>
<li><code>'b'</code>: Es el tamaño original en bytes.</li>
</ul>
<p>Para entenderla, lo mejor es mostrar algunos ejemplos:</p>
<div class="codehilite"><pre><span class="gp">>>> </span><span class="kn">import</span> <span class="nn">get_size</span>
<span class="gp">>>> </span><span class="n">size</span> <span class="o">=</span> <span class="n">get_size</span><span class="o">.</span><span class="n">best_unit_size</span><span class="p">(</span><span class="mi">38467206502</span><span class="p">)</span>
<span class="gp">>>> </span><span class="s">"{0:.2f} {1}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">size</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">size</span><span class="p">[</span><span class="s">'u'</span><span class="p">])</span>
<span class="go">'35.83 GiB'</span>
<span class="gp">>>> </span><span class="n">size</span> <span class="o">=</span> <span class="n">get_size</span><span class="o">.</span><span class="n">best_unit_size</span><span class="p">(</span><span class="mi">45332</span><span class="p">)</span>
<span class="gp">>>> </span><span class="s">"{0:.2f} {1}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">size</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">size</span><span class="p">[</span><span class="s">'u'</span><span class="p">])</span>
<span class="go">'44.27 KiB'</span>
<span class="gp">>>> </span><span class="n">size</span> <span class="o">=</span> <span class="n">get_size</span><span class="o">.</span><span class="n">best_unit_size</span><span class="p">(</span><span class="mi">9878323</span><span class="p">)</span>
<span class="gp">>>> </span><span class="s">"{0:.2f} {1} es igual a {2} bytes"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">size</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">size</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span> <span class="n">size</span><span class="p">[</span><span class="s">'b'</span><span class="p">])</span>
<span class="go">'9.42 MiB es igual a 9878323 bytes'</span>
</pre></div>
<p>Y evidentemente, combinar las dos funciones en una, nos evita tener que pasar
las dos a un mismo directorio/fichero. </p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">get_unit_size</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="sd">"""Calculate size of a directory/file & convert it for the best IEC prefix.</span>
<span class="sd"> Return a dictionary with three pair of keys/values:</span>
<span class="sd"> "s" -- (float) Size of path converted to the best unit for easy read</span>
<span class="sd"> "u" -- (str) The prefix (IEC) for s (from bytes(2^0) to YiB(2^80))</span>
<span class="sd"> "b" -- (int / long) The original size in bytes</span>
<span class="sd"> """</span>
<span class="n">bytes_size</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">directories</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
<span class="n">bytes_size</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">lstat</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">))</span><span class="o">.</span><span class="n">st_size</span>
<span class="k">for</span> <span class="n">directory</span> <span class="ow">in</span> <span class="n">directories</span><span class="p">:</span>
<span class="n">bytes_size</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">lstat</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">directory</span><span class="p">))</span><span class="o">.</span><span class="n">st_size</span>
<span class="n">bytes_size</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">getsize</span><span class="p">(</span><span class="n">the_path</span><span class="p">)</span>
<span class="k">for</span> <span class="n">exp</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">90</span> <span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
<span class="n">bu_size</span> <span class="o">=</span> <span class="nb">abs</span><span class="p">(</span><span class="n">bytes_size</span><span class="p">)</span> <span class="o">/</span> <span class="nb">pow</span><span class="p">(</span><span class="mf">2.0</span><span class="p">,</span> <span class="n">exp</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">bu_size</span><span class="p">)</span> <span class="o"><</span> <span class="mi">2</span> <span class="o">**</span> <span class="mi">10</span><span class="p">:</span>
<span class="n">unit</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">:</span><span class="s">"bytes"</span><span class="p">,</span> <span class="mi">10</span><span class="p">:</span><span class="s">"KiB"</span><span class="p">,</span> <span class="mi">20</span><span class="p">:</span><span class="s">"MiB"</span><span class="p">,</span> <span class="mi">30</span><span class="p">:</span><span class="s">"GiB"</span><span class="p">,</span> <span class="mi">40</span><span class="p">:</span><span class="s">"TiB"</span><span class="p">,</span> <span class="mi">50</span><span class="p">:</span><span class="s">"PiB"</span><span class="p">,</span>
<span class="mi">60</span><span class="p">:</span><span class="s">"EiB"</span><span class="p">,</span> <span class="mi">70</span><span class="p">:</span><span class="s">"ZiB"</span><span class="p">,</span> <span class="mi">80</span><span class="p">:</span><span class="s">"YiB"</span><span class="p">}[</span><span class="n">exp</span><span class="p">]</span>
<span class="k">break</span>
<span class="k">return</span> <span class="p">{</span><span class="s">"s"</span><span class="p">:</span><span class="n">bu_size</span><span class="p">,</span> <span class="s">"u"</span><span class="p">:</span><span class="n">unit</span><span class="p">,</span> <span class="s">"b"</span><span class="p">:</span><span class="n">bytes_size</span><span class="p">}</span>
</pre></div>
<p>Que nos devuelve un diccionario similar al anterior, lo que nos proporciona la
posibilidad de disponer tanto del tamaño en bytes como en la mejor unidad IEC
posible con una única función. </p>
<p>Todas estas funciones con ejemplos (y además una clase que hace uso de ellas),
se pueden encontrar en el fichero <code>get_size.py</code> en mi repositorio
<em>Python Recipes</em> que se encuentra alojado en <a href="http://github.com/joedicastro/python-recipes">github</a>. Si se ejecuta el
fichero como un script puede verse una comparativa de las diversas funciones en
rendimiento y precisión con respecto al comando <code>du -bs</code></p>Logger, informes legibles para tus scripts Python2011-05-07T23:02:00+02:00joe di castrohttp://joedicastro.com/logger-informes-legibles-para-tus-scripts-python.html<p>Normalmente los scripts que se crean para ser ejecutados periódicamente, como
los de administración de sistemas, realizan tareas que generan cambios que
queremos conocer. Esta información se suele guardar en logs o se envía por
correo a una dirección de email. Hay muchas formas de generar estos logs, pero
normalmente es una simple salida de texto plano en consola, que no suele tener
una presentación muy "amigable" o legible. Cuando tienes una cantidad generosa
de estos scripts, los recibes por correo electrónico y con una frecuencia diaria
o mayor, lo que menos deseas es andar buscando la información entre lineas de
texto plano. Lo que yo quiero es poder identificar la información rápidamente de
un vistazo, además tenía una idea rondando por la cabeza, que seria aún más
cómodo si todos emplearan un formato similar. Con estas premisas cree un módulo
<strong>Python</strong>, <code>logger.py</code>, que empleo en muchos de mis scripts en Python y que me
permite analizar adecuadamente la información que me interesa.</p>
<p>Un ejemplo de informe generado por este modulo Python sería el siguiente (el
mismo que se generaría si ejecutáramos el modulo como script):</p>
<div class="codehilite"><pre>SCRIPT =========================================================================
logger (ver. 0.3)
http://joedicastro.com
This is a test of class Logger
================================================================================
START TIME =====================================================================
Saturday 05/07/11, 21:51:27
================================================================================
BLOCK ==========================================================================
This
is
a
sample
of
Logger.block()
================================================================================
LIST ___________________________________________________________________________
This
is
a
sample
of
Logger.list()
This a sample of logger.free() text.
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque sed
tortor eget justo vehicula consequat vel eu quam. Suspendisse non lectus eget
orci varius adipiscing."
END TIME =======================================================================
Saturday 05/07/11, 21:51:27
================================================================================
</pre></div>
<p>Este modulo contiene una clase, <code>Logger()</code>, que dispone de una serie de métodos
que nos permiten diversas tareas, supongamos que tenemos un objeto <code>log</code> de la
clase <code>Logger()</code>:</p>
<ul>
<li>
<p><code>log.header(url, msg)</code></p>
<p>Nos crea una cabecera para el script que nos aporta cierta información que
nos sirve para identificar el mismo y el contexto en el que se ejecuta. </p>
<div class="codehilite"><pre>SCRIPT =========================================================================
nombre del script (versión)
http://miweb.com/script
mensaje que nos informa de la finalidad del mismo o de
información dependiente del contexto. Totalmente
personalizable.
================================================================================
</pre></div>
</li>
<li>
<p><code>log.time(title)</code></p>
<p>Nos permite registrar el tiempo de un evento. Normalmente lo empleo para
registrar el comienzo y el final de la ejecución del script. </p>
<div class="codehilite"><pre>TITULO =========================================================================
Sábado 07/05/11, 22:18:27
================================================================================
</pre></div>
</li>
<li>
<p><code>log.block(title, content)</code></p>
<p>Crea un bloque de texto con titulo y enmarcado por líneas compuestas por el
carácter <code>=</code>. Entre esas lineas y el contenido no hay ninguna linea en
blanco. Es útil para destacar cierto contenido del resto de forma muy
notable, suelo utilizarlo para ubicar el script en su contexto. </p>
<div class="codehilite"><pre>TITULO =========================================================================
contenido
sigue el contenido
...
================================================================================
</pre></div>
</li>
<li>
<p><code>log.list(title, content)</code></p>
<p>Como su nombre bien indica, es una lista de líneas con un simple encabezado
que lo distinga del resto. Suelo emplearlo para volcar la información generada
por el script. Entre el encabezado y la primera línea de texto existe una
línea en blanco.</p>
<div class="codehilite"><pre>TITULO __________________________________________________
Primera línea del contenido
Segunda línea del contenido
...
</pre></div>
</li>
<li>
<p><code>log.free(content)</code></p>
<p>Texto libre, párrafos que se mostrarán tal y como son, sin formato ni
cabecera alguna. Raramente lo empleo, pero es útil por ejemplo para
introducir comentarios, licencias, etc.</p>
</li>
<li>
<p><code>log.send(subject, send_from='', dest_to='', mail_server='localhost',
server_user='', server_pass='')</code></p>
<p>El meollo del script. Se emplea para mandar el resultado por
correo electrónico. Si solo especificamos el asunto, empleará nuestro
servidor de correo local para mandar el informe al buzón local del usuario
que programó/ejecuto el script. Pero se pueden especificar tanto el empleo
de un servidor de correo (SMTP) distinto como otro (o varios)
destinatario(s) en particular. </p>
</li>
<li>
<p><code>log.write(append=False)</code></p>
<p>Escribe el resultado del log en un fichero de texto. El nombre de este
fichero estará compuesto por el nombre del script sin extensión, más la
extensión <em>.log</em>. Si <code>append</code> es <code>True</code> entonces añadirá el resultado al
final de texto, si no, lo reescribirá en cada ejecución guardando
únicamente el último informe. </p>
</li>
<li>
<p><code>log.get()</code></p>
<p>Nos devuelve el contenido del log. Es útil cuando estamos con tareas de
depurado, con un <code>print</code> llamando a este método podemos volcar en la
consola la información registrada en el log.</p>
</li>
</ul>
<p>El código del script es el siguiente: </p>
<div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span>
<span class="c"># -*- coding: utf8 -*-</span>
<span class="sd">"""</span>
<span class="sd"> logger.py: Create a log object to log script messages in a elegant way</span>
<span class="sd">"""</span>
<span class="c">#===============================================================================</span>
<span class="c"># This module create a log object to log script messages in a elegant way</span>
<span class="c">#===============================================================================</span>
<span class="c">#===============================================================================</span>
<span class="c"># Copyright 2010 joe di castro <joe@joedicastro.com></span>
<span class="c">#</span>
<span class="c"># This program is free software: you can redistribute it and/or modify</span>
<span class="c"># it under the terms of the GNU General Public License as published by</span>
<span class="c"># the Free Software Foundation, either version 3 of the License, or</span>
<span class="c"># (at your option) any later version.</span>
<span class="c">#</span>
<span class="c"># This program is distributed in the hope that it will be useful,</span>
<span class="c"># but WITHOUT ANY WARRANTY; without even the implied warranty of</span>
<span class="c"># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span>
<span class="c"># GNU General Public License for more details.</span>
<span class="c">#</span>
<span class="c"># You should have received a copy of the GNU General Public License</span>
<span class="c"># along with this program. If not, see <http://www.gnu.org/licenses/>.</span>
<span class="c">#===============================================================================</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">"joe di castro <joe@joedicastro.com>"</span>
<span class="n">__license__</span> <span class="o">=</span> <span class="s">"GNU General Public License version 3"</span>
<span class="n">__date__</span> <span class="o">=</span> <span class="s">"10/09/2010"</span>
<span class="n">__version__</span> <span class="o">=</span> <span class="s">"0.3"</span>
<span class="k">try</span><span class="p">:</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">smtplib</span>
<span class="kn">import</span> <span class="nn">socket</span>
<span class="kn">from</span> <span class="nn">email.mime.text</span> <span class="kn">import</span> <span class="n">MIMEText</span>
<span class="kn">from</span> <span class="nn">email.mime.multipart</span> <span class="kn">import</span> <span class="n">MIMEMultipart</span>
<span class="kn">from</span> <span class="nn">email.utils</span> <span class="kn">import</span> <span class="n">COMMASPACE</span><span class="p">,</span> <span class="n">formatdate</span>
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
<span class="c"># Checks the installation of the necessary python modules </span>
<span class="k">print</span><span class="p">((</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">"An error found importing one module:"</span><span class="p">,</span>
<span class="nb">str</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">exc_info</span><span class="p">()[</span><span class="mi">1</span><span class="p">]),</span> <span class="s">"You need to install it"</span><span class="p">,</span> <span class="s">"Stopping..."</span><span class="p">]))</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Logger</span><span class="p">():</span>
<span class="sd">"""</span>
<span class="sd"> Create a log object to log script messages.</span>
<span class="sd"> These messages can be sended via email or writed in a log file</span>
<span class="sd"> """</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""Create the object Logger itself and set various attributes.</span>
<span class="sd"> These attributes are about the python file wich invokes this module:</span>
<span class="sd"> __script_vers = The version of python file which invokes this module</span>
<span class="sd"> __script_name = The name of the python file which invokes this module</span>
<span class="sd"> filename = the log file's name</span>
<span class="sd"> """</span>
<span class="kn">from</span> <span class="nn">__main__</span> <span class="kn">import</span> <span class="n">__dict__</span> <span class="k">as</span> <span class="n">__dict</span>
<span class="bp">self</span><span class="o">.</span><span class="n">__log</span> <span class="o">=</span> <span class="s">''</span>
<span class="k">if</span> <span class="s">'__version__'</span> <span class="ow">in</span> <span class="n">__dict</span><span class="o">.</span><span class="n">keys</span><span class="p">():</span>
<span class="bp">self</span><span class="o">.</span><span class="n">__script_vers</span> <span class="o">=</span> <span class="n">__dict</span><span class="p">[</span><span class="s">'__version__'</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">__script_vers</span> <span class="o">=</span> <span class="s">'Unknown'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">__script_name</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">__dict</span><span class="p">[</span><span class="s">'__file__'</span><span class="p">])</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">'.'</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">filename</span> <span class="o">=</span> <span class="s">'{0}.log'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__script_name</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">__len__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__log</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">__format__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tit</span><span class="p">,</span> <span class="n">cont</span><span class="p">,</span> <span class="n">decor</span><span class="p">):</span>
<span class="sd">"""Format a block or a list of lines to enhance comprehension.</span>
<span class="sd"> (str) tit -- title for the block or list</span>
<span class="sd"> (str or iterable) cont -- line/s for the list/block content</span>
<span class="sd"> ('=' or '_') decor - define if it's list or block and decorate it</span>
<span class="sd"> make the looks of self.block() and self.list()</span>
<span class="sd"> """</span>
<span class="n">ending</span> <span class="o">=</span> <span class="p">{</span><span class="s">'='</span><span class="p">:</span><span class="s">''</span><span class="p">,</span> <span class="s">'_'</span><span class="p">:</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">}[</span><span class="n">decor</span><span class="p">]</span>
<span class="n">end</span> <span class="o">=</span> <span class="p">{</span><span class="s">'='</span><span class="p">:</span> <span class="s">'='</span> <span class="o">*</span> <span class="mi">80</span><span class="p">,</span> <span class="s">'_'</span><span class="p">:</span><span class="s">''</span><span class="p">}[</span><span class="n">decor</span><span class="p">]</span>
<span class="n">begin</span> <span class="o">=</span> <span class="s">' '</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">tit</span><span class="o">.</span><span class="n">upper</span><span class="p">(),</span> <span class="p">(</span><span class="mi">80</span> <span class="o">-</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">tit</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span> <span class="o">*</span> <span class="n">decor</span><span class="p">])</span> <span class="o">+</span> <span class="n">ending</span>
<span class="n">cont</span> <span class="o">=</span> <span class="p">[</span><span class="n">cont</span><span class="p">]</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">cont</span><span class="p">,</span> <span class="nb">str</span><span class="p">)</span> <span class="k">else</span> <span class="n">cont</span>
<span class="n">sep</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span>
<span class="bp">self</span><span class="o">.</span><span class="n">__log</span> <span class="o">+=</span> <span class="n">sep</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">begin</span><span class="p">,</span> <span class="n">sep</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">cont</span><span class="p">),</span> <span class="n">end</span><span class="p">,</span> <span class="n">sep</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">block</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
<span class="sd">"""A block of text lines headed and followed by a line full of '='.</span>
<span class="sd"> (str) title -- The title that start the first line of '='</span>
<span class="sd"> (str or iterable) content -- The line/s between the '=' lines</span>
<span class="sd"> There's not any empty line between the '=' lines and content, e.g.:</span>
<span class="sd"> TITLE ==================================================</span>
<span class="sd"> content</span>
<span class="sd"> ========================================================</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="n">content</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">__format__</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="s">'='</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">list</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
<span class="sd">"""A list of text lines headed by a line full of '_'.</span>
<span class="sd"> (str) title -- The title that start the line of '_'</span>
<span class="sd"> (str or iterable) content -- The line/s after the '_' line</span>
<span class="sd"> After the '_' line is a empty line between it and the content, e.g.:</span>
<span class="sd"> TITLE __________________________________________________</span>
<span class="sd"> content</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="n">content</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">__format__</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="s">'_'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">free</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
<span class="sd">"""Free text unformatted.</span>
<span class="sd"> (str) content -- Text free formated</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">__log</span> <span class="o">+=</span> <span class="n">content</span> <span class="o">+</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span>
<span class="k">def</span> <span class="nf">time</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">title</span><span class="p">):</span>
<span class="sd">"""A self.block() formated line with current time and date.</span>
<span class="sd"> (str) title -- Title for self.block()</span>
<span class="sd"> Looks like this, the data and time are right-justified:</span>
<span class="sd"> TITLE ==================================================</span>
<span class="sd"> Friday 09/10/10, 20:01:39</span>
<span class="sd"> ========================================================</span>
<span class="sd"> """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">block</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="s">'{0:>80}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">'%A </span><span class="si">%x</span><span class="s">, </span><span class="si">%X</span><span class="s">'</span><span class="p">)))</span>
<span class="k">def</span> <span class="nf">header</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">msg</span><span class="p">):</span>
<span class="sd">"""A self.block() formated header for the log info.</span>
<span class="sd"> (str) url -- The url of the script</span>
<span class="sd"> (str) msg -- Message to show into the header. To Provide any useful info</span>
<span class="sd"> It looks like this:</span>
<span class="sd"> SCRIPT =================================================</span>
<span class="sd"> script name and version</span>
<span class="sd"> url</span>
<span class="sd"> msg</span>
<span class="sd"> ========================================================</span>
<span class="sd"> """</span>
<span class="n">script</span> <span class="o">=</span> <span class="s">'{0} (ver. {1})'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__script_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">__script_vers</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">block</span><span class="p">(</span><span class="s">'Script'</span><span class="p">,</span> <span class="p">[</span><span class="n">script</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="s">''</span><span class="p">,</span> <span class="n">msg</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""Get the log content."""</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">__log</span>
<span class="k">def</span> <span class="nf">send</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">subject</span><span class="p">,</span> <span class="n">send_from</span><span class="o">=</span><span class="s">''</span><span class="p">,</span> <span class="n">dest_to</span><span class="o">=</span><span class="s">''</span><span class="p">,</span> <span class="n">mail_server</span><span class="o">=</span><span class="s">'localhost'</span><span class="p">,</span>
<span class="n">server_user</span><span class="o">=</span><span class="s">''</span><span class="p">,</span> <span class="n">server_pass</span><span class="o">=</span><span class="s">''</span><span class="p">):</span>
<span class="sd">"""Send a email with the log.</span>
<span class="sd"> Arguments:</span>
<span class="sd"> (str) send_from -- a sender's email address (default '')</span>
<span class="sd"> (str or list) dest_to -- a list of receivers' email addresses ('')</span>
<span class="sd"> (str) subject -- the mail's subject</span>
<span class="sd"> (str) mail_server -- the smtp server (default 'localhost')</span>
<span class="sd"> (str) server_user -- the smtp server user (default '')</span>
<span class="sd"> (str) server_pass --the smtp server password (default '')</span>
<span class="sd"> If 'send_from' or 'dest_to' are empty or None, then script user's</span>
<span class="sd"> mailbox is assumed instead. Useful for loggin scripts</span>
<span class="sd"> """</span>
<span class="n">local_email</span> <span class="o">=</span> <span class="s">'@'</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s">'LOGNAME'</span><span class="p">),</span> <span class="n">socket</span><span class="o">.</span><span class="n">gethostname</span><span class="p">()])</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">send_from</span><span class="p">:</span>
<span class="n">send_from</span> <span class="o">=</span> <span class="n">local_email</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">dest_to</span><span class="p">:</span>
<span class="n">dest_to</span> <span class="o">=</span> <span class="p">[</span><span class="n">local_email</span><span class="p">]</span>
<span class="n">dest_to_addrs</span> <span class="o">=</span> <span class="n">COMMASPACE</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dest_to</span><span class="p">)</span> <span class="c"># receivers mails</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">MIMEMultipart</span><span class="p">()</span>
<span class="n">message</span><span class="p">[</span><span class="s">'Subject'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'{0} - {1}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">subject</span><span class="p">,</span>
<span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">'%A </span><span class="si">%x</span><span class="s">, </span><span class="si">%X</span><span class="s">'</span><span class="p">))</span>
<span class="n">message</span><span class="p">[</span><span class="s">'From'</span><span class="p">]</span> <span class="o">=</span> <span class="n">send_from</span>
<span class="n">message</span><span class="p">[</span><span class="s">'To'</span><span class="p">]</span> <span class="o">=</span> <span class="n">dest_to_addrs</span>
<span class="n">message</span><span class="p">[</span><span class="s">'Date'</span><span class="p">]</span> <span class="o">=</span> <span class="n">formatdate</span><span class="p">(</span><span class="n">localtime</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">message</span><span class="o">.</span><span class="n">preamble</span> <span class="o">=</span> <span class="s">"You'll not see this in a MIME-aware mail reader.</span><span class="se">\n</span><span class="s">"</span>
<span class="n">message</span><span class="o">.</span><span class="n">attach</span><span class="p">(</span><span class="n">MIMEText</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__log</span><span class="p">))</span>
<span class="c"># initialize the mail server</span>
<span class="n">server</span> <span class="o">=</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTP</span><span class="p">()</span>
<span class="c"># Connect to mail server</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">server</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">mail_server</span><span class="p">)</span>
<span class="k">except</span> <span class="n">socket</span><span class="o">.</span><span class="n">gaierror</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'mail error'</span><span class="p">,</span> <span class="s">'Wrong server, are you sure is correct?'</span><span class="p">)</span>
<span class="k">except</span> <span class="n">socket</span><span class="o">.</span><span class="n">error</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'mail error'</span><span class="p">,</span> <span class="s">'Server unavailable or connection refused'</span><span class="p">)</span>
<span class="c"># Login in mail server</span>
<span class="k">if</span> <span class="n">mail_server</span> <span class="o">!=</span> <span class="s">'localhost'</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">server</span><span class="o">.</span><span class="n">login</span><span class="p">(</span><span class="n">server_user</span><span class="p">,</span> <span class="n">server_pass</span><span class="p">)</span>
<span class="k">except</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTPAuthenticationError</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'mail error'</span><span class="p">,</span> <span class="s">'Authentication error'</span><span class="p">)</span>
<span class="k">except</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTPException</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'mail error'</span><span class="p">,</span> <span class="s">'No suitable authentication method'</span><span class="p">)</span>
<span class="c"># Send mail</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">server</span><span class="o">.</span><span class="n">sendmail</span><span class="p">(</span><span class="n">send_from</span><span class="p">,</span> <span class="n">dest_to_addrs</span><span class="p">,</span> <span class="n">message</span><span class="o">.</span><span class="n">as_string</span><span class="p">())</span>
<span class="k">except</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTPRecipientsRefused</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'mail error'</span><span class="p">,</span> <span class="s">'All recipients were refused.'</span>
<span class="s">'Nobody got the mail.'</span><span class="p">)</span>
<span class="k">except</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTPSenderRefused</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'mail error'</span><span class="p">,</span> <span class="s">'The server didn’t accept the from_addr'</span><span class="p">)</span>
<span class="k">except</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTPDataError</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'mail error'</span><span class="p">,</span> <span class="s">'An unexpected error code, Data refused'</span><span class="p">)</span>
<span class="c"># Disconnect from server</span>
<span class="n">server</span><span class="o">.</span><span class="n">quit</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">append</span><span class="o">=</span><span class="bp">False</span><span class="p">):</span>
<span class="sd">"""Write the log to a file.</span>
<span class="sd"> The name of the file will be like this:</span>
<span class="sd"> script.log</span>
<span class="sd"> where 'script' is the name of the script file without extension (.py)</span>
<span class="sd"> (boolean) append -- If true appends log to file, else writes a new one</span>
<span class="sd"> """</span>
<span class="n">mode</span> <span class="o">=</span> <span class="s">'ab'</span> <span class="k">if</span> <span class="n">append</span> <span class="k">else</span> <span class="s">'wb'</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="p">)</span> <span class="k">as</span> <span class="n">log_file</span><span class="p">:</span>
<span class="n">log_file</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__log</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="sd">"""Main section"""</span>
<span class="n">url</span> <span class="o">=</span> <span class="s">'http://joedicastro.com'</span>
<span class="n">head</span> <span class="o">=</span> <span class="s">'This is a test of class Logger'</span>
<span class="n">log</span> <span class="o">=</span> <span class="n">Logger</span><span class="p">()</span>
<span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">head</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">'Start time'</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">block</span><span class="p">(</span><span class="s">'Block'</span><span class="p">,</span> <span class="s">'This is a sample of Logger.block()'</span><span class="o">.</span><span class="n">split</span><span class="p">())</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'List'</span><span class="p">,</span> <span class="s">'This is a sample of Logger.list()'</span><span class="o">.</span><span class="n">split</span><span class="p">())</span>
<span class="n">log</span><span class="o">.</span><span class="n">free</span><span class="p">(</span><span class="s">'''This a sample of logger.free() text.</span>
<span class="s">"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque sed</span>
<span class="s">tortor eget justo vehicula consequat vel eu quam. Suspendisse non lectus eget</span>
<span class="s">orci varius adipiscing."'''</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">'End time'</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">'This is mail test'</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">free</span><span class="p">(</span><span class="s">'All of this had been recorded in {0}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">filename</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">get</span><span class="p">())</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>Para acceder a la versión más reciente del mismo, acudir a el repositorio
<em>Python Recipes</em> en <a href="http://github.com/joedicastro/python-recipes">github</a>.</p>
<p>Ejemplos de utilización de este modulo pueden ser
<a href="http://joedicastro.com/sincronizar_una_carpeta_local_y_una_remota_a_traves_de_ftp_lftp_mirror">lftp-mirror</a> (incluido dentro del mismo), <a href="http://joedicastro.com/ted_talks_descargar_videos_y_subtitulos_de_las_charlas">TEDTalks</a>, <a href="http://joedicastro.com/generar_informes_de_cambios_en_paquetes_instalados_en_debian_y_ubuntu">dpkg_diff</a> y <a href="http://joedicastro.com/combatir_el_spam_en_drupal">ban_drupal_spammers</a></p>Generar informes de cambios en paquetes instalados en Debian y Ubuntu.2011-05-06T22:55:00+02:00joe di castrohttp://joedicastro.com/generar-informes-de-cambios-en-paquetes-instalados-en-debian-y-ubuntu.html<p>Una de las políticas de seguridad que tengo con mis sistemas <strong>Linux</strong>, es
además de efectuar respaldos periódicos (diarios) del contenido del directorio
<code>/home</code> (en mi caso siempre es una partición o disco independiente), es siempre
guardar también una lista de los <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr> instalados en los mismos.
Prácticamente nunca hago una copia completa de la partición o disco de de
sistema, y aunque si hago una copia de los directorios más importantes, prefiero
tener siempre una relación actualizada de los <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr> instalados en cada
maquina.</p>
<p>En sistemas Linux <a href="http://debian.org">Debian</a> y otras distribuciones derivadas (<a href="http://ubuntu.com">Ubuntu</a>,
<a href="http://www.linuxmint.com/">Mint</a>, <a href="http://www.mepis.org/">Mepis</a>, <a href="http://www.knoppix.org/">Knoppix</a>, ...) obtener esta lista es realmente
sencillo, pues solo es necesario ejecutar este comando:</p>
<div class="codehilite"><pre><span class="go">dpkg -l > lista_paquetes.txt</span>
</pre></div>
<p>Y con eso, se generaría una lista de los <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr> instalados<sup id="fnref:nota"><a href="#fn:nota" rel="footnote">1</a></sup> en el
sistema en el fichero <em>lista_paquetes.txt</em>. Y mantener actualizado este fichero
es tan sencillo como programar (via <code>crontab</code> por ejemplo) la ejecución de este
comando con un sencillo <a href="http://es.wikipedia.org/wiki/">script</a> <a href="http://es.wikipedia.org/wiki/Shell_de_UNIX">shell</a>. Y así lo he realizado durante
años, hasta que quise tener aun más información.</p>
<p>Entonces me interesó conocer también los cambios que se producen en los
<abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr>, es decir nuevas (des)instalaciones y actualizaciones. Es lógico pensar
que si uno mismo es el que las realiza, pues ya sabe esta información de primera
mano. Pero la memoria es frágil y no demasiado confiable, además ¿que ocurre
cuando en una misma maquina tienen permisos para administrar los <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr> más de
un usuario? </p>
<p>Se pueden conocer estos cambios de los <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr> en el tiempo de varios modos,
desde acudir a los logs de <strong>dpkg</strong> en <code>/var/log/dpkg.log</code> y examinarlos con
algún analizador de logs (o el más sencillo e inmediato comando <code>less</code>) hasta
consultarlos de una manera más sencilla y gráfica con el gestor de <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr>
<strong>Synaptic</strong> (en el menú <em>Archivo -> Histórico</em>). Pero me interesaba automatizar
esto, para conocer esos cambios poco después de que se produjeran y lo lógico
era emplear el mismo script shell que empleaba para generar la lista de <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr>
instalados. Así lo hice entonces, combinando los comandos <code>awk</code>, <code>grep</code>, <code>diff</code>
y <code>mail</code> con el comando del principio para obtener la lista, tenia los cambios
en mi buzón de correo al poco tiempo de producirse. Y ha funcionado
perfectamente hasta ahora.</p>
<p>Pero desde que me introduje en el lenguaje <strong>Python</strong>, he ido migrando poco a
poco los scripts <a href="http://es.wikipedia.org/wiki/Bash">bash</a> que tengo para reescribirlos en este lenguaje. Y
recientemente le ha tocado a este. Ha sido muy fácil y la verdad es que el
resultado aunque funcionalmente es el mismo, me ha permitido entregar unos
informes más elegantes y fáciles de interpretar de un golpe de vista. </p>
<p>Ahora, cuando se realiza algún cambio en los <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr> del sistema, de forma
automática, por mi o por otro usuario autorizado, yo recibo en mi correo un
informe similar a este:</p>
<div class="codehilite"><pre>SCRIPT =========================================================================
dpkg_diff (ver. 0.1)
http://joedicastro.com
Changes of packages installed on yourmachine
===============================================================================
START TIME =====================================================================
Thursday 05/05/11, 10:30:01
===============================================================================
INSTALLED PACKAGES LIST FILE ___________________________________________________
/your/path/to/package_list.txt
CHANGES DIFF ___________________________________________________________________
<span class="gd">--- previous Wed May 4 22:59:51 2011</span>
<span class="gi">+++ current Thu May 5 10:30:01 2011</span>
<span class="gu">@@ -34,1 +34,1 @@</span>
<span class="gd">-ii apt 0.7.25.3ubuntu9.3</span>
<span class="gi">+ii apt 0.7.25.3ubuntu9.4</span>
<span class="gu">@@ -36,2 +36,2 @@</span>
<span class="gd">-ii apt-transport-https 0.7.25.3ubuntu9.3</span>
<span class="gd">-ii apt-utils 0.7.25.3ubuntu9.3</span>
<span class="gi">+ii apt-transport-https 0.7.25.3ubuntu9.4</span>
<span class="gi">+ii apt-utils 0.7.25.3ubuntu9.4</span>
END TIME =======================================================================
Thursday 05/05/11, 10:30:01
===============================================================================
</pre></div>
<p>Donde se puede ver la información que nos indica el script que ha generado el
correo, la maquina en la que se han realizado los cambios, la localización del
fichero con la relación de todos los <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr> instalados en el sistema, la fecha
y hora de la ejecución del script y el <a href="http://es.wikipedia.org/wiki/Diff">diff</a> con la relación de cambios en
los <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr> en formato <a href="http://en.wikipedia.org/wiki/Diff#Unified_format">Unified format</a>. En este ejemplo podemos ver como se
han actualizado tres <abbr title="En sistemas *NIX se denomina así a los programas">paquetes</abbr> en esa maquina. No nos dice la hora en que se
efectuó la modificación (podemos verlo en el log de <code>dpkg</code>) pero si podemos
saber que se efectuó entre dos intervalos de ejecución del script, información
que será más que suficiente la mayoría de las veces. Para datos concretos, ir al
log y filtrarlo con <code>grep</code> y en segundos sabremos la respuesta. Si además como
es mi caso, ejecutamos el script cada 12 o 24 horas, pues será fácil saber
cuando se han realizado los cambios.</p>
<p>Un fragmento de código de la parte principal del script es el siguiente:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">pretty_diff</span><span class="p">(</span><span class="n">diff</span><span class="p">):</span>
<span class="sd">"""Better format for package lines in diff."""</span>
<span class="n">pkg</span> <span class="o">=</span> <span class="p">{}</span> <span class="c"># diff's packages lines</span>
<span class="c"># Get columns info for diff package lines</span>
<span class="k">for</span> <span class="n">idx</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">diff</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">findall</span><span class="p">(</span><span class="s">"^-{3}|^\+{3}|^@{2}"</span><span class="p">,</span> <span class="n">line</span><span class="p">):</span>
<span class="c"># split the line in columns and remove the description column</span>
<span class="n">cols</span> <span class="o">=</span> <span class="n">split</span><span class="p">(</span><span class="s">"\s{2,}"</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="mi">3</span><span class="p">)[:</span><span class="mi">3</span><span class="p">]</span>
<span class="c"># A nested dict, for each line index we have a dict that contains </span>
<span class="c"># the package line columns: 's' (status), 'n' (name) & 'v' (version)</span>
<span class="c"># and the width of the name column: w(width)</span>
<span class="n">pkg</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="s">'s'</span><span class="p">:</span><span class="n">cols</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="s">'n'</span><span class="p">:</span><span class="n">cols</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">'v'</span><span class="p">:</span><span class="n">cols</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="s">'w'</span><span class="p">:</span><span class="nb">len</span><span class="p">(</span><span class="n">cols</span><span class="p">[</span><span class="mi">1</span><span class="p">])}</span>
<span class="c"># maximum width in packages' name column for all lines</span>
<span class="n">mxw</span> <span class="o">=</span> <span class="nb">max</span><span class="p">((</span><span class="n">pkg</span><span class="p">[</span><span class="n">index</span><span class="p">][</span><span class="s">'w'</span><span class="p">]</span> <span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="n">pkg</span><span class="p">))</span>
<span class="c"># Replace each package line for a prettier one (more legible) </span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">diff</span><span class="p">)):</span>
<span class="k">if</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">pkg</span><span class="p">:</span>
<span class="n">diff</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="s">"{0} {1} {2}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">pkg</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="s">'s'</span><span class="p">],</span> <span class="n">pkg</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="s">'n'</span><span class="p">]</span> <span class="o">+</span> <span class="s">" "</span> <span class="o">*</span>
<span class="p">(</span><span class="n">mxw</span> <span class="o">-</span> <span class="n">pkg</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="s">'w'</span><span class="p">]),</span> <span class="n">pkg</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="s">'v'</span><span class="p">]))</span>
<span class="k">return</span> <span class="n">diff</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">old</span><span class="o">=</span><span class="s">""</span><span class="p">):</span>
<span class="sd">"""Main section"""</span>
<span class="c"># The path to store the debian packages list file</span>
<span class="n">pkg_lst_file</span> <span class="o">=</span> <span class="s">"./package_list.txt"</span>
<span class="c"># Start logging</span>
<span class="n">log</span> <span class="o">=</span> <span class="n">Logger</span><span class="p">()</span>
<span class="n">url</span> <span class="o">=</span> <span class="s">"http://joedicastro.com"</span>
<span class="n">head</span> <span class="o">=</span> <span class="s">"Changes of packages installed on {0}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">platform</span><span class="o">.</span><span class="n">node</span><span class="p">())</span>
<span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">head</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">"Start time"</span><span class="p">)</span>
<span class="c"># Read the old file and clean the list</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">pkg_lst_file</span><span class="p">):</span>
<span class="n">old</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">pkg_lst_file</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span><span class="o">.</span><span class="n">readlines</span><span class="p">()</span>
<span class="n">old_date</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">ctime</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">stat</span><span class="p">(</span><span class="n">pkg_lst_file</span><span class="p">)</span><span class="o">.</span><span class="n">st_mtime</span><span class="p">)</span>
<span class="c"># Get the current list of debian packages installed on system </span>
<span class="n">current</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">([</span><span class="s">"dpkg"</span><span class="p">,</span> <span class="s">"-l"</span><span class="p">],</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">readlines</span><span class="p">()</span>
<span class="c"># First, save the list file</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">pkg_lst_file</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">out</span><span class="p">:</span>
<span class="n">out</span><span class="o">.</span><span class="n">writelines</span><span class="p">(</span><span class="n">current</span><span class="p">)</span>
<span class="n">curr_date</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">ctime</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">stat</span><span class="p">(</span><span class="n">pkg_lst_file</span><span class="p">)</span><span class="o">.</span><span class="n">st_mtime</span><span class="p">)</span>
<span class="c"># Compare both lists</span>
<span class="k">if</span> <span class="n">old</span><span class="p">:</span>
<span class="n">file_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">realpath</span><span class="p">(</span><span class="n">pkg_lst_file</span><span class="p">)</span>
<span class="n">diff</span> <span class="o">=</span> <span class="p">[</span><span class="n">ln</span> <span class="k">for</span> <span class="n">ln</span> <span class="ow">in</span> <span class="n">unified_diff</span><span class="p">(</span><span class="n">old</span><span class="p">,</span> <span class="n">current</span><span class="p">,</span> <span class="n">fromfile</span><span class="o">=</span><span class="s">"previous"</span><span class="p">,</span>
<span class="n">tofile</span><span class="o">=</span><span class="s">"current "</span><span class="p">,</span>
<span class="n">fromfiledate</span><span class="o">=</span><span class="n">old_date</span><span class="p">,</span>
<span class="n">tofiledate</span><span class="o">=</span><span class="n">curr_date</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
<span class="n">lineterm</span><span class="o">=</span><span class="s">""</span><span class="p">)]</span>
<span class="c"># If there are differences write the log to disk and send mail</span>
<span class="k">if</span> <span class="n">diff</span><span class="p">:</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">"Installed packages list file"</span><span class="p">,</span> <span class="n">file_path</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">"Changes diff"</span><span class="p">,</span> <span class="n">pretty_diff</span><span class="p">(</span><span class="n">diff</span><span class="p">))</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">"End time"</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="c"># Send mail to current system user. For other options, see logger </span>
<span class="c"># module info</span>
<span class="n">log</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">"Debian packages changes"</span><span class="p">)</span>
</pre></div>
<p>Este script hace uso del modulo logger, que comento en este <a href="http://joedicastro.com/logger_informes_legibles_para_tus_scripts_python">artículo</a>. </p>
<p>Para obtener la versión más reciente del script y del modulo logger, consultar
mi repositorio <em>Python Recipes</em> en <a href="http://github.com/joedicastro/python-recipes">github</a>.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:nota">
<p>Es una relación de los programas instalados empleando el sistema de
paqueteria de Debian, los programas instalados manualmente vía compilación
u otros medios no aparecerán en ella.  <a href="#fnref:nota" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>Comprobar si un programa está instalado con Python2011-04-28T14:04:00+02:00joe di castrohttp://joedicastro.com/comprobar-si-un-programa-esta-instalado-con-python.html<p>Cuando creamos un script en <strong>Python</strong>, sobre todo aquellos orientados a
ejecutarse en la línea de comandos, a veces necesitamos echar mano de un
programa externo que no siempre viene por defecto instalado en el sistema. Por
ejemplo, en el script que empleo en
<a href="http://joedicastro.com/optimizar_imagenes_para_la_web">optimizar imágenes para la web</a>
empleo los programas externos <em>pngcrush</em> y <em>jpegtran</em>. ¿Como comprobamos
entonces si el programa está instalado? Desde luego es siempre mejor
comprobarlo y avisar al usuario, que dejar que arroje un feo error.</p>
<p>Una forma de comprobar si el programa está instalado es capturando la excepción
cuando se produzca con las sentencias <code>try</code> y <code>except</code>, incluyendo la llamada al
ejecutable dentro de ellas y devolver un mensaje de error avisando de la
necesidad de la presencia de este ejecutable. Pero al igual que en el ejemplo
anterior, yo prefiero comprobar esto incluso antes de ejecutar cualquier otro
código dentro del script. Para ello empleo el <a href="http://ibiblio.org/g2swap/byteofpython/read/module-name.html">conocido truco</a> que nos
permite ejecutar cierto código solo cuando es ejecutado como script y no cuando
es importado como módulo:</p>
<div class="codehilite"><pre><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">chequeo_ejecutable</span><span class="p">()</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>Donde <code>main()</code> es la función principal donde albergaríamos el código fundamental
del script. Es un conocido <a href="http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html">Python Idiom</a> que me sirve perfectamente para
esta tarea. De hecho lo empleo habitualmente en todos mis scripts para separar
el código principal de las funciones. </p>
<p>Comprobar en Linux si existe el ejecutable es realmente sencillo (siempre y
cuando conozcamos el nombre exacto del ejecutable) con este código:</p>
<div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span>
<span class="c"># -*- coding: utf8 -*-</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">Popen</span><span class="p">,</span> <span class="n">PIPE</span>
<span class="k">def</span> <span class="nf">check_execs</span><span class="p">(</span><span class="o">*</span><span class="n">progs</span><span class="p">):</span>
<span class="sd">"""Check if the programs are installed, if not exit and report."""</span>
<span class="k">for</span> <span class="n">prog</span> <span class="ow">in</span> <span class="n">progs</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">Popen</span><span class="p">([</span><span class="n">prog</span><span class="p">,</span> <span class="s">'--help'</span><span class="p">],</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">OSError</span><span class="p">:</span>
<span class="n">msg</span> <span class="o">=</span> <span class="s">'The {0} program is necessary to run this script'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">prog</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
<span class="k">return</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="c"># Incluir aquí el código fundamental del script</span>
<span class="k">pass</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">check_execs</span><span class="p">(</span><span class="s">'python'</span><span class="p">)</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>Si no existiera el ejecutable que le pasamos a la función <code>check_execs()</code>
entonces se detendrá el script y nos dirá que necesitamos instalarlo para
ejecutar el script. Hacerlo así nos evita cualquier tipo de manipulación previa
antes de darnos cuenta de que nos falta un elemento esencial para ejecutarlo al
completo. Esta función funciona porque en Linux los ejecutables normalmente
están colgando de una ruta del PATH. En Windows, por ejemplo, la cosa cambia.</p>
<p>Si queremos que el script sea multiplataforma y que nos funcione tanto en
sistemas *NIX como en Windows entonces necesitamos una función algo más
compleja. En Windows un ejecutable no necesita colgar del PATH y es muy
frecuente que no sea así. Al mismo tiempo en Windows podemos tener el sistema de
ficheros repartido en varias unidades de disco, y necesitamos explorar todas
ellas para buscar nuestro ejecutable. Aunque lo más frecuente es que se
encuentre en la unidad C: y la función se detiene en cuanto encuentra el primer
ejecutable, el hecho de realizar está búsqueda hace que el proceso sea más lento
en Windows que en Linux. </p>
<p>La función multiplataforma es la siguiente:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">check_execs_posix_win</span><span class="p">(</span><span class="n">progs</span><span class="p">):</span>
<span class="sd">"""Check if the program is installed.</span>
<span class="sd"> Returns one dictionary with 1+n pair of key/values:</span>
<span class="sd"> A fixed key/value:</span>
<span class="sd"> "WinOS" -- (boolean) True it's a Windows OS, False it's a *nix OS</span>
<span class="sd"> for each program in progs a key/value like this:</span>
<span class="sd"> "program" -- (str or boolean) The Windows executable path if founded else </span>
<span class="sd"> '' if it's Windows OS. If it's a *NIX OS True</span>
<span class="sd"> if founded else False</span>
<span class="sd"> """</span>
<span class="n">execs</span> <span class="o">=</span> <span class="p">{</span><span class="s">'WinOS'</span><span class="p">:</span><span class="bp">True</span> <span class="k">if</span> <span class="n">platform</span><span class="o">.</span><span class="n">system</span><span class="p">()</span> <span class="o">==</span> <span class="s">'Windows'</span> <span class="k">else</span> <span class="bp">False</span><span class="p">}</span>
<span class="c"># get all the drive unit letters if the OS is Windows</span>
<span class="n">windows_drives</span> <span class="o">=</span> <span class="n">findall</span><span class="p">(</span><span class="s">r'(\w:)</span><span class="se">\\</span><span class="s">'</span><span class="p">,</span>
<span class="n">Popen</span><span class="p">(</span><span class="s">'fsutil fsinfo drives'</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span><span class="o">.</span>
<span class="n">communicate</span><span class="p">()[</span><span class="mi">0</span><span class="p">])</span> <span class="k">if</span> <span class="n">execs</span><span class="p">[</span><span class="s">'WinOS'</span><span class="p">]</span> <span class="k">else</span> <span class="bp">None</span>
<span class="n">progs</span> <span class="o">=</span> <span class="p">[</span><span class="n">progs</span><span class="p">]</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">progs</span><span class="p">,</span> <span class="nb">str</span><span class="p">)</span> <span class="k">else</span> <span class="n">progs</span>
<span class="k">for</span> <span class="n">prog</span> <span class="ow">in</span> <span class="n">progs</span><span class="p">:</span>
<span class="k">if</span> <span class="n">execs</span><span class="p">[</span><span class="s">'WinOS'</span><span class="p">]:</span>
<span class="c"># Set all commands to search the executable in all drives</span>
<span class="n">win_cmds</span> <span class="o">=</span> <span class="p">[</span><span class="s">'dir /B /S {0}\*{1}.exe'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">letter</span><span class="p">,</span> <span class="n">prog</span><span class="p">)</span> <span class="k">for</span>
<span class="n">letter</span> <span class="ow">in</span> <span class="n">windows_drives</span><span class="p">]</span>
<span class="c"># Get the first location (usually in C:) where the executable exists</span>
<span class="k">for</span> <span class="n">cmd</span> <span class="ow">in</span> <span class="n">win_cmds</span><span class="p">:</span>
<span class="n">execs</span><span class="p">[</span><span class="n">prog</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">Popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">shell</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span><span class="o">.</span>
<span class="n">communicate</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)[</span><span class="mi">0</span><span class="p">])</span>
<span class="k">if</span> <span class="n">execs</span><span class="p">[</span><span class="n">prog</span><span class="p">]:</span>
<span class="k">break</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">Popen</span><span class="p">([</span><span class="n">prog</span><span class="p">,</span> <span class="s">'--help'</span><span class="p">],</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span>
<span class="n">execs</span><span class="p">[</span><span class="n">prog</span><span class="p">]</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">except</span> <span class="ne">OSError</span><span class="p">:</span>
<span class="n">execs</span><span class="p">[</span><span class="n">prog</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">return</span> <span class="n">execs</span>
</pre></div>
<p>En la parte de Windows (la de *NIX es básicamente igual) esta función lo que
hace es obtener primero las letras de las unidades de disco disponibles en el
sistema. Luego busca el ejecutable en cada una de ellas y en cuanto encuentra
la primera ruta al ejecutable se detiene. La función en este caso devuelve un
diccionario donde hay una clave fija que es <code>WinOS</code> que será <code>True</code> si estamos
en Windows y <code>False</code> en caso contrario. Luego nos devuelve una clave por cada
uno de los programas que le mandemos comprobar. Esta clave sera <abbr title="Verdadero o Falso. El nombre proviene de la Álgebra de Boole.">booleana</abbr> en el
caso de *NIX y la ruta del programa (o una cadena vacía) en el caso de Windows.</p>
<p>Las funciones y un ejemplo de su funcionamiento podéis encontrarlas en el
fichero <code>check_execs.py</code> en mi repositorio <em>Python Recipes</em> que se encuentra
alojado en <a href="http://github.com/joedicastro/python-recipes">github</a>.</p>Crear un arbol de directorios falso para entorno de pruebas2011-04-27T20:52:00+02:00joe di castrohttp://joedicastro.com/crear-un-arbol-de-directorios-falso-para-entorno-de-pruebas.html<p>A veces, para realizar pruebas con nuestros propios programas o para, por
ejemplo, hacer pruebas con programas de Backup o FTP, necesitamos crear un
entorno de pruebas que nos permita hacer todo tipo de operaciones sin comprometer
la seguridad de nuestros datos reales. Una practica habitual es hacer copias de
directorios de nuestros propios archivos y trabajar sobre ellos. Es algo que
creo se debería dejar únicamente para la última fase de las pruebas, cuando en
teoría ya todo funciona. Confundir ubicaciones y malograr los datos reales es
más fácil de lo que pueda parecer, sobre todo si se comparte el mismo sistema de
archivos. Otra veces lo que necesitamos es generar una estructura de directorios
totalmente aleatoria, con sus correspondientes archivos de forma temporal. </p>
<p>He creado un script <strong>Python</strong> que hace precisamente esto último, generar una
jerarquía completa de directorios, de forma aleatoria, con sus correspondientes
archivos de texto para simular una estructura de directorios real. Cuando le
estaba dando vueltas a como generar de forma aleatoria los nombres de los
archivos y que al mismo tiempo fuera multiplataforma (respetando los caracteres
no permitidos según el sistema de archivos) me acorde de <a href="http://es.wikipedia.org/wiki/Lorem_ipsum">Lorem Ipsum</a>. El
texto que se lleva empleando desde los tiempos de la imprenta para generar
borradores y que aún se emplea en diseño gráfico para los contenidos de prueba
(al igual que en la web). No solo hacía de este modo un guiño a esta veterana
prueba de maquetación, si no que me solucionaba la papeleta de generar los
nombres de directorios y archivos. Al estar compuesto de "palabras" en latín
(solo caracteres del alfabeto) me olvidaba de las incompatibilidades entre
plataformas y sistemas de archivo. Un homenaje y un problema resuelto en un solo
paso.</p>
<p>Cuando me disponía ya a crear un modo de extraer palabras aleatorias del texto
original de Lorem Ipsum, pensé que seguro que no era el primero al que se le
ocurría hacer esto con Python, así que después de una pequeña búsqueda en el
<a href="http://pypi.python.org/pypi"><abbr title="Python Package Index"><abbr title="Python Package Index">PyPI</abbr></abbr></a>, di con el magnifico proyecto de James Hales,
<a href="http://code.google.com/p/lorem-ipsum-generator/">Lorem-Ipsum-Generator</a>. Importando el modulo de este proyecto en mi script,
tenía medio trabajo hecho.</p>
<p>El script, al que llamo <strong>test_dir_tree</strong>, genera una estructura de directorios
de entre 7 y 39 directorios (estos parámetros pueden ser fácilmente cambiados en
el script), con sus correspondientes archivos. El número de archivos se decide
en función del número de directorios y se reparten también de forma aleatoria
entre los mismos. Como he comentado, los nombres tanto de directorios como
ficheros proceden de Lorem Ipsum. El tamaño de los archivos oscila entre 3 y 512
Kbytes, para no ocupar demasiado espacio en disco. Son archivos de texto
normales en los cuales el contenido está formado a su vez por párrafos
aleatorios de Lorem Ipsum.</p>
<p>No es necesario ningún parámetro para ejecutar el script:</p>
<div class="codehilite"><pre><span class="go">python test_dir_tree.py</span>
</pre></div>
<p>Una vez ejecutado el script, se genera una estructura de directorios como la de
este ejemplo:</p>
<div class="codehilite"><pre>test
├── blandit
│ ├── [234K] bibendum.txt
│ ├── [319K] congue.txt
│ ├── [382K] consequat.txt
│ └── [298K] ligula.txt
├── elit
├── est
│ ├── [ 22K] condimentum.txt
│ ├── [401K] hac.txt
│ ├── [139K] nibh.txt
│ └── [ 12K] tincidunt.txt
├── habitasse
│ ├── [359K] etiam.txt
│ ├── [ 59K] facilisi.txt
│ ├── [ 12K] integer.txt
│ ├── [ 23K] metus.txt
│ └── [ 31K] non.txt
├── lacus
│ ├── [394K] faucibus.txt
│ └── [ 29K] torquent.txt
├── laoreet
│ └── [424K] eros.txt
├── litora
│ └── [ 87K] tellus.txt
└── nostra
├── [349K] curabitur.txt
├── [241K] neque.txt
├── [3.4K] odio.txt
└── [ 78K] sapien.txt
</pre></div>
<p>Donde el principio del contenido de uno de esos archivos de texto, sería algo así:</p>
<div class="codehilite"><pre>Eget. Sem turpis tempus sagittis arcu, nullam ac rutrum turpis. Sem
fusce lacus, cum neque fermentum potenti. Est aliquam donec, leo
amet elit dapibus ipsum. Quam pellentesque eu fusce pellentesque
torquent netus vivamus velit at nisl. Senectus ligula, erat dictum.
Natoque metus urna quis vivamus in duis. Cras. Massa nam. Quisque.
Potenti, sit. Urna metus, et eu duis suspendisse per primis. Duis,
viverra massa enim hac.
Fames ut leo in a turpis proin gravida ac, auctor. Natoque
suspendisse nisl. Dictumst mus, amet pede, eni velit velit. Elit.
Cum. Congue pretium ad id porta in massa enim purus. Tempus, porta
donec molestie habitasse, urna urna nam. Tempor massa sed quam sit
nec dapibus at, duis. Leo pellentesque. Orci arcu iaculis ac, elit
netus conubia. Penatibus platea rutrum netus suspendisse non. Purus
consequat. Inceptos platea, tempus metus eu consectetuer feugiat,
urna at lorem pellentesque curae. Dapibus a, scelerisque cum, auctor
tincidunt primis. Augue nisi id per nulla, sit enim mus id eleifend
taciti, semper est.
</pre></div>
<p>El script tarda muy poco en generar esta estructura, por ejemplo en un Core 2
Duo a 2 GHz, se tardan 1.72s en generar una estructura de 71 directorios y 114
ficheros.</p>
<p>Este es el núcleo central del script <strong>test_dir_tree.py</strong>:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">test_tree</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">min_dirs</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">max_dirs</span><span class="o">=</span><span class="mi">79</span><span class="p">):</span>
<span class="sd">"""make a fake directory hierarchy with files for test purposes."""</span>
<span class="k">def</span> <span class="nf">latin_words</span><span class="p">(</span><span class="n">generator</span><span class="p">):</span>
<span class="sd">"""Generate a list of latin words"""</span>
<span class="n">words</span> <span class="o">=</span> <span class="n">generator</span><span class="o">.</span><span class="n">generate_paragraphs_plain</span><span class="p">(</span><span class="mi">9</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
<span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">words</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'.'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s">','</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">()))</span>
<span class="k">def</span> <span class="nf">check_path</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
<span class="sd">"""If no exists a path, make it."""</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
<span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="n">lorem</span> <span class="o">=</span> <span class="n">lipsum</span><span class="o">.</span><span class="n">MarkupGenerator</span><span class="p">()</span>
<span class="n">latins</span> <span class="o">=</span> <span class="n">latin_words</span><span class="p">(</span><span class="n">lorem</span><span class="p">)</span>
<span class="n">dirs</span> <span class="o">=</span> <span class="n">latins</span><span class="p">[:</span><span class="n">randrange</span><span class="p">(</span><span class="n">min_dirs</span><span class="p">,</span> <span class="n">max_dirs</span><span class="p">)]</span>
<span class="n">files</span> <span class="o">=</span> <span class="p">[</span><span class="n">f</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">latins</span> <span class="k">if</span> <span class="n">f</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">dirs</span><span class="p">][:</span><span class="nb">len</span><span class="p">((</span><span class="n">dirs</span><span class="p">)</span> <span class="o">*</span> <span class="mi">3</span><span class="p">)</span> <span class="o">-</span> <span class="mi">3</span><span class="p">]</span>
<span class="n">check_path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="k">for</span> <span class="n">directory</span> <span class="ow">in</span> <span class="n">dirs</span><span class="p">:</span>
<span class="n">check_path</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">directory</span><span class="p">))</span>
<span class="k">for</span> <span class="n">fil</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">choice</span><span class="p">(</span><span class="n">dirs</span><span class="p">),</span> <span class="s">'{0}.txt'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">fil</span><span class="p">))</span>
<span class="n">text</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">size</span> <span class="o">=</span> <span class="n">randint</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">524288</span><span class="p">)</span> <span class="c"># Files not bigger than 512 Kbytes</span>
<span class="n">sample</span> <span class="o">=</span> <span class="n">lorem</span><span class="o">.</span><span class="n">generate_paragraphs_plain</span><span class="p">(</span><span class="n">randrange</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">9</span><span class="p">))</span>
<span class="k">while</span> <span class="nb">len</span><span class="p">(</span><span class="n">text</span><span class="p">)</span> <span class="o"><</span> <span class="n">size</span><span class="p">:</span>
<span class="n">text</span> <span class="o">+=</span> <span class="n">sample</span> <span class="o">+</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">out</span><span class="p">:</span>
<span class="n">out</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">text</span><span class="p">[:</span><span class="n">size</span><span class="p">])</span>
<span class="k">return</span> <span class="n">dirs</span><span class="p">,</span> <span class="n">files</span>
</pre></div>
<p>Para obtener el script completo, acudir a mi repositorio <em>Python Recipes</em> que
está alojado en <a href="http://github.com/joedicastro/python-recipes">github</a>.</p>Mover todos los archivos del mismo tipo de un arbol de directorios a la vez2011-04-26T23:17:00+02:00joe di castrohttp://joedicastro.com/mover-todos-los-archivos-del-mismo-tipo-de-un-arbol-de-directorios-a-la-vez.html<p>A veces tenemos la necesidad de <strong>mover (o copiar o borrar)</strong> varios ficheros
del mismo tipo a la vez, pero estos no "cuelgan" de un solo directorio, si no
que se encuentran repartidos dentro una jerarquía de subdirectorios. En este
caso los típicos comandos para mover/copiar/borrar archivos no nos sirven, ni
siquiera los útiles comodines nos resuelven el problema. ¿Como lo hacemos
entonces?</p>
<p>Por ejemplo, si tenemos una estructura de directorios como esta:</p>
<div class="codehilite"><pre>raiz
├── dir_1
│ ├── imagen_1.jpg
│ ├── pdf_1.pdf
│ ├── texto_1.txt
│ └── texto_2.txt
├── dir_2
│ ├── imagen_2.jpg
│ ├── imagen_3.gif
│ └── texto_3.txt
├── dir_3
│ ├── doc_1.doc
│ ├── pdf_2.pdf
│ ├── pdf_3.pdf
│ ├── pdf_4.pdf
│ ├── texto_4.txt
│ └── texto_5.txt
├── dir_4
│ ├── subdir_1
│ │ └── imagen_4.png
│ ├── subdir_2
│ │ ├── pdf_5.pdf
│ │ └── texto_7.txt
│ ├── doc_2.doc
│ └── texto_6.txt
└── dir_5
</pre></div>
<p>Y queremos mover todos los ficheros <code>.txt</code> al <code>dir_5</code> que tenemos vacío, ¿Como
lo hacemos? Bueno, en el caso de <strong>Linux</strong> (y UNIX) podemos hacerlo en una sola
operación gracias al siempre útil y versátil <code>find</code> con el siguiente comando
(ejecutado desde el directorio raíz):</p>
<div class="codehilite"><pre>find . -type f -name *.txt -exec mv <span class="o">{}</span> ./dir_5 <span class="se">\;</span>
</pre></div>
<p>Este comando nos encontraría todos los archivos con la extensión <code>.txt</code> dentro
de todos los directorios y subdirectorios y los iría moviendo uno a uno al
<code>dir_5</code>. Es un comando rápido y efectivo. Genéricamente, el comando sería así:</p>
<div class="codehilite"><pre>find directorio_origen -type f -name *.EXT -exec mv <span class="o">{}</span> ./directorio_destino <span class="se">\;</span>
</pre></div>
<p>donde <code>EXT</code> sería la extensión del tipo de archivo que queremos mover. Se puede
emplear esta variante para copiar los archivos en vez de moverlos:</p>
<div class="codehilite"><pre>find directorio_origen -type f -name *.EXT -exec cp <span class="o">{}</span> ./directorio_destino <span class="se">\;</span>
</pre></div>
<p>Y esta otra para eliminarlos (¡emplear con cuidado!):</p>
<div class="codehilite"><pre>find directorio_origen -type f -name *.EXT -exec rm -f <span class="o">{}</span> <span class="se">\;</span>
</pre></div>
<p>Partiendo de la idea de hacer algo equivalente en <strong>Python</strong>, he creado un
script que hace algo similar a esto, sin la necesidad de teclear todo el comando
(y con el riesgo a equivocarse y liarla parda) y que también sirve para que los
no trabajen habitualmente con la consola no necesiten recordar (o anotar) esos
comandos.</p>
<p>La parte principal del script <strong>move_by_ext.py</strong> es la siguiente:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">find_and_process</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">count</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">log</span><span class="o">=</span><span class="s">""</span><span class="p">):</span>
<span class="sd">"""Find the files by file extension and process (move/copy/remove) them."""</span>
<span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">the_path</span><span class="p">,</span> <span class="n">the_file</span><span class="p">):</span>
<span class="sd">"""Process each file."""</span>
<span class="n">processed</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">src_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">the_path</span><span class="p">,</span> <span class="n">the_file</span><span class="p">)</span>
<span class="n">dst_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">dst</span><span class="p">,</span> <span class="n">the_file</span><span class="p">)</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">rm</span><span class="p">:</span>
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">src_file</span><span class="p">)</span>
<span class="n">processed</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">dst_file</span><span class="p">):</span> <span class="c"># not replace if already exists </span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">cp</span><span class="p">:</span>
<span class="n">shutil</span><span class="o">.</span><span class="n">copy2</span><span class="p">(</span><span class="n">src_file</span><span class="p">,</span> <span class="n">dst_file</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">shutil</span><span class="o">.</span><span class="n">move</span><span class="p">(</span><span class="n">src_file</span><span class="p">,</span> <span class="n">dst_file</span><span class="p">)</span>
<span class="n">processed</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">return</span> <span class="n">processed</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">dst</span><span class="p">):</span>
<span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">dst</span><span class="p">)</span>
<span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">directories</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">src</span><span class="p">):</span>
<span class="k">for</span> <span class="n">fil</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
<span class="c"># ignore files without extension (can have the same name as the ext)</span>
<span class="n">file_ext</span> <span class="o">=</span> <span class="n">fil</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">'.'</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">fil</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">'.'</span><span class="p">))</span> <span class="o">></span> <span class="mi">1</span> <span class="k">else</span> <span class="bp">None</span>
<span class="c"># ignore dots in given extensions</span>
<span class="n">extensions</span> <span class="o">=</span> <span class="p">[</span><span class="n">ext</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'.'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span> <span class="k">for</span> <span class="n">ext</span> <span class="ow">in</span> <span class="n">args</span><span class="o">.</span><span class="n">ext</span><span class="p">]</span>
<span class="k">if</span> <span class="n">file_ext</span> <span class="ow">in</span> <span class="n">extensions</span><span class="p">:</span>
<span class="n">count</span> <span class="o">+=</span> <span class="n">process</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">fil</span><span class="p">)</span>
<span class="n">opt</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="s">"{0}{1}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">rm</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">cp</span><span class="p">)),</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">log</span> <span class="o">=</span> <span class="s">"Files {0}: {1}"</span><span class="o">.</span><span class="n">format</span><span class="p">({</span><span class="mi">0</span><span class="p">:</span><span class="s">"moved"</span><span class="p">,</span> <span class="mi">1</span><span class="p">:</span><span class="s">"copied"</span><span class="p">,</span> <span class="mi">2</span><span class="p">:</span><span class="s">"removed"</span><span class="p">}[</span><span class="n">opt</span><span class="p">],</span> <span class="n">count</span><span class="p">)</span>
<span class="k">return</span> <span class="n">log</span>
</pre></div>
<p>Este script nos permite realizar las mismas operaciones que los comandos
anteriores y además tiene dos ventajas adicionales:</p>
<ul>
<li>
<p><strong>Permite emplear varias extensiones a la vez</strong>, lo que nos permite
mover/copiar/borrar distintos tipos de archivo en una sola operación.
Por ejemplo si queremos mover todos los tipos de imagen de la estructura de
directorios anterior, podríamos hacerlo indicándole que emplee las extensiones
<code>.jpg</code>, <code>.gif</code> y <code>.png</code> en la misma operación. El comando sería el siguiente
(las extensiones pueden llevar o no el punto, funciona exactamente igual),
ejecutado desde el directorio raíz:</p>
<p><code>python move_by_ext.py jpg .gif png -d ./dir_5</code></p>
</li>
<li>
<p><strong>Funciona en Windows</strong>. A diferencia del comando para Linux, este se puede
emplear en Windows sin necesidad de instalar ningún entorno que nos simule un
shell Linux (como <a href="http://www.cygwin.com/">Cygwin</a>) y solo es necesario tener
instalado Python 2.7</p>
</li>
</ul>
<p>La sintaxis del script es muy sencilla y se puede ver reflejada en la ayuda del
mismo:</p>
<div class="codehilite"><pre><span class="gp">$</span> python move_by_ext.py -h
<span class="go">usage: move_by_ext.py ext [-s SRC] [-d DST] [-c | -r] [--help]</span>
<span class="go">Move (or copy/remove) all files selected by extension into a directory tree to</span>
<span class="go">a destination directory.</span>
<span class="go">positional arguments:</span>
<span class="go"> ext the extension(s) of the files to process. To use more</span>
<span class="go"> than one extension, separate them with a space</span>
<span class="go">optional arguments:</span>
<span class="go"> -h, --help show this help message and exit</span>
<span class="go"> -s SRC, --src SRC the source path. Current dir if none is provided</span>
<span class="go"> -d DST, --dst DST the destination path. Current dir if none is provided</span>
<span class="go"> -c, --copy copy all the files with the given extension(s) to the</span>
<span class="go"> destination directory.</span>
<span class="go"> -r, --remove remove all the files with the given extension(s). Use</span>
<span class="go"> with caution! remove also in the subdirectories</span>
<span class="go"> -v, --version show program's version number and exit</span>
</pre></div>
<p>El script completo puede encontrarse en el repositorio <em>Python Recipes</em> alojado
en <a href="http://github.com/joedicastro/python-recipes">github</a>.</p>Sincronizar una carpeta local y una remota a través de FTP: lftp-mirror2010-12-19T14:58:00+01:00joe di castrohttp://joedicastro.com/sincronizar-una-carpeta-local-y-una-remota-a-traves-de-ftp-lftp-mirror.html<p>A veces tenemos la necesidad de subir (o bajar) contenido a un servidor
y posteriormente tener actualizados los cambios que se produzcan en uno
(o ambos) de los lados. Es decir tener sincronizados el directorio
remoto y el local. Esto es relativamente fácil cuanto tenemos acceso via
<a href="http://es.wikipedia.org/wiki/L%C3%ADnea_de_comandos">consola</a> y <a href="http://es.wikipedia.org/wiki/Ssh">ssh</a> al servidor y podemos utilizar programas tan
potentes como <a href="http://es.wikipedia.org/wiki/Rsync">rsync</a>. ¿Pero que ocurre cuando el único método del
que disponemos para intercambiar ficheros con el servidor es a través
del protocolo <a href="http://es.wikipedia.org/wiki/Ftp">FTP</a>, como ocurre con muchos servidores web?</p>
<p>Bien, en ese caso, tenemos un pequeño problema. El protocolo <strong>FTP</strong>
aunque perfectamente valido para las funciones para las que fue
originalmente creado, la transferencia de archivos, no contempla este
caso. La solución manual y menos efectiva es volver a transferir todos
los archivos cada vez que se produce un cambio, solución nada
recomendable a nada que el tamaño de estos empiece a ser superior a
decenas de Megabytes. También podríamos ir comprobando manualmente que
ficheros han cambiado y transferir únicamente estos, algo también muy
poco recomendable si el número de archivos es elevado. Afortunadamente
algunos clientes gráficos de <strong>FTP</strong> nos permiten comprobar que ficheros
son distintos en uno y otro lado y luego transferir únicamente estos,
lo cual ya es un método bastante más efectivo y adecuado. Aunque si se
trata de directorios con muchos archivos y una estructura jerárquica
compleja (muchos directorios y subdirectorios) el proceso es bastante
lento pues ha de ir comprobando en un lado y en el otro las diferencias
entre los archivos (fecha, tamaño y atributos únicamente)
recorriendolos todos. ¿Pero que ocurre si queremos realizar esta
operación de forma periódica y automática? entonces esta solución
tampoco es valida, pues necesitaríamos un programa de línea de comandos
o un script para realizarlo.</p>
<p>Por suerte para nosotros, esta solución también está disponible a través
de varios programas y scripts para consola, entre los cuales el mejor
es <a href="http://lftp.yar.ru/"><strong>lftp</strong></a> de <strong>Alexander V. Lukyanov</strong>. Este fantástico programa
es una navaja suiza para todo aquello que necesitemos hacer a través
de <strong>FTP</strong>, siendo uno de los mejores clientes <strong>FTP</strong>, si no el
mejor, que existen. Y una de las innumerables posibilidades que ofrece
es precisamente la de <strong>sincronizar dos directorios con la opción
mirror</strong> (espejar). De esta manera podemos mantener perfectamente
sincronizados dos directorios de forma automática. <strong>Nos permite hacer
la sincronización en ambas direcciones, remoto → local y local →
remoto</strong>.</p>
<p>Como ya he mencionado es muy potente y repleto de opciones y permite muchas más
operaciones más allá de la sincronización entre directorios. Por este motivo
<strong>he creado un <a href="http://es.wikipedia.org/wiki/Script">script</a> en <a href="http://es.wikipedia.org/wiki/Python">Python</a> que empleando lftp, se centra
únicamente en la sincronización entre directorios a través de FTP y añade
algunas nuevas funcionalidades, <code>lftp-mirror</code>.</strong></p>
<h2 id="+que_ventajas_aporta_este_script">¿Que ventajas aporta este script?</h2>
<ul>
<li><strong>Proporciona un log detallado y legible</strong> que graba en un fichero en disco
y <strong>que puede ser enviado por correo electrónico</strong> a una o varias direcciones
empleando el servidor de correo local o uno externo.</li>
<li><strong>Permite crear una copia comprimida por día de la semana del directorio
local sincronizado</strong>. Esto nos permite tener el directorio actualizado y una
copia de seguridad por cada uno de los últimos 7 días, para poder revertir algún
cambio o borrado accidental.</li>
<li><strong>Se centra únicamente en la sincronización (mirror)</strong> entre directorios,
obviando las otras opciones que nos ofrece lftp</li>
<li><strong>Nos proporciona</strong> (en el log) <strong>el tamaño del espacio ocupado en el disco
duro por el directorio local y las copias de seguridad.</strong></li>
<li><strong>Permite tres modos de ejecución distintos,</strong> lo que lo convierte en muy
versátil:<ul>
<li><strong>Como tarea programada</strong>. En este modo los parámetros de la sincronización
se incluyen directamente dentro del script y solo es necesario programar su
ejecución para automatizar el proceso. Es ideal para la sincronización
periódica de un único directorio/servidor <strong>FTP</strong></li>
<li><strong>Interactivo.</strong> En este modo los parámetros se introducen directamente
como argumentos en la línea de comandos. Es ideal para ejecutar una
sincronización puntual manual</li>
<li><strong>Importando los parámetros desde un fichero de configuración.</strong> Este modo
es similar al primero, con la diferencia de que en este caso los parámetros
los tomamos de un fichero de configuración externo. Este fichero que podemos
crear nosotros mismos (se sirve uno de ejemplo) nos permite establecer
múltiples operaciones de sincronización que se ejecutaran de manera
secuencial una detrás de otra.</li>
</ul>
</li>
<li><strong>En sistemas operativos que lo soporten nos muestra notificaciones
emergentes</strong> a través de la librería libnotify de la ejecución del script y
su correcta finalización. Por ejemplo, a través de las notificaciones
emergentes de <a href="http://es.wikipedia.org/wiki/Ubuntu"><strong>Ubuntu</strong></a>. Muy útil para conocer cuando se está ejecutando
una tarea programada sin salida por consola.</li>
<li>Si empleamos los modos de ejecución no interactivos, <strong>emplea <a href="http://es.wikipedia.org/wiki/Base64">base64</a>
para una mínima protección de la contraseñas de acceso</strong> a los servidores
<strong>FTP</strong> y evitar almacenarlas las mismas en texto claro. No es una fuerte
medida de seguridad, pero es lo mínimo que deberíamos tener en cuenta.</li>
</ul>
<h2 id="+para_que_nos_puede_servir_este_script">¿Para que nos puede servir este script?</h2>
<p>Vamos a ver un ejemplo de lo más común, las <strong>copias de seguridad de una página
web</strong>. En muchos <a href="http://es.wikipedia.org/wiki/Hosting#Alojamiento_compartido_.28shared_hosting.29">hosting compartidos</a> la única posibilidad de transferir
archivos con el servidor es a través de una cuenta <strong>FTP</strong>. Empleando este
script, podemos crear un directorio en local donde haremos las copias de
seguridad de los ficheros de la web y luego sincronizarlo automáticamente todos
los días, descargando únicamente los ficheros que han cambiado. Con esto
tendremos no solo el directorio actualizado diariamente, si no que además
dispondremos de una copia de seguridad por cada uno de los siete días
anteriores para poder corregir cualquier problema ocurrido entre esas fechas.
Configurar algo así es realmente sencillo, únicamente tendríamos que cambiar
los valores incorporados dentro del script por los que necesitamos y luego
programar su ejecución diaria con cron.</p>
<p>Para una introducción más detallada, instrucciones de ejecución, control de
versiones y enlaces para la descarga, acudir al repositorio del script en
<a href="http://github.com/joedicastro/lftp-mirror">github</a>.</p>
<p>Un extracto del código de <strong>lftp-mirror.py</strong>:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">mirror</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">log</span><span class="p">):</span>
<span class="sd">"""Mirror the directories."""</span>
<span class="n">user</span> <span class="o">=</span> <span class="s">''</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">anonymous</span> <span class="k">else</span> <span class="s">' '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">login</span><span class="p">)</span>
<span class="n">local</span><span class="p">,</span> <span class="n">remote</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">normpath</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">local</span><span class="p">),</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">normpath</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">remote</span><span class="p">)</span>
<span class="n">port</span> <span class="o">=</span> <span class="s">'-p {0}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">port</span><span class="p">)</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">port</span> <span class="k">else</span> <span class="s">''</span>
<span class="n">include</span> <span class="o">=</span> <span class="s">' --include-glob {0}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">inc_glob</span><span class="p">)</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">inc_glob</span> <span class="k">else</span> <span class="s">''</span>
<span class="n">exclude</span> <span class="o">=</span> <span class="s">' --exclude-glob {0}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">exc_glob</span><span class="p">)</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">exc_glob</span> <span class="k">else</span> <span class="s">''</span>
<span class="n">url</span> <span class="o">=</span> <span class="s">'http://joedicastro.com'</span>
<span class="n">msg</span> <span class="o">=</span> <span class="s">'Connected to {1} as {2}{0}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="p">,</span> <span class="s">'anonymous'</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">anonymous</span>
<span class="k">else</span> <span class="n">args</span><span class="o">.</span><span class="n">login</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">msg</span> <span class="o">+=</span> <span class="s">'Mirror {0} to {1}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">local</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="k">else</span> <span class="n">remote</span><span class="p">,</span>
<span class="n">remote</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="k">else</span> <span class="n">local</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">msg</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">'Start time'</span><span class="p">)</span>
<span class="n">notify</span><span class="p">(</span><span class="s">'Mirroring with {0}...'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="p">),</span> <span class="s">'sync'</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">local</span><span class="p">):</span>
<span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">local</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'Created new directory'</span><span class="p">,</span> <span class="n">local</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">local</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">pardir</span><span class="p">))</span>
<span class="c"># create the script file to import with lftp</span>
<span class="n">scp_args</span> <span class="o">=</span> <span class="p">(</span><span class="s">'-vvv'</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">erase</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">newer</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">parallel</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span>
<span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">del_first</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">depth_first</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">no_empty_dir</span> <span class="o">+</span>
<span class="n">args</span><span class="o">.</span><span class="n">no_recursion</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">dry_run</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">use_cache</span> <span class="o">+</span>
<span class="n">args</span><span class="o">.</span><span class="n">del_source</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">missing</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">existing</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">loop</span> <span class="o">+</span>
<span class="n">args</span><span class="o">.</span><span class="n">size</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">time</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">no_perms</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">no_umask</span> <span class="o">+</span>
<span class="n">args</span><span class="o">.</span><span class="n">no_symlinks</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">suid</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">chown</span> <span class="o">+</span> <span class="n">args</span><span class="o">.</span><span class="n">dereference</span> <span class="o">+</span>
<span class="n">exclude</span> <span class="o">+</span> <span class="n">include</span><span class="p">)</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'ftpscript'</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">script</span><span class="p">:</span>
<span class="n">lines</span> <span class="o">=</span> <span class="p">(</span><span class="s">'open {0}ftp://{1} {2}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">secure</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="p">,</span> <span class="n">port</span><span class="p">),</span>
<span class="s">'user {0}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">user</span><span class="p">),</span>
<span class="s">'mirror {0} {1} {2}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">scp_args</span><span class="p">,</span>
<span class="n">local</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="k">else</span> <span class="n">remote</span><span class="p">,</span>
<span class="n">remote</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="k">else</span> <span class="n">local</span><span class="p">),</span>
<span class="s">'exit'</span><span class="p">)</span>
<span class="n">script</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">))</span>
<span class="c"># mirror</span>
<span class="n">cmd</span> <span class="o">=</span> <span class="p">[</span><span class="s">'lftp'</span><span class="p">,</span> <span class="s">'-d'</span><span class="p">,</span> <span class="s">'-f'</span><span class="p">,</span> <span class="n">script</span><span class="o">.</span><span class="n">name</span><span class="p">]</span>
<span class="n">sync</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="p">{</span><span class="bp">True</span><span class="p">:</span><span class="n">STDOUT</span><span class="p">,</span> <span class="bp">False</span><span class="p">:</span><span class="bp">None</span><span class="p">}[</span><span class="n">args</span><span class="o">.</span><span class="n">quiet</span><span class="p">])</span>
<span class="c"># end mirroring</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'lftp output'</span><span class="p">,</span> <span class="s">''</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sync</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">readlines</span><span class="p">()))</span>
<span class="c"># compress the dir and create a .gz file with date</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">args</span><span class="o">.</span><span class="n">reverse</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">args</span><span class="o">.</span><span class="n">no_compress</span><span class="p">:</span>
<span class="n">notify</span><span class="p">(</span><span class="s">'Compressing folder...'</span><span class="p">,</span> <span class="s">'info'</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'Rotate compressed copies'</span><span class="p">,</span> <span class="n">compress</span><span class="p">(</span><span class="n">local</span><span class="p">))</span>
<span class="c"># end compress</span>
<span class="n">gz_size</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">([</span><span class="n">get_size</span><span class="p">(</span><span class="n">gz</span><span class="p">)</span> <span class="k">for</span> <span class="n">gz</span> <span class="ow">in</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">'{0}*.gz'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">local</span><span class="p">))])</span>
<span class="n">log_size</span> <span class="o">=</span> <span class="n">get_size</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">filename</span><span class="p">)</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">filename</span><span class="p">)</span> <span class="k">else</span> <span class="mi">0</span>
<span class="n">local_size</span> <span class="o">=</span> <span class="n">get_size</span><span class="p">(</span><span class="n">local</span><span class="p">)</span>
<span class="n">size</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">local_size</span> <span class="o">+</span> <span class="n">gz_size</span> <span class="o">+</span> <span class="n">log_size</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">block</span><span class="p">(</span><span class="s">'Disk space used'</span><span class="p">,</span> <span class="s">'{0:>76.2f} {1}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">size</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">size</span><span class="p">[</span><span class="s">'u'</span><span class="p">]))</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">'End Time'</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">free</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">script</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</pre></div>
<p>Para obtener el código completo, ir al <a href="https://github.com/joedicastro/lftp-mirror/blob/master/src/lftp_mirror.py">fichero fuente</a>.</p>Combatir el spam en Drupal2010-10-14T01:42:00+02:00joe di castrohttp://joedicastro.com/combatir-el-spam-en-drupal.html<h3 id="articulo_publicado_originalmente_en_el_antiguo_sitio_deaparatoscom">Articulo publicado originalmente en el antiguo sitio deaparatos.com</h3>
<p style="text-align: center;"><img src="pictures/spam_stats.png"
alt="deaparatos spam statistiscs" title="estadísticas de spam en deaparatos"
height="550" width="593" /></p>
<p>En esta gráfica se puede observar la <strong>disminución a lo largo del tiempo de los
ataques de spam a este sitio</strong>, <strong>deaparatos.com</strong>, que funciona sobre
<strong>Drupal</strong>. Esto se ha conseguido gracias a una doble estrategia:</p>
<ul>
<li>emplear uno de los mejores módulos antispam existentes para Drupal, <strong>Mollom</strong></li>
<li>emplear un script en python de elaboración propia, <strong>ban_drupal_spammers.py</strong></li>
</ul>
<p>Esta doble estrategia no solo ha conseguido una más que <strong>notable reducción</strong>
de la incidencia <strong>del molesto spam</strong> en este sitio, <strong>de casi un 70%</strong>, si no
que además ha conseguido <strong>una más que notable reducción del ancho de banda
consumido por los spammers</strong>, como se puede observar en la siguiente tabla:</p>
<p>Estadísticas de Trafico generado por ataques de spam en deaparatos.com</p>
<table>
<thead>
<tr>
<th>Estrategia</th>
<th>Días</th>
<th>Ataques</th>
<th>Trafico (GB)</th>
<th>Media pagina (KB)</th>
<th>Trafico mes (MB)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mollom</td>
<td><strong>359</strong></td>
<td>48741</td>
<td>7,116</td>
<td>146,000</td>
<td>602,927</td>
</tr>
<tr>
<td>Mollom + script</td>
<td><strong>359</strong></td>
<td>358666</td>
<td>0,016</td>
<td>0,046</td>
<td>1,389</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>359</strong></td>
<td><strong>407407</strong></td>
<td><strong>7,133</strong></td>
<td><strong>17,507</strong></td>
<td><strong>604,316</strong></td>
</tr>
</tbody>
</table>
<p>Si solo hubiera empleado el modulo Mollom, sin emplear mi script</p>
<table>
<thead>
<tr>
<th>Trafico spam</th>
<th>Calculo</th>
<th>Trafico (GB)</th>
<th>Ahorro (GB)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Trafico total</strong></td>
<td>(358666 * (146,000 – 0,046) KB) + 7,133 GB =</td>
<td>59,481</td>
<td><strong>52,349</strong></td>
</tr>
<tr>
<td><strong>Trafico mensual</strong></td>
<td>((59,465 GB * 365) / 359) / 12) MB =</td>
<td>5,040</td>
<td><strong>4,435</strong></td>
</tr>
</tbody>
</table>
<p>Como podemos ver en las cifras mostradas de esta tabla, <strong>se ha conseguido en</strong>
un periodo de aproximadamente <strong>un año reducir el ancho de banda consumido por
los ataques de spam en más de 52 Gigabytes!</strong>, una autentica barbaridad de
tráfico que de otro modo se hubiera malgastado. Estamos hablando de <strong>un ahorro
de</strong> consumo de tráfico <strong>de casi 4,5 Gigabytes al mes!!!</strong> Un ahorro de ancho
de banda mensual que en un hosting compartido puede tranquilamente suponer el
cambio de un plan de hosting a otro, simplemente basta con que los molestos
spammers pongan tu sitio web en su punto de mira. Y <strong>ha de tenerse en cuenta</strong>
una cosa, <strong>que este trafico mensual hubiera sido muy superior si</strong> esta doble
estrategia <strong>no hubiera conseguido reducir el numero de spammers en un 69.25%</strong>,
no quiero ni pensar en las cifras que hubieran resultado...</p>
<p>Para que nos hagamos una idea del ahorro de ancho de banda que ha supuesto el
emplear mi script <strong>python</strong>, en el siguiente gráfico podemos ver la diferencia
entre emplear solo <strong>Mollom</strong> y emplear <strong>Mollom</strong> combinado con
<strong>ban_drupal_spammers.py</strong></p>
<p style="text-align: center;"><img src="pictures/Ahorros.png"
alt="Eficacia del script" title="Eficacia del script" height="292"
width="600" /></p>
<p>El gráfico es meridianamente claro, como podemos ver, <strong>por cada 1% de ataques
que son rechazados por ban_drupal_spammers.py</strong> y no por Mollom, <strong>ahorramos un
1% de ancho de banda</strong>, tanto en el peso por página como en el tráfico total.
Como podemos ver, <strong>hemos ahorrado un total de un 88% de ancho de banda del
trafico que sería generado por los ataques de spam en deaparatos.com</strong></p>
<p>Después de comprobar la eficacia de esta doble estrategia durante más de un año
(las estadísticas se interrumpen antes por el cambio de hosting) voy a
explicaros el porqué y el como he llegado a ella, a continuación. También se
puede ver el script que ha marcado la diferencia de tráfico.</p>
<h2 id="el_spam_en_internet">El spam en internet</h2>
<p>El <a href="http://es.wikipedia.org/wiki/Spam">spam</a> es una de las lacras más tediosas y difíciles de combatir en
Internet, por no mencionar las tareas delictivas que se apoyan en él. Después
de 15 años combatiendo el spam en el correo electrónico, el problema aún está
lejos de solucionarse, si bien es cierto que con una adecuada configuración de
las herramientas de correo, se ha convertido en una molestia trivial para el
usuario final. Pero para los servidores de correo y el tráfico de internet
sigue siendo un problema de dimensiones colosales, la lucha contra el mismo se
ha convertido una tarea titánica en la que se invierten ingentes sumas de dinero
todos los años. De hecho gran parte del tráfico de toda internet se
debe al spam (hay quien arroja cifras del 80%, e incluso superiores al 90%), lo
que ha acarreado un costosísimo sobredimensionamiento en el equipamiento de
proveedores de internet y servidores.</p>
<p>Como antes comentaba, lejos de una solución definitiva (en gran medida depende
de un tipo muy común de usuario final con escasa cultura informática), esta
lacra se expandió hace unos años a hilos en foros, a comentarios en blogs,
redes sociales, irc,... es decir, se ha expandido por toda la red. La explosión
de la llamada web 2.0 no ha incrementado si no este problema, multiplicándolo.
Y he aquí como un problema que afectaba a los usuarios de email y a los
proveedores de internet, se ha convertido también en un gran problema para los
webmasters. Todo aquel que gestione un sitio web, ha tenido que enfrentarse
antes o después con este maldito problema. Un problema que no solo se traduce en
cientos o miles de detestables mensajes de spam, que se han de combatir de uno
u de otro modo (algunos o bien se rinden o bien tienen abandonados sus sitios y
se convierten en auténticos cementerios de spam), si no que además se traduce en
un serio problema para el trafico de una web. El número de solicitudes que
producen los ataques de spam puede llegar a ser tan elevado, que congestione
totalmente ya no solo la página, si no el servidor cuando se trate de un hosting
compartido, convirtiéndose casi de facto en un <a href="http://es.wikipedia.org/wiki/DoS">ataque DoS</a> en toda regla.
Aún sin llegar a este indeseable extremo, el incremento del tráfico en el sitio
debido al spam puede llegar a suponer un porcentaje muy importante del ancho de
banda contratado (incluso más del 50% con contramedidas ineficientes), con los
consiguientes perjuicios económicos que suponen al webmaster. Los spammers
siempre han ido por delante de las contra-medidas, y la actual situación, con
extensas <a href="http://es.wikipedia.org/wiki/Botnet">botnets</a> a su disposición y con el <a href="http://es.wikipedia.org/wiki/Cloud_computing">cloud computing</a> (se ha
detectado el año pasado la primera
<a href="http://www.idg.es/pcworldtech/Los-hackers-controlan-una-botnet-desde-Amazon-EC2/doc88089-actualidad.htm">botnet que empleaba los servicios de Amazon EC2</a>) , nos ha llevado a un
combate continuo en las que tienen todas las de ganar a medio plazo... y observo
esto con cierta tristeza, por que entiendo que la solución final pasa
necesariamente por la educación del usuario final, haciéndole inmune a los -en
gran medida patéticos, infantiles, ridículos y chapuceros- reclamos del spam. Y
esto último desgraciadamente dista mucho de acercarse a una situación ideal.
También cabe mencionar que
<a href="http://googlewebmaster-es.blogspot.com/2009/12/comentarios-spam-la-dura-realidad.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+ElBlogParaWebmasters+%28El+Blog+para+Webmasters%29">el spam también perjudica al posicionamiento de una web,</a> a su prestigio, a
su funcionalidad, a su aspecto, etc.</p>
<h2 id="deaparatoscom_y_el_spam">deaparatos.com y el spam</h2>
<p>Y <strong>deaparatos</strong> no está exento de esta amenaza, de hecho se había convertido en
un serio problema en el 2009. Este sitio está gestionado con <a href="http://drupal.org/">Drupal</a>, y
después de probar con distintos módulos y métodos, unos más frustrantes que
otros, ninguno solucionaba por completo el problema, ni me satisfacía como
solución. Al final, combinando el módulo más idóneo para combatir esta plaga
(idóneo por resultados y por comportamiento) con un script de factura propia en
<a href="http://drupal.org/project/Modules">Python</a>, he logrado, no acabar con todo el spam (se me antoja tarea cuasi
imposible), pero si minimizar sus efectos a un nivel muchísimo más que aceptable.
Y minimizar los efectos tanto a la hora de impedir/eliminar los comentarios spam,
como de reducir el abultado tráfico que estos ataques consumían. ¿Por qué un
script en <strong>Python</strong>? bueno buscaba algo rápido, un prototipo para probar la
solución que tenia en la cabeza y porque estoy "enamorado" de este lenguaje de
programación. Quizás si veo que merece la pena, me plantee migrarlo a PHP y
convertirlo en un módulo de <strong>Drupal</strong>, o bien modifique el modulo oficial que
estoy empleando para mitigar el spam y le incorpore el código que empleo ahora.
Bueno, veamos como he llevado a cabo esta solución y porqué.</p>
<p>Generalmente los métodos para combatir el spam se centran en:</p>
<ul>
<li>medidas activas: análisis <a href="http://es.wikipedia.org/wiki/Heuristica">heurísticos</a>, <a href="http://es.wikipedia.org/wiki/Inferencia_estad%C3%ADstica">filtros estadísticos</a> (<a href="http://en.wikipedia.org/wiki/Bayesian_spam_filtering">bayesianos</a>), diferenciación de <a href="http://es.wikipedia.org/wiki/Bot">bots</a>/humanos (<a href="http://es.wikipedia.org/wiki/Captcha">captchas</a>), filtros por <a href="http://es.wikipedia.org/wiki/Host">host</a>/email, mediante <a href="http://es.wikipedia.org/wiki/Cookie">cookies</a>, <a href="http://es.wikipedia.org/wiki/Timestamp">timestamps</a>, filtros por <a href="http://es.wikipedia.org/wiki/User_agent">user-agent</a>, ...</li>
<li>medidas pasivas: <a href="http://es.wikipedia.org/wiki/Ofuscaci%C3%B3n">ofuscar</a> direcciones de correo, moderación de comentarios, <a href="http://es.wikipedia.org/wiki/Nofollow">enlaces nofollow</a>, permisos de publicación, políticas de contraseñas, campos ocultos mediante <a href="http://es.wikipedia.org/wiki/Css">css</a>/<a href="http://es.wikipedia.org/wiki/Javascript">javascript</a>, cerrar los comentarios de un post pasado un tiempo, ...</li>
</ul>
<p>De entrada tenía, y tengo, algo muy claro, no voy a emplear ninguno de estos
tres métodos habituales: <em>captchas</em>, <em>moderación de comentarios</em> y <em>requerir
registro</em> para enviar comentarios. Y es una decisión inamovible, no pienso
claudicar de ningún modo en este sentido. Son métodos que o bien me harían
perder un tiempo del que ni dispongo, ni estoy dispuesto a perder, o bien
suponen un incordio que me personalmente me incomodan mucho cuando me los
encuentro en otros sitios y por los que no quiero hacer pasar a mis lectores.
Esto evidentemente deja fuera algunas de las medidas más efectivas para combatir
el spam, pero son medidas en las que el usuario o el webmaster siempre pierden,
de un modo u otro, y no estoy dispuesto a permitir que los spammers condicionen
en ningún modo el compartimiento de este sitio. Aunque suene contradictorio con
lo que acabo de decir, el método que voy a comentar aquí, y que empleo
actualmente, emplea en alguna medida el uso de captchas, aunque de modo tan
limitado, que afecta a menos del 0,5% de los comentarios enviados. Digamos que
lo acepto como una razonable excepción a la regla. Si empleo en cierta medida
algunos de los otros métodos.</p>
<p style="text-align: center;"><img src="pictures/ammap.png"
alt="Eficacia del script" title="Eficacia del script" height="419"
width="629" /></p>
<p>En este mapa podemos ver el país de origen de los ataques de spam contra
deaparatos.com</p>
<h2 id="drupal_y_el_spam">Drupal y el spam</h2>
<p>Con que armas contamos en <strong>Drupal</strong> para combatir el spam? Por un lado tenemos
el clásico modulo <a href="http://drupal.org/project/spam">Spam</a>, que emplee en este mismo sitio durante más de dos
años, y que su mayor ventaja es contar con un <a href="http://es.wikipedia.org/wiki/Clasificador_bayesiano_ingenuo">filtro Bayesiano</a>. Este
módulo es usado actualmente en unos 4.893 sitios <sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup> con <strong>Drupal</strong>. Durante
mucho tiempo funcionó perfectamente, de vez en cuando se colaba algún comentario
spam, pero era cuestión de reportarselo al modulo y el iba aprendiendo, así como
podíamos crear nuestros propios filtros personalizados. El problema comenzó
cuando los que se colaban ya eran unos 20 spam diarios y aumentando, y entonces
ya no era viable, ni cómodo, perder tanto tiempo para que el filtro bayesiano
aprendiera a combatir unos ataques que eran cada vez más sofisticados. Así que
tocaba mirar otra alternativa de entre alguna de las disponibles:</p>
<ul>
<li><a href="http://drupal.org/project/captcha">Captcha</a>, uno de los más usados en <strong>Drupal</strong>
(<strong><em>80.286 sitios</em></strong>), y uno de los métodos más empleados en la red.
Es la clásica opción donde mediante una pregunta al usuario se intenta
diferenciar entre humano y maquina. Los captchas pueden ser de varios tipos,
desde cálculos matemáticos sencillos hasta gráficos donde se encuentran unos
caracteres ofuscados y que el usuario debe introducir. Hay varios módulos que
lo complementan, aportando distintos tipos de captchas, donde <a href="http://drupal.org/project/recaptcha">reCaptcha</a>
es uno de los más empleados (16.684). </li>
<li><a href="http://drupal.org/project/akismet">Akismet</a>, todo un clásico, basado en el servicio homónimo,
<a href="http://wordpress.org/">Akismet</a>, que creara en su día <a href="http://wordpress.org/">Wordpress</a> y que hoy es sostenido por
<a href="http://automattic.com/">Automattic</a>, empresa donde trabajan la mayoría de los desarrolladores
oficiales de <strong>Wordpress</strong>. Es uno de los métodos más difundidos en la red,
en parte por venir de la mano de la empresa más emblemática de los blogs.
Esta implementación del servicio <strong>akismet</strong> es ya un tanto antigua (ya no
tiene soporte) y hay un modulo más reciente que lo supera y aporta más
funcionalidades, <strong>Antispam</strong>, también en esta lista. Es usado actualmente por
solo <strong><em>947 sitios</em></strong>.</li>
<li><a href="http://drupal.org/project/spamicide">Spamicide</a>, se basa en la premisa de que la mayoría de los ataques spam
se producen con bots que acceden a la página con navegadores en modo texto
(<a href="http://es.wikipedia.org/wiki/Script_%28inform%C3%A1tica%29">scripts</a> en realidad), por lo tanto ni hacen uso de <strong>css</strong>, ni de
<strong>javascript</strong>. Aprovechando esta circunstancia, crean un campo de formulario
que es ocultado con css, con lo cual el usuario normal no lo ve, pero si el
bot, que si lo rellena con texto, queda descartado. Pero los spammers aprenden
muy rapido, así que la medida no es muy efectiva. Además últimamente empiezan
a aparecer personas dedicadas a introducir comentarios spam a mano (de ahí
vienen muchos de esos comentarios <a href="http://www.frikipedia.es/friki/HOYGAN">hoygan</a> absurdos que no parecen tener
mucho sentido) y que cobraran una miseria en países subdesarrollados, en parte
para saltarse los captcha. Por eso su efectividad es muy dudosa. Aunque si
puede ser usado combinado con otros módulos spam, para reforzar su eficacia.
Es muy poco usado, apenas <strong><em>377 sitios</em></strong> lo emplean. </li>
<li><a href="http://drupal.org/project/antispam">Antispam</a>, uno de los mejores módulos antispam para drupal. Con el se
puede usar algunos de los mejores servicios antispam externos que hay en la
red: <a href="http://akismet.com/">Akismet</a>, <a href="http://antispam.typepad.com/">Typepad</a> y <a href="http://defensio.com/">Defensio</a>. Con él podemos abrir una
cuenta en uno de estos servicios y configurar el módulo para emplearlo. Su
eficacia es muy elevada, ya que son algunas de los mejores armas disponibles
contra el spam. El funcionamiento básico es consultar la base de datos de
alguno de estos servicios, muy completas, para comprobar si el comentario u
el posteador son probable spam, y bloquearlo en caso de que la probabilidad
sea muy elevada. En caso de duda, aparecerá un captcha para descartar bots.
Tiene unas gráficas estadísticas muy útiles para comprobar la evolución del
problema en nuestro sitio. No es demasiado empleado, estando instalado en
unos <strong><em>1.718 sitios</em></strong>. </li>
<li><a href="http://drupal.org/project/badbehavior%20">Bad Behavior</a>, otro viejo conocido de las medidas antispam. Este se
basa en parte en un análisis heurístico de las peticiones HTTP del bot y
comparándolo con las bases de datos que poseen de spambots conocidos. Este
hace uso también de las base de datos del <strong>Proyecto Honey Pot</strong>, para
reforzar la identificación de spammers. Es bastante eficiente, pero el
problema está en que van por detrás siempre de los spammers y a veces se les
cuela algún que otro comentario spam. Básicamente porque se basa en que uno
reporte los spammers que aún no están en sus bases de datos, y hasta que
alguien reporta a un spammer, este puede habernos colado unos cuantos
mensajes. Es empleado en unos <strong><em>918 sitios</em></strong> drupal.</li>
<li><a href="http://drupal.org/project/httpbl">http:BL</a>, ese se basa enteramente en el <a href="http://www.projecthoneypot.org/">Proyecto Honey Pot</a>. Usa
sus bases de datos (<a href="http://es.wikipedia.org/wiki/Lista_negra">DNS blacklist</a>) para prevenir comentarios spam y
recolectores de direcciones email. Es eficiente en la misma medida que el
anterior, depende de su base de datos, que no es tan completa como las de los
servicios que soporta el modulo <strong>Antispam</strong>. Una de las virtudes de este
modulo es que bloquea las solicitudes de pagina de aquellas Ips que están en
su lista negra, con el consiguiente beneficio que esto reporta para el trafico
de nuestro sitio. Permite también el uso de <a href="http://en.wikipedia.org/wiki/Whitelist">whitelists</a> y <a href="http://en.wikipedia.org/wiki/Greylist">greylists</a>.
Podría ser uno de los mejores módulos antispam para Drupal si no se colaran
más comentarios spam de lo deseable. Actualmente solo<strong><em>443 sitios</em></strong> emplean
este modulo.</li>
<li><a href="http://drupal.org/project/phpids">PHPIDS</a>, esta emplea una aproximación al problema diferente. Emplea un
sistema de detección de intrusos desarrollado y mantenido por <a href="http://php-ids.org/">PHPIDS</a>.
Este no solo detecta ataques de spam, si no que también otro tipo de ataques
maliciosos al sitio, como <a href="http://es.wikipedia.org/wiki/XSS">XSS (cross site scripting)</a>,
<a href="http://es.wikipedia.org/wiki/Inyeccion_SQL">inyecciones sql</a>, DoS, etc. El problema es que arroja demasiados falsos
positivos y hemos de ir afinando la detección poco a poco, lo cual puede
llegar a ser bastante tedioso. Se puede usar conjuntamente con otros módulos
antispam, pero normalmente este bloqueará el ataque antes de que el otro se
percate. Lo malo, claro, es que hasta que no esté completamente afinado, a
los usuarios les puede dar mucho la lata ante comentarios completamente inocuos.
También puede llegar a generar unos logs muy extensos que pueden incrementar
bastante nuestra base de datos. Puede ser muy útil para aquellos sitios en
los que los ataques van más allá del simple spam. Tampoco es muy empleado,
solo <strong><em>361 sitios</em></strong> lo tienen implantado.</li>
<li><a href="http://drupal.org/project/mollom">Mollom</a>, uno de los últimos en llegar, pero lo ha hecho arrasando, en
dos años ha conseguido que ya sea empleado en <strong>23.983 sitios</strong> drupal. Esto
se debe en parte a que uno de los co-autores es el creador de <strong>Drupal</strong>,
<a href="http://buytaert.net/">Dries Buytaert</a>. <a href="http://mollom.com/">Mollom</a> es un servicio web en la misma linea que
<strong>Askimet</strong> o <strong>Defensio</strong>, con una base de datos de usuarios en la que aparte
de spammers, se registran reputaciones de usuario en función de parámetros
como comentarios ofensivos, comentarios de "baja-calidad" (hoygans),
comentarios off-topic, etc según como nosotros lo reportemos a <strong>Mollom</strong>. Es
decir que nos ayuda también a mejorar la calidad de nuestro sitio filtrando
también a usuarios con baja reputación en función de los parametros que
nosotros marquemos. Esto desde luego es un punto a favor del servicio, que
nos permite matar dos pajaros de un tiro. El servicio analiza el texto del
mensaje, y si es spam, lo bloquea y en caso de dudas mostrara un captcha como
el de la imagen (menos del 2% de las ocasiones). Además todo el código es
opensource, tanto el del modulo como el de la API de <strong>Mollom</strong> y hay
disponibles módulos para otros gestores de contenidos como <strong>Wordpress</strong>,
<a href="http://www.joomla.org/">Joomla</a> o <a href="http://radiantcms.org/">Radiant</a> y librerías para múltiples lenguajes (Java, PHP,
Ruby, Python, Perl, .Net, ...).</li>
</ul>
<p>Después de analizar las posibilidades y probar unos cuantos módulos
(<strong>Antispam</strong>, <strong>Bad Behavior</strong>, <strong>http:BL</strong>, <strong>PHPIDS</strong> y <strong>Mollom</strong>) llegué a
valorar que las dos mejores soluciones en mi caso eran <strong>Antispam</strong> Y
<strong>Mollom</strong>. Aunque <strong>PHPIDS</strong> y <strong>http:BL</strong> tenían algunas características
únicas que echaba de menos en ellos. Después de probar durante unas semanas
tanto <strong>Antispam</strong> como <strong>Mollom</strong>, observe que el indice de fallos de
<strong>Mollom</strong> era mucho menor y además era más transparente al usuario, mostrando
el captcha en menos ocasiones. Si, <strong>Captcha</strong> es la opción más socorrida por la
mayoría de los usuarios de <strong>Drupal</strong>, en cuanto que es la que menos molesta al
webmaster, claro, pero le traspasa la molestia al usuario. Yo odio directamente
los captcha, no los soporto, y he pasado de utilizar alguna web por ellos.
<strong>Mollom</strong> tiene la ventaja de reducir esta molestia a la minima expresión, por
lo que el 98% de los usuarios de la web ni siquiera se darán cuenta de que en
ella funciona un sistema antispam, que es lo que buscaba desde el principio, un
servicio efectivo y transparente.</p>
<p><strong>Mollom</strong> era pues, la opción elegida y la que está funcionando en este sitio
desde entonces.</p>
<p style="text-align: center;"><img src="pictures/captcha.png"
alt="Ejemplo de captcha de Mollom" title="Ejemplo de captcha de Mollom"
height="114" width="600" /></p>
<p>Ejemplo de captcha generado por Mollom</p>
<h2 id="la_soluci+n_definitiva_mollom__ban_drupal_spammerspy">La solución definitiva, Mollom + ban_drupal_spammers.py</h2>
<p><strong>Aunque Mollom funciona de manera muy efectiva, bloqueando aprox. el 99,98%
(en deaparatos.com) de los mensajes spam</strong>, esto no impide que los atacantes
sigan intentando una y otra vez colar su spam en el sitio. Esto nos lleva a que
las páginas se cargan una y otra vez, consumiendo ancho de banda, ya que
<strong>Mollom</strong> actúa a posteriori, cuando se envía el comentario, no antes de cargar
la página, lo que es el funcionamiento normal de estos sistemas antispam. Y
además los spammers tienen cierta inclinación a intentar introducir el spam en
las paginas más populares, las que suelen tener más comentarios y por lo tanto
de mayor peso por lo general. Basta con decir que el ancho de banda medio
generado por cada uno de estos ataques en este sitio ha sido de 146Kb.<br />
</p>
<p>Y es en este aspecto donde echaba de menos una de las características de
<strong>http:BL</strong>, bloquear el acceso al sitio a los que están en su lista negra.
Empecé entonces a darle vueltas a la manera de implementar esta característica
en mi sitio, pero pronto me di cuenta de dos cosas:</p>
<ul>
<li>No quería hacer una consulta a projecthoneypot.org cada vez que alguien
accediera al sitio, por evidentes mermas en el rendimiento del sitio.</li>
<li>No quería tampoco tener una lista negra local que se alimentara
periódicamente de projecthoneypot.org, porque no quería tener que comprobar
miles de ips que probablemente nunca accederían a mi sitio.</li>
</ul>
<p>La solución entonces pasaba por bloquear solo a los que ya hubieran ejecutado
un ataque de spam contra el sitio y que hubieran sido bloqueados al menos una
vez por <strong>Mollom</strong>, de modo que en los sucesivos ataques fueran rechazados antes
siquiera de cargar la página.</p>
<p>Hay una forma de hacer esto de forma manual en <strong>Drupal</strong>, simplemente hay que
añadir las ips de los spammers a través de las <em>reglas de acceso</em> en el menú de
<em>Administración</em>. Claro que el método es evidentemente tedioso y aparatoso,
comprobar las ips atacantes e ir añadiéndolas una por una a través del
formulario. Tenía que hacerse de una manera automatizada.</p>
<p>La primera idea y más evidente era modificar el modulo <strong>Mollom</strong> para lograr
esto, pero no me gusta PHP y procuro evitarlo, además quería un prototipo rápido
para evaluar la eficacia de la solución y su repercusión en el ancho de
banda, así que todo empezó con un sencillo script en <strong>python</strong>. Pronto me di
cuenta de que <strong>Mollom</strong> registraba las ips de todos los atacantes que bloqueaba
en el registro de eventos de <strong>Drupal</strong> (la tabla <em>watchdog</em> del modulo opcional
<em>Database logging</em>), y que alguna de ellas tenía hasta 30 entradas diferentes
en el registro. Y como <strong>Drupal</strong> incorpora el método que citaba antes para
banear IPs, lo único necesario era añadir estas IPs a la tabla <em>access</em>.</p>
<p style="text-align: center;"><img src="pictures/banned.png"
alt="Ejemplo de pagina de ip bloqueada por Drupal"
title="Ejemplo de pagina de ip bloqueada por Drupal" height="30"
width="233" /></p>
<p>Este es un ejemplo de la pàgina que se encontraria un atacante de spam bloqueado
a través de la tabla <em>access</em> en Drupal</p>
<p>Ahora bien, si añadimos automáticamente estas IPs, llegara un momento en que
tendremos varias miles de ellas, y el rendimiento de la página se vera afectado,
al tener que comprobar todas estas ips cada vez que alguien accede a la página.
Además hay que tener en cuenta que algunas de estas IPs tendrán como origen a un
usuario que teniendo el ordenador o router infectado por un
<a href="http://es.wikipedia.org/wiki/Rootkit">rootkit</a>/<a href="http://es.wikipedia.org/wiki/Troyano_%28inform%C3%A1tica%29">troyano</a>, pertenezca a una <a href="http://es.wikipedia.org/wiki/Botnet">Botnet</a> sin saberlo. Es
posible que estos usuarios acaben limpiando de <a href="http://es.wikipedia.org/wiki/Malware">malware</a> su equipo y en un
momento determinado quieran acceder legítimamente al sitio, por lo que no
deberían estar bloqueados de por vida. Esto lo solucioné en el script rotando
las IPs al llegar a umbral determinado, marcado por el número máximo de las IPs
que deseemos almacenar en esta tabla. Al llegar a este número máximo, se borra
un porcentaje de IPs, eligiendo siempre a las más antiguas. En estos momentos,
en función del rendimiento y tiempo que quiero que permanezcan en la tabla,
tengo este valor establecido en unas 2000 IPs. Para controlar la fecha en que
fueron introducidas cada una de las ips en la tabla, modifico la tabla access,
añadiéndole un campo <em>timestamp</em>.</p>
<p>Como se pudo ver al principio del articulo, la efectividad del script es muy
elevada y a día de hoy sigo con este método, con un script que ha evolucionado
varias veces desde entonces y que se adapta perfectamente a mis necesidades.
Los picos que se pueden ver en el primer gráfico del spam bloqueado por el
modulo <strong>Mollom</strong>, se deben precisamente a los breves periodos de tiempo en los
que por una u otra razón el script no estaba funcionando.</p>
<p>El porqué del fantástico ahorro de ancho de banda se puede explicar con la
anterior imagen, que es un ejemplo de la página que se encontraría un atacante
bloqueado por <strong>ban_drupal_spammers.py</strong>. <strong>Esta página tiene un peso ridículo
de entre 33 y 39 bytes, del orden de unas 4000 veces menos que el peso medio de
146 Kilobytes por página del trafico generado por los spammers</strong>.</p>
<p>Este script se puede ejecutar en remoto, para hostings compartidos que no pueden
correr scripts en <strong>python</strong> pero si permiten acceso remoto a la base de datos en
MySQL, como mi anterior <a href="http://www.hostsuar.com/">hosting</a> (quede muy satisfecho). Pero
también puede ser ejecutado de manera local, en hosting compartidos (que
soporten python), en <a href="http://es.wikipedia.org/wiki/Servidor_virtual_privado">VPS</a> y en servidores dedicados. No muchos hostings
compartidos permiten la ejecución de scripts en python, ni siquiera ssh o
acceso remoto a la BDD. Afortunadamente, mi hosting actual, <strong><a href="http://www.webfaction.com/?affiliate=joedicastro">Webfaction</a></strong>,
me permite todas esas posibilidades y no es ningún un problema. De hecho es el
mejor hosting compartido que haya probado nunca y uno de los mejores del mercado,
porque su manera de trabajar es única y es lo más parecido a un VPS, pero con
una facilidad para administrar las tareas más cotidianas apabullante. Eso si,
es distinto a todos los demás y necesita uno adaptarse a su manera de hacer las
cosas, pero luego ya no quieres saber nada de otros hosting compartidos. Si
además quieres trabajar con ruby o python, pocos puede competir con su
flexibilidad, lo que me hizo decidirme por él.</p>
<h2 id="el_script_ban_drupal_spammerspy">El script, ban_drupal_spammers.py</h2>
<p>El script (siempre la versión más actualizada), los ficheros auxiliares y las
instrucciones de como emplearlos, pueden ser encontrados en mi repositorio que
se encuentra alojado en <a href="http://github.com/joedicastro/ban-drupal-spammers">github</a>.Y donde tambien se puede encontrar el
script python que empleo para recoger los datos que se muestran en el mapa de
este árticulo.</p>
<p>El código de <strong>ban_drupal_spammers.py</strong> es el siguiente:</p>
<div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span>
<span class="c"># -*- coding: utf8 -*-</span>
<span class="sd">"""</span>
<span class="sd"> ban drupal spammers.py: ban spammers in Drupal with Mollom's aid</span>
<span class="sd">"""</span>
<span class="c">#===============================================================================</span>
<span class="c"># This Script uses the Mollom reports in Drupal for ban spammers' ips and</span>
<span class="c"># reduce the bandwith usage in the website.</span>
<span class="c">#===============================================================================</span>
<span class="c">#===============================================================================</span>
<span class="c"># Copyright 2010 joe di castro <joe@joedicastro.com></span>
<span class="c">#</span>
<span class="c"># This program is free software: you can redistribute it and/or modify</span>
<span class="c"># it under the terms of the GNU General Public License as published by</span>
<span class="c"># the Free Software Foundation, either version 3 of the License, or</span>
<span class="c"># (at your option) any later version.</span>
<span class="c">#</span>
<span class="c"># This program is distributed in the hope that it will be useful,</span>
<span class="c"># but WITHOUT ANY WARRANTY; without even the implied warranty of</span>
<span class="c"># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span>
<span class="c"># GNU General Public License for more details.</span>
<span class="c">#</span>
<span class="c"># You should have received a copy of the GNU General Public License</span>
<span class="c"># along with this program. If not, see <http://www.gnu.org/licenses/>.</span>
<span class="c">#</span>
<span class="c">#===============================================================================</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">"joe di castro - joe@joedicastro.com"</span>
<span class="n">__license__</span> <span class="o">=</span> <span class="s">"GNU General Public License version 3"</span>
<span class="n">__date__</span> <span class="o">=</span> <span class="s">"15/05/2010"</span>
<span class="n">__version__</span> <span class="o">=</span> <span class="s">"0.52"</span>
<span class="k">try</span><span class="p">:</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">base64</span>
<span class="kn">import</span> <span class="nn">collections</span>
<span class="kn">import</span> <span class="nn">MySQLdb</span>
<span class="kn">import</span> <span class="nn">pygeoip</span>
<span class="kn">import</span> <span class="nn">logger</span>
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
<span class="c"># Checks the installation of the necessary python modules</span>
<span class="k">print</span><span class="p">((</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">"An error found importing one module:"</span><span class="p">,</span>
<span class="nb">str</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">exc_info</span><span class="p">()[</span><span class="mi">1</span><span class="p">]),</span> <span class="s">"You need to install it"</span><span class="p">,</span> <span class="s">"Exit..."</span><span class="p">]))</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">connect_db</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">user</span><span class="p">,</span> <span class="n">pass_</span><span class="p">,</span> <span class="n">db</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">3306</span><span class="p">):</span>
<span class="sd">"""Connect to MySQL database."""</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">data_base</span> <span class="o">=</span> <span class="n">MySQLdb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="n">host</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="n">user</span><span class="p">,</span> <span class="n">passwd</span><span class="o">=</span><span class="n">pass_</span><span class="p">,</span> <span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span>
<span class="n">port</span><span class="o">=</span><span class="n">port</span><span class="p">,</span> <span class="n">client_flag</span><span class="o">=</span><span class="mi">65536</span><span class="p">)</span>
<span class="c"># flag 65536 is to allow multiple statements in a single string, equals</span>
<span class="c"># to CLIENT_MULTI_STATEMENTS</span>
<span class="k">except</span> <span class="n">MySQLdb</span><span class="o">.</span><span class="n">OperationalError</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Database connection fails, check that you gave the right "</span>
<span class="s">"credentials to access the database{0}Exit..."</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">))</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span>
<span class="k">return</span> <span class="n">data_base</span>
<span class="k">def</span> <span class="nf">select</span><span class="p">(</span><span class="n">curs</span><span class="p">,</span> <span class="n">sql</span><span class="p">):</span>
<span class="sd">"""Runs a SQL SELECT query and returns a tuple as output."""</span>
<span class="n">curs</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">sql</span><span class="p">)</span>
<span class="k">return</span> <span class="n">curs</span><span class="o">.</span><span class="n">fetchall</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">alter_table</span><span class="p">(</span><span class="n">curs</span><span class="p">,</span> <span class="n">db_table</span><span class="p">):</span>
<span class="sd">"""Create the aux field in the table if no exists, else do nothing."""</span>
<span class="n">database_string</span> <span class="o">=</span> <span class="s">"""</span>
<span class="s"> ALTER TABLE {0}</span>
<span class="s"> ADD timestamp INT(11) NOT NULL DEFAULT '0';</span>
<span class="s"> """</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">db_table</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">curs</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">database_string</span><span class="p">)</span>
<span class="k">return</span> <span class="s">"Aux Field 'timestamp' in table '{0}' created."</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">db_table</span><span class="p">)</span>
<span class="k">except</span> <span class="n">MySQLdb</span><span class="o">.</span><span class="n">OperationalError</span><span class="p">:</span>
<span class="k">print</span> <span class="p">(</span><span class="s">"Can't create the aux field, seems this exists previously."</span><span class="p">)</span>
<span class="c"># This output is not reported in the log, it will be repetitive.</span>
<span class="k">def</span> <span class="nf">ins_qstr</span><span class="p">(</span><span class="n">q_mask</span><span class="p">,</span> <span class="n">q_timestamp</span><span class="p">):</span>
<span class="sd">"""Create a SQL INSERT query string for the given ip."""</span>
<span class="n">iqstr</span> <span class="o">=</span> <span class="s">"""</span>
<span class="s"> INSERT INTO `access`</span>
<span class="s"> (mask, type, status, timestamp)</span>
<span class="s"> VALUES ('{0}', 'host', '0', {1});{2}</span>
<span class="s"> """</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">q_mask</span><span class="p">,</span> <span class="n">q_timestamp</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)</span>
<span class="k">return</span> <span class="n">iqstr</span>
<span class="k">def</span> <span class="nf">del_qstr</span><span class="p">(</span><span class="n">q_timestamp</span><span class="p">):</span>
<span class="sd">"""Create a DELETE query string for the given timestamp."""</span>
<span class="n">dqstr</span> <span class="o">=</span> <span class="s">"""</span>
<span class="s"> DELETE FROM access</span>
<span class="s"> WHERE timestamp='{0}';{1}</span>
<span class="s"> """</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">q_timestamp</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)</span>
<span class="k">return</span> <span class="n">dqstr</span>
<span class="k">def</span> <span class="nf">ip_and_country</span><span class="p">(</span><span class="n">l_ips</span><span class="p">,</span> <span class="n">geo</span><span class="p">):</span>
<span class="sd">"""Create the log lines about the ips and their countries."""</span>
<span class="n">output</span> <span class="o">=</span> <span class="bp">None</span>
<span class="k">if</span> <span class="n">l_ips</span><span class="p">:</span>
<span class="n">total</span> <span class="o">=</span> <span class="s">"{0} IPs"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">l_ips</span><span class="p">))</span>
<span class="n">ips_and_countries</span> <span class="o">=</span> <span class="p">[(</span><span class="n">geo</span><span class="o">.</span><span class="n">country_name_by_addr</span><span class="p">(</span><span class="n">l</span><span class="p">),</span> <span class="n">l</span><span class="p">)</span> <span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="n">l_ips</span><span class="p">]</span>
<span class="n">ips</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">'{0:16} {1}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">i</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">i</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span>
<span class="nb">sorted</span><span class="p">(</span><span class="n">ips_and_countries</span><span class="p">)])</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">total</span><span class="p">,</span> <span class="s">''</span><span class="p">,</span> <span class="n">ips</span><span class="p">])</span>
<span class="k">return</span> <span class="n">output</span>
<span class="k">def</span> <span class="nf">renew_geoip</span><span class="p">(</span><span class="n">gip_path</span><span class="p">):</span>
<span class="sd">"""Check if the geoip data file is too old."""</span>
<span class="n">out_str</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">gz_file</span> <span class="o">=</span> <span class="p">(</span><span class="s">"http://geolite.maxmind.com/download/geoip/database/"</span>
<span class="s">"GeoLiteCountry/GeoIP.dat.gz"</span><span class="p">)</span>
<span class="n">web_url</span> <span class="o">=</span> <span class="s">"http://www.maxmind.com/app/geolitecountry"</span>
<span class="n">geoip_file_date</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">getmtime</span><span class="p">(</span><span class="n">gip_path</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">geoip_file_date</span><span class="p">)</span> <span class="o">></span> <span class="mi">2592000</span><span class="p">:</span> <span class="c"># 2592000s = 30 days</span>
<span class="n">out_str</span> <span class="o">+=</span> <span class="p">(</span><span class="s">"Your GeoIP data file* is older than 30 days!{0}{0}"</span>
<span class="s">"You can look for a new version in:{0}{1}{0}or{0}{2}{0}{0}"</span>
<span class="s">" *{3}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">,</span> <span class="n">gz_file</span><span class="p">,</span> <span class="n">web_url</span><span class="p">,</span> <span class="n">gip_path</span><span class="p">))</span>
<span class="k">return</span> <span class="n">out_str</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="sd">"""main section"""</span>
<span class="c">#===============================================================================</span>
<span class="c"># SCRIPT PARAMATERS</span>
<span class="c">#===============================================================================</span>
<span class="c"># database host, name or ip ('localhost' by default)</span>
<span class="n">host</span> <span class="o">=</span> <span class="s">'localhost'</span>
<span class="c"># database user name ('root' by default)</span>
<span class="n">user</span> <span class="o">=</span> <span class="s">'root'</span>
<span class="c"># database password, with a minimum security measure, encoded by base64</span>
<span class="c"># ('password' by default)</span>
<span class="n">password</span> <span class="o">=</span> <span class="n">base64</span><span class="o">.</span><span class="n">b64decode</span><span class="p">(</span><span class="s">'cGFzc3dvcmQ='</span><span class="p">)</span>
<span class="c"># database name ('database' by default)</span>
<span class="n">database</span> <span class="o">=</span> <span class="s">'database'</span>
<span class="c"># path to geolocation data file GeoIP.dat</span>
<span class="n">geoip_path</span> <span class="o">=</span> <span class="s">'/your/path/to/file/GeoIP.dat'</span>
<span class="c"># mail server, smtp protocol, to send the log ('localhost' by default)</span>
<span class="n">smtp_server</span> <span class="o">=</span> <span class="s">'localhost'</span>
<span class="c"># sender's email address ('' by default)</span>
<span class="n">from_addr</span> <span class="o">=</span> <span class="s">''</span>
<span class="c"># a list of receiver(s)' email addresses ([''] by default)</span>
<span class="n">to_addrs</span> <span class="o">=</span> <span class="p">[</span><span class="s">''</span><span class="p">]</span>
<span class="c"># smtp server user ('' by default)</span>
<span class="n">smtp_user</span> <span class="o">=</span> <span class="s">''</span>
<span class="c"># smtp server password, with a minimum security measure, encoded by base64</span>
<span class="c"># ('password' by default)</span>
<span class="n">smtp_pass</span> <span class="o">=</span> <span class="n">base64</span><span class="o">.</span><span class="n">b64decode</span><span class="p">(</span><span class="s">'cGFzc3dvcmQ='</span><span class="p">)</span>
<span class="c"># set the perfomace threshold (number of banned ips) for you site</span>
<span class="n">threshold</span> <span class="o">=</span> <span class="mi">2000</span>
<span class="c">#===============================================================================</span>
<span class="c"># END PARAMETERS</span>
<span class="c">#===============================================================================</span>
<span class="c"># Initialize the log</span>
<span class="n">log</span> <span class="o">=</span> <span class="n">logger</span><span class="o">.</span><span class="n">Logger</span><span class="p">()</span>
<span class="c"># log the header</span>
<span class="n">url</span> <span class="o">=</span> <span class="s">'http://joedicastro.com'</span>
<span class="n">connected</span> <span class="o">=</span> <span class="s">'Connected to {0} in {1} as {2}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">database</span><span class="p">,</span> <span class="n">host</span><span class="p">,</span> <span class="n">user</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">connected</span><span class="p">)</span>
<span class="c"># log the start time</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">'Start Time'</span><span class="p">)</span>
<span class="c"># log the warning about old geolocation data file</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'The GeoIp.dat file is old'</span><span class="p">,</span> <span class="n">renew_geoip</span><span class="p">(</span><span class="n">geoip_path</span><span class="p">))</span>
<span class="c"># connect to database, create the cursors & initialize the geolocation info</span>
<span class="n">mysql_db</span> <span class="o">=</span> <span class="n">connect_db</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">user</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">database</span><span class="p">)</span>
<span class="n">cursor</span> <span class="o">=</span> <span class="n">mysql_db</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="n">dict_cursor</span> <span class="o">=</span> <span class="n">mysql_db</span><span class="o">.</span><span class="n">cursor</span><span class="p">(</span><span class="n">MySQLdb</span><span class="o">.</span><span class="n">cursors</span><span class="o">.</span><span class="n">DictCursor</span><span class="p">)</span>
<span class="n">gip</span> <span class="o">=</span> <span class="n">pygeoip</span><span class="o">.</span><span class="n">GeoIP</span><span class="p">(</span><span class="n">geoip_path</span><span class="p">)</span>
<span class="c"># optimize the database (instead a cron task in the server)</span>
<span class="n">all_tables</span> <span class="o">=</span> <span class="p">[</span><span class="n">tabl</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">tabl</span> <span class="ow">in</span> <span class="n">select</span><span class="p">(</span><span class="n">cursor</span><span class="p">,</span> <span class="s">"SHOW TABLES"</span><span class="p">)]</span>
<span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">'OPTIMIZE TABLE {0}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="s">', '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">all_tables</span><span class="p">)))</span>
<span class="c"># Adds the timestamp field to the 'access' table if no exists</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'New aux table field created'</span><span class="p">,</span> <span class="n">alter_table</span><span class="p">(</span><span class="n">cursor</span><span class="p">,</span> <span class="s">'access'</span><span class="p">))</span>
<span class="c"># Query the database and obtain the result. We collect the 'access' table</span>
<span class="c"># ips and ips from spammers reported by Mollom in 'watchdog' table</span>
<span class="c"># access = ({'timestamp':timestamp, 'mask': 'ip'}, ...)</span>
<span class="c"># mollom = ({'timestamp':timestamp, 'mask': 'ip'}, ...)</span>
<span class="n">access</span> <span class="o">=</span> <span class="n">select</span><span class="p">(</span><span class="n">dict_cursor</span><span class="p">,</span> <span class="s">"""SELECT mask, timestamp FROM access"""</span><span class="p">)</span>
<span class="n">mollom</span> <span class="o">=</span> <span class="n">select</span><span class="p">(</span><span class="n">dict_cursor</span><span class="p">,</span> <span class="s">"""SELECT hostname as mask, timestamp</span>
<span class="s"> FROM `watchdog`</span>
<span class="s"> WHERE `type` LIKE '%mollom%'</span>
<span class="s"> AND `message` LIKE '</span><span class="si">%s</span><span class="s">pam:%'"""</span><span class="p">)</span>
<span class="c"># From the 'access' ips, select the ips blocked by this script from Mollom,</span>
<span class="c"># discarding those introduced through the Drupal administration interface</span>
<span class="c"># from_access = {'ip':timestamp, ...}</span>
<span class="n">from_access</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="n">a_row</span> <span class="ow">in</span> <span class="n">access</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">a_row</span><span class="p">[</span><span class="s">'timestamp'</span><span class="p">]):</span>
<span class="n">from_access</span><span class="p">[</span><span class="n">a_row</span><span class="p">[</span><span class="s">'mask'</span><span class="p">]]</span> <span class="o">=</span> <span class="n">a_row</span><span class="p">[</span><span class="s">'timestamp'</span><span class="p">]</span>
<span class="c"># Here we select the ips that Mollom reported, if there are multiple</span>
<span class="c"># occurrences of the same ip, we always choose the most recent</span>
<span class="c"># from_mollom = {'ip':timestamp, ...}</span>
<span class="n">from_mollom</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="n">m_row</span> <span class="ow">in</span> <span class="n">mollom</span><span class="p">:</span>
<span class="k">if</span> <span class="n">m_row</span><span class="p">[</span><span class="s">'mask'</span><span class="p">]</span> <span class="ow">in</span> <span class="n">from_mollom</span><span class="o">.</span><span class="n">keys</span><span class="p">():</span>
<span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">from_mollom</span><span class="p">[</span><span class="n">m_row</span><span class="p">[</span><span class="s">'mask'</span><span class="p">]])</span> <span class="o"><</span> <span class="nb">int</span><span class="p">(</span><span class="n">m_row</span><span class="p">[</span><span class="s">'timestamp'</span><span class="p">]):</span>
<span class="n">from_mollom</span><span class="p">[</span><span class="n">m_row</span><span class="p">[</span><span class="s">'mask'</span><span class="p">]]</span> <span class="o">=</span> <span class="n">m_row</span><span class="p">[</span><span class="s">'timestamp'</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">from_mollom</span><span class="p">[</span><span class="n">m_row</span><span class="p">[</span><span class="s">'mask'</span><span class="p">]]</span> <span class="o">=</span> <span class="n">m_row</span><span class="p">[</span><span class="s">'timestamp'</span><span class="p">]</span>
<span class="c"># Now, from these ips, select the IPs of spammers that were not already</span>
<span class="c"># banned and generate queries to insert into the 'access' table. It's</span>
<span class="c"># necessary to check if some of ips reported through Mollom didn't be</span>
<span class="c"># already banned, because of how the Drupal's event log works. The optional</span>
<span class="c"># core module "Database logging" (which must be enabled to run his script)</span>
<span class="c"># is deleting records by the tail (into the 'watchdog' table) on each cron</span>
<span class="c"># run, according to a maximum limit set in the admin menu. This limit may be</span>
<span class="c"># 100, 1000, 10000, 100000, 1000000 records, as determined in the "Loggin</span>
<span class="c"># and alerts -> Database logging" menu. Then depending on the record limit</span>
<span class="c"># set in the 'watchdog' table, the frequency with which you run the cron job</span>
<span class="c"># and how often you run this script, it's very likely that in the previous</span>
<span class="c"># query we have returned a number of ips that have not yet eliminated from</span>
<span class="c"># the log ('watchdog'), but we have already added to the table of bannedd</span>
<span class="c"># ips ('access'). This will avoid duplicate ips on table 'access'</span>
<span class="c"># ins_ips = ['ip0', 'ip1', ...]</span>
<span class="n">ins_ips</span> <span class="o">=</span> <span class="p">[</span><span class="n">f_ip</span> <span class="k">for</span> <span class="n">f_ip</span> <span class="ow">in</span> <span class="n">from_mollom</span><span class="o">.</span><span class="n">keys</span><span class="p">()</span> <span class="k">if</span> <span class="n">f_ip</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">from_access</span><span class="p">]</span>
<span class="n">query_str</span> <span class="o">=</span> <span class="s">''</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">ins_qstr</span><span class="p">(</span><span class="n">i_ip</span><span class="p">,</span> <span class="n">from_mollom</span><span class="p">[</span><span class="n">i_ip</span><span class="p">])</span> <span class="k">for</span> <span class="n">i_ip</span> <span class="ow">in</span> <span class="n">ins_ips</span><span class="p">)</span>
<span class="c"># number of banned ips through this script</span>
<span class="n">banned_ips</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">from_access</span><span class="p">)</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">ins_ips</span><span class="p">)</span>
<span class="c"># number of banned ips through Drupal administration interface</span>
<span class="n">drupal_banned_ips</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">access</span><span class="p">)</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">from_access</span><span class="p">)</span>
<span class="c"># After a certain number of records in the table 'access', the website's</span>
<span class="c"># perfomance deteriorates and from an even larger number, the behavior of</span>
<span class="c"># Drupal just become erratic. In the case of the site on which to run this</span>
<span class="c"># script, we see a clear loss of performance from the 3000 records and</span>
<span class="c"># becomes erratic over 5000. To avoid this unpleasant side effect, and</span>
<span class="c"># that cure don't be worse than the disease, I set a performance threshold</span>
<span class="c"># in 2000 records, from which records were removed from the table. If the</span>
<span class="c"># number of rows is greater than the performance threshold, we proceed to</span>
<span class="c"># calculate the ips to remove, selecting the oldest. The number of ips to</span>
<span class="c"># delete will be at least the 30% of "from_access". Just delete records</span>
<span class="c"># inserted through this script, never the inserted via Drupal admin</span>
<span class="c"># interface</span>
<span class="n">trigger</span> <span class="o">=</span> <span class="nb">bool</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">access</span><span class="p">)</span> <span class="o">></span> <span class="n">threshold</span><span class="p">)</span> <span class="c"># perfomance threshold</span>
<span class="n">del_ips</span><span class="p">,</span> <span class="n">latest</span> <span class="o">=</span> <span class="p">[],</span> <span class="mi">0</span> <span class="c"># ips to delete (if trigger) & latest ip's date</span>
<span class="k">if</span> <span class="n">trigger</span><span class="p">:</span>
<span class="c"># Now we'll group the ips by date. Use the object collections.defauldict</span>
<span class="c"># to group the ips in a dictionary of lists (values) of ips by date</span>
<span class="c"># (keys)</span>
<span class="c"># ips_by_time = {timestamp:['ip0', ..], ...}</span>
<span class="n">ips_by_time</span> <span class="o">=</span> <span class="n">collections</span><span class="o">.</span><span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</span>
<span class="k">for</span> <span class="n">fa_ip</span> <span class="ow">in</span> <span class="n">from_access</span><span class="p">:</span>
<span class="n">ips_by_time</span><span class="p">[</span><span class="n">from_access</span><span class="p">[</span><span class="n">fa_ip</span><span class="p">]]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">fa_ip</span><span class="p">)</span>
<span class="c"># We selected the oldest ips to have a number of them greater than or</span>
<span class="c"># equal to 30% of blocked by this script</span>
<span class="k">for</span> <span class="n">ips_date</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">ips_by_time</span><span class="o">.</span><span class="n">keys</span><span class="p">()):</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">del_ips</span><span class="p">)</span> <span class="o"><</span> <span class="p">((</span><span class="nb">len</span><span class="p">(</span><span class="n">from_access</span><span class="p">)</span> <span class="o">*</span> <span class="mi">30</span><span class="p">)</span> <span class="o">/</span> <span class="mi">100</span><span class="p">):</span>
<span class="n">query_str</span> <span class="o">+=</span> <span class="n">del_qstr</span><span class="p">(</span><span class="n">ips_date</span><span class="p">)</span> <span class="c"># delete by date, less queries</span>
<span class="k">for</span> <span class="n">d_ip</span> <span class="ow">in</span> <span class="n">ips_by_time</span><span class="p">[</span><span class="n">ips_date</span><span class="p">]:</span>
<span class="n">del_ips</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">d_ip</span><span class="p">)</span>
<span class="n">banned_ips</span> <span class="o">-=</span> <span class="mi">1</span>
<span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">ips_date</span><span class="p">)</span> <span class="o">></span> <span class="n">latest</span><span class="p">:</span>
<span class="n">latest</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ips_date</span><span class="p">)</span>
<span class="n">latest</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">'%A </span><span class="si">%x</span><span class="s">'</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">localtime</span><span class="p">(</span><span class="n">latest</span><span class="p">))</span>
<span class="c"># log spammers' ips deleted from the table</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">"Spammers' Ips deleted"</span><span class="p">,</span> <span class="n">ip_and_country</span><span class="p">(</span><span class="n">del_ips</span><span class="p">,</span> <span class="n">gip</span><span class="p">))</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">"Newest date of deleted IPs"</span><span class="p">,</span> <span class="s">"Date: {0}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">latest</span><span class="p">))</span>
<span class="c"># runs the database query</span>
<span class="k">if</span> <span class="n">query_str</span><span class="p">:</span>
<span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">query_str</span><span class="p">)</span>
<span class="c"># close database cursors</span>
<span class="n">cursor</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="n">dict_cursor</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="c"># log spammers' ips inserted into the table</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">"Spammers' IPs inserted"</span><span class="p">,</span> <span class="n">ip_and_country</span><span class="p">(</span><span class="n">ins_ips</span><span class="p">,</span> <span class="n">gip</span><span class="p">))</span>
<span class="c"># log total banned ips by origin</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'Banned IPs'</span><span class="p">,</span> <span class="p">[</span><span class="s">'Mollom: </span><span class="si">%d</span><span class="s"> IPs'</span> <span class="o">%</span> <span class="n">banned_ips</span><span class="p">,</span>
<span class="s">'Drupal: </span><span class="si">%d</span><span class="s"> IPs'</span> <span class="o">%</span> <span class="n">drupal_banned_ips</span><span class="p">])</span>
<span class="c"># log the end time</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">'End Time'</span><span class="p">)</span>
<span class="c"># send the log by email</span>
<span class="n">log</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">'Ban Drupal Spammers. Ins: {0} Del: {1}'</span><span class="o">.</span>
<span class="n">format</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">ins_ips</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">del_ips</span><span class="p">)),</span> <span class="n">send_from</span><span class="o">=</span><span class="n">from_addr</span><span class="p">,</span>
<span class="n">dest_to</span><span class="o">=</span><span class="n">to_addrs</span><span class="p">,</span> <span class="n">mail_server</span><span class="o">=</span><span class="n">smtp_server</span><span class="p">,</span> <span class="n">server_user</span><span class="o">=</span><span class="n">smtp_user</span><span class="p">,</span>
<span class="n">server_pass</span><span class="o">=</span><span class="n">smtp_pass</span><span class="p">)</span>
<span class="c"># write the log to a file</span>
<span class="n">log</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<br />
<hr />
<h2 id="comentarios_realizados_anteriormente_en_drupal">Comentarios realizados anteriormente en Drupal</h2>
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://inseguridad.org/"><img src="pictures/avtr_jbone.png" height=28
width=28 alt="avatar" title="avatar de bjone"/></a></div>
<h3 id="muy_interesante">Muy interesante</h3>
<p>por <a href="http://inseguridad.org/">bjone</a> el Jue, 14/10/2010 - 11:40</p>
<p>muy interesante... voy a probar el mollom... gracias por la información.</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<img src="pictures/avtr_anonimo.png" height=28 width=28
alt="avatar" title="avatar de anónimo"/></div>
<h3 id="preguntita">Preguntita</h3>
<p>por Anónimo el Mié, 27/10/2010 - 14:00</p>
<p>Estoy armando mi sitio, que poseerá foro y tendré lo que entendí tu llamas
hosting compartido. Es decir, me alquilaran espacio de hosting. La pregunta es:
¿las precauciones contra el spam en mi sitio deberán correr exclusivamente por
mi cuenta o parte de la pelea la lleva el administrador de hospedaje?</p>
<p>Gracias.</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<img src="pictures/avtr_anonimo.png" height=28 width=28
alt="avatar" title="avatar de anónimo"/></div>
<h3 id="gracias_termin+_de_leer_el">Gracias, terminé de leer el</h3>
<p>por Anónimo el Mié, 27/10/2010 - 14:03</p>
<p>Gracias, terminé de leer el artículo y me respondí solo :)</p>
<p>Si, parece que deberé hacerme cargo activamente :(.</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="si_efectivamente_as+_es">Si, efectivamente así es,</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Mié, 27/10/2010 - 20:04</p>
<p>Si, efectivamente así es, pero además es así también en los servidores
administrados y en los servidores dedicados.</p>
<p>A lo sumo se dedican a administrar el hard, el sistema operativo y el sistema
base para la web (Apache, Mysql/PostgreSQL, NGINX, PHP, ...). Pero en cuanto a
la aplicación web en si misma y todo lo que a ella atañe, es la parte que te
toca. Luego dependiendo de según donde acabes teniendo el hosting, en una
situación determinada -de un ataque a la web por ejemplo- pueden desde echarte
una mano de buena fe hasta exigirte que lo arregles o te cierran la cuenta (si
no te la cierran directamente). Depende de con quien des y de la circunstancia
que se dé, en la mayoría de hostings compartidos no se paran mucho a dar soporte
a este tipo de situaciones, y si a penalizar a los que no gestionan
correctamente sus sitios.</p>
<p>De todos modos, si eliges un buen sistema de foros y aplicas un buen sistema
antispam, no deberías tener excesivos problemas y acabaras aprendiendo mucho por
el camino. La mayoría de los problemas vienen por la desidia y la poca
preocupación de los administradores de webs por estos temas.</p>
<p>Saludos y suerte con el foro.</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://sigt.net/"><img src="pictures/avtr_armonth.png" height=28
width=28 alt="avatar" title="avatar de armonth"/></a></div>
<h3 id="sobre_el_spamicide">Sobre el Spamicide</h3>
<p>por <a href="http://sigt.net/archivo/sistema-antispam-del-campo-oculto-para-wordpress.xhtml">Armonth</a> el Sáb, 30/10/2010 - 19:59 </p>
<p>Buenas, yo ese sistema lo conocía por el de "campo oculto" y lo comenté hace ya
más de 3 años en SigT (te he enlazado mi nombre al artículo) con la implementación.</p>
<p>Un detalle que cabe mencionar es que es mucho más efectivo poner como campo "te
echo atrás por spammer" el campo correspondiente a "nombre" originalmente en la
implementación de WordPress (author) y poner un nuevo author que no poner un
campo nuevo a ver si lo rellenan.</p>
<p>La mayoría de scripts para spamear no están hechos para rellenar todos los
campos, están hechos para rellenar el nombre (author), email, url y comment...
ignorando otros campos...</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="si_desde_luego_es_bastante">Si, desde luego es bastante</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Sáb, 30/10/2010 - 22:05</p>
<p>Si, desde luego es bastante más lógico hacerlo de esa manera, engañando
doblemente a los spammers. De todos modos el modulo Spamicide te deja renombrar
el campo como quieras y se puede hacer pasar por uno de esos campos sin
problemas, por lo que se puede hacer lo que comentas.</p>
<p>Ya conocía el articulo que enlazas, hace mucho tiempo que te sigo :), aunque los
dos estamos muy inactivos últimamente.</p>
<p>Saludos</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://sigt.net/"><img src="pictures/avtr_armonth.png" height=28
width=28 alt="avatar" title="avatar de armonth"/></a></div>
<h3 id="bueno">Bueno</h3>
<p>por <a href="http://sigt.net/">Armonth</a> el Sáb, 30/10/2010 - 22:57 </p>
<p>Bueno, yo estoy "inactivo" de sigt que no de otro proyecto aún no revelado y que
dejé los MMO ;P</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>Las estadísticas de uso emplean los datos de drupal.org a 12 de Octubre
de 2010 <a href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>Optimizar imagénes para la web2010-09-07T00:00:00+02:00joe di castrohttp://joedicastro.com/optimizar-imagenes-para-la-web.html<p><strong>El peso</strong> (tamaño) <strong>de las imágenes de un sitio web es determinante para</strong>
dos aspectos fundamentales: <strong>la velocidad de carga de las páginas y el consumo
de ancho de banda.</strong></p>
<p>La velocidad de carga de las páginas es un factor cada día más importante, tanto
para el posicionamiento de la web, como para la experiencia del usuario, que
cada vez es más impaciente. El consumo de ancho de banda también es un
factor muy importante, en cuanto a que suele estar directamente relacionado con
el coste económico del alojamiento del sitio.</p>
<p>Y aunque generalmente los programas de retoque fotográfico más comunes
incorporan ya opciones especiales para guardar imágenes para la web, no todos
exprimen al máximo el potencial de eliminar información superflua de una
imagen (bueno, el EXIF puede no serlo tanto, dependiendo del sitio) sin que se
vea afectada la calidad de la misma.</p>
<p><strong>Yahoo</strong>, en sus consejos para optimizar la velocidad de carga de un sitio web,
<strong>"Yahoo Best Practices for Speeding Up Your Web Site"</strong>, recomienda una serie
de pequeñas aplicaciones en su <a href="http://developer.yahoo.com/performance/rules.html#opt_images">apartado para optimizar las imágenes.</a></p>
<p>Son una serie de ordenes de línea comandos que han de aplicarse a las imágenes,
una por una, algo tedioso y poco optimo si nuestro sitio web tiene cientos o
miles de imágenes.</p>
<p><strong>Por eso he creado un script en Python,</strong> <em>img4web.py</em>, <strong>que automatiza todo
el proceso y procesa todas las imágenes en formatos .png y .jpg que se
encuentren en una carpeta.</strong> </p>
<p>Es muy sencillo de utilizar y al final del proceso tendremos una nueva carpeta
"<strong>processed</strong>" con todas las imágenes y un resumen del proceso como este:</p>
<div class="codehilite"><pre>================================================================================
Summary
================================================================================
Original Processed Save
.jpgs: ( 31) 2.12 MiB ( 31) 1.82 MiB 301.28 KiB
.pngs: ( 10)489.46 KiB ( 10)368.93 KiB 120.53 KiB
--------------------------------------------------------------------------------
Total: ( 41) 2.60 MiB ( 41) 2.19 MiB 421.81 KiB
</pre></div>
<p>El script, las revisiones del código y las instrucciones de como descargarlo y
usarlo podéis encontrarlas en el repositorio que está alojado en <a href="http://github.com/joedicastro/img4web">github</a>.</p>
<p>Funciona tanto en Linux como en Windows, no lo he probado en un Mac.</p>
<p>El código del script, es el siguiente:</p>
<div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span>
<span class="c"># -*- coding: <utf8> -*-</span>
<span class="sd">"""</span>
<span class="sd"> img4web.py: optimize .jpg and .png images for the web</span>
<span class="sd">"""</span>
<span class="c">#===============================================================================</span>
<span class="c"># This Script optimizes .jpg and .png images for the web.</span>
<span class="c">#</span>
<span class="c"># This follows the "Yahoo Best Practices for Speeding Up Your Web Site" about</span>
<span class="c"># optimize images. </span>
<span class="c"># http://developer.yahoo.com/performance/rules.html#opt_images</span>
<span class="c"># </span>
<span class="c"># Uses the program pngcrush and the command jpegtran of the libjpeg library</span>
<span class="c"># </span>
<span class="c"># pngcrush, http://pmt.sourceforge.net/pngcrush/</span>
<span class="c"># libjpg, http://www.ijg.org/</span>
<span class="c">#</span>
<span class="c"># In Linux they are usually available in the most popular distribution </span>
<span class="c"># repositories, e.g.: </span>
<span class="c"># In debian, Ubuntu as these packages:</span>
<span class="c"># pngcrush</span>
<span class="c"># libjpeg-progs</span>
<span class="c">#</span>
<span class="c"># In Windows pngcrush can be downloaded at </span>
<span class="c"># http://sourceforge.net/projects/pmt/files/pngcrush-executables/</span>
<span class="c"># and libjpeg can be downloaded (as gnuwin32) at </span>
<span class="c"># http://gnuwin32.sourceforge.net/downlinks/jpeg.php</span>
<span class="c">#</span>
<span class="c"># How it runs?</span>
<span class="c">#</span>
<span class="c"># Get a list of .jpg and .png images in the working directory (where script </span>
<span class="c"># runs) and process all of them one by one. Store the processed images in a new</span>
<span class="c"># subdirectory named 'processed' (I know, I didn't killed myself worrying about </span>
<span class="c"># the name)</span>
<span class="c">#===============================================================================</span>
<span class="c">#===============================================================================</span>
<span class="c"># Copyright 2009 joe di castro <joe@joedicastro.com></span>
<span class="c"># </span>
<span class="c"># This program is free software: you can redistribute it and/or modify</span>
<span class="c"># it under the terms of the GNU General Public License as published by</span>
<span class="c"># the Free Software Foundation, either version 3 of the License, or</span>
<span class="c"># (at your option) any later version.</span>
<span class="c">#</span>
<span class="c"># This program is distributed in the hope that it will be useful,</span>
<span class="c"># but WITHOUT ANY WARRANTY; without even the implied warranty of</span>
<span class="c"># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span>
<span class="c"># GNU General Public License for more details.</span>
<span class="c">#</span>
<span class="c"># You should have received a copy of the GNU General Public License</span>
<span class="c"># along with this program. If not, see <http://www.gnu.org/licenses/>.</span>
<span class="c">#</span>
<span class="c">#===============================================================================</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">"joe di castro - joe@joedicastro.com"</span>
<span class="n">__license__</span> <span class="o">=</span> <span class="s">"GNU General Public License version 2"</span>
<span class="n">__date__</span> <span class="o">=</span> <span class="s">"30/12/2010"</span>
<span class="n">__version__</span> <span class="o">=</span> <span class="s">"0.4"</span>
<span class="k">try</span><span class="p">:</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">glob</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">platform</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">Popen</span><span class="p">,</span> <span class="n">PIPE</span><span class="p">,</span> <span class="n">call</span>
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
<span class="c"># Checks the installation of the necessary python modules </span>
<span class="k">print</span><span class="p">((</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">"An error found importing one module:"</span><span class="p">,</span>
<span class="nb">str</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">exc_info</span><span class="p">()[</span><span class="mi">1</span><span class="p">]),</span> <span class="s">"You need to install it"</span><span class="p">,</span> <span class="s">"Stopping..."</span><span class="p">]))</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">best_unit_size</span><span class="p">(</span><span class="n">bytes_size</span><span class="p">):</span>
<span class="sd">"""Get a size in bytes & convert it to the best IEC prefix for readability.</span>
<span class="sd"> Return a dictionary with three pair of keys/values:</span>
<span class="sd"> 's' -- (float) Size of path converted to the best unit for easy read</span>
<span class="sd"> 'u' -- (str) The prefix (IEC) for s (from bytes(2^0) to YiB(2^80))</span>
<span class="sd"> """</span>
<span class="k">for</span> <span class="n">exp</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">90</span> <span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
<span class="n">bu_size</span> <span class="o">=</span> <span class="nb">abs</span><span class="p">(</span><span class="n">bytes_size</span><span class="p">)</span> <span class="o">/</span> <span class="nb">pow</span><span class="p">(</span><span class="mf">2.0</span><span class="p">,</span> <span class="n">exp</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">bu_size</span><span class="p">)</span> <span class="o"><</span> <span class="mi">2</span> <span class="o">**</span> <span class="mi">10</span><span class="p">:</span>
<span class="n">unit</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">:</span><span class="s">'bytes'</span><span class="p">,</span> <span class="mi">10</span><span class="p">:</span><span class="s">'KiB'</span><span class="p">,</span> <span class="mi">20</span><span class="p">:</span><span class="s">'MiB'</span><span class="p">,</span> <span class="mi">30</span><span class="p">:</span><span class="s">'GiB'</span><span class="p">,</span> <span class="mi">40</span><span class="p">:</span><span class="s">'TiB'</span><span class="p">,</span> <span class="mi">50</span><span class="p">:</span><span class="s">'PiB'</span><span class="p">,</span>
<span class="mi">60</span><span class="p">:</span><span class="s">'EiB'</span><span class="p">,</span> <span class="mi">70</span><span class="p">:</span><span class="s">'ZiB'</span><span class="p">,</span> <span class="mi">80</span><span class="p">:</span><span class="s">'YiB'</span><span class="p">}[</span><span class="n">exp</span><span class="p">]</span>
<span class="k">break</span>
<span class="k">return</span> <span class="p">{</span><span class="s">'s'</span><span class="p">:</span><span class="n">bu_size</span><span class="p">,</span> <span class="s">'u'</span><span class="p">:</span><span class="n">unit</span><span class="p">}</span>
<span class="k">def</span> <span class="nf">get_size</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="sd">"""Get size of a directory tree or a file in bytes."""</span>
<span class="n">path_size</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">directories</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">the_path</span><span class="p">):</span>
<span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
<span class="n">path_size</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">lstat</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">))</span><span class="o">.</span><span class="n">st_size</span>
<span class="k">for</span> <span class="n">directory</span> <span class="ow">in</span> <span class="n">directories</span><span class="p">:</span>
<span class="n">path_size</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">lstat</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">directory</span><span class="p">))</span><span class="o">.</span><span class="n">st_size</span>
<span class="n">path_size</span> <span class="o">+=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">getsize</span><span class="p">(</span><span class="n">the_path</span><span class="p">)</span>
<span class="k">return</span> <span class="n">path_size</span>
<span class="k">def</span> <span class="nf">check_execs_posix_win</span><span class="p">(</span><span class="o">*</span><span class="n">progs</span><span class="p">):</span>
<span class="sd">"""Check if the programs are installed.</span>
<span class="sd"> Returns two values:</span>
<span class="sd"> (dict) windows_paths - a dictionary of executables/paths (keys/values)</span>
<span class="sd"> (boolean) is_windows - True it's a Windows OS, False it's a *nix OS</span>
<span class="sd"> """</span>
<span class="k">def</span> <span class="nf">not_found</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
<span class="sd">""" If executable is not installed, exit and report."""</span>
<span class="n">msg</span> <span class="o">=</span> <span class="s">'The {0} program is necessary to run this script'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
<span class="n">windows_paths</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">is_windows</span> <span class="o">=</span> <span class="bp">True</span> <span class="k">if</span> <span class="n">platform</span><span class="o">.</span><span class="n">system</span><span class="p">()</span> <span class="o">==</span> <span class="s">'Windows'</span> <span class="k">else</span> <span class="bp">False</span>
<span class="c"># get all the drive unit letters if the OS is Windows</span>
<span class="n">windows_drives</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s">r'(\w:)</span><span class="se">\\</span><span class="s">'</span><span class="p">,</span>
<span class="n">Popen</span><span class="p">(</span><span class="s">'fsutil fsinfo drives'</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span><span class="o">.</span>
<span class="n">communicate</span><span class="p">()[</span><span class="mi">0</span><span class="p">])</span> <span class="k">if</span> <span class="n">is_windows</span> <span class="k">else</span> <span class="bp">None</span>
<span class="k">for</span> <span class="n">prog</span> <span class="ow">in</span> <span class="n">progs</span><span class="p">:</span>
<span class="k">if</span> <span class="n">is_windows</span><span class="p">:</span>
<span class="c"># Set all commands to search the executable in all drives</span>
<span class="n">win_cmds</span> <span class="o">=</span> <span class="p">[</span><span class="s">'dir /B /S {0}\*{1}.exe'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">letter</span><span class="p">,</span> <span class="n">prog</span><span class="p">)</span> <span class="k">for</span>
<span class="n">letter</span> <span class="ow">in</span> <span class="n">windows_drives</span><span class="p">]</span>
<span class="c"># Get the first location (usually in C:) of the all founded where </span>
<span class="c"># the executable exists</span>
<span class="n">exe_paths</span> <span class="o">=</span> <span class="p">(</span><span class="s">''</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">Popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span>
<span class="n">shell</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span><span class="o">.</span><span class="n">communicate</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span>
<span class="n">cmd</span> <span class="ow">in</span> <span class="n">win_cmds</span><span class="p">]))</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="c"># Assign the path to the executable or report not found if empty</span>
<span class="n">windows_paths</span><span class="p">[</span><span class="n">prog</span><span class="p">]</span> <span class="o">=</span> <span class="n">exe_paths</span> <span class="k">if</span> <span class="n">exe_paths</span> <span class="k">else</span> <span class="n">not_found</span><span class="p">(</span><span class="n">prog</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">Popen</span><span class="p">([</span><span class="n">prog</span><span class="p">,</span> <span class="s">'--help'</span><span class="p">],</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">OSError</span><span class="p">:</span>
<span class="n">not_found</span><span class="p">(</span><span class="n">prog</span><span class="p">)</span>
<span class="k">return</span> <span class="n">windows_paths</span><span class="p">,</span> <span class="n">is_windows</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">execs</span><span class="p">,</span> <span class="n">windows</span><span class="p">):</span>
<span class="sd">"""Main section."""</span>
<span class="c"># Check if exists the subdirectory for store the results, else create it</span>
<span class="n">dest_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">(),</span> <span class="s">'processed'</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">dest_path</span><span class="p">):</span>
<span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">dest_path</span><span class="p">)</span>
<span class="c"># Get the list of all .png an .jpg images in the current folder by type</span>
<span class="n">jpg</span><span class="p">,</span> <span class="n">png</span> <span class="o">=</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">'*.jp[e|g]*'</span><span class="p">),</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">'*.png'</span><span class="p">)</span>
<span class="c"># Get the original size of the images in bytes by type</span>
<span class="n">org_jpg_sz</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">((</span><span class="n">get_size</span><span class="p">(</span><span class="n">orig_jpg</span><span class="p">)</span> <span class="k">for</span> <span class="n">orig_jpg</span> <span class="ow">in</span> <span class="n">jpg</span><span class="p">))</span>
<span class="n">org_png_sz</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">((</span><span class="n">get_size</span><span class="p">(</span><span class="n">orig_png</span><span class="p">)</span> <span class="k">for</span> <span class="n">orig_png</span> <span class="ow">in</span> <span class="n">png</span><span class="p">))</span>
<span class="c"># Get the executable's names (and path for windows) of the needed programs </span>
<span class="n">jpegtran</span> <span class="o">=</span> <span class="n">execs</span><span class="p">[</span><span class="s">'jpegtran'</span><span class="p">]</span> <span class="k">if</span> <span class="n">windows</span> <span class="k">else</span> <span class="s">'jpegtran'</span>
<span class="n">pngcrush</span> <span class="o">=</span> <span class="n">execs</span><span class="p">[</span><span class="s">'pngcrush'</span><span class="p">]</span> <span class="k">if</span> <span class="n">windows</span> <span class="k">else</span> <span class="s">'pngcrush'</span>
<span class="c"># Process all .jpg images</span>
<span class="k">for</span> <span class="n">jpg_img</span> <span class="ow">in</span> <span class="n">jpg</span><span class="p">:</span>
<span class="n">call</span><span class="p">([</span><span class="n">jpegtran</span><span class="p">,</span> <span class="s">'-copy'</span><span class="p">,</span> <span class="s">'none'</span><span class="p">,</span> <span class="s">'-optimize'</span><span class="p">,</span> <span class="s">'-perfect'</span><span class="p">,</span> <span class="s">'-outfile'</span><span class="p">,</span>
<span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dest_path</span><span class="p">,</span> <span class="n">jpg_img</span><span class="p">),</span> <span class="n">jpg_img</span><span class="p">])</span>
<span class="c"># Process all .png images</span>
<span class="k">for</span> <span class="n">png_img</span> <span class="ow">in</span> <span class="n">png</span><span class="p">:</span>
<span class="n">call</span><span class="p">([</span><span class="n">pngcrush</span><span class="p">,</span> <span class="s">'-rem'</span><span class="p">,</span> <span class="s">'alla'</span><span class="p">,</span> <span class="s">'-reduce'</span><span class="p">,</span> <span class="s">'-brute'</span><span class="p">,</span>
<span class="n">png_img</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dest_path</span><span class="p">,</span> <span class="n">png_img</span><span class="p">)])</span>
<span class="c"># Get the size of the processed images in bytes by type</span>
<span class="n">os</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="n">dest_path</span><span class="p">)</span>
<span class="n">prc_jpg</span> <span class="o">=</span> <span class="p">[</span><span class="n">j</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">'*.jp[e|g]*'</span><span class="p">)</span> <span class="k">if</span> <span class="n">j</span> <span class="ow">in</span> <span class="n">jpg</span><span class="p">]</span>
<span class="n">prc_png</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">'*.png'</span><span class="p">)</span> <span class="k">if</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">png</span><span class="p">]</span>
<span class="n">prc_jpg_sz</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">((</span><span class="n">get_size</span><span class="p">(</span><span class="n">new_j</span><span class="p">)</span> <span class="k">for</span> <span class="n">new_j</span> <span class="ow">in</span> <span class="n">prc_jpg</span><span class="p">))</span>
<span class="n">prc_png_sz</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">((</span><span class="n">get_size</span><span class="p">(</span><span class="n">new_p</span><span class="p">)</span> <span class="k">for</span> <span class="n">new_p</span> <span class="ow">in</span> <span class="n">prc_png</span><span class="p">))</span>
<span class="c"># Get a human readable size</span>
<span class="n">ojs</span><span class="p">,</span> <span class="n">ops</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">org_jpg_sz</span><span class="p">),</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">org_png_sz</span><span class="p">)</span>
<span class="n">pjs</span><span class="p">,</span> <span class="n">pps</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">prc_jpg_sz</span><span class="p">),</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">prc_png_sz</span><span class="p">)</span>
<span class="n">tot_org</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">org_jpg_sz</span> <span class="o">+</span> <span class="n">org_png_sz</span><span class="p">)</span>
<span class="n">tot_prc</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">prc_jpg_sz</span> <span class="o">+</span> <span class="n">prc_png_sz</span><span class="p">)</span>
<span class="n">sjs</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">org_jpg_sz</span> <span class="o">-</span> <span class="n">prc_jpg_sz</span><span class="p">)</span>
<span class="n">sps</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">org_png_sz</span> <span class="o">-</span> <span class="n">prc_png_sz</span><span class="p">)</span>
<span class="n">tts</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">((</span><span class="n">org_jpg_sz</span> <span class="o">+</span> <span class="n">org_png_sz</span><span class="p">)</span> <span class="o">-</span> <span class="p">(</span><span class="n">prc_jpg_sz</span> <span class="o">+</span> <span class="n">prc_png_sz</span><span class="p">))</span>
<span class="c"># print a little report </span>
<span class="k">print</span><span class="p">(</span><span class="s">'{0}{1}{0}{2:^80}{0}{1}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">,</span> <span class="s">'='</span> <span class="o">*</span> <span class="mi">80</span><span class="p">,</span> <span class="s">'Summary'</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s">' Original Processed Save'</span> <span class="o">+</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">'.jpgs: ({6:3}){0:>6.2f} {1:8}({7:3}){2:>6.2f} {3:8}{4:>6.2f} {5}'</span><span class="o">.</span>
<span class="n">format</span><span class="p">(</span><span class="n">ojs</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">ojs</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span> <span class="n">pjs</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">pjs</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span> <span class="n">sjs</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">sjs</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span>
<span class="nb">len</span><span class="p">(</span><span class="n">jpg</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">prc_jpg</span><span class="p">)))</span>
<span class="k">print</span><span class="p">(</span><span class="s">'.pngs: ({6:3}){0:>6.2f} {1:8}({7:3}){2:>6.2f} {3:8}{4:>6.2f} {5}'</span><span class="o">.</span>
<span class="n">format</span><span class="p">(</span><span class="n">ops</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">ops</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span> <span class="n">pps</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">pps</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span> <span class="n">sps</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">sps</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span>
<span class="nb">len</span><span class="p">(</span><span class="n">png</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">prc_png</span><span class="p">)))</span>
<span class="k">print</span><span class="p">(</span><span class="s">'-'</span> <span class="o">*</span> <span class="mi">80</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">'Total: ({6:3}){0:>6.2f} {1:8}({7:3}){2:>6.2f} {3:8}{4:>6.2f} {5}'</span><span class="o">.</span>
<span class="n">format</span><span class="p">(</span><span class="n">tot_org</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">tot_org</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span> <span class="n">tot_prc</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">tot_prc</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span>
<span class="n">tts</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">tts</span><span class="p">[</span><span class="s">'u'</span><span class="p">],</span>
<span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">jpg</span><span class="p">)</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">png</span><span class="p">)),</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">prc_jpg</span><span class="p">)</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">prc_png</span><span class="p">))))</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">WIN_EXECS</span><span class="p">,</span> <span class="n">WIN_OS</span> <span class="o">=</span> <span class="n">check_execs_posix_win</span><span class="p">(</span><span class="s">'jpegtran'</span><span class="p">,</span> <span class="s">'pngcrush'</span><span class="p">)</span>
<span class="n">main</span><span class="p">(</span><span class="n">WIN_EXECS</span><span class="p">,</span> <span class="n">WIN_OS</span><span class="p">)</span>
</pre></div>
<p>Para una versión actualizada del código ir a <a href="https://github.com/joedicastro/img4web/blob/master/src/img4web.py">el fichero fuente.</a></p>TED Talks, descargar videos y subtitulos de las charlas2010-07-29T22:01:00+02:00joe di castrohttp://joedicastro.com/ted-talks-descargar-videos-y-subtitulos-de-las-charlas.html<p><img alt="TED logo" src="http://joedicastro.com/pictures/ted_logo.png" title="TED logo" /></p>
<p>Como introducción, para aquellos que aun no conozcan <a href="http://www.ted.com/">TED</a> <strong>(Technology,
Enternaiment, Design)</strong> y sus famosas charlas, <strong>TED Talks</strong>, podría resumirlo
como algo así como la versión en vídeo de las revistas de "divulgación
científica y tecnológica" (acogiendo bajo esta denominación desde las más
banales a las mas prestigiosas) que todos conocemos como <a href="http://www.muyinteresante.es/">Muy Interesante</a>,
<a href="http://www.investigacionyciencia.es/">Investigación y Ciencia</a>, <a href="http://www.mundo-geo.es/">Geo</a>, <a href="http://www.quo.es/">Quo</a>, <a href="http://www.nationalgeographic.com.es/">National Geographic</a>,
<a href="http://www.popsci.com/">Popular Science</a>, <a href="http://www.nature.com/">Nature</a>, <a href="http://www.sciencemag.org/">Science</a>, <a href="http://www.thelancet.com/">Lancet</a>, etc... con
la ventaja de la escasa distorsión del mensaje y lo conciso del mismo. Esto se
debe a que no tenemos que pasar por el filtro del periodista (cuando no es un
<a href="http://es.wikipedia.org/wiki/Art%C3%ADculo_cient%C3%ADfico">paper</a>), <strong>es el propio interesado</strong> quien nos <strong>relata sus ideas
personalmente</strong> y que la gran mayoría de las charlas esta sometida a un
<strong>tiempo limite de 18 minutos</strong>, ya que es el tiempo máximo que se ha calculado
que podemos estar centrados en un solo asunto sin perder el interés o la
atención. Evidentemente no son comparables a un articulo amplio, detallado y
bien redactado o a un buen documental, ni tampoco lo pretenden.
No se trata de profundizar en los temas, <strong>se trata de que</strong> el ponente
<strong>consiga transmitirte sus ideas, su entusiasmo y la trascendencia de las
mismas</strong>, y en la mayoría de los casos <strong>lo consiguen</strong>, y algunas de estas
charlas son de una calidad y de una trascendencia tales, que deberían tener
lugar también en los medios tradicionales (TV). La calidad de estas charlas es
tal que es difícil no quedarte impresionado (cuando no deslumbrado) al final de
las mismas y contagiado del entusiasmo de los ponentes, la apertura de miras,
conocimiento y humildad que te proporcionan (amen de ayudarte a creer un poquito
más en la humanidad) no tiene parangón actualmente. Desde la serie <a href="http://es.wikipedia.org/wiki/Cosmos:_un_viaje_personal">Cosmos</a>
de <a href="http://es.wikipedia.org/wiki/Carl_Sagan">Carl Sagan</a>, no he encontrado nada parecido que me invitara tanto a
pensar y me abriera tanto la mente. Lo único que se acerca, entre la telebasura
reinante, en el mundo audiovisual actual, es el programa
<a href="http://www.rtve.es/television/redes/">Redes</a> de <a href="http://es.wikipedia.org/wiki/Eduardo_Punset">Eduardo Punset</a>.</p>
<p>Para una definición más precisa, un extracto del <a href="http://es.wikipedia.org/wiki/TED">articulo de la Wikipedia</a>:</p>
<blockquote>
<p><strong>TED</strong> (<strong>Tecnología, Entretenimiento, Diseño</strong>, del inglés: <em>Technology,
Entertainment, Design</em>) es una organización sin ánimo de lucro dedicada a las
"ideas que vale la pena difundir" (del inglés: <em>Ideas worth spreading</em>). TED es
ampliamente conocida por su conferencia anual (<strong>TED Conference</strong>) y sus charlas
(<strong>TED Talks</strong>) que cubren un amplio espectro de temas que incluyen <strong>ciencias,
arte y diseño, política, educación, cultura, negocios, asuntos globales,
tecnología y desarrollo, y entretenimiento</strong>. Los conferenciantes han incluido
a personas como el ex-Presidente de los Estados Unidos <strong>Bill Clinton</strong>, los
laureados con el Premio<strong> Nobel James D. Watson</strong>, <strong>Murray Gell-Mann</strong>, y Al
Gore, el co-fundador de Microsoft, <strong>Bill Gates</strong>, los co-fundadores de Google
<strong>Sergey Brin</strong> y <strong>Larry Page</strong>, y <strong>Billy Graham</strong></p>
</blockquote>
<p>Estas charlas se publican en su pagina web actualmente a un ritmo aproximado de
una por día de semana (esto es, lunes a viernes). <strong>Actualmente (julio 2010)
hay más de 700 charlas disponibles en su página y se han visionado más de
290.000.000 de veces (julio 2010)</strong>, para que os hagáis una idea de la
repercusión y trascendencia de las mismas.</p>
<p>El caso es que yo las sigo habitualmente, vamos que veo todas las que publican.
Al principio me limitaba a enterarme de la publicación de las mismas por mi
lector de fuentes RSS, veía el asunto de las mismas y si me parecía interesante,
las veía directamente a través de su página web. Luego, cansado de los
inconvenientes de verlas a través de un navegador web a pantalla completa, en
flash desde Linux, dí un paso más y me pase a un programa como
<strong><a href="http://www.getmiro.com/">Miro</a></strong>, de hecho hay una versión del mismo para <strong>TED</strong>. Para el que no
lo conozca, <strong>Miro</strong> es un reproductor multimedia open source multiplataforma
que cuenta con un agregador de fuentes RSS y torrents para podcasts de audio y
vídeo. Así que él automáticamente se baja las charlas a medida que se van
publicando y tu solo tienes que visionarlas desde el mismo, decidiendo después
si las eliminas o las conservas.</p>
<p style="text-align: center;"><img src="/pictures/miro_TED.jpg"
alt="Miro TED Talks" title="Miro TED Talks" height="327" width="540"/></p>
<p>Pero <strong>Miro</strong> para mi tenia dos grandes problemas, el primero que no descarga
los subtitulos, y el segundo que la visualización de estos es bastante pobre
(al menos comparado con la configuración que tengo en mi <a href="http://smplayer.sourceforge.net/"><strong>SMPlayer</strong></a>).
No es que los subtitulos me sean esenciales (todas las charlas son en ingles)
pero si que ayudan mucho para el contexto de aquellas donde uno ni de lejos
domina los tecnicismos de algunos campos, o porque no decirlo, cuando el
acento o la pronunciación de algunos de los ponentes me hace imposible
seguirlos sin los mismos. Siempre veo los subtitulos en el idioma original,
inglés, pero siempre descargo también la versión en español porque me es muy
útil en algunos momentos, como por ejemplo cuando se refieren a especies
animales o vegetales de los que no conozco la traducción al español, y en lugar
de tirar de diccionario continuamente, gano tiempo visionandolos en
castellano.</p>
<p>Como uno ha de reconocer humildemente, rara es la vez que a uno no se le presente
un problema que no se le haya presentado antes a otro y no haya dado con una
solución valida. Luego en vez de reinventar la rueda (aunque finalmente es lo
que haya acabado haciendo) me dirigí a San Google para encontrar una solución
para descargar los subtitulos de las <strong>TED Talks</strong>, y he aquí, que doy con
<a href="http://fci-h.blogspot.com/2010/05/python-script-to-download-ted-talks.html">este script</a> en <a href="http://python.org/"><strong>Python</strong></a> (mi lenguaje de programación favorito).
Lo empleé durante unos días (tres para ser exactos), pero no me acaba de
convencer y me puse a modificarlo, y cuando estaba en ello, me dije: Que
demonios! si al final no te convence <strong>Miro</strong> para visionar los vídeos con
subtitulos, porque no descargar también los mismos y utilizar el <strong>SMPlayer</strong>
para visionarlos? Así que me puse manos a la obra, y esto es lo que salio y
actualmente empleo.</p>
<p><strong>El script hace uso de la fuente RSS de las TED Talks en calidad HD para
conocer cuando se publica una nueva charla en la pagina</strong>. Comprueba si no esta
ya descargado, si no es así, lo descarga y luego comprueba la disponibilidad de
los subtitulos (español e ingles) para las charlas ya descargadas que se
encuentren en la carpeta. Si encuentra alguno disponible y no descargado
anteriormente, lo convierte en un nuevo fichero .srt en la carpeta. Finalmente,
si se ha descargado algo (charlas o subs), manda un correo a mi usuario local
notificándomelo. Esta script lo tengo automatizado mediante una tarea cron en
un pequeño servidor que tengo y que se ejecuta todas las mañanas a eso de las
9:30 y que normalmente me descarga la charla del día anterior y los subtitulos
en ingles de la misma y los de español de dos días antes. Esto depende mucho de
la velocidad de traducción de los colaboradores, ya que a veces, cuando una
charla es muy interesante, es posible que a las pocas horas dispongamos de los
subtitulos en los dos idiomas.</p>
<p>La parte fundamental del código del <a href="https://github.com/joedicastro/ted-talks-download/blob/master/src/TEDTalks.py" title="TEDTalks.py">script</a>, es la siguiente:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">get_sub</span><span class="p">(</span><span class="n">tt_id</span> <span class="p">,</span> <span class="n">tt_intro</span><span class="p">,</span> <span class="n">sub</span><span class="p">):</span>
<span class="sd">"""Get TED Subtitle in JSON format & convert it to SRT Subtitle</span>
<span class="sd"> Obtiene el subtitulo de TED en formato JSON y lo convierte al formato SRT"""</span>
<span class="k">def</span> <span class="nf">srt_time</span><span class="p">(</span><span class="n">tst</span><span class="p">):</span>
<span class="sd">"""Format Time from TED Subtitles format to SRT time Format</span>
<span class="sd"> Convierte el formato de tiempo del subtitulo TED al formato de SRT"""</span>
<span class="n">secs</span><span class="p">,</span> <span class="n">mins</span><span class="p">,</span> <span class="n">hours</span> <span class="o">=</span> <span class="p">((</span><span class="n">tst</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">%</span> <span class="mi">60</span><span class="p">),</span> <span class="p">(</span><span class="n">tst</span> <span class="o">/</span> <span class="mi">60000</span><span class="p">),</span> <span class="p">(</span><span class="n">tst</span> <span class="o">/</span> <span class="mi">3600000</span><span class="p">)</span>
<span class="n">right_srt_time</span> <span class="o">=</span> <span class="s">"{0:02d}:{1:02d}:{2:02d},000"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">hours</span><span class="p">,</span> <span class="n">mins</span><span class="p">,</span> <span class="n">secs</span><span class="p">)</span>
<span class="k">return</span> <span class="n">right_srt_time</span>
<span class="n">srt_content</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">sub_log</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">tt_url</span> <span class="o">=</span> <span class="s">'http://www.ted.com/talks'</span>
<span class="n">sub_url</span> <span class="o">=</span> <span class="s">'{0}/subtitles/id/{1}/lang/{2}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">tt_url</span><span class="p">,</span> <span class="n">tt_id</span><span class="p">,</span> <span class="n">sub</span><span class="p">[</span><span class="o">-</span><span class="mi">7</span><span class="p">:</span><span class="o">-</span><span class="mi">4</span><span class="p">])</span>
<span class="c">## Get JSON sub</span>
<span class="k">if</span> <span class="n">FOUND</span><span class="p">:</span>
<span class="n">json_file</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">([</span><span class="s">'wget'</span><span class="p">,</span> <span class="s">'-q'</span><span class="p">,</span> <span class="s">'-O'</span><span class="p">,</span> <span class="s">'-'</span><span class="p">,</span> <span class="n">sub_url</span><span class="p">],</span>
<span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">readlines</span><span class="p">()</span>
<span class="k">if</span> <span class="n">json_file</span><span class="p">:</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">json_file</span><span class="p">:</span>
<span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s">'captions'</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span> <span class="ow">and</span> <span class="n">line</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s">'status'</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">:</span>
<span class="n">json_file</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">sub_log</span> <span class="o">+=</span> <span class="s">"Subtitle '{0}' not found.{1}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">sub</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">json_file</span> <span class="o">=</span> <span class="n">urllib2</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">sub_url</span><span class="p">)</span><span class="o">.</span><span class="n">readlines</span><span class="p">()</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">json_object</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">json_file</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="k">if</span> <span class="s">'captions'</span> <span class="ow">in</span> <span class="n">json_object</span><span class="p">:</span>
<span class="n">caption_idx</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">json_object</span><span class="p">[</span><span class="s">'captions'</span><span class="p">]:</span>
<span class="n">sub_log</span> <span class="o">+=</span> <span class="p">(</span><span class="s">"Subtitle '{0}' not available.{1}"</span><span class="o">.</span>
<span class="n">format</span><span class="p">(</span><span class="n">sub</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">))</span>
<span class="k">for</span> <span class="n">caption</span> <span class="ow">in</span> <span class="n">json_object</span><span class="p">[</span><span class="s">'captions'</span><span class="p">]</span> <span class="p">:</span>
<span class="n">start</span> <span class="o">=</span> <span class="n">tt_intro</span> <span class="o">+</span> <span class="n">caption</span><span class="p">[</span><span class="s">'startTime'</span><span class="p">]</span>
<span class="n">end</span> <span class="o">=</span> <span class="n">start</span> <span class="o">+</span> <span class="n">caption</span><span class="p">[</span><span class="s">'duration'</span><span class="p">]</span>
<span class="n">idx_line</span> <span class="o">=</span> <span class="s">'{0}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">caption_idx</span><span class="p">)</span>
<span class="n">time_line</span> <span class="o">=</span> <span class="s">'{0} --> {1}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">srt_time</span><span class="p">(</span><span class="n">start</span><span class="p">),</span> <span class="n">srt_time</span><span class="p">(</span><span class="n">end</span><span class="p">))</span>
<span class="n">text_line</span> <span class="o">=</span> <span class="s">'{0}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">caption</span><span class="p">[</span><span class="s">'content'</span><span class="p">]</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s">"utf-8"</span><span class="p">))</span>
<span class="n">srt_content</span> <span class="o">+=</span> <span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">idx_line</span><span class="p">,</span> <span class="n">time_line</span><span class="p">,</span> <span class="n">text_line</span><span class="p">,</span> <span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">])</span>
<span class="n">caption_idx</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="s">'status'</span> <span class="ow">in</span> <span class="n">json_object</span><span class="p">:</span>
<span class="n">sub_log</span> <span class="o">+=</span> <span class="p">(</span><span class="s">"This is an error message returned by TED:{0}{0} - {1}"</span>
<span class="s">"{0}{0}Probably because the subtitle '{2}' is not "</span>
<span class="s">"available.{0}{0}{0}"</span>
<span class="s">""</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">,</span> <span class="n">json_object</span><span class="p">[</span><span class="s">'status'</span><span class="p">][</span><span class="s">'message'</span><span class="p">],</span>
<span class="n">sub</span><span class="p">))</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="n">sub_log</span> <span class="o">+=</span> <span class="p">(</span><span class="s">"Subtitle '{0}' it's a malformed json file.{1}"</span><span class="o">.</span>
<span class="n">format</span><span class="p">(</span><span class="n">sub</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">))</span>
<span class="k">return</span> <span class="n">srt_content</span><span class="p">,</span> <span class="n">sub_log</span>
<span class="k">def</span> <span class="nf">check_subs</span><span class="p">(</span><span class="n">ttalk</span><span class="p">,</span> <span class="n">v_name</span><span class="p">):</span>
<span class="sd">"""Check if the subtitles for the talk are downloaded, if not try to get</span>
<span class="sd"> them. Checks it for english and spanish languages</span>
<span class="sd"> Comprueba si los subtitulos para la charla estan descargados, si no, intenta</span>
<span class="sd"> obtenerlos. Lo comprueba para los idiomas español e ingles"""</span>
<span class="c">## Get the names for the subtitles (for english and spanish languages) only</span>
<span class="c"># if they not are already downloaded</span>
<span class="n">subs</span> <span class="o">=</span> <span class="p">(</span><span class="n">s_name</span> <span class="k">for</span> <span class="n">s_name</span> <span class="ow">in</span>
<span class="p">(</span><span class="s">"{0}.{1}.srt"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">v_name</span><span class="p">[:</span><span class="o">-</span><span class="mi">4</span><span class="p">],</span> <span class="n">lang</span><span class="p">)</span> <span class="k">for</span> <span class="n">lang</span> <span class="ow">in</span> <span class="p">(</span><span class="s">'eng'</span><span class="p">,</span> <span class="s">'spa'</span><span class="p">))</span>
<span class="k">if</span> <span class="n">s_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">'*.srt'</span><span class="p">))</span>
<span class="n">s_log</span> <span class="o">=</span> <span class="s">''</span>
<span class="k">for</span> <span class="n">sub</span> <span class="ow">in</span> <span class="n">subs</span><span class="p">:</span>
<span class="c">## Reads the talk web page, to search the talk's intro duration</span>
<span class="k">if</span> <span class="n">FOUND</span><span class="p">:</span>
<span class="n">tt_webpage</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">([</span><span class="s">'wget'</span><span class="p">,</span> <span class="s">'-q'</span><span class="p">,</span> <span class="s">'-O'</span><span class="p">,</span> <span class="s">'-'</span><span class="p">,</span>
<span class="n">ttalk</span><span class="o">.</span><span class="n">feedburner_origlink</span><span class="p">],</span>
<span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">tt_webpage</span> <span class="o">=</span> <span class="n">urllib2</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">ttalk</span><span class="o">.</span><span class="n">feedburner_origlink</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">tt_intro</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">search</span><span class="p">(</span><span class="s">"introDuration:(\d+),"</span><span class="p">,</span> <span class="n">tt_webpage</span><span class="p">)</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
<span class="n">subtitle</span><span class="p">,</span> <span class="n">get_log</span> <span class="o">=</span> <span class="n">get_sub</span><span class="p">(</span><span class="n">ttalk</span><span class="o">.</span><span class="n">id</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">':'</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">tt_intro</span><span class="p">,</span> <span class="n">sub</span><span class="p">)</span>
<span class="n">s_log</span> <span class="o">+=</span> <span class="n">get_log</span>
<span class="k">if</span> <span class="n">subtitle</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">sub</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">srt_file</span><span class="p">:</span>
<span class="n">srt_file</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">subtitle</span><span class="p">)</span>
<span class="n">s_log</span> <span class="o">+=</span> <span class="s">"{0}{1} downloaded.{0}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">,</span> <span class="n">sub</span><span class="p">)</span>
<span class="k">return</span> <span class="n">s_log</span>
<span class="k">def</span> <span class="nf">get_video</span><span class="p">(</span><span class="n">ttk</span><span class="p">,</span> <span class="n">vid_url</span><span class="p">,</span> <span class="n">vid_name</span><span class="p">):</span>
<span class="sd">"""Gets the TED Talk video</span>
<span class="sd"> Obtiene el video de la TED Talk"""</span>
<span class="k">if</span> <span class="n">FOUND</span><span class="p">:</span>
<span class="n">Popen</span><span class="p">([</span><span class="s">'wget'</span><span class="p">,</span> <span class="s">'-q'</span><span class="p">,</span> <span class="s">'-O'</span><span class="p">,</span> <span class="n">vid_name</span><span class="p">,</span> <span class="n">vid_url</span><span class="p">],</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">urllib</span><span class="o">.</span><span class="n">urlretrieve</span><span class="p">(</span><span class="n">vid_url</span><span class="p">,</span> <span class="n">vid_name</span><span class="p">)</span>
<span class="n">v_log</span> <span class="o">=</span> <span class="s">'{0} ({1})</span><span class="se">\n</span><span class="s">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">ttk</span><span class="o">.</span><span class="n">subtitle</span><span class="p">,</span> <span class="n">ttk</span><span class="o">.</span><span class="n">itunes_duration</span><span class="p">)</span>
<span class="n">v_log</span> <span class="o">+=</span> <span class="s">'{0}</span><span class="se">\n\n</span><span class="s">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="s">'='</span> <span class="o">*</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">ttk</span><span class="o">.</span><span class="n">subtitle</span><span class="p">)</span> <span class="o">+</span> <span class="mi">11</span><span class="p">))</span>
<span class="n">v_log</span> <span class="o">+=</span> <span class="s">'{0}</span><span class="se">\n\n</span><span class="s">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">ttk</span><span class="o">.</span><span class="n">feedburner_origlink</span><span class="p">)</span>
<span class="n">v_log</span> <span class="o">+=</span> <span class="s">'{0}</span><span class="se">\n\n</span><span class="s">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">ttk</span><span class="o">.</span><span class="n">content</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>
<span class="n">v_log</span> <span class="o">+=</span> <span class="s">'file://{0}</span><span class="se">\n</span><span class="s">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">(),</span> <span class="n">vid_name</span><span class="p">))</span>
<span class="n">vid_size</span> <span class="o">=</span> <span class="n">best_unit_size</span><span class="p">(</span><span class="n">get_size</span><span class="p">(</span><span class="n">vid_name</span><span class="p">))</span>
<span class="n">v_log</span> <span class="o">+=</span> <span class="s">'{0:.2f} {1}</span><span class="se">\n\n</span><span class="s">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">vid_size</span><span class="p">[</span><span class="s">'s'</span><span class="p">],</span> <span class="n">vid_size</span><span class="p">[</span><span class="s">'u'</span><span class="p">])</span>
<span class="k">return</span> <span class="n">v_log</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="sd">"""main section"""</span>
<span class="c">#===============================================================================</span>
<span class="c"># SCRIPT PARAMETERS</span>
<span class="c">#===============================================================================</span>
<span class="c">## The directory to store the videos and subs.</span>
<span class="c"># For Windows change the character '\' for the character '/', I know is</span>
<span class="c"># akward but is because how escape strings python</span>
<span class="n">ttalk_vid_dir</span> <span class="o">=</span> <span class="s">'/your/path/to/TED/Talks/Videos'</span>
<span class="c">#===============================================================================</span>
<span class="c"># END PARAMETERS</span>
<span class="c">#===============================================================================</span>
<span class="c"># initalize the log</span>
<span class="n">log</span> <span class="o">=</span> <span class="n">Logger</span><span class="p">()</span>
<span class="c"># log the header</span>
<span class="n">url</span> <span class="o">=</span> <span class="s">'http://joedicastro.com'</span>
<span class="n">msg</span> <span class="o">=</span> <span class="s">'Download TED Talks from HD RSS Feed'</span>
<span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">msg</span><span class="p">)</span>
<span class="c"># log the start time</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">'Start Time'</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">normpath</span><span class="p">(</span><span class="n">ttalk_vid_dir</span><span class="p">))</span>
<span class="c">## Get a list of the current TED Talks downloaded in the dir</span>
<span class="n">videos</span> <span class="o">=</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">'*.mp4'</span><span class="p">)</span>
<span class="c">## Get the last download Talk video date</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'.data.pkl'</span><span class="p">,</span> <span class="s">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">pkl_file</span><span class="p">:</span>
<span class="n">last</span> <span class="o">=</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pkl_file</span><span class="p">)</span>
<span class="k">except</span> <span class="p">(</span><span class="ne">EOFError</span><span class="p">,</span> <span class="ne">IOError</span><span class="p">,</span> <span class="n">pickle</span><span class="o">.</span><span class="n">PickleError</span><span class="p">):</span>
<span class="n">last</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">localtime</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="mi">86400</span><span class="p">)</span>
<span class="n">video_dates</span> <span class="o">=</span> <span class="p">[]</span>
<span class="c">## The TED Talks HD RSS feed</span>
<span class="n">ttalk_feed_url</span> <span class="o">=</span> <span class="s">'http://feeds.feedburner.com/tedtalksHD'</span>
<span class="n">ttalk_feed</span> <span class="o">=</span> <span class="n">feedparser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">ttalk_feed_url</span><span class="p">)</span>
<span class="c">## If the feed is erroneous or occurs a http or network error, log and exit!</span>
<span class="k">if</span> <span class="n">ttalk_feed</span><span class="o">.</span><span class="n">bozo</span><span class="p">:</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'An error occurred'</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">ttalk_feed</span><span class="o">.</span><span class="n">bozo_exception</span><span class="p">))</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">WIN_OS</span><span class="p">:</span>
<span class="n">log</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">'Download TED Talks'</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="c">## If correct, process the feed entries</span>
<span class="n">vids_log</span><span class="p">,</span> <span class="n">subs_log</span> <span class="o">=</span> <span class="s">''</span><span class="p">,</span> <span class="s">''</span>
<span class="k">for</span> <span class="n">ttalk_entrie</span> <span class="ow">in</span> <span class="n">ttalk_feed</span><span class="o">.</span><span class="n">entries</span><span class="p">:</span>
<span class="c">## Get The video url and name</span>
<span class="n">tt_vid_url</span> <span class="o">=</span> <span class="n">ttalk_entrie</span><span class="o">.</span><span class="n">enclosures</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">href</span>
<span class="n">tt_vid_name</span> <span class="o">=</span> <span class="n">tt_vid_url</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">'/'</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="c">## If the video is new, download it!</span>
<span class="k">if</span> <span class="n">ttalk_entrie</span><span class="o">.</span><span class="n">updated_parsed</span> <span class="o">></span> <span class="n">last</span> <span class="ow">and</span> <span class="n">tt_vid_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">videos</span><span class="p">:</span>
<span class="n">vids_log</span> <span class="o">+=</span> <span class="n">get_video</span><span class="p">(</span><span class="n">ttalk_entrie</span><span class="p">,</span> <span class="n">tt_vid_url</span><span class="p">,</span> <span class="n">tt_vid_name</span><span class="p">)</span>
<span class="n">videos</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">tt_vid_name</span><span class="p">)</span>
<span class="n">video_dates</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ttalk_entrie</span><span class="o">.</span><span class="n">updated_parsed</span><span class="p">)</span>
<span class="c">## If video is already downloaded, check if subs exists, if not, get it!</span>
<span class="k">if</span> <span class="n">tt_vid_name</span> <span class="ow">in</span> <span class="n">videos</span><span class="p">:</span>
<span class="n">subs_log</span> <span class="o">+=</span> <span class="n">check_subs</span><span class="p">(</span><span class="n">ttalk_entrie</span><span class="p">,</span> <span class="n">tt_vid_name</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'Talks downloaded'</span><span class="p">,</span> <span class="n">vids_log</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">'Subs downloaded'</span><span class="p">,</span> <span class="p">[</span><span class="n">subs_log</span><span class="p">])</span>
<span class="c">## Set the last download video date</span>
<span class="k">if</span> <span class="n">video_dates</span><span class="p">:</span>
<span class="n">last</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="n">video_dates</span><span class="p">)</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'.data.pkl'</span><span class="p">,</span> <span class="s">'wb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">output</span><span class="p">:</span>
<span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">last</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">'End time'</span><span class="p">)</span>
<span class="c">## If logs any activity, sends the information mail</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">WIN_OS</span><span class="p">:</span>
<span class="n">log</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">'Download TED Talks'</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">WIN_OS</span> <span class="o">=</span> <span class="bp">True</span> <span class="k">if</span> <span class="n">platform</span><span class="o">.</span><span class="n">system</span><span class="p">()</span> <span class="o">==</span> <span class="s">'Windows'</span> <span class="k">else</span> <span class="bp">False</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">WIN_OS</span><span class="p">:</span>
<span class="n">FOUND</span> <span class="o">=</span> <span class="n">check_exec_posix</span><span class="p">(</span><span class="s">'wget'</span><span class="p">)</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>Y un ejemplo de un correo enviado por el mismo quedaría así:</p>
<div class="codehilite"><pre>De: youruser@yourcomputer
Para: youruser@yourcomputer
Asunto: Download TED Talks - Thursday 07/29/10, 17:04:52
Fecha: Thu, 29 Jul 2010 17:04:52 +0200
New TED Talk downloaded!
========================
http://www.ted.com/talks/view/id/927
Laurie Santos: A monkey economy as irrational as ours (00:19:45)
Laurie Santos looks for the roots of human irrationality by watching the way
our primate relatives make decisions. A clever series of experiments in
"monkeynomics" shows that some of the silly choices we make, monkeys make too.
file:///your/path/to/TEDTalks/LaurieSantos_2010G_480.mp4
240.7 MiB
SusanShaw_2010X_480.eng.srt downloaded
</pre></div>
<p>Pudiendo acceder al vídeo directamente desde el correo, y si se encontraran los
subtítulos para el mismo ya disponibles, visualizarlos automáticamente.</p>
<p>El que el script esté fundamentalmente en ingles (la mayoría de comentarios y
los nombres de las variables) es que me siento más cómodo de esta manera y
ayuda a que un espectro más amplio de personas sea capaz de comprenderlo. Si
este script le sirve a alguien más o le inspira para crear el suyo propio, pues
habrá valido la pena el publicarlo. El resto del script, <strong>TEDTalks.py</strong> está
disponible en mi repositorio alojado en <a href="http://github.com/joedicastro/ted-talks-download">github</a>, al igual que instrucciones
ampliadas de su uso.</p>
<p>Finalmente como ejemplo, publico una de las charlas más impactantes este año y
ganadora del <strong>TED Prize</strong>, <a href="http://www.ted.com/talks/lang/spa/jamie_oliver.html">Jamie Oliver: Enseñarle a todos los niños acerca de
la comida</a></p>
<div><p style="text-align: center;"><object data="http://video.ted.com/assets/player/swf/EmbedPlayer.swf" type="application/x-shockwave-flash" height="326" width="446"><param name="data" value="http://video.ted.com/assets/player/swf/EmbedPlayer.swf"><param name="allowFullScreen" value="true"><param name="allowScriptAccess" value="always"><param name="wmode" value="transparent"><param name="bgColor" value="#ffffff"><param name="flashvars" value="vu=http://video.ted.com/talks/dynamic/JamieOliver_2010-medium.mp4&su=http://images.ted.com/images/ted/tedindex/embed-posters/JamieOliver-2010.embed_thumbnail.jpg&vw=432&vh=240&ap=0&ti=765&introDuration=15330&adDuration=4000&postAdDuration=830&adKeys=talk=jamie_oliver;year=2010;theme=a_taste_of_ted2010;theme=ted_prize_winners;event=TED2010;&preAdTag=tconf.ted/embed;tile=1;sz=512x288;"><param name="src" value="http://video.ted.com/assets/player/swf/EmbedPlayer.swf"><param name="bgcolor" value="#ffffff"><param name="allowfullscreen" value="true"></object></p></div>
<br />
<hr />
<h2 id="comentarios_realizados_anteriormente_en_drupal">Comentarios realizados anteriormente en Drupal</h2>
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://www.elnaranjal.com.ve/"><img src="pictures/avtr_dennis.png"
height=28 width=28 alt="avatar" title="avatar de Dennis"/></a></div>
<h3 id="excelente">Excelente!</h3>
<p>por <a href="http://www.elnaranjal.com.ve/">Dennis</a> el Sáb, 31/07/2010 - 21:12 </p>
<p>Amigo, he leído con atención su articulo acerca de los subtítulos. El nivel de
automatización alcanzado resulta sorprendente, sin embargo, para aquellos que no
contamos con el hardware necesario se torna complicada la cosa.</p>
<p>En mi caso, me interesa descargar algunas (no todas) las charlas. De hecho,
quisiera unas pocas, cuidadosamente seleccionadas, para presentarlas en el
colegio en el cual doy clases a mis alumnos de secundaria.</p>
<p>¿Conoce Ud. de alguna forma de descargar en un formato legible por VLC (.SUB o
.SRT, por ejemplo) los subtítulos en español de charlas seleccionadas? He estado
tratando de conseguir algunos en forma infructuosa desde hace algún tiempo,
aunque he conseguido algunos en formato JSON.</p>
<p>Gracias mil por la ayuda con este tema.</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="script_para_subtitulos_de_una_charla">Script para subtitulos de una charla</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Dom, 01/08/2010 - 01:38</p>
<p>Bueno, para conseguir eso que me comentas, he modificado el script anterior un
poco y he creado este <a href="https://github.com/joedicastro/ted-talks-download/blob/master/src/TEDSubs.py"><strong>nuevo script</strong></a>.</p>
<p>La forma de utilizarlo es muy sencilla, por ejemplo para bajarse la charla de
Jamie Oliver, simplemente en la linea de comandos escribes</p>
<div class="codehilite"><pre><span class="go">python TEDSubs.py http://www.ted.com/talks/lang/eng/jamie_oliver.html</span>
</pre></div>
<p>Y el te baja tanto los subtítulos como el vídeo. Si quisieras bajarte solo los
subtítulos, bastaría con que añadieras la opción <code>-s</code> ó <code>--only_subs</code>, quedaría
así:</p>
<div class="codehilite"><pre><span class="go">python TEDSubs.py -s http://www.ted.com/talks/lang/eng/jamie_oliver.html</span>
</pre></div>
<p>Como ves es muy sencillo, solo tendrías que sustituir la dirección de la charla
de Oliver por la que deseas bajar.</p>
<p>Espero que te sirva.</p>
<p>Saludos</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<img src="pictures/avtr_ignacio.png" height=28 width=28
alt="avatar" title="avatar de Ignacio"/></div>
<h3 id="ted">TED</h3>
<p>por Ignacio el Mié, 04/08/2010 - 01:02 </p>
<p>Estimado muy bueno lo que haces, me parecen excelentes los vídeos y me gustaría
contar con ellos. ¿Donde corro el script?¿podrías subírmelos con subtítulos a
algún gestor o ftp?</p>
<p>Muchas gracias</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="vamos_por_partes">Vamos por partes...</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Jue, 05/08/2010 - 19:47</p>
<p>Vamos por partes...</p>
<p>Los scripts los ejecutas en tu ordenador, lo único necesario es el lenguaje de
programación <strong>Python</strong> y una conexión a Internet. Si tu maquina corre en linux,
el python lo tienes instalado por defecto en casi todas las distribuciones. Si
corre en Windows o Mac, lo puedes descargar en <a href="http://www.python.org/download/"><strong>Python.org</strong></a></p>
<p>Para correr los scripts, en la linea de comandos solo necesitas ejecutar estas
lineas de comando:</p>
<div class="codehilite"><pre><span class="go">python TEDTalks.py</span>
</pre></div>
<p>para el primer script o las lineas de comando ya comentadas anteriormente para
el segundo script.</p>
<p>Para descargarlos, puedes o bien copiar y pegar el código tal y como aparece en
el articulo, o bien descargarlos:</p>
<p><a href="https://github.com/joedicastro/ted-talks-download/archive/master.zip"><strong>TEDTalks.py & TEDSubs.py</strong></a></p>
<p>o bien vas directamente al mi <strong><a href="https://github.com/joedicastro/ted-talks-download">repositorio en github</a></strong></p>
<p>Eso si, hay que tener en cuenta que en el caso del primer script, este solo se
baja el vídeo del dia anterior en su primera ejecución o bien el más reciente no
descargado en caso de ejecuciones sucesivas. Y como la gente de <strong>TED</strong> parece
que se ha tomado un descanso en Agosto (el último vídeo es del 30 de Julio),
este no descargara nada, porque de momento no hay nada que descargar. Hasta que
no retornen la publicación de nuevas charlas, el primer script no bajara nuevos
contenidos, igual que los tradicionales lectores RSS o el mismo Miro.</p>
<p>Saludos</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<img src="pictures/avtr_daniel.png" height=28 width=28
alt="avatar" title="avatar de Daniel"/></div>
<h3 id="gracias">Gracias</h3>
<p>por Daniel el Mié, 22/09/2010 - 04:38 </p>
<p>Hermano, muchas gracias.. excelente script.. ya te imaginaras lo importante que
es que para muchos, poder adquirir estas excelente conferencias. Gracias de
nuevo</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<img src="pictures/avtr_xavier.png" height=28 width=28
alt="avatar" title="avatar de Xavier"/></div>
<h3 id="funcionan_los_subtitulos">Funcionan los Subtitulos?</h3>
<p>por Xavier el Mié, 24/11/2010 - 00:35 </p>
<p>Hola,</p>
<p>muchas gracias por disponibilizar los scripts.</p>
<p>Estoy intentando usar el segundo script (para bajar vídeos concretos) pero solo
me baja el vídeo. Si lo uso con -s no me baja nada (tampoco devuelve ningún
mensaje de error.</p>
<p>Podría ser que hayan cambiado la manera de bajar los subtítulos?</p>
<p>gracias por adelantado</p>
<p>Xavier</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="si_funcionan_pero">Si, funcionan, pero...</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Mié, 24/11/2010 - 09:28</p>
<p>Si, funcionan, pero a veces los subtítulos aún no están disponibles, o peor, el
fichero original en formato json es defectuoso. Lo de que el formato json sea
defectuoso suele pasar de vez en cuando y generalmente acaban
solucionándolo al cabo de unos días. Debe de ser algún defecto en la traducción
o al subir el fichero.</p>
<p>De todos modos ya he modificado el script para que te muestre un mensaje de
error en caso de que no esté disponible o este defectuoso, e indicado si es el
subtitulo en español o ingles. Puedes descargarlo de aquí o del repositorio.
Gracias por informarme del error, se me había escapado el proporcionar esta
información.</p>
<p>Y voy a mirar la manera de convertir igualmente el subtitulo de json a srt
aunque el json esté malformado siempre que sea posible. No será muy difícil.</p>
<p>Saludos</p>
<p><strong>Actualización</strong>: Bueno, he encontrado un fallo, a veces cuando la página
termina un cierto tiempo en responder, no cargaba algunos ficheros json de
subtítulos completos, y lo interpretaba como un fichero json defectuoso. Ya he
corregido el error, substituyendo la función que empleaba para la descarga por
otra que siempre descarga el fichero completo.</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="bueno_ya_estan_incorporadas">Bueno, ya estan incorporadas</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Mié, 24/11/2010 - 23:26</p>
<p>Bueno, ya están incorporadas las mejoras también al otro script. En principio no
veo necesario el crear el método para los ficheros JSON defectuosos, en parte
porque sería replicar más o menos lo que ya hace el modulo json y en parte
porque los JSON defectuosos provenían del empleo de la librería de python urllib
para descargarlos, lo que a veces por timeouts no conseguía hacerlo del todo. El
emplear la librería urllib2 soluciona este problema puesto que si los descarga
al completo.</p>
<p>De todos modos ahora se reportan todos los códigos de error que aparezcan, tanto
los que genero yo en el script, como los originales generados por TED (en
realidad dotSUB, que es el que proporciona los subtítulos para TED) y les haré
un seguimiento. Porque puede que si estén cambiando algo, dados los errores que
reportan algunos de los últimos vídeos.</p>
<p>Saludos y gracias por reportarme el fallo, Xavier.</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<img src="pictures/avtr_xavier.png" height=28 width=28
alt="avatar" title="avatar de Xavier"/></div>
<h3 id="aun_tengo_algun_problemilla">Aun tengo algun problemilla</h3>
<p>por Xavier el Jue, 25/11/2010 - 00:40</p>
<p>Muchas gracias por tan rápida respuesta. Me he copiado el código para bajar los
vídeos individuales, pero aun tengo problemas. Esta vez parece como si me
faltara algún modulo de python, aunque no se ni cual ni como debo actualizarlo.
Tengo el siguiente error:</p>
<div class="codehilite"><pre><span class="gt">Traceback (most recent call last):</span>
File <span class="nb">"./TEDVideos.py"</span>, line <span class="m">184</span>, in <span class="n"><module></span>
<span class="n">main</span><span class="p">()</span>
File <span class="nb">"./TEDVideos.py"</span>, line <span class="m">172</span>, in <span class="n">main</span>
<span class="n">ttalk_webpage</span><span class="p">)</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
<span class="gr">AttributeError</span>: <span class="n">'NoneType' object has no attribute 'group'</span>
</pre></div>
<p>(El programa TEDVideos.py es el nombre que le he dado yo a tu código)</p>
<p>muchas gracias de antemano</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="no_en_este_caso_no_te_falta">No, en este caso no te falta</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Jue, 25/11/2010 - 10:12</p>
<p>No, en este caso no te falta ningún modulo para ejecutar el script, eso es algo
que ya tengo contemplado y es lo primero que compruebo, y lanzo un aviso del
modulo que es necesario para ejecutarlo si no se encuentra.</p>
<p>El error que me muestras es porque intento seleccionar uno de entre los
resultados de una búsqueda de datos dentro de una página web, y al ser el
resultado de la búsqueda vacío arroja ese error. Es un error que tampoco tenía
contemplado porque asumía que en un principio las direcciones web introducidas
serían correctas, algo que voy a contemplar a partir de ahora, para avisar al
usuario.</p>
<p>Ese error te lo da porque no encuentra lo que busca, el identificador único de
la charla, en la página web que le proporcionas. Lo raro es que si fuera una
dirección web incorrecta, el error tendría que producirse antes, cuando busca
la duración de la introducción previa al vídeo. Lo he probado con unas 25
charlas diferentes y no he conseguido reproducir el error. La mejora forma de
entender lo que ha pasado es que me proporciones la dirección web que te da el
error para comprobar lo que ocurre.</p>
<p>Saludos</p>
<p><strong>Actualización</strong>: He añadido mensajes de error para contemplar estas
posibilidades, así será más fácil diagnosticar los fallos.</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<img src="pictures/avtr_xavier.png" height=28 width=28
alt="avatar" title="avatar de Xavier"/></div>
<h3 id="en_efecto_no_encuentra_la_p+gina">En efecto, no encuentra la página</h3>
<p>por Xavier el Jue, 25/11/2010 - 12:01 </p>
<p>Tal y como predices, no parece encontrar la pagina buscada. Ahora me lanza este
error:</p>
<div class="codehilite"><pre>Some data not found in this URL:
http://www.ted.com/talks/lang/eng/jamie_oliver.html
Please report this error and provides the URL to check at:
http://code.joedicastro.com/ted-talks-download/issues/new
Thanks for helping to fix errors.
</pre></div>
<p>Lo curioso es que estoy probándolo con el ejemplo que tu nos proporcionas:</p>
<div class="codehilite"><pre><span class="go">TEDSubs.py -s http://www.ted.com/talks/lang/eng/jamie_oliver.html</span>
</pre></div>
<p>Para asegurarme que tengo conectividad desde mi ordenador he intentado ir a la
pagina web en un browser (sale ok) y he intentado bajarla con wget (baja ok).</p>
<p>No creo que afecte, pero estoy usando OS X 10.6</p>
<p>gracias</p>
<p>Xavier</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="bueno_teniendo_en_cuenta_los">Bueno, teniendo en cuenta los</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Jue, 25/11/2010 - 12:33</p>
<p>Bueno, teniendo en cuenta los errores que te da ahora y que al principio si
podías bajar el vídeo, el problema está en el cambio que hice de urllib a
urllib2, pero si el segundo te da este error, el primero no te bajaba los
subtítulos. He mirado googleando un poco y si parece haber algunos problemas
con estas librerías y MAC OSX, es algo que no puedo corroborar directamente
puesto que no dispongo de este SO para probarlo.</p>
<p>Así que voy a ir por terreno seguro y aprovechando el feedback que me das, voy
a prepararte una versión que emplee la herramienta externa wget para coger los
datos de la página, los subtítulos y el vídeo. De este modo nos aseguramos de
sortear este problema.</p>
<p>De nada, la pena es no tener un Mac, si no, no te hacia dar tantas vueltas.</p>
<p>Saludos</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="ya_tienes_la_nueva_versi+n">Ya tienes la nueva versión</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Jue, 25/11/2010 - 14:00</p>
<p>Ya tienes la nueva versión con wget, esta no debería darte esos problemas. Ya me
dirás el resultado.</p>
<p>Saludos</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<img src="pictures/avtr_xavier.png" height=28 width=28
alt="avatar" title="avatar de Xavier"/></div>
<h3 id="fant+stico">fantástico!</h3>
<p>por Xavier el Jue, 25/11/2010 - 14:43 </p>
<p>Ahora ya funciona perfectamente!</p>
<p>muchas gracias por añadir estas funcionalidades y por los scripts. Van a serme
de grande utilidad.</p>
<p>un saludo,</p>
<p>Xavier</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="de_nada_gracias_a_ti_por_el">De nada, gracias a ti por el</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Jue, 25/11/2010 - 14:47</p>
<p>De nada, gracias a ti por el feedback, le servirá a futuros usuarios.</p>
<p>Luego modifico también el otro script para emplear wget.</p>
<p>Un saludo</p>
<p>Joe</p>
<hr />
<div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;">
<a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28
width=28 alt="avatar" title="avatar de joedicastro"/></a></div>
<h3 id="nuevas_versiones_mejoradas_de">Nuevas versiones mejoradas de</h3>
<p>por <a href="http://joedicastro.com">joe di castro</a> el Mié, 01/12/2010 - 23:51</p>
<p>Nuevas versiones mejoradas de los scripts (versión 1.3) y probadas en las 831
charlas disponibles hasta hoy, contemplando todos los casos diferentes.</p>
<p>Saludos</p>