• 作成:

Pythonでwebスクレイピングをする時に必要だった知識

Pythonでスクレイピングする仕事が降ってきました

半月前ぐらいに、 Pythonでwebサイトをスクレイピングする仕事が降ってきました。

自分が始めるならHaskellやScala、他人が読み書き出来ることを要求されてもTypeScriptあたりで書くのですが、まあ既存のスクレイピングコードがPythonでそれに追加することを要求されたので仕方がありません。

と言うわけでちょっと書いていました。仕事があるたびに即座に(その日以内あたり)終わらせていた気がするので、あまり本格的にはやっていませんが。

必要だったライブラリを書いていきます。

ルールを守って楽しくスクレイピング!

requests

Requests: HTTP for Humans™ — Requests 2.24.0 documentation

これは自分で探してきたと言うより元々使われていたものですね。

色々調べましたが最終的にはallow_redirects=Falsegetメソッドに設定して不意のリダイレクトを防ぐぐらいしか使いませんでしたね。

mypyはtypeshedに登録してある型定義を自動的に認識すると見ましたが、全然読み込まれません。どうなってるんですか。

bs4

beautifulsoup4 · PyPI

これも前から使われていたものですね。

CSSクエリでselect出来るのは良いのですが、 lxmlだとパースできないHTMLが存在してhtml5libを使う必要があったり、 textプロパティにHTMLソースコード中の無視されるべきスペースが入っていたりでちょっとつらいです。

get_textstripを付けると方法は改行含めて消してしまうのでダメです。 stripped_stringsもちゃんと消していないのでダメです。

とりあえず今回の要件では本当に行頭にスペース入れてる場合は考慮しなくても良かったのでサクッと正規表現で加工しましたが。

bs4の対応パーサはバギーなHTMLに対応してなかったりテキスト取得がつらいので厳しい。

やはりwebのスクレイピングを真面目にやるなら動的コンテンツを含んでいなくてもHeadless Chrome/Firefoxなのですかね、でもHeadlessブラウザ立ち上げるのは流石に重たいので、今後jsdomなどはちゃんとしているのか調査したいです。

retry

主に

があるみたいですがまあどちらでも良いのではないですか。私はretryを選びましたが。

サーバの問題なのかクライアントの問題なのか時折コネクション生成に失敗するのでその時は時間をかけて3回ぐらいは再試行したい所です。

最初は自前で再帰していましたが、 Pythonの変数スコープは型チェック時にあまりちゃんとチェックしてくれないので、 expect部で例外を起こしてしまったりしてつらい。

スクレイピングで何か間違えた時はraise ValueError(url)のようにURLを情報に含めると便利。

concurrent.futures.ProcessPoolExecutor

concurrent.futures -- 並列タスク実行 — Python 3.9.0 ドキュメント

データを読み込んでパースして結果を出しますが、パースしている時にデータを読み込んでいないのは無駄ですね。

並列処理で解決です。

やる前に相手がsitemapを公開しているかとかrobots.txtに拒否設定書いてないかとか確認しましょう。いや並列じゃなくても確認はするべきですが。

ThreadPoolExecutor とか asyncio とかはDOMの解析は速くしなさそうなので微妙に思えます。 asyncioの記述めっちゃ面倒に見えますし…

実際はワーカープロセスへの変数の受け渡しのオーバーヘッドで遅くなる可能性もありそうですね。将来的にDOMの処理量が増えていった場合優位なのはProcessPoolExecutorでしょうけど。

サイトマップを見て収集する対象のURLの配列を確保したら、 mapに突っ込んであげましょう。

tqdm

tqdm/tqdm: A Fast, Extensible Progress Bar for Python and CLI

量が多いと本当に進んでるのか不安になるのでプログレスバーは欲しいですね。

マルチプロセスに処理を投げてもexecutor.mapは普通にイテレータを返すようなので、 total=len(urls)のように設定しておけば動きます。

random.sample

random --- 擬似乱数を生成する — Python 3.9.0 ドキュメント

たくさん収集した後処理コードが思いっきり間違ってるとかだったら悲惨なので、最初に100件ぐらいURLを限定して走らせた方が良いでしょう。

総論

やっぱりPythonはwebスクレイピングに不向きな言語だと思います。たくさん収集した後にランタイムエラーが出たら悲惨ですし、 GILがあるから並列処理も気軽に書けません。

可能なら他の言語を使いたいですね。