質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
HTTP

HTTP(Hypertext Transfer Protocol)とはweb上でHTML等のコンテンツを交換するために使われるアプリケーション層の通信プロトコルです。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

SSH

SSH(Secure Shell)は、セキュアチャネルを通してデータを交換するためのネットワークプロトコルです。リモートサーバーへのコマンド実行やファイル転送を行う時に一般的に使用されます。

ネットワーク

ネットワークとは、複数のコンピューター間を接続する技術です。インターネットが最も主流なネットワークの形態で、TCP/IP・HTTP・DNSなどの様々なプロトコルや、ルータやサーバーなどの様々な機器の上に成り立っています。

Q&A

解決済

1回答

1604閲覧

python urllibのエラー処理, 関数再帰呼び出し, IP偽装について

teityura

総合スコア84

HTTP

HTTP(Hypertext Transfer Protocol)とはweb上でHTML等のコンテンツを交換するために使われるアプリケーション層の通信プロトコルです。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

SSH

SSH(Secure Shell)は、セキュアチャネルを通してデータを交換するためのネットワークプロトコルです。リモートサーバーへのコマンド実行やファイル転送を行う時に一般的に使用されます。

ネットワーク

ネットワークとは、複数のコンピューター間を接続する技術です。インターネットが最も主流なネットワークの形態で、TCP/IP・HTTP・DNSなどの様々なプロトコルや、ルータやサーバーなどの様々な機器の上に成り立っています。

0グッド

0クリップ

投稿2019/03/23 13:10

編集2019/03/24 08:17

下記ドキュメントを元に、下記スクリプトを作成しました。
https://docs.python.org/ja/3/howto/urllib2.html#wrapping-it-up

注釈 except HTTPError が 必ず 最初に来る必要があります、そうしないと except URLError も HTTPError を捕捉してしまいます。

HTTP の ステータスコードによって処理を分けたいので、
except句で、HTTPError, URLErrorを分けたいのですが、
ドキュメントの注釈に背くのが何か嫌なので、
URLError に code属性が「ある」・「なし」で
「HTTPError」・「URLError」か判定することにしました。
→質問1へ

【質問1】code属性を持っていれば必ずHTTPErrorと判定してよいですか。
【質問2】関数を再帰呼び出しし、main()のresに値を返すことはできないのですか。
【質問3】このサンプルを通して、ユーザーエージェントを簡単に偽装できるのを知ったのですが、IPを偽装したりはできないのでしょうか。
例えば、IP制限をかけているサイトに対し、IPを偽装して、リクエストを投げることはできますか。
【質問4】質問3ができない場合の回避策をできるだけ多く教えてもらえませんか。
やったことがないですが、私が思いつくのは下記2つです。
・許可されたネットワーク帯から、自宅へVPNを張る
・SSHポートフォワード?を利用して、トンネル?を掘る
(指定ドメインの80番だけトンネルを経由する?)

python

1#!/usr/bin/env python 2# encoding: utf-8 3import sys 4from urllib.request import Request, urlopen 5from urllib.error import URLError, HTTPError 6 7 8def main(): 9 url = "https://gihyo.jp/dp" 10 # url = "http://not_exist_site/" 11 res = throw_request(url) 12 html = gen_html(res) 13 save_file(html, "index.html") 14 15 16def throw_request(url, req=None, recursive=False): 17 if req is None: 18 req = Request(url) 19 try: 20 res = urlopen(req) 21 except HTTPError as e: 22 print('raise HTTPError') 23 print('StatusCode: ' + str(e.code)) 24 print('ErrorReason: ' + str(e.reason)) 25 if e.code == 403 and recursive is False: 26 print("アクセスが禁止されています") 27 print("ユーザーエージェントを偽装して再接続します") 28 req = Request(url, headers={'User-Agent': 'Mozilla/5.0'}) 29 return throw_request(url, req, True) # 再帰呼び出し 30 else: 31 print('正常なレスポンスでないため、エラー終了します') 32 sys.exit(1) 33 except URLError as e: 34 print('raise URLError') 35 print('ErrorReason: ' + str(e.reason)) 36 print('正常なレスポンスでないため、エラー終了します') 37 sys.exit(1) 38 else: 39 print("request was successful") 40 print('StatusCode: ' + str(res.getcode())) 41 return res 42 43 44def gen_html(res): 45 # f.read() の戻り値は bytes型 46 # 文字列(str型)として扱うには、文字コードを指定してデコードする 47 # res の charset でデコードして、保存する 48 encoding = res.info().get_content_charset(failobj="utf-8") 49 print("encoding: ", encoding) 50 html = res.read().decode(encoding) 51 return html 52 53 54def save_file(text, file_name): 55 with open(file_name, 'w') as f: 56 f.write(text) 57 print("save: " + file_name) 58 59 60if __name__ == "__main__": 61 main()

【補足】
質問1 につきましては、
ドキュメントの探し方が上手く慣れるようにしていかなきゃですね。
参考リンク先の情報で解決しました。ありがとうございます。

質問2 につきましては、
return throw_request(url, req)
の部分で再帰呼び出しした結果をreturnしていますが、
throw_request(url, req)
だけで、else句のreturn resより、
main()関数のresにthrow_requestのresを返したいということです。
なんとなく、変な気がしていたのですが、
else句のreturnを返す先はif句のthrow_requestに対してで、
return throw_request(url, req)
としなければ、返す先がないので、これが普通の動きなんだなと納得してきました。

質問3 につきましては、
HTTPプロトコルではできないとのことで、一旦納得しました。
きちんと理解するには、TCP/IPやOSI参照モデルを勉強する必要がありそうです。

質問4 につきましては、
別質問にして、再度掲載させていただこうと思います。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

【質問1】code属性を持っていれば必ずHTTPErrorと判定してよいですか。

そういうことを知りたいときは、urllibのドキュメントを参照します。urllib.error --- urllib.request が投げる例外の章に例外の仕様が記述してあります。URLError例外はcode属性を持っていません。

【質問2】関数を再帰呼び出しし、main()のresに値を返すことはできないのですか。

ここはちょっと質問の意味がわかりませんでした。どういう処理をイメージしているのか質問文に追記していただけますか。

【質問3】このサンプルを通して、ユーザーエージェントを簡単に偽装できるのを知ったのですが、IPを偽装したりはできないのでしょうか。
例えば、IP制限をかけているサイトに対し、IPを偽装して、リクエストを投げることはできますか。

それはHTTPプロトコルの層ではできません。IPプロトコルかそれ以下のの層での処理です。しかし今どきのインターネットではソースIPアドレスを偽装したパケットを破棄するのが普通ですので、実用的に不可能でしょう。

【質問4】質問3ができない場合の回避策をできるだけ多く教えてもらえませんか。

これは最初の質問と話題が離れすぎるので、別の質問にしていただいたほうがいいとおもいます。

質問への補足追記後の回答

まず【質問1】の回答に補足です。HTTPErrorURLErrorの基底クラスなので、次のようにも書けます (派生クラス→基底クラスの順で例外を捕捉するexcept節を書く)。code属性を持っているかどうかを調べるよりもすっきりしますし、汎用性があります (将来、code属性をもつ別の例外が追加されるかもしれませんから)。

python

1def throw_request(url, req=None): 2 if req is None: 3 req = Request(url) 4 try: 5 res = urlopen(req) 6 except HTTPError as e: 7 print('raise HTTPError') 8 ... 9 10 if e.code == 403: 11 ... 12 13 sys.exit(1) 14 except URLError as e: 15 print('raise URLError') 16 ... 17 18 sys.exit(1) 19 else: 20 print("success Request") 21 ... 22 23 return res

で、【質問2】ですが、「例外が発生したか否かにかかわらず実行したい」というものはfinally節に書きます。例外がexcept節で捕捉されればその節の処理が実行され、そのあとでfinally節の処理が実行されます (成功の場合はfinally節だけ実行されます)。

ちなみに今回のコードの場合、例外発生時の処理の最後にsys.exit(1)でプログラムを終了するように書いてありますが、finally節で最終的にreturnして呼び出し元に戻るので、これらはいらないです (あってもききません)。

python

1def throw_request(url, req=None): 2 if req is None: 3 req = Request(url) 4 try: 5 res = urlopen(req) 6 except HTTPError as e: 7 print('raise HTTPError') 8 ... 9 10 if e.code == 403: 11 ... 12 throw_request(url, req) 13 14 #sys.exit(1) 15 except URLError as e: 16 print('raise URLError') 17 ... 18 19 #sys.exit(1) 20 finally: 21 print("success Request") 22 ... 23 24 return res

以上の説明のうち、例外処理の言語仕様については、Python言語仕様の「例外」節をみてください。


ところで、ご質問のとおりエラー時に再帰的にthrow_request()を呼び出せたとして、ユーザエージェントを変えてもアクセス拒否されたとしたら、再帰がとまらなくなりますね。明確な終了条件のない再帰呼び出しにはそういう危険があります。ここは再帰ではなく、単にもう一度リクエストを発行してみるだけのほうがいいかもしれません。

[3/30追記] 質問文で、再帰に終了条件を設けて無限の再帰にならないようにされたことを確認しました。

投稿2019/03/23 16:04

編集2019/03/30 04:52
ikedas

総合スコア4227

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teityura

2019/03/24 08:19

回答ありがとうございます。 finally句を使えば、最後に実行させられるのですね。 > ユーザエージェントを変えてもアクセス拒否されたとしたら、再帰がとまらなくなりますね。 ご提示いただいた改善案を元に、コードを修正してみました! 2重回答の件、少し様子見して、消えなかったら、ベストアンサーにしてクローズさせていただきます。 色々教えていただき、ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問