Python で並列処理のマルチスレッドを実装する

シェアする

Python で並列処理のマルチスレッドを実装します。

当たり前の話ですが、普通にプログラムを書いた場合、そのプログラムはシングルスレッドになります。シングルスレッドで実行時間が長いプログラムは、マルチスレッドにすることで、実行時間を大幅に短縮することができます。

以前、Python で Web スクレイピングを実装するという記事を書きましたが、今回はそのプログラムをマルチスレッド化してみて、どれだけ実行時間が短縮するか検証したいと思います。

バージョン

  • 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 の影響でマルチスレッドにすると遅くなる可能性があることを忘れていました。詳細は別途レポートしたいと思いますが、マルチスレッドにすると遅くなる可能性があることに気をつけてください。

シェアする

フォローする