Луковое ПО: используем TOR для анонимного парсинга

Пришло время для анонимного парсинга веб-страниц. В этом тебе поможет мощная команда: TOR, Privoxy, Python, Linux и наша статья.

Где можно использовать? В случаях, когда нужно изменить свой IP-адрес при выполнении нескольких запросов за единицу времени без блокировки соединения.

Настройка

Луковое ПО: используем TOR для анонимного парсинга

Для успешной разработки парсинг-агента на прокси-сервере нужно установить Linux с такими инструментами:

  • TOR: распределенная, анонимная и зашифрованная сеть, в которой данные пользователей и сами пользователи никогда не будут раскрыты.
  • Stem: контроллер Python для TOR.
  • Privoxy: не кеширующий веб-прокси с фильтрацией, повышенной конфиденциальностью и возможностью изменения данных веб-страниц и заголовков HTTP.

TOR (установка и настройка)

Установи TOR через терминал:

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

Далее:

  • включи слушатель "ControlPort" для TOR по порту 9051– в нем TOR будет слушать все сообщения, направленные контроллеру;
  • создай хэш нового пароля, предотвращающий случайный доступ к порту от внешних агентов;
  • реализуй аутентификацию по cookie.

Пароль создаем так:

tor --hash-password my_password

Для примера 1234 превратится в:

16:9529EB03A306DE6F60171DE514EA2FCD49235BAF1E1E55897209679683

Отредактируй или раскоментируй файл /etc/tor/torrc следующим образом:

ControlPort 9051

# hashed password below is obtained via `tor --hash-password my_password`

HashedControlPassword 16:9529EB03A306DE6F60171DE514EA2FCD49235BAF1E1E55897209679683

CookieAuthentication 1

Перезагрузимся:

sudo /etc/init.d/tor restart

Если всплыли какие-либо проблемы, используй ключ --controlport:

tor --controlport 9051 &

Python-Stem

Данный модуль используется для взаимодействия с контроллером Tor и программного отправления/получения команд управления.

sudo apt-get install python-stem

Privoxy

Установка:

sudo apt-get install privoxy

Укажи ему использовать TOR для маршрутизации всего трафика через серверы SOCKS в localhost по порту 9050:

sudo vim /etc/privoxy/configforward-socks5 / 127.0.0.1:9050

Перезагрузись:

sudo /etc/init.d/privoxy restart

Парсинг

После настройки прокси беремся за разработку агента для парсинга страниц с изменением IP-адреса каждые n-запросов. Класс назовем ConnectionManager.py и вставим в него следующую реализацию:

# -*- coding: utf-8 -*-

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

        # Если мы получим тот же ip-адрес, ждем 5 секунд, 
          чтобы запросить новый IP-адрес пока self.old_ip == self.new_ip:
            time.sleep(5)
            seg += 5
            print ("Ожидаем получения нового IP: %s секунд" % seg)
            self.new_ip = self.request("http://icanhazip.com/").read()

        print ("Новое подключение с IP: %s" % self.new_ip)

Примечание 1: это не совсем нормальная практика "жестко" программировать элементы в коде (IP, порт, пароль и т. д.), хотя в этом учебном случае полезно для понимания.

Примечание 2: на сайте ican можно увидеть IP-адрес клиента, делающего запрос. При выполнении запросов через сеть TOR наш IP скрывается, а TOR назначает нам другой, показанный на ican.

В конструкторе класса два атрибута (два IP) – текущий и новый IP, который нужно получить для TOR. Если TOR выдает тот же IP, отбрасываем и запрашиваем новый.

Также в коде есть два публичных метода: request(url) и new_identity(). Первый будет использоваться для обращения к странице, которая передается в качестве параметра, а второй – для назначения нового IP. Приватные методы _get_connection() и _set_url_proxy() нужны для установки нового соединения в сети TOR и отправки запроса через прокси.

Приведем пример изменения IP-адреса после выполнения 3 запросов к странице:

# -*- coding: utf-8 -*-

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()

При выполнении 3 запросов к веб-странице "icanhazip" запрашивается изменение IP вызовом метода new_identity(). Результат выполнения этого кода:

Новое подключение с IP: 185.38.14.171
		        185.38.14.171
		        185.38.14.171
		        185.38.14.171

Ожидаем получения нового IP: 5 секунд
Ожидаем получения нового IP: 10 секунд
Новое подключение с IP: 94.23.173.249
		        94.23.173.249
		        94.23.173.249
		        94.23.173.249

Ожидаем получения нового IP: 5 секунд
Новое подключение с IP: 144.217.99.46
		        144.217.99.46
		        144.217.99.46
		        144.217.99.46

Ожидаем получения нового IP: 5 секунд
Ожидаем получения нового IP: 10 секунд
Новое подключение с IP: 62.210.129.246
		        62.210.129.246
		        62.210.129.246
		        62.210.129.246

Ожидаем получения нового IP: 5 секунд
Ожидаем получения нового IP: 10 секунд
Новое подключение с IP: 185.34.33.2
		        185.34.33.2
		        185.34.33.2
		        185.34.33.2

А теперь более реальный пример. В процессе анонимного парсинга обрабатывается сайт со статьями. IP-адрес перезапрашивается через каждые 5 запросов к странице:анонимного парсинга

 -*- coding: utf-8 -*-

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:
        # если код состояния не 200
        break

    # получаем новый ip, если 5 запросов уже были сделаны
    if i % 5 == 0:
        cm.new_identity()

Результат работы программы:

Новое подключение с 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

Ожидаем получения нового IP: 5 секунд
Новое подключение с 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

Новое подключение с 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

Ожидаем получения нового IP: 5 секунд
Новое подключение с 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/

А вы пользуетесь TOR-ом? Поделитесь опытом.

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Продуктовый аналитик в поддержку
по итогам собеседования
Продуктовый аналитик
Екатеринбург, по итогам собеседования
Аналитик данных
Екатеринбург, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ