Pythonウェブクローラー学習ノート
Pythonウェブクローラー学習ノート
By 中環(ちゅうかん
2014年9月4日 更新:2014年9月4日
プレゼンテーションの様子。
蜘蛛は餌を得るために巣を作りますが、私たちのクローラーも、ウェブ上の資源を得るためのものです。このブログは、私の学習の過程を記録したものです。Python2.7にはurllibとurllib2という2つのモジュールがあり、ウェブへのアクセスを提供しています。以下では、それらについて、より良い感触を得ることができるでしょう。python3では、urllibとurllib2の2つのモジュールが1つのurllibに統合されていることを述べておきます。 こちら
urllib と urllib2 は python で Web を扱うための強力なライブラリで、Web にファイルのようにアクセスすることができます (例えば、ファイルアクセスのためにはまずファイルを open() しなければなりませんが、この例で後で説明するように、同様のことをします)。なぜこのように便利になるかというと、これらのモジュールはこれらの機能を実現するために、内部で異なるネットワークプロトコルをうまく使っているからです。(ネットワークについて勉強したことがある人なら、Web ページにアクセスするという単純な処理が、実際には http や dns などの多くのネットワークプロトコルを含んでいることを理解できるでしょう。urllib と urllib2 はこれらのプロトコルをカプセル化して、それらを扱う必要がないようにして、単にこれら (のモジュールのメソッドを呼び出して必要なことを実行させる) を呼び出すだけにしています。また、これらのモジュールは、ユーザ認証、クッキー、プロキシなどの状況を処理するために、少し複雑な言い訳を提供します。では、それらについて勉強を始めましょう。
簡単な文章から始める。
先に述べたように、2つのモジュールを使うことで、Webページへのアクセスはファイルへのアクセスと同じくらい簡単になります。一般的には urllib2 によるアクセスの方が優れています (効率という点では urllib を使う必要がありますし、後述する urllib の使い方もあります)。そこで、urllib2 を使った最も簡単な例を見てみましょう。
import urllib2; response = urllib2.urlopen("http://www.zhonghuan.info"); html = response.read(); print html;
ターミナルでコマンドライン python test.py > zhonghuan.html をタイプしてください。
ファイルが開き、私の個人ブログのトップページのhtmlコードが表示されます。
これは、urllib2 を使って Web ページにアクセスする最も単純な例です。urllib2 は、URL の : の前の部分から、Web ページにアクセスするためにどのプロトコルが使われるかを判断します。例えば、上の例ではhttpを使っていますが、ここではftp:、file:などに置き換えることも可能です。これらのプロトコルをどのようにカプセル化しているのかを理解しなくても使うことができます。これらのネットワークプロトコルを内部でどのようにカプセル化しているかを理解する必要はないのです。
urllib2では、Request Objectを使用して、私たちのhttpアクセスを表現することができます。これは、アクセスしたいURLを示しているので、次の例を見てみましょう。
import urllib2
req = urllib2.Request('http://www.zhonghuan.info')
response = urllib2.urlopen(req)
the_page = response.read()
print(the_page)
req変数はRequestオブジェクトです。これは、あなたがアクセスしたい正確なURLアドレスをマークします。(ここでは http://www.zhonghuan.info)。ftpやfileなど他の形式のアクセスでは、[こちら][2]のように似たような形式があります。 ];
実は、Requestオブジェクトはさらに2つのことをすることができる。
- サーバーにデータを送ることができる。
- いくつかの追加情報(別名メタデータ、データを説明するデータ)を送ることができます。言語によっては、その中にクラスを生成するメタクラスがあり、pythonはそのような言語です。メタデータはその名の通り、データを記述するデータですが、ではこのデータは何を記述しているのでしょうか?上記では、リクエストオブジェクトの情報もありますが、これらの記述は、送出されるhttpヘッダーに置かれます。httpヘッダについては ここで );
サーバーへのデータ送信
時には、URLで表されるサーバー、通常はCGI(Common Gateway Interface)スクリプトやその他のウェブアプリケーションにデータを送信する必要があります。(CGIスクリプトについては こちら これは、単にデータのアップロードを処理するスクリプトである)。HTTPアクセスでは、どのPOSTメソッドを使用してデータを送信するのが一般的です。ちょうど、htmlでフォームに入力し終わったときに、フォーム内のデータを送信する必要がありますが、通常はここでpost requestを使用します。もちろん、フォームだけでなく、他にもpostが使われるケースはあります。
次のコードを見てみましょう。
import urllib import urllib2 url = 'http://www.someserver.com/cgi-bin/register.cgi' values = {'name' : 'Michael Foord', 'location' : 'Northampton', 'language' : 'Python' } data = urllib.urlencode(values) # The data needs to be re-encoded into the proper format, which is done in urllib, since there is no encoding method in urllib2 req = urllib2.Request(url, data) # # Here the data to be uploaded is passed to the request object as its parameter response = urllib2.urlopen(req) the_page = response.read()
その他のデータアップロードの種類については こちら
データをアップロードするには、postの他にgetも使えます。getとpostの明らかな違いは、getでアップロードされたデータはURLの末尾に表示されることです。これは、以下のコードで確認できます。
import urllib import urllib2 data = {} data['name'] = 'Somebody Here' data['location'] = 'Northampton' data['language'] = 'Python' url_values = urllib.urlencode(data) print url_values # The order here is not necessarily url = 'http://www.example.com/example.cgi' full_url = url + '?' + url_values data = urllib2.urlopen(full_url)
url_value フォームは静かにプリントアウトできます。
HTTPヘッダー - データを記述するデータ
では、HTTPのRequestオブジェクトにHTTPヘッダを追加する方法について説明します。
サイトには、より賢く、プログラムによるアクセスを好まないものがあります(人間以外のクリックはサーバーの負荷を増やすだけです)。あるいは、もう少し賢くて、ブラウザごとに異なるページデータを送信するサイトもあります。
しかし、デフォルトでは、urllib2 は自分自身をこのようにラベル付けしています。
Python-urllib/x.y
(ここで、xとyはバージョン番号で、例えば、私が
Python-urllib/2.7
このデータはサイトによっては混乱を招くかもしれませんし、プログラムによる訪問を好まないサイトに遭遇した場合、そのような訪問は単に無視されるかもしれません)。そこで、サイトが拒絶しないように、何らかのIDを構築することができます。次の例を見てください。
import urllib import urllib2 url = 'http://www.someserver.com/cgi-bin/register.cgi' user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' #user_agent is used to mark your browser, which in this case is mozilla values = {'name' : 'Michael Foord', 'location' : 'Northampton', 'language' : 'Python' } headers = { 'User-Agent' : user_agent } data = urllib.urlencode(values) req = urllib2.Request(url, data, headers) response = urllib2.urlopen(req) the_page = response.read()
例外事項
例外は常に起こるので、気をつけましょう ファイルを操作したときに何が起こるか考えてみてください。ファイルを開けない、パーミッションが足りない、ファイルが存在しない、などなど。URLへのアクセスも同様です。(ValueErrorやTypeErrorなど、python内部の例外も発生することがあります)
URLError
ネットワーク接続がない場合、またはアクセスしたサーバーのアドレスが存在しない場合、URLErrorがスローされます。このとき、URLError例外はquot;reason"属性を持ち、それはエラーコード( int )とテキストエラーメッセージ( string )から成るタプルであり、次のコードを参照してください。
import urllib import urllib2 req = urllib2.Request('http://www.pretend_server.org') try: urllib2.urlopen(req) except urllib2.URLError as e: print e.reason
出力
[Errno 8] nodename nor servname provided, or not known
出力は、以下の理由です。
HTTPError
各HTTPアクセスは、"ステータスコード&quotを取得します。サーバーから、通常、これらのステータスコードは、サーバーがいくつかのアクセスを満たすことができないことを教えて(単刀直入に言うと、いくつかのデータが、現在のアクセスなどのステータスは、現在のアクセスが拒否された。ステータスコードはあなたの行動があまりにも多く、通常のうち、注、塩辛い手は、ああ〜〜を持っていないことができるかどうかを伝えることができます)。
しかし、urllib2 のデフォルトハンドラは、いくつかのサーバーレスポンスを処理するのに役立ちます。例えば、現在アクセスしている URL がサーバーによってリダイレクトされた場合、つまりサーバーが新しい URL を教えてくれた場合、ハンドラはあなたのために新しい URL に直接移動してくれます。
しかし、デフォルトの処理系には限界があり、例えば、アクセスしているサイトが存在しない場合(404エラーに相当、時々見かける)、アクセスが無効になっている場合(403エラーに相当、十分な権限がないなどの理由が考えられる)、認証が必要な場合(401に相当)などは、すべての問題を解決できるわけではありません。その他の具体的なエラーについては、今回は紹介しませんので、具体的な ここで
HTTPErrorが404エラー、つまりページが存在しない場合、どのような出力をするか、次のプログラムを見てみましょう。
import urllib import urllib2 req = urllib2.Request('http://www.zhonghuan.info/no_way') try: urllib2.urlopen(req) except urllib2.HTTPError as e: print e.code; print e.read();
出力してください。
404
<!DOCTYPE html>
...
<title>Page not found · GitHub Pages</title>
...
例外処理
HTTPErrorとURLErrorをキャッチする場合、2つの基本的な方法があります。
1つ目は
from urllib2 import Request, urlopen, URLError, HTTPError req = Request(http://zhonghuan.info) try: response = urlopen(req) except HTTPError as e: print 'The server couldn't fulfill the request.' print 'Error code: ', e.code except URLError as e: print 'We failed to reach a server.' print 'Reason: ', e.reason else: # everything is fine
最初のアプローチでは、HTTPErrorをURLErrorの前に置かなければなりません。多くの言語の例外処理機構と同様に、HTTPErrorはURLErrorのサブクラスであり、HTTPErrorが発生した場合、URLErrorとしてキャッチすることができるためです。
2つ目は
from urllib2 import Request, urlopen, URLError req = Request(someurl) try: response = urlopen(req) except URLError as e: if hasattr(e, 'reason'): print 'We failed to reach a server.' print 'Reason: ', e.reason elif hasattr(e, 'code'): print 'The server couldn\'t fulfill the request.' print 'Error code: ', e.code else: # everything is fine
情報およびgeturl
ここでは、info()とgeturl()という2つのメソッドについて説明します。
geturl():このメソッドは、訪問したページの実際のURLを返します。訪問したページはリダイレクトされる可能性があり、結果として訪問したURLは入力したものと同じでない場合があるので、これは貴重なものです。次の例を見てください。
import urllib import urllib2 url = 'http://weibo.com/u/2103243911'; req = urllib2.Request(url); response = urllib2.urlopen(req) print "URL:",url; print "After redirection:",response.geturl();
私のTwitterの個人ページを例にとると、出力に見られるように、実際の訪問は実際のURLにリダイレクトされたのです。
URL: http://weibo.com/u/2103243911
After redirection: http://passport.weibo.com/visitor/visitor?a=enter&url=http%3A%2F%2Fweibo.com%2Fu%2F2103243911&_rand= 1409761358.1794
info():ページの説明情報を取得することができます。
httplib.HTTPMessage
のインスタンスで、辞書のように出力されます。次のコードを見てください。
import urllib import urllib2 url = 'http://zhonghuan.info'; req = urllib2.Request(url); response = urllib2.urlopen(req); print response.info(); print response.info(). __class__;
出力してください。
Server: GitHub.com Content-Type: text/html; charset=utf-8 Last-Modified: Tue, 02 Sep 2014 17:01:39 GMT Expires: Wed, 03 Sep 2014 15:23:02 GMT Cache-Control: max-age=600 Content-Length: 4784 Accept-Ranges: bytes Date: Wed, 03 Sep 2014 16:38:29 GMT Via: 1.1 varnish Age: 5127 Connection: close X-Served-By: cache-lax1433-LAX X-Cache: HIT X-Cache-Hits: 1 X-Timer: S1409762309.465760,VS0,VE0 Vary: Accept-Encoding Class: httplib.HTTPMessage
オープナーとハンドラ
ここでは、OpenerとHandlerについて紹介します。
Openerとは?実は、上記の例ではOpenerを使用していますが、これはurlopenです。これはデフォルトのオープナーであり、より適切なオープナーを作成できるネットワークアクセスのケースが多く存在します。
ハンドラとは?実はOpenerはHandlerを呼び出してアクセスの雑学を処理しています。Handlerは特定のプロトコル(例えばFTPやHTTP)に対して、アクセスをどう処理するか知っているので重要で、例えばリダイレクトの処理を助けてくれるでしょう。
アクセス時に、あなたは、例えば、Openerがクッキーを処理できるようにしたい、あるいは、Openerがリダイレクトの処理を手助けすることを望まない、Openerに対する要求を持っているかもしれません。
必要なOpenerをどのように生成するか?(余談ですが、ここでのOpenerの生成方法は、デザインパターンのBuilderパターンに似ていると思います。全く同じではありませんが、パターンを理解した上で読み進めると良いと思います。 ビルダーパターン );
オープナーを作成するには、以下のような OpenerDirector をインスタンス化します。
を実行し、 .add_handler(some_handler_instance) を呼び出します。
build_opener はデフォルトでいくつかのハンドラを追加しますが、デフォルトのハンドラを追加または更新する迅速な方法を提供します。
その他、プロキシ、バリデーションなど、一般的ではあるがやや特殊なケースもハンドラで扱いたいところである。
ハンドラがリダイレクトを処理してくれるということですが、リダイレクトを望まない場合はどうすればいいのでしょうか。ハンドラをカスタマイズしてください。
mport urllib import urllib2 class RedirectHandler(urllib2.HTTPRedirectHandler):# This RedirectHandler inherits from HTTPRedirectHandler, however, it overrides the methods of the parent class, leaving it to do nothing and lose its redirect functionality. def http_error_301(self, req, fp, code, msg, headers): pass def http_error_302(self, req, fp, code, msg, headers): pass webo = "http://weibo.com/u/2103243911"; # The visit is to my Twitter page, because normally a redirect occurs on the visit opener = urllib2.build_opener(RedirectHandler) # Here, we customize an opener, adding a custom handler that handles redirects when they occur response = opener.open(webo);# response = urllib2.urlopen(webo); print response.geturl(); urllib2.install_opener(opener); # install the custom opener, which will be returned when urllib2 is called later.
と出力されます。
urllib2.HTTPError: HTTP Error 302: Moved Temporarily
HTTPエラー302が発生したのは、Twitterのプロフィールにアクセスしたときに、リダイレクトするはずなのに、自前のリダイレクトハンドラーが何もせず、結果的に例外が発生してしまったからです。
カスタムOpenerを作成するためのurllib2クラス図は以下のとおりです。
基本認証
ウェブサイトが登録やログイン機能を提供している場合、一般的にユーザー名/パスワードが必要です。システムがユーザー名/パスワードの入力を求めるページにアクセスした場合、この処理を「認証」と呼び、サーバー側で行われる操作となります。これは、一部のページにセキュリティを提供するものです。
基本的なAuthenticationの流れは、以下のようになります。
- クライアントが、あるページにアクセスするためのリクエストを行う。
- サーバーは認証を求めるエラーを返します。
- クライアントは、ユーザー名/パスワードをエンコードして(通常はこのように)サーバーに送信します。
- サーバーは、ユーザー名とパスワードのペアが正しいかどうかをチェックし、ユーザーが要求したページか何らかのエラーを返します。
上記の処理、そしてもしかしたら他の形態もあるかもしれませんが、ここではより一般的な例としてご紹介します。
通常、サーバーはアクセスされたページが許可されていないことを示す401エラーを返し、返ってきたレスポンスのヘッダには
WWW-Authenticate: SCHEME realm="REALM".
例えば、cPanelの管理者用アプリケーションにアクセスしたい場合、次のようなヘッダーを受け取ります。
WWW-Authenticate: Basic realm="cPanel"
(cPanel は、ウェブホスティング業界で最も評判の高い商用ソフトウェアパッケージの一つで、Linux と BSD をベースに PHP で開発されたクローズドソースです。cPanel は主にクライアント指向のコントロールシステムです)
ページにアクセスすると、opener は様々な状況を処理するためにハンドラを呼び出します。Authentication を処理するハンドラは urllib2.HTTPBasicAuthHandler で、これにはユーザーパスワード管理用の urllib2.HTTPPasswordMgr も必要です。
残念ながら、HTTPPasswordMgrには、ページを取得する前にそのrealmを知る必要があるという小さな問題があります。幸い、これにはHTTPPasswordMgrWithDefaultRealmといういとこがあり、realmを事前に知らなくても、realmパラメータの位置に、Noneを渡すことができ、より使い勝手がよくなっています。
以下のコードを参照してください。
import urllib2 url = 'http://www.weibo.com'# corresponding to domain, account, password username = 'zhonghuan' password = 'forget_it' passman = urllib2.HTTPPasswordMgrWithDefaultRealm() # Create a password manager passman.add_password(None, url, username, password)# parameter form (realm, URL, UserName, Password) authhandler = urllib2.HTTPBasicAuthHandler(passman)# create Authentication's handler opener = urllib2.build_opener(authhandler) urllib2.install_opener(opener) # Same as described above, after install_opener, every time urllib2's urlopen is called, it returns this opener pagehandle = urllib2.urlopen(url)
プロキシ
urllib2 はこのセットアッププロキシをよくサポートしており、パラメータにマップ、キーにプロキシのアクセスプロトコル名、値にプロキシのアドレスを指定した ProxyHandler を直接インスタンス化することが可能です。以下のコードの実装を見てください。
import urllib2 enable_proxy = True proxy_handler = urllib2.ProxyHandler({"http" : 'http://some-proxy.com:8080'}) null_proxy_handler = urllib2. if enable_proxy: opener = urllib2.build_opener(proxy_handler) else: opener = urllib2.build_opener(null_proxy_handler) urllib2.install_opener(opener)
タイムアウトの設定
古いバージョンのPythonでは、urllib2 APIはTimeoutの設定を公開しておらず、Timeoutの値を設定するには、ソケットのグローバルなTimeoutの値を変更するしか方法がありません。
import urllib2 import socket socket.setdefaulttimeout(10) # timeout after 10 seconds urllib2.socket.setdefaulttimeout(10) # Another way
Python 2.6 以降では、タイムアウトは urllib2.urlopen() の timeout パラメータで直接設定することができます。
import urllib2 response = urllib2.urlopen('http://www.google.com', timeout=10)
クッキー
urllib2によるクッキーの扱いも自動化されています。もし、クッキーの項目の値を取得する必要がある場合は、それを行うことができます。
import urllib2 import cookielib cookie = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) response = opener.open('http://www.google.com') for item in cookie: if item.name == 'some_cookie_item_name': print item.value
デバッグログ
urllib2 を使用する場合、以下の方法でデバッグログをオンにすると、送受信されるパケットの内容が画面に表示され、デバッグが容易になり、パケットをキャプチャする必要がなくなる場合もあります。
import urllib2 httpHandler = urllib2.HTTPHandler(debuglevel=1) httpsHandler = urllib2.HTTPSHandler(debuglevel=1) opener = urllib2.build_opener(httpHandler, httpsHandler) urllib2.install_opener(opener) response = urllib2.urlopen('http://www.google.com')
参考
関連
-
Pythonによるjieba分割ライブラリ
-
[解決済み】python + NumPy / SciPyを使用してローリング/移動平均を計算する方法は?
-
[解決済み】範囲外のポップインデックス【重複あり
-
[解決済み】Pip: バージョンが見つかりませんでした。一致するディストリビューションは見つかりませんでした
-
レポート libc++abi.dylib: NSException 型の捕捉されない例外で終了する pycharm
-
[解決済み] データフレーム全体に対するpandas.factorize
-
[解決済み] ImportError: flask_sqlalchemy' という名前のモジュールがない/2バージョンのPythonがインストールされている
-
[解決済み] Pythonでvirtualenvの名前を変更する方法は?
-
pip install MySQL-python reports "EnvironmentError: mysql_config not found" (環境エラー:mysql_configが見つかりません。
-
花火、桜、薔薇を美しく実装するPython
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】Sqlite3, OperationalError: unable to open database file.
-
[解決済み] ctypes error: libdc1394 error: libdc1394 の初期化に失敗しました。
-
[解決済み] Python 3: 星付き式でリストを展開する
-
[解決済み] AxisError: クラスの精度を計算する際、軸1が1次元の配列の境界を外れています。
-
[解決済み] Python:辞書を添え字可能な配列に変換する方法は?
-
[解決済み] ブラウザでJupyter/ipythonノートブックのセル幅を広げるにはどうすればよいですか?
-
[解決済み] TypeError: 'unicode'オブジェクトは呼び出し可能ではありません。
-
[解決済み] gobjectモジュールのインストールは?
-
[解決済み] なぜ Django は "DisallowedHost at /" というエラーを投げるのでしょうか?
-
LinearAlgebraError: SVDが収束しなかった(PYTHON)