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:
- 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.
- Crear un hash de nueva contraseña que impida el acceso aleatorio al puerto.
- 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)
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
Example.py doesn’t work, does not print any output plus it only assigns one new_identity, that’s all!
Muy buenas, es muy interesante esta información de como hacer scraping anomino , solo que para ejecutarlo necesito ayuda, por favor, lo que pasa es que al instalar python en ubuntu instala la version 3.2 y el ejemplo esta con la version 2 lo cual como hacer o cual modificar para la version 3 de python, les ruego suayuda, gracias de antemano.