Python でいろいろスクレイピング

Python でいろいろスクレイピングを試してみました。これらをAWSや可視化ツールと組み合わせて、何か面白いサービスが実現できるかもしれません。しかしやはりPandasは魅力的ですね。

今回利用した主なライブラリです。

* lxm
* BeautifulSoup
* Scrapy
* requests-oauthlib (Twitter)
* pandas_datareader




#１．PC価格情報 - lxml

以下のページをスクレイピングします。製品名と価格情報を取得します。
https://product.rakuten.co.jp/product/-/568c382be2381ebaa925cee393f09d8d/?l-id=www_PC_category/computer_PartsRanking


ここではBeatifulSoupは使わずに、**lxml**だけを使ってスクレイピングします。


```
pip install lxml
```

必要な**Xpath**はChromeの「デベロッパーツール」を使って簡単に取得できます。次のサイトに詳しく解説してあります。「[Nintendo Switchのネット在庫監視ツールをPythonによるスクレイピングで作ってみたい](http://onyox.xyz/2017/07/05/post-266/)」

```python:test.py
import urllib.request
import lxml.html

url = "https://product.rakuten.co.jp/product/-/568c382be2381ebaa925cee393f09d8d/?l-id=www_PC_category/computer_PartsRanking"
html = urllib.request.urlopen(url).read()
tree = lxml.html.fromstring(html)
result = tree.xpath('//*[@id="contents"]/div[1]/div/div/div[3]/div[1]/div[1]/span')
for elem in result:
    print (elem.text)


result = tree.xpath('//*[@id="contents"]/div[1]/div/div/div[3]/div[3]/div[1]/p/span[1]')
for elem in result:
    print (elem.text)
```

実行結果です。

```
$ python test2.py
エイサー XC-885-N38F Aspire XC-885i3 8GB 1TB DSM WLAN Win10H64
52,800
```


#２．アマゾン売れ筋ランキング - BeautifulSoup

アマゾンの売れ筋ランキングのページをスクレイピングします。本プログラムはスクレイピングの結果をAWSのSESを使ってメール送信してくれます。同等のプログラムをLambada関数化してdeployしたバージョンは過去記事の「[【AWS】Pythonの開発環境Chaliceを使ってみる - アマゾン売れ筋ランキング](https://qiita.com/sand/items/cfdc9957caa0aee63c0f)」にあります。


必要なライブラリである**beautifulsoup4**と**boto3**をインストールします。boto3はPythonからAWSサービスを利用するためのライブラリです。

```
pip install beautifulsoup4
pip install boto3
```

以下がプログラムです。SESでメール作成する箇所が長いですが、BeautifulSoupでスクレイピングする部分は短いものです。


```python:amazon-rank.py
import boto3
from botocore.exceptions import ClientError
from bs4 import BeautifulSoup
import urllib.request
import datetime

url = "https://www.amazon.co.jp/gp/top-sellers/books/ref=crw_ratp_ts_books"
data = urllib.request.urlopen(url)
soup = BeautifulSoup(data, "lxml")

today = datetime.date.today().strftime('%Y/%m/%d')
res = ""
for el in soup.find_all("div", class_="zg_itemRow"):
    rank  = el.find("span", class_="zg_rankNumber").string.strip()
    name  = el.find_all("div", class_="p13n-sc-truncate")[0].string.strip()
    price = el.find("span", class_="p13n-sc-price").string.strip()
    out = "{} {} {}".format(rank, price, name)
    res = res + out + "<br />\n"


    # -- SENDERとRECIPIENTのemailは予めSESでVerifyする必要がある
    data = urllib.request.urlopen(url)
    soup = BeautifulSoup(data, "lxml")

    today = datetime.date.today().strftime('%Y/%m/%d')
    res = ""
    for el in soup.find_all("div", class_="zg_itemRow"):
        rank  = el.find("span", class_="zg_rankNumber").string.strip()
        name  = el.find_all("div", class_="p13n-sc-truncate")[0].string.strip()
        price = el.find("span", class_="p13n-sc-price").string.strip()
        # print("{} {} {}".format(rank, price, name))
        out = "{} {} {}".format(rank, price, name)
        res = res + out + "<br />\n"


    # -- SENDERとRECIPIENTのemailは予めSESでVerifyする必要がある
    SENDER = "Sender Name <melopachi367@gmail.com>"
    RECIPIENT = "sand.mypress@gmail.com"

    AWS_REGION = "us-west-2"   # オレゴンを設定。SESでは東京は使えない。
    SUBJECT = "アマゾン売れ筋ランキング（本）" + today

    BODY_TEXT = ("アマゾン売れ筋ランキング（本）\r\n" + res)

    BODY_HTML = """<html>
<head></head>
<body>
  <h1>アマゾン売れ筋ランキング（本）</h1>
""" +"<div>\n"+res+ "\n</div></body></html>"

    CHARSET = "UTF-8"
    client = boto3.client('ses',region_name=AWS_REGION)

    try:
        response = client.send_email(
            Destination={
                'ToAddresses': [
                    RECIPIENT,
                ],
            },
            Message={
                'Body': {
                    'Html': {
                        'Charset': CHARSET,
                        'Data': BODY_HTML,
                    },
                    'Text': {
                        'Charset': CHARSET,
                        'Data': BODY_TEXT,
                    },
                },
                'Subject': {
                    'Charset': CHARSET,
                    'Data': SUBJECT,
                },
            },
            Source=SENDER,
        )

    except ClientError as e:
        print(e.response['Error']['Message'])
    else:
        print("Email sent! Message ID:"),
        print(response['ResponseMetadata']['RequestId'])

    return today    # 適当なreturn値
```


以下は、スクレイピングの結果をgmailで受信したものです。「日本国紀」はまだ2位で頑張っているようですね。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/61915/b019ace0-412e-e871-71e3-395d8fd023c3.png)





#３．ヤクテナ監視 - Scrapy

ヤクテナは、日本の最新ニュースへの海外の反応を翻訳してくれるサイトです。このトップページを**Scrapy**を用いてスクレイピングします。**Scrapy**はスクレイピングのためのフレームワークです。

ここまでも**lxml**や**BeatifulSoup**も使ってきましたが、新たに**Scrapy**の登場です。私は調査段階で、特別な知見を持ち合わせていませんが、スクレイピング規模の小、中、大にあわせて、lxml、BeatifulSoup、Scrapyの順かなと大雑把に整理しています。もっとまともな知見は次のサイトを参考にすればよいでしょうか。「[Pythonを用いたWebスクレイピングの開発ノウハウ〜スポーツデータの場合(野球風味)](https://shinyorke.hatenablog.com/entry/2016/12/25/172917)」


早速、scrapyをインストールしてみます。


```
pip install scrapy
```

次にscrapyコマンドでプロジェクトを作成します。

```
scrapy startproject yakutena  # プロジェクトyakutena作成
cd yakutena                   # プロジェクトトップ
cd yakutena                   # トップの下のyakutena
```


まずはitems.pyの雛形が生成されているので、これを編集します。**Items**はスクレイピングしたデータのスキーム？を定義するものです。itemsの中身は**Spider**がセットします。最後に**Pipeline**でItemsをDBやファイルに保存します。今回は単なるテストですので、ItemsとSpiderだけを示します。

それではItemsを以下のように編集しましょう。

```python:yakutena/items.py
import scrapy

class YakutenaItem(scrapy.Item):
    title = scrapy.Field()
    date = scrapy.Field()
```

プロジェクトトップでSpiderの雛形を生成します。このコマンドは手作業で行えば必須ではありません。

```
scrapy genspider scrapy_yakutena www.yakutena.com
```


Scrapyは基本的にCSSセレクタを使うようです。ちょっとソースコードを読んで分析する必要があるようですね。


```python:yakutena/spiders/scrapy_yakutena.py
# -*- coding: utf-8 -*-
import scrapy
from yakutena.items import YakutenaItem

class ScrapyYakutenaSpider(scrapy.Spider):
    name = 'scrapy_yakutena'
    allowed_domains = ['www.yakutena.com']
    start_urls = ['http://www.yakutena.com/']

    def parse(self, response):
        for tr in response.css('#antena-topics table tr'):
            # print("bbb", li)
            item = YakutenaItem()
            item['title'] = tr.css('span.link-image::text').extract_first()
            item['date'] = tr.css('span.time::text').extract_first()
            yield item
```


今回はSpiderで終了ですので、この時点での結果を出力する必要があります。scrapy crawlコマンドに以下のようなオプションをつけると、ItemsをCSVに変化して標準出力に表示してくれます。

```
scrapy crawl scrapy_yakutena -t csv -o stdout: --nolog
```

以下が出力例です。一部タイトルが欠けているので、CSSセレクタの記述が十分ではないのでしょう。これに関するデバッグは止めておきます。

```
12:20,
12:19,ポケモン開発会社の「靖国参拝」投稿に中韓から批判殺到（海外の反応）
12:11,外国人「日本は凄く良い雰囲気だな…」世界の都市部を貼っていくスレ
12:10,
12:07,韓国人「日本人たちも“伝説の1軍”ネタを知っていることが判明する（笑）」
12:05,日本の小学校の「性のあり方ポスター」が「これで救われる生徒がきっといる」と話題に！【台湾人の反応】
11:50,韓国人「日本の飲食店が出した、韓国では絶対に不可能な広告」
```



##３－１．埼玉県のトップページ
Scrapyにおいては、通常はひとつのプロジェクトに複数のSpiderを作って運用するようです。

ここでは上で作ったプロジェクトで、もうひとつ、全く別のSpiderを作ってみます。Itemsはそのまま流用します。dateのところには「税務課」とかの部署名を入れることにします。


```python:yakutena/spiders/scrapy_saitama.py
# -*- coding: utf-8 -*-
import scrapy
from yakutena.items import YakutenaItem


class ScrapyYakutenaSpider(scrapy.Spider):
    name = 'scrapy_saitama'
    allowed_domains = ['www.pref.saitama.lg.jp']
    start_urls = ['https://www.pref.saitama.lg.jp/']

    def parse(self, response):
        for li in response.css('#tmp_topnews_cnt li'):
            item = YakutenaItem()
            item['title'] = li.css('li a::text').extract_first()
            item['date'] = li.css('li span::text').extract_first()
            yield item
```

以下が出力例です。

```
$ scrapy crawl scrapy_saitama -t csv -o stdout: --nolog
date,title
税務課,【公売情報】 平成30年度 第5回 インターネット公売を実施します！
農業技術研究センター,平成30年度埼玉県農業技術研究センター試験研究成果発表会を開催します。
農業ビジネス支援課,農ある暮らしツアー「北本で、埼玉1年生になる。」を開催します
防犯・交通安全課,熊谷市を交通事故防止特別対策地域に指定しました
保健医療政策課,医師・歯科医師・薬剤師の方は就業の状況などについて、届出をお願いします。
,新着情報一覧を見る
,
埼玉県収用委員会事務局,埼玉県収用委員会の会長及び会長代理の互選結果について
学事課,「平成31年度埼玉県私立中学校入試応募状況（中間） ［平成31年1月4日（金曜日）現在］」について
保健医療政策課,インフルエンザの流行注意報を発令します  咳エチケット、手洗いの励行を
越谷北高校,県立越谷北高校  スーパーサイエンスハイスクール講演会を開催します  ジャズピアニスト・数学者  中島さち子 氏による「感性×論理
が生み出す21世紀：数学と音楽の世界から」
所沢おおぞら特別支援学校,所沢おおぞら特別支援学校  子供たちが「ヤングアメリカンズ」の歌やダンスのワークショップに取組み、ショーを披露
します
,県政ニュース一覧を見る
```


「ジャズピアニスト・数学者  中島さち子 氏」って誰だろう？




#４．池袋のラーメン - Twitter


Twitter APIを利用してキーワード検索するプログラムです。「池袋」と「ラーメン」のキーワードを含むtweetsを検索します。

まずTwitter API利用に必要なライブラリ**requests-oauthlib**をインストールします。

```
pip install requests-oauthlib
```

プログラムです

```python:search.py
import json, config
from requests_oauthlib import OAuth1Session

CK = config.CONSUMER_KEY
CS = config.CONSUMER_SECRET
AT = config.ACCESS_TOKEN
ATS = config.ACCESS_TOKEN_SECRET
twitter = OAuth1Session(CK, CS, AT, ATS)    # 認証

url = "https://api.twitter.com/1.1/search/tweets.json"
print('----------------------------------------------------')
params = {'q' : "池袋 ラーメン", 'count' : 5}
req = twitter.get(url, params = params)

if req.status_code == 200:
    search_timeline = json.loads(req.text)
    for tweet in search_timeline['statuses']:
        print(tweet['user']['name'] + '::' + tweet['text'])
        print(tweet['created_at'])
        # いずれgeo情報でマップ表示したい。
        if 'geo' in tweet and tweet['geo'] is not None :
            print("**********", tweet['geo'])

        print('----------------------------------------------------')
else:
    print("ERROR: %d" % req.status_code)
```

Twitter APIの利用に必要な認証情報を以下のように設定します。

```python:config.py
CONSUMER_KEY = "xxxxxxxxxx"
CONSUMER_SECRET = "xxxxxxxxxx"
ACCESS_TOKEN = "xxxxxxxxxx"
ACCESS_TOKEN_SECRET = "xxxxxxxxxx"
```

検索結果は以下のようになります。

```
$ python search.py 
----------------------------------------------------
こみ、::年明けラーメン、天空。
辛旨い😋
#スパイスらぁめん釈迦#池袋#スパイシー# https://t.co/TRnKeTMztD
Mon Jan 07 12:56:30 +0000 2019
----------------------------------------------------
ぷっちょ♂::RT @tokiojaponia: ラーメン（少なめ）600円／ラーメン二郎三田本店
新年最初の営業。10人目に並び17分後に着丼。麺上げオヤジさん、助手は西台Aさんだったが途中で小金井ヒゲさんと交代。他に池袋助手の計4人。麺もスープもぶたもかつての三田とは大違いで美味い。こん…
Mon Jan 07 12:54:36 +0000 2019
----------------------------------------------------
❄しるふ❄GE3クリア済み::ピカブイカフェ行けたし、スプラトゥーンの消しゴム詰めも出来たし、ポケセン行きたいってずっと言ってたけど行けて靴下可愛いのも買えたしラーメンも食べたしアニメイトで色々買えたし最高🙌池袋ってホントヲタクに優しい街
Mon Jan 07 12:53:41 +0000 2019
----------------------------------------------------
とみー::RT @tomitahonten: 1/7

おはようございます！
本日、9時30分より
電話予約の受付をさせて頂きます。

本日も精一杯頑張って参ります！

明日１月８日〜１４日まで
毎年恒例の池袋西武さんの
催事があります！

今年は、つけめんと
年越しラーメンでも
好評頂…
Mon Jan 07 12:45:33 +0000 2019
----------------------------------------------------
萌えスマ店長::池袋の北口にある土佐っ子ラーメンが好きです。テキーラの後はパンチがききすぎですが…■Android専用二次元美少女満載→https://t.co/2wlMSB0rOP パソコンからは→https://t.co/hshlpdZ53O #スマホ #無料 #アダルト
Mon Jan 07 12:44:33 +0000 2019
----------------------------------------------------
```




#５．日経平均 - pandas_datareader & matplotlib

##５－１．端末で実行

日経平均のデータを端末に表示し、CSVファイルに保存するプログラムです。

まず必要なライブラリをインストールします。


```
pip install pandas_datareader
```

プログラムです。ここでは**pandas_datareader**を使えば、一発で日経平均のデータが取得できるのが、本質です。

```python:nikkei.py
import pandas_datareader.data as pdr 
import datetime

end = datetime.date.today()
start = end - datetime.timedelta(days=90)

nk = pdr.DataReader("NIKKEI225", 'fred', start, end)

print(nk)
nk.to_csv("nikkei.csv")
```


```
$ python nikkei.py 
            NIKKEI225
DATE                 
2018-12-18   21115.45
2018-12-19   20987.92
2018-12-20   20392.58
2018-12-21   20166.19
2018-12-24        NaN
2018-12-25   19155.74
2018-12-26   19327.06
2018-12-27   20077.62
2018-12-28   20014.77
2018-12-31        NaN
2019-01-01        NaN
2019-01-02        NaN
2019-01-03        NaN
2019-01-04   19561.96
```

##５－２．jupyter notebookで実行

数字だけではわかりにくいので、出力をプロットに変更します。そのために同等のプログラムをjupyter notebookで走らせ、 **matplotlib**を使ってプロットします。

必要なライブラリをインストールします。

```
pip install matplotlib
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/61915/6bf3e931-2885-1ee6-aeb3-4a43dfe75caf.png)



今回は以上です。


#★最近の投稿
[Python でいろいろスクレイピング](https://qiita.com/sand/items/f2466320c8dde905cb3b)
[【AWS】Pythonの開発環境Chaliceを使ってみる - アマゾン売れ筋ランキング](https://qiita.com/sand/items/cfdc9957caa0aee63c0f)
[【AWS】Pythonの開発環境Chaliceを使ってみる - CloudWatch Events](https://qiita.com/sand/items/4a4c273872b266af6680)
[【AWS】Pythonの開発環境Chaliceを使ってみる - API Key](https://qiita.com/sand/items/2f8615b03c6f24c85b49)
[【AWS】Python Lambdaのdeploy - Chalice](https://qiita.com/sand/items/4642f417bf0d2b49c7e2)
[【AWS】Python Lambdaのdeploy - CloudFormation](https://qiita.com/sand/items/2b918a29bf08bfb86d32)