Python で並列処理のマルチスレッドを実装します。
当たり前の話ですが、普通にプログラムを書いた場合、そのプログラムはシングルスレッドになります。シングルスレッドで実行時間が長いプログラムは、マルチスレッドにすることで、実行時間を大幅に短縮することができます。
以前、Python で Web スクレイピングを実装するという記事を書きましたが、今回はそのプログラムをマルチスレッド化してみて、どれだけ実行時間が短縮するか検証したいと思います。
Contents
バージョン
- Python 3.5.4
- requests 2.21.0
- BeautifulSoup 4.7.1
実装
シングルスレッド
import argparse, requests, bs4, urllib parser = argparse.ArgumentParser(description='Google Search Web Scraping.') parser.add_argument('--num', default=5, type=int, help='Number.') parser.add_argument('--query', help='Search Query.') args = parser.parse_args() res = requests.get('http://google.com/search?q=' + args.query) res.raise_for_status() soup = bs4.BeautifulSoup(res.text, 'html.parser') link_elems = soup.select('.r a') num_open = min(args.num, len(link_elems)) def scraping(index, url): res = requests.get(url) soup = bs4.BeautifulSoup(res.text, 'html.parser') title = soup.select('title')[0].get_text() print_line = '{index}, {url}, {title}'.format(index=index, url=url, title=title) print(print_line) for i in range(num_open): rank = i + 1 url = link_elems[i]['href'] qs = urllib.parse.urlparse(url).query qs_d = urllib.parse.parse_qs(qs) scraping(rank, qs_d['q'][0])
マルチスレッド化しやすいように一部関数化していますが、それ以外は前回とほぼ同じプログラムです。
プログラムの内容については、前回の記事を参照してください。
マルチスレッド
修正箇所
$ diff multithread_google_search.py google_search.py 2d1 < import threading 28,29c27 < scraping_thread = threading.Thread(target=scraping, args=(rank, qs_d['q'][0])) < scraping_thread.start() --- > scraping(rank, qs_d['q'][0])
それでは、マルチスレッド化していきたいと思います。
マルチスレッド化したプログラムと diff をとりましたが、ほんの数行の変更だけでマルチスレッド化されているのがわかると思います。
最終形
import argparse, requests, bs4, urllib import threading parser = argparse.ArgumentParser(description='Google Search Web Scraping.') parser.add_argument('--num', default=5, type=int, help='Number.') parser.add_argument('--query', help='Search Query.') args = parser.parse_args() res = requests.get('http://google.com/search?q=' + args.query) res.raise_for_status() soup = bs4.BeautifulSoup(res.text, 'html.parser') link_elems = soup.select('.r a') num_open = min(args.num, len(link_elems)) def scraping(index, url): res = requests.get(url) soup = bs4.BeautifulSoup(res.text, 'html.parser') title = soup.select('title')[0].get_text() print_line = '{index}, {url}, {title}'.format(index=index, url=url, title=title) print(print_line) for i in range(num_open): rank = i + 1 url = link_elems[i]['href'] qs = urllib.parse.urlparse(url).query qs_d = urllib.parse.parse_qs(qs) scraping_thread = threading.Thread(target=scraping, args=(rank, qs_d['q'][0])) scraping_thread.start()
動作確認
それでは、どれだけ実行時間が短縮されたか検証してみたいと思います。
検証方法は、time コマンドでそれぞれのプログラムの実行時間を計測し、比較します。
シングルスレッド
(venv) $ time python google_search.py --query PythonでWebスクレイピング --num 10 1, https://qiita.com/Azunyan1111/items/9b3d16428d2bcc7c9406, Python Webスクレイピング 実践入門 - Qiita 2, https://qiita.com/Azunyan1111/items/b161b998790b1db2ff7a, Python Webスクレイピング テクニック集「取得できない値は無い」JavaScript対応@追記あり6/12 - Qiita 3, https://dividable.net/python/python-scraping/, 【保存版】Pythonでスクレイピングする方法を初心者向けに徹底解説!【サンプルコードあり】 | DAINOTE 4, https://www.sejuku.net/blog/51241, åå¿è åãã«Pythonã§Webã¹ã¯ã¬ã¤ãã³ã°ãããæ¹æ³ãã¾ã¨ãã | ä¾ã¨ã³ã¸ãã¢å¡¾ããã°ï¼Samurai Blogï¼ - ããã°ã©ãã³ã°å ¥éè åããµã¤ã 5, https://takahiromiura.github.io/, Python による Web スクレイピングにようこそ! — Python で行う Web Scraping ドキュメント 6, https://takahiromiura.github.io/web_scraping.html, Web スクレイピング入門 — Python で行う Web Scraping ドキュメント 7, https://yokonoji.work/python-scraping-1, 素人がPythonでWebスクレイピングを実装する1 | よこのじ.work 8, https://blog.codecamp.jp/python-scraping, 【Python入門】Webスクレイピングとは?サンプルコード付きでご紹介 | CodeCampus 9, https://vaaaaaanquish.hatenablog.com/entry/2017/06/25/202924, PythonでWebスクレイピングする時の知見をまとめておく - Stimulator 10, https://udemy.benesse.co.jp/development/web/web-scraping.html, 【入門】Python用ライブラリを使用したWebスクレイピ…|Udemy メディア real 0m9.381s user 0m2.673s sys 0m0.143s
シングルスレッドの実行時間は、9.3秒でした。(結果の一部が文字化けしていますが、今回は気にしないことにします・・・)
マルチスレッド
(venv) $ time python multithread_google_search.py --query PythonでWebス クレイピング --num 10 5, https://takahiromiura.github.io/, Python による Web スクレイピングにようこそ! — Python で行う Web Scraping ドキュメント 6, https://takahiromiura.github.io/web_scraping.html, Web スクレイピング入門 — Python で行う Web Scraping ドキュメント 7, https://yokonoji.work/python-scraping-1, 素人がPythonでWebスクレイピングを実装する1 | よこのじ.work 10, https://udemy.benesse.co.jp/development/web/web-scraping.html, 【入門】Python用ライブラリを使用したWebスクレイピ…|Udemy メディア 8, https://blog.codecamp.jp/python-scraping, 【Python入門】Webスクレイピングとは?サンプルコード付きでご紹介 | CodeCampus 1, https://qiita.com/Azunyan1111/items/9b3d16428d2bcc7c9406, Python Webスクレイピング 実践入門 - Qiita 2, https://qiita.com/Azunyan1111/items/b161b998790b1db2ff7a, Python Webスクレイピング テクニック集「取得できない値は無い」JavaScript対応@追記あり6/12 - Qiita 9, https://vaaaaaanquish.hatenablog.com/entry/2017/06/25/202924, PythonでWebスクレイピングする時の知見をまとめておく - Stimulator 3, https://dividable.net/python/python-scraping/, 【保存版】Pythonでスクレイピングする方法を初心者向けに徹底解説!【サンプルコードあり】 | DAINOTE 4, https://www.sejuku.net/blog/51241, åå¿è åãã«Pythonã§Webã¹ã¯ã¬ã¤ãã³ã°ãããæ¹æ³ãã¾ã¨ãã | ä¾ã¨ã³ã¸ãã¢å¡¾ããã°ï¼Samurai Blogï¼ - ããã°ã©ãã³ã°å ¥éè åããµã¤ã real 0m3.151s user 0m2.872s sys 0m0.181s
マルチスレッドの実行時間は、3.1秒でした。
まとめ
Python で並列処理のマルチスレッドを実装しました。
そして、マルチスレッド化することで大幅に実行時間が短縮しました。
並列処理プログラミングは強力な機能な反面、書き方を間違えると CPU を全部持っていってしまい、実行環境がフリーズして制御不能になってしまうので、気をつけましょう。(私がよくやらかすので w)
2019/5/16 追記
Python は GIL の影響でマルチスレッドにすると遅くなる可能性があることを忘れていました。詳細は別途レポートしたいと思いますが、マルチスレッドにすると遅くなる可能性があることに気をつけてください。