Help us understand the problem. What is going on with this article?

python + selenium + Firefox + socks でスクレイピングしたときにハマったことと解決策

はじめに

タイトルのとおりの変なことをしようとして色々ハマり、調べて解決したのでまとめておきます。

環境

$ cat /etc/centos-release
CentOS Linux release 8.0.1905 (Core)
$ python -V
Python 3.7.4
$ pip show selenium
Version: 3.141.0

Firefox: 69.0.2
geckodriver: v0.25.0

ハマりと解決

getting started

selenium - PyPIに従ってやっていきましょう。要約すると以下のとおりです。

  1. pip install selenium
  2. geckodriverをダウンロードしてPATHの通った場所に置く
  3. サンプルコードを動かす
サンプルコード(転載)
from selenium import webdriver

browser = webdriver.Firefox()
browser.get('http://seleniumhq.org/')

たったこれだけなのにハマりました。

ハマり:webdriver.Firefox()でエラーが出る → 解決:Firefoxを入れる

今確認できず正確なメッセージが記載できませんが、capabilities云々のエラーが出ました。capabilitiesとはなんじゃいと思いFirefox webdriverのドキュメントを読むと以下の記述がありました。

Based on the combination and specificity of the various keyword arguments, a capabilities dictionary will be constructed that is passed to the remote end.
The keyword arguments given to this constructor are helpers to more easily allow Firefox WebDriver sessions to be customised with different options. They are mapped on to a capabilities dictionary that is passed on to the remote end.
As some of the options, such as firefox_profile and options.profile are mutually exclusive, precedence is given from how specific the setting is. capabilities is the least specific keyword argument, followed by options, followed by firefox_binary and firefox_profile.

つまりcapabilitiesとはその名の引数だけではなく色んなものがマージされてできていて、その中にfirefox_binaryが含まれ……ってFirefoxインストールしてない! と気づきました。

ハマり:Firefoxを入れるってことはデスクトップ環境が必要なの? → 解決:headlessモードで動かせば良い

スクレイピングを行うマシンはサーバとしてセットアップしていて、デスクトップ環境を入れたくありませんでした。調べたところ、最終的にはFirefoxをheadlessモードで動作させればよいということがわかりました。
headlessモードにする方法も色々あるようですが、本記事の環境では以下のとおりです。

options = FirefoxOptions()
options.headless = True
browser = webdriver.Firefox(options=options)

ハマり:まだwebdriver.Firefox()でエラーが出る → 解決:Firefoxの依存関係を解決する

これも正確なメッセージが記載できませんが、"cannot kill exited process"といったようなエラーメッセージが出ていました。調べても解決しませんでしたが、ふとgeckodriver.logに気づき見ると以下のようなログが出ていました。

XPCOMGlueLoad error for file /path/to/firefox/libmozgtk.so:
libgtk-3.so.0: cannot open shared object file: No such file or directory

実は、Firefoxをパッケージマネージャでインストールすると依存が多いのが嫌で、webサイトからアーカイブをダウンロードしていました。そのため依存ライブラリが入っていませんでした。
本記事の環境ではgtk3libXtをインストールして解決しました。

ハマり:なんか動作が重くなってきた → 解決:quit()

サンプルコードを何度も動かしていたらなんだかマシンが重くなってきました。pgrep firefoxするとなかなかの数がヒットしました。これは明示的に終了させなければなりません。

browser = webdriver.Firefox()
try:
    # do something
finally:
    browser.quit()

socks

ハマり:プロキシが設定できない → 解決:プロファイルを使う

プロキシ設定は、どこにどう設定するのか、バリエーションが多すぎます。

どこに

  • webdriver.Firefox()proxyキーワード引数
  • Optionsproxyプロパティ
  • FirefoxProfileset_proxy()メソッド
  • Proxy.add_to_capabilities()してcapabilitieswebdriver.Firefox()に渡す

どう

  • プロパティに直接host:port文字列を渡す
  • Proxyインスタンスを作る
    • httpProxysocks5://host:portを渡す
    • socksProxyhost:portを渡す

試行錯誤の末、以下のようにして設定できました。

proxy = Proxy()
proxy.socksProxy = f'{host}:{port}'
proxy.proxyType = ProxyType.MANUAL
profile = FirefoxProfile()
profile.set_proxy(proxy)
browser = Firefox(firefox_profile=profile)

ちなみにset_proxy()の呼び出しで以下のように怒られます。

DeprecationWarning: This method has been deprecated. Please pass in the proxy object to the Driver Object
  profile.set_proxy(proxy)

それで動かんかったからこうしとんのじゃ。

ハマり:プロキシに名前解決させたい → プロファイルを使う

結局、プロファイルとはFirefoxの設定を含んでいて、Firefoxは設定でsocksプロキシに名前解決させるかどうかを決定できるので、プロファイルを直接いじる1しかありません。
では「socksプロキシに名前解決させるかどうか」はどういうキーなのか。調べたところこんなページに当たりました。これによると、network.proxy.socks_remote_dnsだそうです。

profile.set_preference('network.proxy.socks_remote_dns', True)

その他

ハマり:WebElement.textで要素のテキストが取れない → get_property('textContent')を使う

textプロパティでは空文字列が返ります。要素の下にテキスト要素があるという構造なのかもしれませんが、WebElementには「idなどを何も指定せず、子要素を取得する」というメソッドがないため、アクセス不能です。
get_property()メソッドでDOMプロパティが取得できるのでこれを使います。テキストはtextContentプロパティで取れます。

elem.get_property('textContent')

innerHTML, innerTextでも大丈夫かと思います。

終わりに

selenium周りはseleniumライブラリ以外にもWebドライバ・ブラウザ・OS等環境要因が色々あり、更にそれぞれのバージョンで互換性があったりなかったりするので混乱しました。様々な言語のバインディングがあるのも調べづらさに拍車をかけていると思います。それだけ人気のプロジェクトであるということだとは思うのですが……。


  1. プロキシ設定もそれでできると思います 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした