Torで接続元を匿名化してスクレイピングしてみる

Torで接続元を匿名化してスクレイピングしてみる

Torネットワークを使うことで接続元IPアドレスを秘匿化してWebスクレイピングできるかを検証してみました。

Webサイトをスクレイピングする際、同じIPアドレスからリクエストし続けると運営側からアカウントをBANされるリスクがあります。
処理途中でランダムに待機を入れて人が操作するかのよう振る舞ったとしても、同一の接続元からリクエストすることに変わりはないためBANを回避する方法として不十分です。
※少量のリクエストで済むなら同一の接続元でも可能だと思いますが、数千以上のリクエストをする場合はIPの分散を考えたいところです。

そこで、何とか接続元を秘匿化(ローテーション)する方法はないかと探していたところ、Torというインターネット上の通信を秘匿化するプロジェクトの存在を知ったので試しにDockerコンテナで検証してみました。
コードはGitHubにあげています。

Torとは

Torは”The Onion Router”の頭文字で、インターネット上の通信を秘匿化するプロジェクトの名称です。
Torネットワークを使うことでユーザーはインターネット上のコンテンツに匿名で(IPアドレスを適宜切り替えながら)アクセスできます。
Torの仕組みやプロジェクトの経緯はこちらの記事が詳しいです。


環境

本記事で紹介するコードはDockerコンテナでの実行を想定しています。
もしDockerを利用できない場合でも、Linux環境であれば以下のパッケージをインストールすれば動作するはずです。

Debianパッケージ
apt-get update && apt-get install -y tor privoxy
Pythonライブラリ(Python3.9)
pip install pandas==1.4.0
pip install numpy==1.22.2
pip install beautifulsoup4==4.10.0
pip install lxml==4.7.1
pip install PySocks==1.7.1
pip install stem==1.8.0
pip install click==8.0.4

実装

Torでは、SOCKS5プロキシ経由でWebにアクセスすることで接続元IPアドレスを秘匿化できます。
Torのデフォルト設定では127.0.0.1:9050でSOCKS5プロキシとの接続をリッスンし、Torネットワーク上でトラフィックをリレーしていきます。
※設定ファイルはコンテナ内の/etc/tor/torsocks.confを参照

引用: How to Stay Anonymous on the Internet using TOR Network?

Torではデフォルト設定だと10分ごとにIPアドレスが切り替わりますが、Torを再起動することでもIPアドレスを切り替えられます。

まずはTorネットワークを使うことで接続元IPアドレスが切り替わることを検証します。
以下の処理を5回繰り返し、それぞれの処理で別のIPアドレスが割り当てられていることを確認します。

  1. Torを再起動する
  2. アクセス元のグローバルIPアドレスを取得する

main.py

import urllib.request, urllib.error
import click
from bs4 import BeautifulSoup
import socks, socket
import subprocess
import logging.config
import time
import random


# ランダムにスリープさせる
def wait():
    time.sleep(random.randint(2,5))


# Torを再起動する
def restart_tor():
    socks.set_default_proxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', 9050)
    socket.socket = socks.socksocket
    # restart TOR
    subprocess.run(["killall -HUP tor"], shell=True)
    subprocess.run(["/etc/init.d/tor restart"], shell=True)


# clickパラメータはDockerコンテナ複数起動の検証で使用する
@click.command()
@click.option(
    "--seq",
    type=int,
    default=0,
    required=False
)
def scraping(seq):
    logging.config.fileConfig("config/logging.ini")
    logging.getLogger("__name__")

    for i in range(5):
        
        restart_tor()
        
        res = urllib.request.urlopen('http://checkip.dyndns.com/')
        html = BeautifulSoup(res, 'html.parser')
        current_ip = html.body.text.split(': ')[1]
        
        wait()

        logging.info(f'[{seq}]Current IP address is {current_ip}')


if __name__ == "__main__":
    scraping()

Dockerコンテナを実行し、ログを確認します。

docker-compose run --rm app python -m main
cat /log/scraping.log
[2022-02-19 00:29:23,402][INFO](main.py:48) [0]Current IP address is 62.102.148.68
[2022-02-19 00:29:27,700][INFO](main.py:48) [0]Current IP address is 205.185.126.167
[2022-02-19 00:29:33,724][INFO](main.py:48) [0]Current IP address is 89.58.27.84
[2022-02-19 00:29:38,757][INFO](main.py:48) [0]Current IP address is 198.98.61.131
[2022-02-19 00:29:44,766][INFO](main.py:48) [0]Current IP address is 109.70.100.35

処理ごとにIPアドレスが切り替わっていることを確認できました。

コンテナを並列起動

先ほどは1つのDockerコンテナ内でTorを再起動させて接続元を匿名化できることを確認しました。
次はDockerコンテナを複数非同期で実行させても接続元IPを匿名化できるかを検証します。

今回はコンテナを5つ非同期で実行させて、それぞれ別のIPアドレスが割り当てられているかを確認します。
まず、単純にコンテナを非同期実行させるShellを作成します。
run.sh

docker-compose run --rm app python -m main --seq=0 &
docker-compose run --rm app python -m main --seq=1 &
docker-compose run --rm app python -m main --seq=2 &
docker-compose run --rm app python -m main --seq=3 &
docker-compose run --rm app python -m main --seq=4

Shellを実行させ、ログを確認します。

./run.sh
/log/scraping.log
[2022-02-19 00:27:12,496][INFO](main.py:48) [0]Current IP address is 141.136.0.117
[2022-02-19 00:27:15,506][INFO](main.py:48) [4]Current IP address is 89.234.157.254
[2022-02-19 00:27:15,572][INFO](main.py:48) [3]Current IP address is 185.244.31.4
[2022-02-19 00:27:17,019][INFO](main.py:48) [1]Current IP address is 193.218.118.158
[2022-02-19 00:27:19,460][INFO](main.py:48) [0]Current IP address is 51.15.244.188
[2022-02-19 00:27:19,773][INFO](main.py:48) [2]Current IP address is 185.220.102.244
[2022-02-19 00:27:20,629][INFO](main.py:48) [3]Current IP address is 185.100.85.25
[2022-02-19 00:27:20,741][INFO](main.py:48) [4]Current IP address is 185.220.101.69
[2022-02-19 00:27:21,310][INFO](main.py:48) [1]Current IP address is 81.17.18.58
[2022-02-19 00:27:23,297][INFO](main.py:48) [0]Current IP address is 185.220.102.240
[2022-02-19 00:27:27,271][INFO](main.py:48) [2]Current IP address is 81.17.18.58
[2022-02-19 00:27:27,428][INFO](main.py:48) [3]Current IP address is 89.163.252.230
[2022-02-19 00:27:27,626][INFO](main.py:48) [4]Current IP address is 185.107.70.56
[2022-02-19 00:27:27,740][INFO](main.py:48) [0]Current IP address is 185.220.102.249
[2022-02-19 00:27:28,310][INFO](main.py:48) [1]Current IP address is 45.12.134.108
[2022-02-19 00:27:31,644][INFO](main.py:48) [4]Current IP address is 185.220.101.59
[2022-02-19 00:27:33,601][INFO](main.py:48) [0]Current IP address is 109.70.100.26
[2022-02-19 00:27:34,305][INFO](main.py:48) [2]Current IP address is 45.153.160.132
[2022-02-19 00:27:35,253][INFO](main.py:48) [1]Current IP address is 185.220.101.56
[2022-02-19 00:27:35,914][INFO](main.py:48) [3]Current IP address is 62.102.148.68
[2022-02-19 00:27:39,092][INFO](main.py:48) [4]Current IP address is 95.154.24.73
[2022-02-19 00:27:39,998][INFO](main.py:48) [3]Current IP address is 109.70.100.84
[2022-02-19 00:27:41,313][INFO](main.py:48) [2]Current IP address is 5.2.69.50
[2022-02-19 00:27:42,689][INFO](main.py:48) [1]Current IP address is 104.244.75.80
[2022-02-19 00:27:45,331][INFO](main.py:48) [2]Current IP address is 185.220.101.32

どのDockerコンテナでも別々の接続元IPアドレスが割り当てられていることを確認できました。

最後に

Torネットワークを使うことで接続元IPアドレスを秘匿化できることが分かりました。
また、Dockerコンテナを複数非同期で実行させても接続元を匿名化できたので、1つのPCでも並列化のさせ方次第で大量のリクエストが可能になるなと思いました。

参考