Scraping anónimo por la red TOR


El proyecto de este post los puedes descargar pulsando AQUI.

En este tutorial vamos a explicar como configurar un servidor proxy para realizar scraping de forma anónima por medio de la red TOR, utilizando para ello TOR (https://www.torproject.org), Privoxy (http://www.privoxy.org) y la librería de python Stem (https://stem.torproject.org). Por otro lado este servidor correrá bajo una distribución Linux con paquetería Ubuntu (para realizar este tutorial se ha utilizado un Linux Mint 18.1).

El caso de uso más común cuando se está "scrapeando" un sitio web es poder cambiar su identidad (IP) mediante TOR (o un proxy que rote la IP) cuando se hayan realizado un número determinado de peticiones por unidad de tiempo con la misma IP, para que la web a la que se están realizando las peticiones no bloquee su conexión y pueda seguir "scrapeando" este sitio web (por ejemplo como ocurre con Google).

Configuración

Para desarrollar programas que realicen scraping de forma anónima en un servidor proxy, es necesario instalar en una distribución Linux (para este tutorial es necesario un Linux basado en paquetería Ubuntu) las siguientes herramientas:

  • TOR: es una abreviatura de "The Onion Project", un proyecto que busca el poder crear una red de comunicaciones distribuida de baja latencia por encima de la capa de internet de manera que nunca se revelen los datos de los usuarios que la utilizan, manteniéndose así como una red privada y anónima; es decir, no revela su identidad (dirección IP).
  • Stem: Librería de Python para el manejo de la red TOR.
  • Privoxy: Privoxy es un equipamiento lógico que sirve como Proxy y que tiene capacidades avanzadas de filtrado para proteger la privacidad, modificar los datos de las webs visitadas, cabeceras http, eliminar anuncios, etc. Tiene aplicación tanto para sistemas autónomos como para redes con multiples usuarios.

TOR (Instalación y configuración)

Abrimos un terminal e instalamos TOR de la siguiente manera:

sudo apt-get update
sudo apt-get install tor
sudo /etc/init.d/tor restart

Después de instalar TOR tenemos que:

  1. Activar el "ControlPort" para que TOR escuche por puerto 9051, ya que este es el puerto en el que TOR escuchará cualquier comunicación desde las aplicaciones que hablen con el controlador de TOR.
  2. Crear un hash de nueva contraseña que impida el acceso aleatorio al puerto.
  3. Implementar la autenticación de cookies.

Creamos el hash de una nueva contraseña de la siguiente manera

tor --hash-password my_password

Para este tutorial hemos puesto como contraseña '1234' y el hash generados a sido el siguiente:

16:9529EB03A306DE6F60171DE514EA2FCD49235BAF1E1E55897209679683

Por último en el fichero de configuración de TOR que se encuentra en /etc/tor/torrc actualizamos el puerto, el password (hash) y habilitamos la autenticación de cookies. Para ello abrimos con el vim (podéis abrirlo con otros editores: vi, nano, gedit, etc.) el fichero para su edición de la siguiente manera:

sudo vim /etc/tor/torrc

En el fichero tenemos que descomentar y modificar lo siguiente:

ControlPort 9051
# hashed password below is obtained via `tor --hash-password my_password`
HashedControlPassword 16:9529EB03A306DE6F60171DE514EA2FCD49235BAF1E1E55897209679683
CookieAuthentication 1

Hacemos un Restart de TOR para aplicar los cambios de la siguiente manera:

sudo /etc/init.d/tor restart

Puede darse el caso de que haya problemas o conflictos a la hora de habilitar el ControlPort. En ese caso se tiene que habilitar de la siguiente manera, poniendo al final el '&' para que se ejecute en segundo plano:

tor --controlport 9051 &

Python-Stem

Para instalar python-stem; que es un módulo basado en Python que se utiliza para interactuar con el controlador Tor, hay que ejecutar lo siguiente:

sudo apt-get install python-stem

Privoxy

TOR por si mismo no es un proxy http, así que para obtener acceso a la red TOR, se tiene que utilizar Privoxy como proxy http a través de socks5. Instalamos Privoxy de la siguente manera:

sudo apt-get install privoxy

Tenemos que indicar a Privoxy que use TOR llevando todo el tráfico a través del servidor SOCKS en el puerto local 9050. Para ello abrimos el fichero de configuración con vim de la siguiente manera:

sudo vim /etc/privoxy/config

Activamos el forward-socks5 descomentando la siguiente linea:

forward-socks5 / 127.0.0.1:9050

Hacemos un Restart de Privoxy para aplicar los cambios de la siguiente manera:

sudo /etc/init.d/privoxy restart

Scraping

Una vez configurado nuestro servidor proxy, vamos a desarrollar un programa que realizará scraping a páginas web, cambiando la IP cada X peticiones. Para ello se ha desarrollado una clase llamada "ConnectionManager.py" para gestionar: conexiones, cambio de IP, peticiones, etc. Esta clase se ha implementado de la sigueinte manera:

# -*- coding: utf-8 -*-
__author__ = 'RicardoMoya'

import time
import urllib2
from stem import Signal
from stem.control import Controller


class ConnectionManager:
    def __init__(self):
        self.new_ip = "0.0.0.0"
        self.old_ip = "0.0.0.0"
        self.new_identity()

    @classmethod
    def _get_connection(self):
        """
        TOR new connection
        """
        with Controller.from_port(port=9051) as controller:
            controller.authenticate(password="1234")
            controller.signal(Signal.NEWNYM)
            controller.close()

    @classmethod
    def _set_url_proxy(self):
        """
        Request to URL through local proxy
        """
        proxy_support = urllib2.ProxyHandler({"http": "127.0.0.1:8118"})
        opener = urllib2.build_opener(proxy_support)
        urllib2.install_opener(opener)

    @classmethod
    def request(self, url):
        """
        TOR communication through local proxy
        :param url: web page to parser
        :return: request
        """
        try:
            self._set_url_proxy()
            request = urllib2.Request(url, None, {
                'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) "
                              "AppleWebKit/535.11 (KHTML, like Gecko) "
                              "Ubuntu/10.10 Chromium/17.0.963.65 "
                              "Chrome/17.0.963.65 Safari/535.11"})
            request = urllib2.urlopen(request)
            return request
        except urllib2.HTTPError, e:
            return e.message

    def new_identity(self):
        """
        new connection with new IP
        """
        # First Connection
        if self.new_ip == "0.0.0.0":
            self._get_connection()
            self.new_ip = self.request("http://icanhazip.com/").read()
        else:
            self.old_ip = self.new_ip
            self._get_connection()
            self.new_ip = self.request("http://icanhazip.com/").read()

        seg = 0

        # If we get the same ip, we'll wait 5 seconds to request a new IP
        while self.old_ip == self.new_ip:
            time.sleep(5)
            seg += 5
            print ("Waiting to obtain new IP: %s Seconds" % seg)
            self.new_ip = self.request("http://icanhazip.com/").read()

        print ("New connection with IP: %s" % self.new_ip)
Nota 1: No es una buena práctica de programación hardcodear elementos en el código (IP, puerto, password, etc.) aunque en este caso al tratarse de un tutorial con fines didácticos se ha realizado así para que sea más fácil entender que hace el código.
Nota 2: http://icanhazip.com es una página web que muestra la dirección IP de la máquina que realiza la petición. Al hacer peticiones por la red TOR no podemos saber (preguntándole al sistema operativo) cual es nuestra IP ya que por la red TOR salimos con otra IP; que en este caso, es la que nos muetra la web de icanhazip.

En el constructor de la clase tenemos como atributos dos IPs que sirven para mantener el estado de la IP (o identidad) que tenemos actualmente y la nueva IP que queremos conseguir para obtener una nueva identidad. De esta manera si TOR nos asigna la misma IP que teniamos anteriormente la descartamos y pedimos otra.

Por otro lado tenemos dos métodos (públicos) que son request(url) y new_identity(). El método request(url) lo utilizaremos para hacer una petición a una página que le pasamos como parámetro y el método new_identity() nos asignará una nueva IP. Los métodos privados _get_connection() y _set_url_proxy() los utilizamos para establecer una nueva conexión por la red TOR y para realizar una petición por el proxy local.

Una vez mostrada la utilidad de esta clase, vamos a pasar a mostrar un sencillo ejemplo de cambio de identidad (IP) tras realizar 3 peticiones a una página web; en este caso, a la misma web de "icanhazip". El código de este ejemplo es el siguiente (Example.py):

# -*- coding: utf-8 -*-
__author__ = 'RicardoMoya'

from ConnectionManager import ConnectionManager

cm = ConnectionManager()
for j in range(5):
    for i in range(3):
        print ("\t\t" + cm.request("http://icanhazip.com/").read())
    cm.new_identity()

Como puede observarse en el código al realizar 3 peticiones a la web de "icanhazip", pedimos un cambio de IP llamando al método new_identity() de la clase ConnectionManager. El resultado de la ejecución de este código es el siguiente:

New connection with IP: 185.38.14.171
		185.38.14.171
		185.38.14.171
		185.38.14.171

Waiting to obtain new IP: 5 Seconds
Waiting to obtain new IP: 10 Seconds
New connection with IP: 94.23.173.249
		94.23.173.249
		94.23.173.249
		94.23.173.249

Waiting to obtain new IP: 5 Seconds
New connection with IP: 144.217.99.46
		144.217.99.46
		144.217.99.46
		144.217.99.46

Waiting to obtain new IP: 5 Seconds
Waiting to obtain new IP: 10 Seconds
New connection with IP: 62.210.129.246
		62.210.129.246
		62.210.129.246
		62.210.129.246

Waiting to obtain new IP: 5 Seconds
Waiting to obtain new IP: 10 Seconds
New connection with IP: 185.34.33.2
		185.34.33.2
		185.34.33.2
		185.34.33.2

Podemos observar como al solucitar nuevas IPs (ver en el código del método new_identity() ), nos asignan la misma IP que teniamos anteriormente y el programa se espera 5 segundos para volver a solicitar una nueva IP.

Veamos ahora un ejemplo un poco más práctico, basándonos en el segundo ejemplo del tutorial "Scraping en Python (BeautifulSoup), con ejemplos" en el que obteniamos todas las entradas de esta web. En este caso modificaremos el código para que cada 5 peticiones realizadas nos cambie la IP. El código es el siguiente (Scraping_All_Post.py):

# -*- coding: utf-8 -*-
__author__ = 'RicardoMoya'

from bs4 import BeautifulSoup
from ConnectionManager import ConnectionManager

URL_BASE = "http://jarroba.com/"
MAX_PAGES = 30
counter_post = 0

cm = ConnectionManager()
for i in range(1, MAX_PAGES):

    # Build URL
    if i > 1:
        url = "%spage/%d/" % (URL_BASE, i)
    else:
        url = URL_BASE
    print (url)

    # Do the request
    req = cm.request(url)
    status_code = req.code if req != '' else -1
    if status_code == 200:
        html = BeautifulSoup(req.read(), "html.parser")
        posts = html.find_all('div', {'class': 'col-md-4 col-xs-12'})
        for post in posts:
            counter_post += 1
            title = post.find('span', {'class': 'tituloPost'}).getText()
            author = post.find('span', {'class': 'autor'}).getText()
            date = post.find('span', {'class': 'fecha'}).getText()
            print (
            str(counter_post) + ' - ' + title + ' | ' + author + ' | ' + date)

    else:
        # if status code is diferent to 200
        break

    # obtain new ip if 5 requests have already been made
    if i % 5 == 0:
        cm.new_identity()

Como resultado esperado vemos que cada 5 peticiones nos devuelve una nueva IP para realizar las siguientes 5 peticiones con una nueva identidad. El resultado obtenido (se muestra parcialmente) es el siguiente:

New connection with IP: 192.42.116.16

http://jarroba.com/
1 - Bit | Por: Ramón	Invarato | 02-Abr-2017

...........

http://jarroba.com/page/5/
37 - Multitarea e Hilos en Java con ejemplos II (Runnable & Executors) | Por: Ricardo	Moya | 06-Dic-2014

...........

45 - MEAN (Mongo-Express-Angular-Node) Desarrollo Full Stack JavaScript (Parte I) | Por: Ricardo	Moya | 09-Jul-2014

Waiting to obtain new IP: 5 Seconds
New connection with IP: 178.175.131.194

http://jarroba.com/page/6/
46 - Atributos para diseñadores Android (tools:xxxxx) | Por: Ramón	Invarato | 26-May-2014

...........

http://jarroba.com/page/10/
82 - Error Android – java.lang.NoClassDefFoundError sin motivo aparente | Por: Ramón	Invarato | 30-May-2013

...........

90 - ArrayList en Java, con ejemplos | Por: Ricardo	Moya | 28-Mar-2013

New connection with IP: 216.239.90.19

http://jarroba.com/page/11/
91 - Intent – Pasar datos entre Activities – App Android (Video) | Por: Ricardo	Moya | 03-Mar-2013

...........

http://jarroba.com/page/15/
127 - Modelo “4+1” vistas de Kruchten (para Dummies) | Por: Ricardo	Moya | 31-Mar-2012

...........

135 - Aprender a programar conociendo lo que es un Entorno de Desarrollo Integrado (IDE) | Por: Ramón	Invarato | 14-Feb-2012

Waiting to obtain new IP: 5 Seconds
New connection with IP: 144.217.99.46

http://jarroba.com/page/16/
136 - Instalación del XAMPP para Windows | Por: Ricardo	Moya | 13-Feb-2012

...........

151 - Error Android – Aplicación no especifica el nivel de la API | Por: Ramón	Invarato | 12-Dic-2011
http://jarroba.com/page/18/

Referencias

Para realizar este artículo se ha tomado como referencia el contenido de los siguientes enlaces:

1.- Artículo Crawling anonymously with Tor in Python:  http://sacharya.com/crawling-anonymously-with-tor-in-python/

2.- Proyecto "PyTorStemPrivoxy" de la cuenta de github de FrackingAnalysis: https://github.com/FrackingAnalysis/PyTorStemPrivoxy

Comparte esta entrada en:
Safe Creative #1401310112503
Scraping anónimo por la red TOR por "www.jarroba.com" esta bajo una licencia Creative Commons
Reconocimiento-NoComercial-CompartirIgual 3.0 Unported License.
Creado a partir de la obra en www.jarroba.com

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies

ACEPTAR
Aviso de cookies