毎日テキストマイニング

180日間、毎日テキストマイニングをするブログです

2018/8/3【41日目】AKBの歌詞をスクレイピングで取得した

昨日からの続きです。 タイトルは取得できましたので、曲ごとの歌詞も取得していきます。

歌詞はclass="○○"の中にありますので、ここを指定してあげれば良さそうです(昨日に続いて、クラス名までかくと怒られそうなのでぼかしてます)。

実験で使うURLはこちら Teacher Teacher

Teacher Teacher AKB48 歌詞情報 - うたまっぷ 歌詞無料検索

まずはいつものようにShellを起動します。

$ scrapy shell "https://www.utamap.com/showkasi.php?surl=k-180530-084"

Shellが起動できましたら、歌詞があるクラスを取得してみます。

In [3]: table = response.xpath('//*[@class="〇〇"]').extract()

実行結果。

In [4]: table
Out[4]: ['<td style="padding-left:30px;" class="noprint kasi_honbun">\n学校じゃ 気づいていなかった<br>街で会って はっとしてしまった<br>男性だって 今さら思い出した<br>イケてるんじゃない?<br><br>声掛けようか迷って<br>溢(こぼ)れそうなカフェラテ<br>運命とは奥の手<br>話を聞かせて<br>誰かに見られて噂されても<br>私は平気<br><br>Teacher Teacher なぜ 逃げ腰で<br>Teacher Teacher なぜ 微笑むのかしら?<br>Teacher Teacher 
(以下、省略)

一発で歌詞が取得できましたので、これをコードに落としていきます。

しかし、cssのところをどうやってxpathにするんですかね?

↓この部分。

    def parse(self, response):
        for title in response.css('title'): #response.css('div.quote'):
            yield {
                'title': title.css('title').extract(),
                'song': response.xpath('//*[@class="〇〇"]').extract()
            }

ここの書き方がわからないので、似たようなコードがないかチェックします。 パッとみた感じですと、次のPython scrapy.spider() Examplesが参考になりそうです。

scrapy.spider Python Example

中身はこんな感じです。上記の引用です。

def parse(self, response):
        
        """
        The lines below is a spider contract. For more info see:
        http://doc.scrapy.org/en/latest/topics/contracts.html
        @url http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/
        @scrapes name
        """
        sel = Selector(response)
        sites = sel.xpath('//ul[@class="directory-url"]/li')
        items = []
        for site in sites:
            item = DmozItem()
            item['title'] = site.xpath('a/text()').extract()
            item['link'] = site.xpath('a/@href').extract()
            item['desc'] = site.xpath('text()').re('-\s[^\n]*\\r')
            items.append(item)          
        return items 

responseをSelectorに入れていますね。ここで使われているSelector関数が気になりますね。 scrapyのドキュメントを確認してみます。

Selectors — Scrapy 1.5.1 documentation

Scrapyセレクタは、テキストまたは オブジェクトSelectorを渡すことによって構築されたクラスのインスタンスです。入力タイプに基づいて最適な解析ルール(XML対HTML)が自動的に選択されます。

とのこと。 よくわかっていませんが、ひとまずこのSelector関数を利用してみます。

import scrapy
from scrapy.selector import Selector

class AKB_Spider(scrapy.Spider):
    name = "akb"
    start_urls = [
        'http://artists.utamap.com/fasearchkasi.php?artistfid=1003855_1',
    ]

    def parse(self, response):
        sel = Selector(response)
        sites = sel.xpath('//*[@class="〇〇"]')
        for site in sites:
            yield {
       'title': site.xpath('//*[@class="〇〇"]').extract(),
            'song': site.xpath('//*[@class="〇〇"]').extract()
            }

        next_pages = response.xpath('//*[@class="〇〇"]//a/@href').extract() #response.css('li.next a::attr(href)').extract_first()
        for next_page in next_pages:
            if next_page is not None:
                next_page = response.urljoin(next_page)
                yield scrapy.Request(next_page, callback=self.parse)

実行結果。

[
{"title": ["<td class=\"kasi1\">Teacher Teacher</td>"], "song": ["<td style=\"padding-left:30px;\" class=\"noprint kasi_honbun\">\n学校じゃ 気づいていなかった<br>街で会って はっとしてしまった<br>男性だって 今さら思い出した<br>イケてるんじゃない?<br><br>声掛けようか迷って<br>溢(こぼ)れそうなカフェラテ<br>
(以下、省略)

お、歌詞が取得できてますね。ただこのままですと、タグなどの余計なものも入ってしまっていますので、text()を追加して本文だけを取得するようにします。

            yield {
            'title': site.xpath('//*[@class="〇〇"]/text()').extract(),
            'song': site.xpath('//*[@class="〇〇"]/text()').extract()
            }

実行結果。

[
{"title": ["Teacher Teacher"], "song": ["\n学校じゃ 気づいていなかった", "街で会って はっとしてしまった", "男性だって 今さら思い出した", "イケてるんじゃない?", "声掛けようか迷って", "溢(こぼ)れそうなカフェラテ", "運命とは奥の手", "話を聞かせて",

リストの中の要素がやけに多くなってしまいましたが、まぁ、後でPythonでいじれば良さそうです。

次のページに行く

最初のページ内にある曲は取得できましたので、2P目も同じようにスクレイピングしたいと思います。 それで問題なのが、次のページに行くためのurlがclass名も何もないtableの中に入っています。

↓こんな感じです。

<TD>
&nbsp; <a href="/fasearchkasi.php?page=1&b=1003855&s=1&sortname=&act=search">次の 20 件に続く >>></a>
</TD>

とりあえず、tdを取得してみます。

In [91]: next_page = response.xpath('//tr//td').extract()

In [92]: next_page
Out[92]: 
['<td class="ct">\n<a href="http://www.utamap.com/index.html" target="_self"><img src="http://www.utamap.com/images/utamap-s.GIF" width="150" height="30" border="0" alt="うたまっぷトップへ"></a>\n\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0[\xa0<a href="http://www.utamap.com/indexkasi.html">キーワード検索、全文検索、フレーズ検索、アーティスト50音検索に戻る</a>\xa0]\n\n<hr color="#009999"></td>',
 '<td class="ct" align="left">\n\n<!-- 広告 -->\n<script type="text/javascript"><!--\ngoogle_ad_client = "ca-pub-9713789160175344";\n/* rk_728_90 */\ngoogle_ad_slot = "1286030285";\ngoogle_ad_width = 728;\ngoogle_ad_height = 90;\n//-->\n</script>\n<script type="text/javascript" src="//pagead2.googlesyndication.com/pagead/show_ads.js">\n</script>\n<!-- 広告ここまで -->\n</td>',
(以下、省略)

tdが全部取得できました。この配列の中をよくよく見ていくと[-4]番目に欲しいurlがあります。 ここのhrefを取得してみます。

In [101]: next_page = response.xpath('//tr//td//a/@href')[-4].extract()

In [102]: next_page
Out[102]: '/fasearchkasi.php?page=6&b=1003855&s=1&sortname='

取得できたurlの前後が途切れ途切れなので、何かを付け足す必要がありそうですが、scrapyの場合はこれでいいみたいです。ここまでできたらコードにできそうです。

import scrapy
from scrapy.selector import Selector

class AKB_Spider(scrapy.Spider):
    name = "akb"
    start_urls = [
        'http://artists.utamap.com/fasearchkasi.php?artistfid=1003855_1',
    ]

    def parse(self, response):
        sel = Selector(response)
        sites = sel.xpath('//*[@class="noprint kasi_honbun"]')
        for site in sites:
            yield {
            'title': site.xpath('//*[@class="〇〇"]/text()').extract(),
            'song': site.xpath('//*[@class="〇〇"]/text()').extract()
            }

        song_pages = response.xpath('//*[@class="〇〇"]//a/@href').extract() #response.css('li.next a::attr(href)').extract_first()
        for song_page in song_pages:
            if song_page is not None:
                song_page = response.urljoin(song_page)
                yield scrapy.Request(song_page, callback=self.parse)

        next_page = response.xpath('//tr//td//a/@href')[-4].extract()
        if next_page is not None:
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page, callback=self.parse)

実行結果。

[
{"title": ["ジャーバージャ"], "song": ["\nSay!ジャーバージャ", "ジャーバージャ", "ジャーバージャ", "ジャーバージャ", "All night long", "ジャーバージャ", "ジャーバージャ", "In the groove", "YEAH!", "深く息を", "一つついて", "嫌なことは", "もう 忘れてしまえ", "星さえない真っ暗なこの街でも", "遠い空から夜は明けるんだ", "午前0時過ぎたら", "誰もが生まれ変われるよ", "緩いビートに揺れて", "Tonight Tonight Tonight…YEAH!", "いいことと悪いこと", "ループする人生よ", "瞳(め)を閉じて踊ろうか", "ジャーバージャ Funky night!All right!", "ジャーバージャ", "ジャーバージャ", "ジャーバージャ", "All night long", "ジャーバージャ", "ジャーバージャ", "In the groove", "YEAH!", "ムカついたら", "殴ればいい", "それも無駄だと", "マジ思い直した", "Oh No 恋してどんなに浮かれても", "胸のモヤモヤは晴れやしないよ", "今日と明日(あす)の間で", "誰もが踊りたくなるよ", "気分上々 もうちょっと", "All right All right All right…YEAH!", "ラッキーもアンラッキーも", "順番にやって来る", "感情を切り替えろ!", "ジャーバージャ Crazy night!", "混雑して来た", "夜更けのダンスフロアで", "昨日の自分に", "サヨナラ言おうか?", "午前0時過ぎたら", "誰もが生まれ変われるよ", "緩いビートに揺れて", "Tonight Tonight Tonight…YEAH!", "いいことと悪いこと", "ループする人生よ", "瞳(め)を閉じて踊ろうか", "ジャーバージャ Funky night!All right!", "Everybody!", "ジャーバージャ", "ジャーバージャ", "ジャーバージャ", "All night long", "ジャーバージャ", "ジャーバージャ", "In the groove", "YEAH!\n\n", "\n"]},
{"title": ["Teacher Teacher"], "song": ["\n学校じゃ 気づいていなかった",
(以下、省略)

順番が全くわかりませんが、どうやら5P目まで取得できたようです。 settingsなどで設定できるような気もするのですが、どうもうまく行かないので、AKBで検索した結果を9P分載せることにしました。 始めからこうやっていたらよかったです。

import scrapy
from scrapy.selector import Selector

class AKB_Spider(scrapy.Spider):
    name = "akb"
    start_urls = [
        'http://artists.utamap.com/fasearchkasi.php?page=0&b=1003855&s=1&sortname=',
        'http://artists.utamap.com/fasearchkasi.php?page=1&b=1003855&s=1&sortname=',
        'http://artists.utamap.com/fasearchkasi.php?page=2&b=1003855&s=1&sortname=',
        'http://artists.utamap.com/fasearchkasi.php?page=3&b=1003855&s=1&sortname=',
        'http://artists.utamap.com/fasearchkasi.php?page=4&b=1003855&s=1&sortname=',
        'http://artists.utamap.com/fasearchkasi.php?page=5&b=1003855&s=1&sortname=',
        'http://artists.utamap.com/fasearchkasi.php?page=6&b=1003855&s=1&sortname=',
        'http://artists.utamap.com/fasearchkasi.php?page=7&b=1003855&s=1&sortname=',
        'http://artists.utamap.com/fasearchkasi.php?page=8&b=1003855&s=1&sortname=',
    ]

    def parse(self, response):
        sel = Selector(response)
        sites = sel.xpath('//*[@class="〇〇"]')
        for site in sites:
            yield {
            'title': site.xpath('//*[@class="〇〇"]/text()').extract(),
            'song': site.xpath('//*[@class="〇〇"]/text()').extract()
            }

        song_pages = response.xpath('//*[@class="〇〇"]//a/@href').extract() #response.css('li.next a::attr(href)').extract_first()
        for song_page in song_pages:
            if song_page is not None:
                song_page = response.urljoin(song_page)
                yield scrapy.Request(song_page, callback=self.parse)

AKBの全歌詞を取得することができました。

今日の結果

今日のAKBメンバーによる呟きは65件でした。 「衣装」という単語がやたらと目立ちます。ライブを行なっていると衣装が気になるんでしょうね。 f:id:rimt:20180804215156p:plain

{'楽しい': 6, 'いい': 6, '嬉しい': 6, '可愛い': 3, '凄い': 3, 'よい': 2, '欲しい': 2, '遅い': 1, '仲良い': 1, 'すごい': 1, '辛い': 1, '苦しい': 1, '鋭い': 1, '美味しい': 1, '大きい': 1, '優しい': 1, '良い': 1, '遠い': 1, 'うるさい': 1, '拙い': 1, 'かっこよい': 1, 'ない': 1, 'たのしい': 1, '暑い': 1, 'あたたかい': 1})

'カンシャサイ': 18, 'エーケービーフォーティーエイト': 9, 'グループ': 9, '衣装': 9, 'みんな': 8, '大好き': 8, '私': 8, 'さん': 8, '写真': 7, '笑': 7, '選抜': 7, 'コンサート': 7, 'ん': 7, 'こと': 7, '皆さん': 7, '楽しい': 6, 'いい': 6, '嬉しい': 6, '素敵': 6, 'ちゃん': 6, '今日': 6, 'ステージ': 6, '時': 6, '感謝': 6,
'する': 32, 'カンシャサイ': 18, 'いる': 12, 'てる': 11, 'くれる': 10, 'やる': 10, 'エーケービーフォーティーエイト': 9, 'グループ': 9, '衣装': 9, 'みんな': 8, '大好き': 8, '私': 8, 'さん': 8, '着る': 8, '写真': 7, '笑': 7, '選抜': 7, 'コンサート': 7, 'ん': 7, 'こと': 7, '皆さん': 7, 'せる': 7, 

今日勉強したこと