Scraping anónimo por la red TOR
En este tutorial vamos a explicar como configurar un servidor proxy para realizar scraping de forma anónima por medio de 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 (Anonymity Online (Tor Project)), Privoxy (Privoxy – Home Page (Privoxy)) y la librería de python Stem (Welcome to Stem! — Stem 1.8.1-maint documentation (Stem)). 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 múltiples 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 generado ha 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 sí 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 línea:
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 siguiente 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
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
Nota 2: 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 muestra 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 teníamos 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 solicitar nuevas IPs (ver en el código del método new_identity() ), nos asignan la misma IP que teníamos 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 obteníamos 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: Ver en GitHubFrackingAnalysis/PyTorStemPrivoxy
«Scraping anónimo por la red TOR», de Ricardo Moya en jarroba.com, con licencia Creative Commons Reconocimiento-NoComercial-CompartirIgual 3.0. Obra registrada en Safe Creative.

Te puede interesar
«Lo que funciona no se toca» y otras frases que ya no cuelan
Cuatro frases-excusa de empresa, por qué el cálculo ha cambiado con la IA, y qué hacer si estás atrapado en una organización inmovilista.
Trucos para usar la IA bien
Patrones con respaldo empírico para usar la IA: cuándo aplicar CoT, la frontera dentada de BCG, lost in the middle y cómo filtrar el ruido.
Comentarios (2)
-
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.



Deja un comentario