Python3.5のAsync構文を利用してスクレイピングを行う

Pythonでスクレイピングする方法としては既存のライブラリであるScrapyやdemiurge等を利用するという方法がありますが、
今回はPython3.5から追加されているAsync構文を利用して自作してみようと思います。

なおasync・awaitとはなんぞやという説明は行いません。
async・await構文の使い方は[こちら](http://qiita.com/Lspeciosum/items/98e05c7495369ab9d102)の記事が参考になりました。

---

## 環境
* Python3.5.0
* beautifulsoup4==4.4.1

## How To

まずはWebサイトのダウンロード部分から

```py3
import asyncio
import urllib.request


class Downloader:
    def __init__(self, urls):
        self.urls = urls

    def run(self):
        loop = asyncio.get_event_loop()
        return loop.run_until_complete(self.fetch())

    async def fetch(self):
        return await asyncio.wait([self.download(i, url) for i, url in enumerate(self.urls)])

    async def download(self, n, url):
        request = urllib.request.Request(url)
        html = urllib.request.urlopen(request).read()
        print("{0} {1} download finish...".format(n, url))
        return html


if __name__ == "__main__":
    downloader = Downloader([
        "https://www.python.org/", 
        "https://www.python.org/about/", 
        "https://www.python.org/downloads/"
    ])

    downloader.run()
```

**結果**

```
1 https://www.python.org/about/ download finish
2 https://www.python.org/downloads/ download finish
0 https://www.python.org/ download finish
```

コードで特出すべきところとしては、downloadメソッドを並列に実行していることです。
同期的に1つずつダウンロードするのではなく、非同期でダウンロードできていることが確認できると思います。

## スクレイピング
これだけだとHTMLをダウンロードしただけで、パースが面倒なのでパーサーを追加するためにコードを修正します。
今回はBeautifulSoupを使い、WebサイトのTitleタグの中身を取得します。

```py3
import asyncio
import urllib.request
from bs4 import BeautifulSoup


class Scraping:
    def __init__(self, urls):
        self.urls = urls

    def run(self):
        loop = asyncio.get_event_loop()
        return loop.run_until_complete(self.fetch())

    async def fetch(self):
        return await asyncio.wait(
            [self.scraping(url) for url in self.urls]
        )

    async def scraping(self, url):
        request = urllib.request.Request(url)
        html = urllib.request.urlopen(request).read()
        bs = BeautifulSoup(html, "html.parser")
        print(bs.title.string)


if __name__ == "__main__":
    scraping = Scraping([
        "https://www.python.org/", 
        "https://www.python.org/about/", 
        "https://www.python.org/downloads/"
    ])

    scraping.run()
```

**結果**

```
Welcome to Python.org
Download Python | Python.org
About Python™ | Python.org
```

---


## まとめ
と簡単にはですが、これで自作のスクレイピング処理が実装できました。
後はクローリング機能を実装することで立派なフレームワークになります。
クローリングについては、、、[クローラー／Webスクレイピング Advent Calendar](http://qiita.com/advent-calendar/2015/crawler)等を参考にすると良いのではないでしょうか（丸投げ

Async構文の3.4との比較ですが、
* コルーチンを定義する際に<code>@asyncio.coroutine</code>デコレータを付ける必要がなくなり、async defと完結になった。
* <code>yield from</code> とジェネレータと混同しやすかった構文が<code>await</code>文になりシンプルでわかりやすくなった。
かなと思います。

今回のコードは全て[Github](https://github.com/teitei-tk/Python-Async-Syntax-Example)に公開しているので何かあればそちらを参照してください。

## 参考
* [asyncio – 非同期 I/O、イベントループ、コルーチンおよびタスク](http://docs.python.jp/3/library/asyncio.html)
* [PEP 0492 -- Coroutines with async and await syntax](https://www.python.org/dev/peps/pep-0492/)
* [Python3.5から導入されるasyncとawaitでコルーチンを扱う](http://qiita.com/Lspeciosum/items/98e05c7495369ab9d102)
* [Beautiful Soup Documentation](http://www.crummy.com/software/BeautifulSoup/bs4/doc/)