データ分析エンジニアのブログ

日常のことからプログラミングや機械学習まで@六本木

PythonでScrapyを使ってクローリングをしてみる


前回はurllib2というモジュールを使った簡単なスクレイピングの方法を紹介しました。
今日はScrapyというクローリング+スクレイピングフレームワークを使って、
ウェブから情報を取ってくる方法をご紹介します。


フレームワークな分、以前紹介した手法よりも大掛かりなものになるので
クローリング対象の大小によって、使い分けするといいでしょう。

スクレイピングとクローリングの違い

クローリング: Webページをリンクを辿り情報を収集すること
スクレイピング: Webページから意図した情報を抜き出す作業のこと
たまに一緒の意味で使う人がいるのですが、厳密にはこのような定義です。

Scrapyとは

Scrapyはクローリングとスクレイピングの両方のフレームワークです。
robots.txtやsitemapをパースしてくれたり、
クローリングの間隔を一括で定義したりできるので便利です。

インストール方法

pipからインストールできます。

$ sudo pip install Scrapy
前準備

以下のコマンドでプロジェクトのフォルダとファイルが生成されます。

$ scrapy startproject rakutenscrapy

設定ファイルを変更します。

# rakutenscrapy/rakutenscrapy/settings.py
DOWNLOAD_DELAY = 3 # sleep間隔
ROBOTSTXT_OBEY = True # robots.txtに従うかどうか
出力データのファイルを作成

スクレイピングしたデータはデフォルトでJSONline形式で出力されます。
初めて見る形式でしたが、一行毎にJSONがある形式です。
出力されるデータの構造は以下のように設定できます。

# rakutenscrapy/rakutenscrapy/items.py
class RakutenItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    link = scrapy.Field()
クローリングとスクレイピングのプログラムを作成
# coding: utf-8

from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.selector import Selector

from rakutenscrapy.items import RakutenItem


class RakutenSpider(CrawlSpider):
    name = 'rakuten'
    allowed_domains = ['http://product.rakuten.co.jp']
    start_urls = [http://product.rakuten.co.jp/503169/]
    rules = [
        # 正規表現にマッチするリンクをクローリング
        Rule(SgmlLinkExtractor(allow=(r'/503169/(.*)', ), restrict_xpaths=('/html', ))),
        # 正規表現にマッチするリンクをparseメソッドでスクレイピング
        Rule(SgmlLinkExtractor(allow=(r'/503169/(.*)', ), restrict_xpaths=('/html', )), callback='parse_news'),
    ]

    def parse(self, response):
        sel = Selector(response)
        match = sel.xpath('//div[@class="proListItemName"]/table')

        for m in match:
            item = RakutenItem()
            item['title'] = m.xpath('.//a/text()').extract()[0]
            item['link'] = m.xpath('.//a/@href').extract()[0]
            yield item
Scrapyの実行

コマンドひとつで実行することができます!

$ scrapy crawl rakuten -o rakuten-item.jl


以上で大規模なクローリングとスクレイピングが楽に実装できます。


urllib2とElementTreeを使う方法に比べると、かなり手間に感じますが、
何回かすると、とても簡単にクローリングできるようになるのでぜひやってみてください。


参考
http://orangain.hatenablog.com/entry/scrapy
http://akiniwa.hatenablog.jp/entry/2013/04/15/001411

【Python】urllib2とElementTreeで簡単スクレイピング


こんばんは!
今回はurllib2とElementTreeというモジュールを使った、
Pythonでの簡単なスクレイピングの方法について書いてみようと思います。


クローリングのような大規模なものになると、
scrapyやBeautifulSoup、Mechanize等のモジュールを使った方が楽ですが、
1ページだけスクレイピングして処理したいなどの場合には
こちらの方法が手軽で便利です!


今回は、Wikipediaの職業一覧から職業名を全てスクレイピングして、
コンソールに出力する、というプログラムを書いてみようと思います。


以下がコードになります。

#! /usr/bin/python
# -*- coding: utf-8 -*-

import urllib2
import xml.etree.ElementTree as etree

resp = urllib2.urlopen('http://ja.wikipedia.org/wiki/%E8%81%B7%E6%A5%AD%E4%B    8%80%E8%A6%A7').read()

tree = etree.fromstring(resp)

# HTML内のcontent部分のdivを取得
a = tree.findall('.//div[@id="mw-content-text"]/p/a[@title]')

for i in a:
  for t in i.items():
    if (t[0] == 'title'): print t[1]


ほとんど解説するところもないんですが、流れとしては、


1. urllibでHTTPリクエストを送ってHTMLを文字列として取得
2. 文字列として取得したHTMLをElementTreeを使って職業を取得
3. 出力


という感じになっています。


API以外でも、Webから情報を取得できるようになると、
様々な対象を解析できるようになるので、クローリングやスクレイピングの手法は
ぜひ使いながら覚えておくといいと思います。

AICとBICについて


最近、重回帰分析やx-means法について調べていて、
AICBICという指標について気になったので、メモ代わりに。


簡潔に言うと、この2つの値はモデルの当てはまりの良さを表すために使われています。
定義としては以下の式で表されます。

AIC = T * log(s^2) + 2 * K
BIC = T * log(s^2) + K * log(T)

T: データ数
K: モデルに含まれるパラメータ数
s^2: モデルの誤差項の分散推定量


2項目がペナルティ項と呼ばれていて、複雑なモデルほど大きくなる値です。
この項がないとやたら複雑なモデルが良いモデルということになってしまいます。
これが違うことによって、BICを使った方がよりシンプルなモデルになります。


使い方としてはモデルに合わせて使う情報量を変えるとよいみたいです。
たとえば、モデルパラメータをBayes流で推定するならばBIC
最尤法で求めるならAICというようになります。

補足

x-means法は最適なクラスター数を自動で決定してくれるk-means法のことで、
クラスタリングされた個々のクラスターについて、そのクラスターをkmeans法で2分割し、
BICが低くなれば、その2つをクラスターとして採用し、更に分割していくというアルゴリズムになっています。

git stashで変更内容を一時退避する

今回はgitのstashの使い方について書いていきます。


gitにはstashという領域があり、
一時的に変更内容を退避する仕組みが用意されています。


機能ベースでブランチを切って開発しているのですが、
現在開発しているものをコミットせずに、前回開発したものを
少し修正したいと思い、辿り着いたのがこの方法です。


使い方はとても簡単でだいたい5つほどのコマンドで完結します。

1. 一時保存のメッセージを引数にして変更内容を保存
$ git stash save 'message'

このとき、新しく作成したばかりのファイルはgitの管理下に置かれていないため、
stashに保存できません。
保存したい場合は、git addコマンドでステージングに上げましょう。

2. 保存されているstashを表示
$ git stash list
3. 保存されている変更内容を反映
$ git stash pop stash@{0} --index

indexオプションを追加しないとステージングに反映されません。
ちなみに各git stashコマンドではstash名(この場合ではstash@{0})
を指定していますが、省略することで直前のstashを指定できます。

4. 一時的に退避した変更内容の変更点を確認
$ git stash show stash@{0} -p
5. 一時的に退避した変更内容を削除
$ git stash drop stash@{0}


ほかにも全てのstashを削除するgit stash clearなどのコマンドもありますが、
上記の5つくらいを覚えていれば使いこなせると思います。

ログアウトしてもジョブを継続する方法


こんにちは。
研究などで時間のかかるプログラムをサーバーなどで
実行しておきたいときがあると思います。


そんなときに普通にssh接続してプログラムをコンパイルしてログアウトすると
プログラムは終了してしまいます。


これを解決するためにはLinuxのnohupコマンドを使います。


使い方は簡単で

$ nohup python clustering.py > result.txt &


行末の「&」を忘れないように気をつけてください。
実行している間にコンソールに表示される情報はresult.txtに入ります。


プロセスを確認するコマンド(grepを併せて使うと便利)

$ ps aux

プロセスを終了させるコマンド(PID: プロセスID)

$ kill PID


上記2つのコマンドはnohupを使う上で覚えておくといいと思います。

Vimでクリップボード連携【Mac】

Vimを使っていると他のアプリケーションでコピーした文字列を貼り付けたい、
Vimで書いている文字列を他のアプリケーションにコピーしたい
という状況がよくあると思います。


今回はそんな状況を打破すべく、Vimクリップボード連携をしてみようと思います。
ちなみにクリップボードとは、ほとんどのアプリケーションで共通して
使われているもので、[cmd+c]などでコピーした文字列を保管しているやつです。


1. まずは現在使っているvimクリップボード連携ができるかチェック

$ vim --version | grep clipboard

# -clipboard=クリップボードが無効
# +clipboard=クリップボードが有効

Macで初期導入されているvimではクリップボードが無効になっています。
なのでHomebrewを使って新しくVimを入れてから実施します。


2. Vimのインストール

$ brew update
$ brew install vim


3. 新しいVimクリップボードをチェックして置き換える

$ /usr/local/Cellar/vim/7.4.273/bin/vim --version | grep clipboard
# +clipboard=クリップボードが有効ならOK
$ sudo mv /usr/bin/vim /usr/bin/vim.org
$ sudo ln /usr/local/Cellar/vim/7.4.161/bin/vim /usr/bin/


4. .vimrcに以下の設定を追加

"vimをクリップボード連携
set clipboard+=unnamed


以上でクリップボード連携は完了です。


これでvimでヤンクした文字列などを他のアプリケーションに
貼り付けることもできるようになるので、かなり便利です!

PythonでURLのプロトコル(http://,https://)を消す方法


こんにちは。
最近はPythonを使ってツイートの分析をしています。


具体的にはクラスタリングやtf-idfなどの手法を使って、クラスタリングした後に
そのクラスターの特徴語を抜き出すということをやっています。


tf-idfの結果を見ると、
ところどころhttpやコロン、スラッシュという文字があったので、
形態素解析前の処理として、プロトコルを消す処理を書きました。

#! /usr/bin/python
# coding: UTF-8

import re
text ="https://twitter.com, facebook:http://facebook.com/control/event"

while re.search(r'(https?://[a-zA-Z0-9.-]*)', text):
    match = re.search(r'(https?://[a-zA-Z0-9.-]*)', text)
    if match:
        replace = match.group(1).split('://')
        text =  text.replace(match.group(1), replace[1])


reモジュールのsearchで正規表現で対象の文字列を探して、
文字列のreplaceでテキストを置換しています。


うまい方法を使えば一発でreを使っていけそうな気がするんですが、
やり方が分からなかったので、この方法でやりました。


Pythonの文字列置換には三種類ほどやり方があって、
このURLに各方法の速度について載っていたので、紹介しておきます。
http://sucrose.hatenablog.com/entry/2013/01/10/193205