【Flask】スクレイピング時のエラー処理→Reactに反映させる
前書き
Yahoo! Newsのスクレイピングする上でも記事によっては、要素が異なったり、コメント取得に時間がかかりすぎたりします。
そこでエラー処理をどのようにしていくかを調べて、実装してみました。
今回の実装を以ってしても動作は少々不安定ですが、随分改善されました。
技術
技術に関してはこちらを見ていただけた方が早いと思います。
melheaven.hatenadiary.jp
例外処理
フロントエンド (React側)
'http://0.0.0.0:5000/comment'にPOSTでアクセスすると、バックエンドが動作します。
ReactではFlaskと通信して、返ってきた値がエラーコードを例外として"catch"で取得し、例外処理を実行します。
Axios.post('http://0.0.0.0:5000/comment', { post_articleConent: articleContent }).then(function (res) { // 成功 }).catch(err => { // 失敗(例外) alert('記事にコメント欄が存在しない、もしくはリロードしてもう一度お試しください。') });
バックエンド(Flask側)
@app.route('/comment', methods=['POST']) def comment(): data = request.get_json() article = data['post_articleConent'] try: comments = scraping(article) response = {'result': comments} return make_response(jsonify(response)) except TimeoutError as e: print(e) return jsonify(message='Comment Error'),500 except Exception as e: print(e) return jsonify(message='Comment Error'),500
こちらではReact側からのリクエストをさばいています。
スクレイピングの関数であるscraping()をtryに設定し、React側でエラーを例外処理としてcatchさせるために、以下をを返します。ここではHTTPエラーコード500をReact側に明示的に送信し、React・AJAX側でエラーの発生を識別できます。
ついでにこちらではタイムアウトエラーとその他のエラーの2つに分類しています。
jsonify(message='Comment Error'),500
スクレイピング時のエラー
スクレイピング時のエラーには主に2パターンあるように感じました。
- 単にコメント取得に時間がかかりすぎるパターン
- 要素が存在せずスクレイピングできない(no such element: Unable to locate element when trying to find element within a iframe)
他にも得体の知れないエラーが存在していると思いますが、エラーを見逃しているとバックエンドの動きがものすごく悪くなります。なので例外処理を施して、少しでもエラーに対応できるようにします。
後者のエラーに関しては、例外処理にてexcept Exceptionにて、対応できています。ここでは得体の知れないエラーを全て検知して例外処理をしてくれています。
タイムアウトエラー
以下はスクレイピング関数です。pipでretryとtimeoutをインストールします。
timeoutは時間を計測して、時間内に処理を実行できない場合は上記の例外処理のコードにてexcept TimeoutErrorとして処理してくれます。
retryは不安定な処理の場合に活用しますが、triesにて最大トライ回数、delayにてトライ間隔、backoffはトライの間隔を何倍開けていくかを示します。
from retry import retry from timeout_decorator import timeout, TimeoutError @retry(tries=2, delay=30, backoff=2) @timeout(15) def scraping(article): scraping = sc.Scraping() data = scraping.getComment(article['link']) return data
main関数ではシングルスレッドでしか動かせないエラーが出ました。並列処理ができないみたいです。
時間計測とメインの処理が並列処理として実行されているためかな。
signal only works in main thread
app.runに use_reloader=False, threaded=Falseを加えると実行できました。
app.run(host='0.0.0.0', port=5000, use_reloader=False, threaded=False)