シンプルなスクレイピングライブラリをPythonで作ったよ



`Friday I/O` でーす！
[株式会社ワムウ](http://wamw.jp)では、毎週金曜日は `興味がある事柄に取り組み、その成果を何らかの形でアウトプットする日` としておりますよ。


# Scrapbookというライブラリを作りました

お仕事で `Scrapy` 使う機会があったんですけども、HTMLからデータを抽出する部分（ItemLoader）が私の様な貧弱プログラマーには難しすぎたので、もっとシンプルにわかりやすく書けるものを作りました。
ドキュメントの翻訳をgoogle翻訳さんに頼んだんで、文法とかむちゃくちゃだと思います。。（英語わからない）

Repository: https://github.com/odoku/scrapbook
Document: https://scrapbook.readthedocs.org


# 使い方

PYPIに登録してあります！

```
pip install scrapbook
```

`README` にも載ってますが、一応こちらにも掲載。
ぱっと見で何やってるかは分かると思います。

--- 注意 ---
記事書いた後に気づいたんですけど、Twitterはクローリングすると激怒する様なのでサンプルの実行は自己責任でお願いします。。

https://twitter.com/ja/tos
--- 注意 ---


```python
from scrapbook import Element, Content
import requests


class Twitter(Content):
    username = Element(
        xpath='//*[@id="page-container"]/div[2]/div/div'
              '/div[1]/div/div/div/div[1]/h2/a/span/b/text()',
    )
    screen_name = Element(
        xpath='//*[@id="page-container"]/div[2]/div/div/'
              'div[1]/div/div/div/div[1]/h1/a',
    )


response = requests.get('https://twitter.com/odoku')
data = Twitter().parse(response.text)

print(data)
```


# ScrapyでScrapbookを使う

ほぼ上のサンプルとおんなじ。

```python
# -*- coding: utf-8 -*-

import scrapy
from scrapbook import Content, Element


class Tweet(Content):
    username = Element(
        xpath='.//span[@class="FullNameGroup"]/strong[contains(@class, "fullname")]/text()',
    )
    text = Element(
        xpath='.//p[contains(@class, "tweet-text")]/text()',
    )
    like = Element(
        xpath='.//span[contains(@class, "ProfileTweet-actionCountForPresentation")]/text()',
        filter=int,
    )


class Twitter(Content):
    username = Element(
        xpath='//*[@id="page-container"]/div[2]/div/div'
              '/div[1]/div/div/div/div[1]/h2/a/span/b/text()',
    )
    screen_name = Element(
        xpath='//*[@id="page-container"]/div[2]/div/div/'
              'div[1]/div/div/div/div[1]/h1/a/text()',
    )
    tweets = Tweet(
        xpath='//*[@id="stream-items-id"]/li',
        many=True,
    )


class ScrapbookSpider(scrapy.Spider):
    name = 'scrapbook'

    start_urls = ['https://twitter.com/odoku']

    def parse(self, response):
        return Twitter().parse(response.text)
```


# これをScrapyのItemLoaderで書くと

そもそも `ItemLoader` の使い方もよくわかってないので参考にならないかもですけど、こんな感じになると思います。
ロジックとして記述していくので、ぱっと見でわかりづらいなーと個人的には思います。。

```python
# -*- coding: utf-8 -*-

import scrapy
from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst


class Item(scrapy.Item):
    username = scrapy.Field(output_processor=TakeFirst())
    screen_name = scrapy.Field(output_processor=TakeFirst())
    tweets = scrapy.Field()


class ScrapbookSpider(scrapy.Spider):
    name = 'scrapy'

    start_urls = ['https://twitter.com/odoku']

    def parse(self, response):
        loader = ItemLoader(item=Item(), response=response)
        loader.add_xpath(
            'username',
            '//*[@id="page-container"]/div[2]/div/div/div[1]/div/div/div/div[1]/h2/a/span/b/text()',
        )
        loader.add_xpath(
            'screen_name',
            '//*[@id="page-container"]/div[2]/div/div/div[1]/div/div/div/div[1]/h1/a/text()',
        )
        loader.add_value('tweets', self.get_tweets(response.xpath('//*[@id="stream-items-id"]/li')))

        return loader.load_item()

    def get_tweets(self, selector_list):
        tweets = []

        for selector in selector_list:
            tweet = {}

            xpath = './/span[@class="FullNameGroup"]/strong[contains(@class, "fullname")]/text()'
            tweet['username'] = selector.xpath(xpath).extract_first()

            xpath = './/p[contains(@class, "tweet-text")]/text()'
            tweet['text'] = selector.xpath(xpath).extract_first()

            xpath = './/span[contains(@class, "ProfileTweet-actionCountForPresentation")]/text()'
            tweet['like'] = selector.xpath(xpath).extract_first()
            if tweet['like']:
                tweet['like'] = int(tweet['like'])

            tweets.append(tweet)

        return tweets
```


# まとめ

特に `Scrapy` に依存してるわけではないので、他のライブラリで使うことも出来ますよ。
よかったら使ってみて下さいな。