pythonでwebScrapingしてみた。


## 初めに
ライトウェイト言語では perl が好みなのですが、なんとなく最近 python に興味が出てきたので、
とりあえず勉強がてらに Yahoo!News の一覧から、タイトルと本文を順繰りにスクレイピングしてみました。
http://news.yahoo.co.jp/list/

## 環境
Python 2.7.11
lxml
requests
selenium
phantomjs ※El Capitanだと、brewから入れられないです。
参考：http://qiita.com/labeneko/items/e3b790e06778900f5719
>npm install phantom phantomjs -g

## 早速実装する
ズドン

``` python
#coding: UTF-8
import json
import lxml.html
import requests
from datetime import datetime
from selenium import webdriver
from time import sleep

#--------------------------------------------------
# WebSpider
class WebSpider:
    def __init__(self, rootUrl):
        self._webDriver = webdriver.PhantomJS()
        self._pageSourceMap = {}
        self._expireTime = (60 * 60) * 1
        self._rootUrl = rootUrl
    
    def __del__(self):
        del self._webDriver
        self._pageSourceMap.clear()
    
    def eachContents(self, url, selector, proc):
        for content in self.getContents(url, selector):
            proc(content)
    
    def getContents(self, url, selector):
        self._releaseCaches()
        if self._hasCachedPage(url) and self._rootUrl != url:
            print "> [!] use cached source: " + url
            return self._pageSourceMap[url][1].cssselect(selector)
        sleep(1)
        self._webDriver.get(url)
        pageSource = lxml.html.fromstring(self._webDriver.page_source)
        self._pageSourceMap[url] = (self._getCurrentUnixTime(), pageSource)
        print "> [i] cached page source: " + url
        return self._pageSourceMap[url][1].cssselect(selector)
    
    def _hasCachedPage(self, url):
        return self._pageSourceMap.has_key(url)
    
    def _releaseCaches(self):
        for key, value in self._pageSourceMap.items():
            isExpire = (self._getCurrentUnixTime() - value[0]) >= long(self._expireTime)
            if isExpire:
                print "> [!!!] pop cached source: " + key
                self._pageSourceMap.pop(key, None)

    def _getCurrentUnixTime(self):
        return long(datetime.now().strftime("%s"))

#--------------------------------------------------
# create instance
rootUrl = "http://news.yahoo.co.jp/list/"
webSpider = WebSpider(rootUrl)

#--------------------------------------------------
# eachProcs
def pickUpContents(content):
    webSpider.eachContents(content.attrib["href"], "#link", summaryContents)

def summaryContents(content):
    webSpider.eachContents(content.attrib["href"], "#ym_newsarticle > div.hd > h1", titleContents)
    webSpider.eachContents(content.attrib["href"], "#ym_newsarticle > div.articleMain > div.paragraph > p", mainTextContents)

def titleContents(content):
    print content.text.encode("utf_8")

def mainTextContents(content):
    print lxml.html.tostring(content, encoding="utf-8", method="text")

#--------------------------------------------------
# run
webSpider.eachContents(rootUrl, "#main > div.mainBox > div.backnumber > div.listArea > ul > li > a", pickUpContents)

del webSpider
```
>
> [i] cached page source: http://news.yahoo.co.jp/pickup/6215860
> [!] use cached source: http://headlines.yahoo.co.jp/hl?a=20160927-00000132-spnannex-base
代打・大谷が二塁打も…ハム　西武に零封負け　ソフトＢの結果待ち
> [!] use cached source: http://headlines.yahoo.co.jp/hl?a=20160927-00000132-spnannex-base

>　◇パ・リーグ　日本ハム０―３西武（２０１６年９月２７日　西武プリンス）

>　優勝へのマジックナンバーを「１」としている日本ハムは西武に０―３で敗れたため
(略...)
> [i] cached page source: http://news.yahoo.co.jp/pickup/6215858
> [i] cached page source: http://headlines.yahoo.co.jp/hl?a=20160927-00000361-oric-ent
水樹奈々コンサートで甲子園の天然芝傷む　公式サイトで経緯を説明
> [!] use cached source: http://headlines.yahoo.co.jp/hl?a=20160927-00000361-oric-ent

>　声優アーティスト・水樹奈々が27日、自身の公式サイトを更新。22日に兵庫・阪神甲子園球場で行われた水樹のコンサート後に同球場の天然芝が傷んでしまったと一部で報道された件について、
(略...)
...

出来ました。
~~**あれ、pythonのコード見づらくね？**~~

## ザックリ解説
参考記事：http://qiita.com/beatinaniwa/items/72b777e23ef2390e13f8
やってる事はシンプルで、 **webSpider.eachContents**に対象のURLとCSSセレクタを指定して、
その結果のデータを更に **eachContents** に入れて… をネストして行き、

最後(Yahoo!Newsのlistページから2リンク辿った先)の、タイトルと本文を取得して表示しています。
自分で言うのもあれですが **for文で書いたほうが圧倒的に見やすかったと思います。**

後々気が付いたのですが、 **summaryContents** に実装してあるセレクタ指定だけでは、
一部のパターンのデータが取得できない（動画などが埋め込まれている系など）ので、
全てのデータをするには、何パターンかセレクタを用意する必要があります。

あと無駄に cache や expire を付けたのですが、このコードだけだとあんま意味無いですね。
もっと拡張して行って、mongoDB にでもデータ溜めていって、MeCab やら wordVec2 に食わせて遊ぼうか色々模索中・・・。

おわり。

## 所感
![8.gif](https://qiita-image-store.s3.amazonaws.com/0/102952/1395431c-082c-1d61-2f5f-3041ad9cce7f.gif "8.gif")