Torをスクレイピングで使いやすくするPythonのモジュール作ってみた



TorをPythonスクレイピングに流用しやすくするためのモジュールを作成しました。Torをスクレイピングに流用することによって、IPによる制限を回避することが容易になります。

PythonのスクレイピングでTorを利用する方法はこちらの記事を参考にしてください。

複数のIPアドレスを同時に使い分けたり、reCaptchaが出現した時のリトライ、匿名性の維持などにも役立つため、是非導入していただければと思います。

ソースはnoteで公開しています。

https://note.mu/katsuwo315/n/nc75c0772c27a

事前準備:Torエキスパートバンドル版のインストール

Tor本体のダウンロード

Torのエキスパートバンドル版を公式サイトからダウンロード

リンクはwin32版のエキスパートバンドルへの直リンクです。

ダウンロードしたTor本体を解凍

解凍した状態でのフォルダの構造はこんな感じです。

Tor
├─ libeay32.dll
├─ libevent-2-1-6.dll
├─ libevent_core-2-1-6.dll
├─ libevent_extra-2-1-6.dll
├─ libgcc_s_sjlj-1.dll
├─ libssp-0.dll
├─ libwinpthread-1.dll
├─ ssleay32.dll
├─ tor-gencert.exe
├─ tor.exe
└─ zlib1.dll

tor.exeがTorの本体です。

解凍して、任意のディレクトリTorフォルダを設置してください。
こだわりがなければ以下の例に沿って設置していただければ問題は起きにくいと思います。

TorのディレクトリにPathを通す

例えば以下のフォルダに上で解凍したTorフォルダを設置したとします。

C:
 └─ProgramData
    └─Tor
       ├─ libeay32.dll
       ├─ libevent-2-1-6.dll
       ├─ libevent_core-2-1-6.dll
       ├─ libevent_extra-2-1-6.dll
       ├─ libgcc_s_sjlj-1.dll
       ├─ libssp-0.dll
       ├─ libwinpthread-1.dll
       ├─ ssleay32.dll
       ├─ tor-gencert.exe
       ├─ tor.exe
       └─ zlib1.dll

すると、Pathを通すディレクトリは以下のようになります。

C:ProgramDataTor

システム環境変数を検索して開きます

システム環境変数

システム環境変数を選択

Pathを選択

Pathを選択

新規を選択して出てきた項目に先ほどのPathをコピペ

C:ProgramDataTor

これを追加してOKを押します。

Pathが通ったか確認

コマンドプロンプトを立ち上げる

どのディレクトリでもいいので"tor"と入力する

プログラムが立ち上がればOK

'tor' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

このメッセージが出る場合は設定が間違っていますので、確認してください。

Bootstrapped 100% (done)というメッセージまで進むようであれば、前提のTor本体の設定は完了です。

モジュールの依存関係のライブラリをインストールする

標準モジュール以外で依存関係にあるライブラリは以下です。
全てpipでインストール可能です。

pip install stem
pip install attrdict

このTor操作モジュールでできる事と基本操作

TorをPythonから扱いやすくするためのモジュールです。
Python3系、Windowsを前提として作成しました。

ここでは、基本的な操作方法とできる事を紹介します。

基本的な使い方:インスタンスの生成

※インポートするにはモジュールのファイルをパスに配置する必要があります。

# インポート
from tor_control import TOR
# インスタンスの生成
tor = TOR()

モジュール、Tor本体共にPathが通っている場合はこれでインスタンスが作成されます。

そうでない場合は、インスタンス生成時に各種Pathを引き渡す必要があります。
設定用のtorrcファイルをがある場合も、インスタンスの生成時に指定できます。

tor_path = "C:ProgramDataTor"
rc_path = "C:UsersOwnerAppDataRoamingtortorrc"

tor = TOR(tor_path=tor_path, rc_path=rc_path)

torrcを設定しない場合はディフォルトで起動されますので、なくてもインスタンスの生成は可能です。

Torを起動させるコマンド

生成したインスタンスに対して、色々なコマンドを出すことが出来ます。

モジュールにはいくつかのTorを起動するためのコマンドがあります。
少しずつ役割が違いますので、紹介します。

runメソッド:通常起動

tor.run()

Tor starting port: 9050
Out:0

runメソッドは通常起動用のメソッドです。
起動されたポート番号が標準出力されます。

0がreturnされてきている場合は、起動成功を意味しています。
何らかのエラーで起動のコマンドが通らなかった場合は0以外の数字が返ってきます。

runメソッド:ポートを指定しての起動

runメソッドに引数を渡さない場合は、通常のTor起動と同じ9050番のポートを開放して起動します。

tor.run(9060)

Tor starting port: 9060
Out:0

int(整数型)の数値を引数としてrunメソッドに渡すとそのポート番号でTorが起動します。

multiメソッド:複数のTorを起動

複数のTorを起動させ、同時に複数のIPアドレスを利用してインターネットにアクセスすることが出来ます。もちろん、スクレイピングなどにも利用できます。

tor.multi()

Out:
Tor starting port: 9070
Tor starting port: 9080
Tor starting port: 9090

何も引数に指定しなければ3つのTorを起動させます。
利用するportは極力、衝突を回避するように設計しています。

tor.multi(5)

Out:
Tor starting port: 9050
Tor starting port: 9060
Tor starting port: 9070
Tor starting port: 9080
Tor starting port: 9090

整数を引数にして渡すと、その数だけTorを起動させます。

Torを終了させるコマンド

Torを終了させるコマンドは二つあります。

killメソッド:portを指定して終了させる

portを整数で指定して、Torを終了させます。killした結果、残っているportをリスト型で返却します。

tor.kill(9160)

Out:[9050, 9060, 9070, 9080, 9090, 9100, 9110, 9120, 9130, 9140, 9150]

force_killメソッド:複数のTorをリストで指定して終了させる

tor.force_kill([9120, 9130, 9140, 9150])

返却はありません。

force_killメソッド: 全てのTorを終了させる

引数を渡さないと、全てのTorを終了させます。

tor.force_kill()

プロキシの情報を返却するコマンド

現状起動しているTorの情報を返却するメソッドと、アトリビュートを紹介します。

socksメソッド:socksポートを指定するための文字列を返却

他のライブラリでsocks形式でプロキシを指定するための文字列を返却するメソッドです。

tor.socks()
Out:'socks5://127.0.0.1:9050'

引数を渡さないと、現状起動されているTorが使っているportの中から最小のものを返却します。

socksメソッド:requestsにプロキシを指定する

socksメソッドを利用して、このような形でrequestsにプロキシの指定をすることが可能です。

import requests

# proxies変数を作成
proxies = {
   'https': tor.socks()
}

url = "https://ipinfo.io"
# getメソッドproxies引数に渡す
requests.get(url, proxies=proxies).json()

Out:{'ip': '23.129.64.192',
 'hostname': '192.emeraldonion.org',
 'city': 'Seattle',
 'region': 'Washington',
 'country': 'US',
 'loc': '47.6036,-122.3256',
 'org': 'AS396507 Emerald Onion',
 'postal': '98104',
 'timezone': 'America/Los_Angeles',
 'readme': 'https://ipinfo.io/missingauth'}

socksメソッド:selenium(Chrome)にプロキシを指定する

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
# optionsで指定する
options.add_argument(f'--proxy-server={tor.socks()}')
# 指定したoptionsでchromeを起動
driver = webdriver.Chrome(options=options)

driver.get('https://ipinfo.io')

seleniumでアクセスした結果がこちら

selenium(Chrome)にプロキシを指定する

socksメソッド:利用するsocksポートを指定

利用するsocksを整数型で指定することが可能です。
起動されているTorがそのportを開放していない場合は起動を伴って文字列を返却します。

# 指定したportでTorが起動されていなければ、そのportでTorを起動します。
# 更に、socksプロキシ指定用の文字列を返却します。
tor.socks(9060)
Out:'socks5://127.0.0.1:9060'

socksメソッド:requestsのproxies引数に直接渡せる形で返却する

socksメソッドのfor_req引数にTrueを渡すと、requestsのproxies引数に直接渡せるdict型のデータを返却します。

# for_req引数にTrueを渡す
tor.socks(for_req=True)
# proxies引数に直接渡せる形の辞書データが返ってくる。
Out:{'http': 'socks5://127.0.0.1:9050', 'https': 'socks5://127.0.0.1:9050'}

# 実際に直接引き渡してみる
url = "https://ipinfo.io"
requests.get(url, proxies=tor.socks(for_req=True)).json()

Out:{'ip': '173.244.209.5',
 'hostname': 'slc-exit.privateinternetaccess.com',
 'city': 'New York',
 'region': 'New York',
 'country': 'US',
 'loc': '40.7145,-74.0029',
 'org': 'AS29854 WestHost, Inc.',
 'postal': '10118',
 'timezone': 'America/New_York',
 'readme': 'https://ipinfo.io/missingauth'}

socksメソッド: portをランダムで返却する

Torを複数起動している場合、rand引数にTrueを渡すと、現在オープンしているportの中からランダムに選択した文字列を返却します。

これは複数のIPを入れ替わり立ち代わり、ランダムに利用できることを意味します。for_req引数との組み合わせでも動作します。

# 呼び出される度に起動中のTorが使っているportをランダムに取得して返却します
tor.socks(rand=True, for_req=True)
Out:{'http': 'socks5://127.0.0.1:9060', 'https': 'socks5://127.0.0.1:9060'}

tor.socks(rand=True, for_req=True)
Out:{'http': 'socks5://127.0.0.1:9070', 'https': 'socks5://127.0.0.1:9070'}

tor.socks(rand=True, for_req=True)
Out:{'http': 'socks5://127.0.0.1:9050', 'https': 'socks5://127.0.0.1:9050'}

実用的に使うと、このようなコードになります。

# 10回アクセスしてIPを調べる。
# ipはtor.socks(rand=True, for_req=True)で毎回ランダムに取得される
# multiメソッドなどで複数のTorが立ち上がっていることが条件です。

url = "https://ipinfo.io"
for i in range(10):
    res = requests.get(url, proxies=tor.socks(rand=True, for_req=True))
    print(res.json()['ip'])

Out:199.249.230.122
Out:199.249.230.66
Out:199.249.230.75
Out:162.247.74.27
Out:64.113.32.29
Out:162.247.74.201
Out:162.247.74.27
Out:173.244.209.5
Out:199.249.230.66
Out:162.247.74.201

繰り返しアクセスしても、ランダムに別のTorサーキットを使うのでIPが毎回変わっています。

reqメソッド:requestsのproxies引数に直接渡せる形で返却する

socksメソッドにfor_req=Trueを渡した時と同じ動作をします。基本的には短縮形です。

tor.req()

Out:{'http': 'socks5://127.0.0.1:9050', 'https': 'socks5://127.0.0.1:9050'}

portを指定することも可能。

tor.req(9080)

Out:{'http': 'socks5://127.0.0.1:9080', 'https': 'socks5://127.0.0.1:9080'}

rand引数も使えます。

tor.req(rand=True)

Out:{'http': 'socks5://127.0.0.1:9110', 'https': 'socks5://127.0.0.1:9110'}

少しsocksメソッドと違うのは、reqメソッドでportを指定した場合、ディフォルトでは、そのportが実際にTorが使っているかの確認は取りません。
つまり、実際に使われていないportを指定しても結果を返します。

tor.req(999999)

Out:{'http': 'socks5://127.0.0.1:999999', 'https': 'socks5://127.0.0.1:999999'}

# そのportの起動確認を取りたい場合、run引数を使います。
tor.req(10000, run=True)

Tor starting port: 10000
Out:{'http': 'socks5://127.0.0.1:10000', 'https': 'socks5://127.0.0.1:10000'}

Torの起動状況を確認するコマンド

Torの起動状況を確認するためのアトリビュートを紹介します。

Torが利用しているportの確認

現在起動しているTorが使っているportの中で最小のport番号を返却します。

tor.port
Out: 9050

Torが利用している全てのportの確認

tor.ports

Out: [9050, 9060, 9070, 9080, 9090, 9100, 9110, 9120, 9130]

Torが利用しているtorrcのファイルpathの確認

tor.torrc
Out: WindowsPath('C:/Users/Owner/AppData/Roaming/tor/torrc')

type(tor.torrc)
Out: pathlib.WindowsPath

str(tor.torrc)
Out:'C:UsersOwnerAppDataRoamingtortorrc'

Pathはpathlibのオブジェクト形式で返却されます。

Torのconfig関係のコマンド

Torのconfigをこのモジュールを通して設定することも可能です。
各設定項目はこちらで確認できます。

setアトリビュート:現在のtorrcの設定を参照

インスタンス生成時に、torrcファイルにある設定を自動的に読み込みます。

torrcで設定した項目に追加して、よく使いそうな項目をディフォルトでセットしています。以下のコマンドで設定を確認できます。

tor.set
Out: AttrDict({'CircuitBuildTimeout': '', 'NewCircuitPeriod': '', 'MaxCircuitDirtiness': '30', 'NumEntryGuards': '100', 'ExcludeExitNodes': '200.9.255.32', 'ExcludeNodes': 'SlowServer,200.9.255.32', 'EntryNodes': '', 'ExitNodes': '', 'StrictNodes': '1'})

tor起動時はこの設定を元に起動されます。

setアトリビュート:設定項目を追加/書き換える

tor.setに設定項目名をアトリビュートで続けて参照/代入できます。
設定を書き換えると、新たにTorを起動するコマンドを使った時にその設定を使って起動します。

tor.set.MaxCircuitDirtiness = 60

tor.set.MaxCircuitDirtiness
Out: 60

ディフォルトで表示されない項目名でもここで追加設定することが可能です。
アトリビュートで項目名にアクセスして、直接代入します。
項目名が間違えていると、Tor起動を拒否される場合があります。

tor.set.ClientOnly = 1

apply_settingメソッド:既に起動済みのTorに現在の設定を反映させる

設定を書き換えただけでは、現在すでに起動しているTorに対してその設定は反映されません

設定を起動済みのTorに反映させるためにはapply_settingメソッドを使います。

# portを指定して起動済みのTorに現在の設定を反映させる
tor.apply_setting(9050)

# portをリストで指定することも可能
tor.apply_setting([9050, 9060, 9070])

# portを指定しなければ、起動済みの全てのTorに対して設定を適用
tor.apply_setting()

write_rcメソッド:setアトリビュートの内容をテキストにしてtorrcに出力

現状のsetアトリビュートに登録されている設定内容を元に、新規にtorrcファイルとして出力します。出力先のファイルパスはtorrcアトリビュートで確認できます。

# 出力先のファイルパスを確認できます。
tor.torrc
Out: WindowsPath('C:/Users/Owner/AppData/Roaming/tor/torrc')

tor.write_rc()

出力されたtorrcファイルは以前のtorrcファイルに上書きはされません。
古いtorrcファイルは "torrc.orig.x"として別に保存されます

stemモジュールのコマンドを使う

より、上級者向けの操作として、tor_controlモジュールは、stemモジュールのラッパー的な役割も果たします。

実行しているTor全てに対してcontrol portを設定しています。control port を通して、起動済みのTorを自在に操作できる、stem.control.Controllerオブジェトを内部的に生成しています。

ctrlアトリビュート:Controllerオブジェクトへのアクセス

# port番号とオブジェトが辞書型で確認できる
tor.ctrl
Out: {9050: <stem.control.Controller at 0x2c9249eae48>}

# これでControllerオブジェクトへアクセスできる。
tor.ctrl[9050]
Out: <stem.control.Controller at 0x2c9249eae48>

# Controllerオブジェクトで使えるコマンドを続ける事が出来る。

stemモジュールのcontrol.Controller オブジェクトをインスタンス内に格納しています。Controllerオブジェクトに対して有効なコマンドは、基本的に全て使うことが出来ます。

以下に実行可能なコマンド一覧を記載します。(stemドキュメントより日本語訳しています。リンクは下部)

Controller - General controller class intended for direct use
|-from_port-ポート接続に基づいてコントローラーを提供します。
+-from_socket_file-ソケットファイル接続に基づいてコントローラーを提供します。
|-authenticate-torでこのコントローラーを認証します
|-reconnect-再接続してソケットに認証します
|-get_info-パラメーターのGETINFOクエリを発行します
|-get_version-torバージョンを提供します
|-get_exit_policy-終了ポリシーを提供します
|-get_ports-torが接続をリッスンしているローカルポートを提供します
|-get_listeners-torが接続をリッスンしているアドレスとポートを提供します
|-get_accounting_stats-中継制限に関連する統計を提供します
|-get_protocolinfo-コントローラーインターフェースに関する情報
|-get_user-torが実行されているユーザーを提供します
|-get_pid-torプロセスのpidを提供します
|-get_start_time-torプロセスが開始したときのタイムスタンプ
|-get_uptime-持続時間torが実行されています
|-is_user_traffic_allowed-直接のユーザートラフィックを送受信するかどうかを確認します
|-get_microdescriptor-リレーのマイクロディスクリプタを照会する
|-get_microdescriptors-現在利用可能なすべてのマイクロディスクリプタを提供します
|-get_server_descriptor-リレーのサーバー記述子を照会する
|-get_server_descriptors-現在利用可能なすべてのサーバー記述子を提供します
|-get_network_status-リレーのルーターステータスエントリのクエリ
|-get_network_statuses-現在利用可能なすべてのルーターステータスエントリを提供します
|-get_hidden_​​service_descriptor-指定された隠しサービス記述子を照会します
|-get_conf-構成オプションの値を取得
|-get_conf_map-複数の構成オプションの値を取得します
|-is_set-オプションがデフォルトと異なるかどうかを決定します
|-set_conf-構成オプションの値を設定します
|-reset_conf-構成オプションをデフォルト値に戻します
|-set_options-複数の構成オプションの値を設定またはリセットします
|-get_hidden_​​service_conf-非表示のサービス構成を提供します
|-set_hidden_​​service_conf-非表示のサービス構成を設定します
|-create_hidden_​​service-新しい非表示サービスを作成するか、新しいポートを追加します
|-remove_hidden_​​service-非表示のサービスを削除するか、ポートをドロップします
|-list_ephemeral_hidden_​​services-一時的な隠されたサービスのリスト
|-create_ephemeral_hidden_​​service-新しい一時的な非表示サービスを作成する
|-remove_ephemeral_hidden_​​service-一時的な非表示サービスを削除します
|-add_event_listener-torイベントの通知を受けるイベントリスナーをアタッチします
|-remove_event_listener-リスナーを削除して、以降のイベントが通知されないようにします
|-is_caching_enabled-コントローラーがキャッシュを有効にしている場合はtrue
|-set_caching-キャッシュを有効または無効にする
|-clear_cache-キャッシュされた結果をクリアします
|-load_conf-設定情報をtorrcにあるかのようにロードします
|-save_conf-設定情報をtorrcに保存します
|-is_feature_enabled-特定のコントローラー機能が有効かどうかを確認します
|-enable_feature-デフォルトで無効になっているコントローラー機能を有効にします
|-get_circuit-アクティブな回路を提供します
|-get_circuits-アクティブな回路のリストを提供します
|-new_circuit-新しい回路を作成する
|-extend_circuit-新しい回路を作成し、既存の回路を拡張します
|-repurpose_circuit-回路の目的を変更する
|-close_circuit-回路を閉じる
|-get_streams-アクティブなストリームのリストを提供します
|-attach_stream-ストリームを回線に接続します
|-close_stream-ストリームを閉じる
|-signal-torクライアントにシグナルを送信します
|-is_newnym_available-torが現在NEWNYMシグナルを受け入れる場合はtrue
|-get_newnym_wait-torがNEWNYMシグナルを受け入れるまでの秒数
|-get_effective_rate-効果的な中継レート制限を提供します
|-is_geoip_unavailable-geoip dbが利用できないことがわかった場合はtrue
|-map_address-あるアドレスを別のアドレスにマップして、元のアドレスへの接続を別のアドレスに置き換えます
+-drop_guards-ガードリレーのセットをドロップし、新しいセットを選択します

詳細は:https://stem.torproject.org/api/control.html

例えば、設定値をディフォルトに戻したい場合は以下のように入力するとControllerを利用できます。

tor.ctrl[9050].reset_conf('ExitNodes')

ソースコードはnoteで公開しています。

https://note.mu/katsuwo315/n/nc75c0772c27a