機械語と数学とことば

更新が進みません.

【プログラミング】リンク先のPDFファイルをダウンロードする。(Python3)

毎回久しぶりではありますが、今回は、Python3を使って、リンク先のPDFファイルをダウンロードするコードについてです。

ほぼ備忘録です。

import urllib as ul
result = ul.request.urlretrieve("https://リンク先/files.pdf","保存先とファイル名.pdf")
result.close()

この二行だけで当該のPDFファイルをダウンロードすることができます。 例えば、東京ベーシック・ドリル:東京都教育委員会(東京都教育委員会が設定した小学生〜中学生向けの基礎的なドリル)のサイトから、算数のPDFファイルをダウンロードしてくるには、

import urllib as ul
result = ul.request.urlretrieve("http://www.kyoiku.metro.tokyo.jp/buka/shidou/manabiouen/basic_drill/san-2-sinQ-A.pdf","arithmetics/san-2-sinQ-A.pdf")
result.close()

とするだけで対象のPDFファイルを、実行ディレクトリにあるarithmeticsディレクトリの中に、保存することができます。

なんて便利なんでしょう。

ではまた。

追記:urllibのrequestはresultとして保存し、最後にcloseする必要があるようです。

【読書】増補版 煩悩の文法 定延利之著

久しぶりの投稿。 新宿西口のブックファーストでふと気になったことから、手に取ってみて、

中身が面白かったので即買ってしまったこの本。

本との出会いは不思議なもので、たまたまちょうどその時考えているテーマを、

あえて拾っているのか、それとも偶然拾ってしまうのか。

日本語の仕組みを研究する言語学者の方が書かれていて、「認知モデル」についての言及もあり、

つまらない日本語の文法研究とは大きく異なる、「体験を語る」ときの、

日本語の文法の面白さが紹介されています。

著者は「モノ」と「デキゴト」を区別するのが、日本語である、と述べるところから始まり、

「モノ」について語るはずが「デキゴト」になって語られてしまっている、

例えば文中では

庭に木がありましたよ。

というのが普通の言い方であり、

庭で木がありましたよ。

というと不自然に思えてしまう反面、

北京で四色ボールペンがありましたよ。

というと自然なのは何故なのか、という問いから論を展開していきます。

その違いとは、「ワクワク型」の体験と、「ヒリヒリ型」の体験については、

「モノ」が「デキゴト」になって語られる、探索される空間と衝撃の大きさを、

文法構造で表現してしまう、そんな日本語の特性をあらわにしています。

最近ずっと関心があるのが、「日本語プログラミング」なのですが、

日本語の語順と、英語の語順の違い、そして、アセンブリ言語の語順を比較したり、

文脈情報を規定して動詞を指定する日本語の書かれ方、

主語を除いて語られても伝わってしまう日本語の特殊性、

など、色々なことを考えている中に、大変刺激的な本でした。

もしよかったらぜひご一読ください。

【プログラミング】多階層の辞書を一階層の辞書に変換する(Python3)

大変久しぶりにブログを書きます。最近は研究プロジェクトが再開したり、個人的な勉強会で数学を学ぶ機会を持ったり、仕事も変わったりと色々とありました。

今年の目標としては「書くこと」であるので、今回も研究プロジェクトで必要になったコードをこちらでご紹介します。

今回、DBにそのままバルクインサートすることも躊躇してしまうほど、某APIから全くぐちゃぐちゃなJSONデータが得られました。 jsonパッケージをそのまま使うと、Pythonのディクショナリ形式に変換することができるのですが、 しかしながら、中身がぐちゃぐちゃ・・・keyの値も正規化されておりません。

今回は、anaconda3-2.4.0によってインストールされる、Python3.5やその他パッケージを利用して実行します。

まず、JSONデータの中身は下記の通り・・・ (JSON Data Set Sampleより引用)

{
    "id": "0001",
    "type": "donut",
    "name": "Cake",
    "image":
        {
            "url": "images/0001.jpg",
            "width": 200,
            "height": 200
        },
    "thumbnail":
        {
            "url": "images/thumbnails/0001.jpg",
            "width": 32,
            "height": 32
        }
}

これを全て一階層で取れるようにしたいと思います。

そのためには、「再帰」を使って、ディクショナリ内から、keyがなくなるまで取得し続けるということを繰り返します。

while文でも書くことができるのですが、コードを短く済ませることができ、便利なので再帰を使いましょう。

まずはそこまでのパスを取得する関数です。

# 複雑なディクショナリから、どんなKeyがあるのか再帰的に取り出す。
# 引数は、対象の辞書、ルート(最下階層)、区切り文字です。
# 利用例:get_dict_hierarchy(cake_image,'','_')
def get_dict_hierarchy(target_dict,root_path,sep):
    if isinstance(target_dict,dict):
        for key in target_dict.keys():
            value = target_dict[key]
            target_path = root_path + sep + key
            # valueが辞書じゃない場合にはパスを返す。
            if not isinstance(value,dict):
                yield target_path[1:]
            # 辞書の場合にはもう一度探索する。
            else:
                yield from get_dict_hierarchy(value,target_path,sep)

ループのところ、target_dict.items()でやってもいいかもしれません。そうすると一行削れるかもしれないですね。 ちなみに上のデータで実行すると下記のようになります。

In[2]:
cake_dict = {
    "id": "0001",
    "type": "donut",
    "name": "Cake",
    "image":
        {
            "url": "images/0001.jpg",
            "width": 200,
            "height": 200
        },
    "thumbnail":
        {
            "url": "images/thumbnails/0001.jpg",
            "width": 32,
            "height": 32
        }
}
In[3]: list(get_dict_hierarchy(cake_dict,'','_'))
Out[3]:
['thumbnail_width',
 'thumbnail_height',
 'thumbnail_url',
 'image_width',
 'image_height',
 'image_url',
 'type',
 'id',
 'name']

これで階層ごとのデータを取れました。(データはジェネレータなので、リストに直して表示しています。) では、それぞれの階層からディクショナリを深堀し、ターゲットの値を取得して、 一階層からなるディクショナリに変換します。

掘っていく関数は下記の通り。

# 階層が深いディクショナリを掘っていき、ターゲットの値を取得する。
# 対象となるディクショナリ、探したいデータまでのパス、区切り文字
# 利用例:dig_dict(cake_dict,'thumbnail_url','_')
def dig_dict(target_dict,target_branch,sep):
    limbs = target_branch.split(sep)
    leaf = target_dict
    for one_limb in limbs:
        leaf = leaf[one_limb]
    return leaf

では、データを作りましょう。

In[5]: flat_dict = {}
In[6]: for one_branch in list(get_dict_hierarchy(cake_dict,'','_')):
          flat_dict[one_branch] = dig_dict(cake_dict,one_branch,'_')
In[7]: flat_dict
Out[7]: 
{'id': '0001',
 'image_height': 200,
 'image_url': 'images/0001.jpg',
 'image_width': 200,
 'name': 'Cake',
 'thumbnail_height': 32,
 'thumbnail_url': 'images/thumbnails/0001.jpg',
 'thumbnail_width': 32,
 'type': 'donut'}

と、お目当てのデータが手に入りました。

再帰を使ってうまくネストを解いたり、JSONデータの解析をおこなったりすることもあるでしょう。

APIからくるデータは汚いことが多いですが、まずこれらの関数を使ってみて、 どういう結果が得られるか確かめてみるのもいかがでしょうか。

【プログラミング&テキスト解析】Go言語でJSONファイルを読み込む

前回は,n-gramをつくるスクリプトを書きましたが, やはり,JSONだったり,CSVからデータを読み取りたいですよねー.

というわけで,今回は下記のようなJSONファイルを./input/test.jsonとして格納し,読み込むスクリプトを書いてみました!

[
    {"id":1, "text":"つれずれなるままに"},
    {"id":2, "text":"ひぐらしすずりにむかいて"},
    {"id":3, "text":"こころにうつりゆくよしなしごとを"},
    {"id":4, "text":"そこはかとなくかきつくれば"},
    {"id":5, "text":"あやしうこそものぐるおしけれ"}
]

まずは,Pythonで読み込む場合を紹介します.

import json as js

jsonfile = open('./input/test.json','r')
datasets = js.load(jsonfile)

Pythonの場合はこれだけで,Dictが要素になるlistデータをインポートできます.

では,Go言語で書いた場合はこちら.json_tool.goというファイル名で作ります.

package main
import (
    "encoding/json"
    "fmt"
    "io/ioutil"
)

type Dataset struct {
    ID int `json:"id"`
    Text string `json:"text"`
}

func main(){
    // Loading jsonfile
    file, err := ioutil.ReadFile("./input/test.json")
    // 指定したDataset構造体が中身になるSliceで宣言する
    var datasets []Dataset
    json_err := json.Unmarshal(file, &datasets)
    if err!=nil{
        fmt.Println("Format Error: ", json_err)
    }
    fmt.Println(datasets)
    fmt.Println(datasets[0].Text)
    fmt.Println(datasets[2].ID)
}

実行します

$ go run json_tool.go


[{1 つれづれなるままに} {2 ひぐらしすずりにむかいて} {3 こころにうつりゆくよしなしごとを} {4 そこはかとなくかきつくれば} {5 あやしうこそものぐるおしけれ}]

つれづれなるままに

3

Sliceの参考資料は下記を参照のこと.

Go言語のArrayとSliceについて - done is better than perfect

意外と簡単ですね!

【プログラミング&テキスト解析】Go言語でN-gramを返す関数を書いてみた

N-gramを返す関数はPythonなどではすぐに書くこともできるのですが,(というかそういうパッケージも有るでしょうし)自分で色々実装して試してみよう,ということで書いてみた例を紹介します.

ちなみに,Go言語とは,Googleが開発したプログラミング言語で,クロスコンパイルがすごく簡単にできるがゆえにいいなーと思っていた言語でした.つまり,MacでつくったツールをWindowsで実行するようにするのがすごく簡単ということです.

golang.jp - プログラミング言語Goの情報サイト

ロスコンパイル参考:Goはクロスコンパイルが簡単 - unknownplace.org

では,N-gramを作成する関数を作ったので,ご参考までに.といっても凄く簡単な書き方ができるので,Pythonとくらべて特別な工夫はほとんど必要ありません.

今回は日本語の解析を目的としているので文字列ベースのN-gramを返す関数です.例えば,"徒然なるままに"のBi-gram(n=2)の場合には,["徒然", "然な", "なる", "るま", "まま", "まに"]という配列を返すのがゴールです.

まずは,Pythonのコードから

#文字からN-gramを作成して返す
def ngram(target_string,n):
    if len(target_string)<n:
        error_message = "Error: Input string's length is less than n value ({} < {})"
        print(error_message.format(len(target_string),n))
        return False
    ngrams = filter(lambda b:b!="", [target_string[k:k+n] for k in range(len(target_string)-n+1)])
    return list(ngrams)

Pythonで気軽に書けばこのくらいです.基本的な処理は,一行で完結するのでかなりシンプルに記述することが出来ます.

Go言語で書いてみるとこんな感じになります.

package main
import (
    "fmt"
    "strings"
    "errors"
)

//実行する
func main(){
    target_string := "徒然なるままに"
    bigrams, err := ngram(target_string,2)
    if err!=nil{
        fmt.Println(err)
    }
    fmt.Println("Bigrams: ",bigrams)
}

// N-gramをstringの配列型で返す
func ngram(target_text string, n int) ([]string, error) {
    sep_text := strings.Split(target_text,"")
    var ngrams []string
    if len(sep_text) < n{
        err := errors.New("Error: Input string's length is less than n value")
        return nil, err
    }
    for i:=0; i<(len(sep_text)-n+1); i++{
        ngrams = append(ngrams,strings.Join(sep_text[i:i+n],""))
    }
    return ngrams, nil
}

多分,appendのところでメモリを少なく使う処理ができるのと,配列を宣言する際にsliceにするとメモリを節約して実行できるのではないかと思います.Go言語は高速で実行できるので,テキスト解析にはかなり向いている言語かと思います.クロスコンパイルも便利となれば,今後ユーザーも伸びていく言語の一つではないでしょうか.

次はjsonファイルをパースしてスコアリングするところまでやってみたいと思います.

追記,Go言語のコードでエラー処理で動かない点がいくつかあったので訂正しました.2015-01-13-01:47

【プログラミング】知識ゼロから学ぶソフトウェアテストから見える人の認知バイアスとフレーム問題(書評)

ごく気が向いた時にしかブログを更新しないのですが,せっかく読み終えた本があったので,こちらに紹介します. ちなみに,

知識ゼロから学ぶソフトウェアテストを読みました![書評] | 酒と涙とRubyとRailsと

こちらのブログを読んで,他のブログにも幾つか紹介されていたので,いい本なんだろうな~と思い,しかもKindle版は安かったので読みました.どんな内容があるかという話は上のブログを読んでもらえればいいと思いますが,私として気になった一点だけを特に絞ってここに書きます.

知識ゼロから学ぶソフトウェアテスト 【改訂版】

知識ゼロから学ぶソフトウェアテスト 【改訂版】

それは,「バグを1つ見つけると背後に多くのバグが潜んでいる」ということ.

これは最近私の個人的な仕事においても同様なことが起こっているのですが,システムから一つのコードのバグを発見すると,それを「精緻化しよう」とするプロセスの中で更にバグが発見されるということです.

これは,バグを一度発見すると,

1.「もっとバグが有るんじゃないか?」と考えてしまう心理的な要因と

2.「バグは単体のものではなく,様々な要素が絡み合い,大きくなったものがある閾値を超えることで発見される」というシステム的な構造の要因

の2つから起こるものではないかなと思いました. ただ,これらの,この本でのことばから言うには,「致命的なバグ」の発見は, 簡単にできるものではないことも,「テストの難しさ」を表していると思います. なので,「バグの発見数曲線が落ち着いてきたらリリースかな」というのは 理にかなった,というかそうする以上のことは出来ない,ということなのだと思います.

はじめからすべて「完全な形」で組む事のできるシステムなど,よほど単純なシステム (例えば掛け算をするシステム等)以外にはありえないということを考えると やはり 「可能な限り現在まで有用だとわかっているテストは実行しつつ,あとは気合で細かくチェックするしかない」 のかなーとも思っています.「境界に注意しろ」というのもまったくもってそのとおりですね. 私も良く書いたコードで境界のミスが生じることは多くあります.

[asin:B00NH1JM8A:detail]

昨年の終わりにはこの本を読みつつ実行していましたが,

テスト駆動開発」というのも,テストを先に書いてそれをクリアしていくやりかたは,

「完全なコード」を書けそうでもありますが,なかなか実際にはそう行かない,という現状を

この本に書いてありました.

「テスト」はまだまだ未成熟の学問分野らしく,この辺りのトピックは,プログラミング,情報工学というよりもむしろ,人間の認知,認知科学認知心理学人工知能などに近い問題と似ている(エラーが起こるまで気づかない問題は,カーネマンのバイアスや,ロボットのフレーム問題に相当する)ため,今後(既にしているかもしれませんが)学問間の横断と融合でなにか新しい手法の開発がされるのではないかな―とも思っています.いずれにせよ今後が楽しみな分野でありつつ,現状のベーシックを取り入れていきたいなーと思います.

【プログラミング】Python+Selenium+PhantomJSで快適ウェブスクレイピング

最近ふと,Pythonでウェブスクレイピングしたいなと思い, まずは入門ソーシャルデータを参考にして beautifulsoupを使って スクレイピングしようとしていた所,JavaScriptなどの影響を受けずに 表示されたままにスクレイピングできるやりかたが有ることを教えてもらい, やってみました.

入門 ソーシャルデータ 第2版 ―ソーシャルウェブのデータマイニング

入門 ソーシャルデータ 第2版 ―ソーシャルウェブのデータマイニング

Seleniumのwebdriver, PhantomJSを使ってスクレイピングする方法です.

MacOSXへのインストール方法

MacOSXへのインストール方法は至って簡単. seleniumはpipを使ってインストールし, PhantomJSはhomebrewを使ってインストールすれば一発です.

$ pip install selenium
$ brew install phantomjs
# これだけ!

CentOSへのインストール方法

CentOSへのインストール方法は,基本的にseleniumがpipでインストールできるところまでは かわりませんが,yumにphantomjsが登録されていないため,基本的にはバイナリからインストール することになります. npmに登録してインストールをすることが出来ますのでその方法を. (PythonCentOSに既にインストールされていることを前提とします.)

# seleniumのインストール
$ pip install selenium

#PhantomJSに必要なものをインストール
$ sudo yum install gcc gcc-c++ make git openssl-devel freetype-devel fontconfig-devel rpm-build

# バイナリなどを入れておくディレクトリをつくるのがよいでしょう.
# 私はホームディレクトリ(~)以下にsrcというディレクトリを作っています.
$ mkdir ~/src
$ cd ~/src

# gitのリポジトリからソースをクローン(ダウンロード)
# 下の1.9は最新バージョンにしてください.
$ git clone git://github.com/ariya/phantomjs.git
$ cd phantomjs
$ git checkout 1.9

# ソースからビルド
$ ./build.sh --jobs 2
$ cd rpm/

# rpmに登録・インストール
$ ./mkrpm.sh phantomjs
$ sudo rpm -ivh /tmp/phantomjs-1.9.7-1.x86_64.rpm

# phantomjsのバージョンを確認
$ phantomjs --version
# > 1.9.xと出ればOK

準備が完了したら...

準備が完了したら,さあスクレイピングしましょう! 個人的にはipythonを使っているのでipythonバージョンで行きます. (ちなみにバージョンはPython2.7)

$ ipython
In [1]: from selenium import webdriver as wd
In [2]: browser = wd.PhantomJS()
In [3]: browser.set_window_size(1024,768)
In [4]: browser.get('http://tomohitoy.hatenablog.com/entry/2014/09/17/001815')
In [5]: content = browser.page_source
In [6]: browser.quit()
In [7]: print content.encode('utf-8')
<!DOCTYPE html><html lang="ja" data-avail-langs="ja en" data-page="entry" data-admin-domain="http://blog.hatena.ne.jp" data-static-domain="http://hatenablog.com" data-blog="tomohitoy.hatenablog.com" data-blogs-uri-base="http://tomohitoy.hatenablog.com" data-globalheader-color="b" data-globalheader-type="pc" data-author="tomohitoy" data-

...(中略)...

</html>

と言った具合でさほどJavaScriptがきついサイトでない限りデータを簡単にスクレイピング出来ます. これらを解析する際には是非lxmlもしくはbeautifulsoupを使いましょう! lxmlについてはまたいつか書きます.

参考URL

NPM install PhantomJS error on centOS 6.2 - Stack Overflow
Scraping website using Python, Selenium, Lxml and PhantomJS - Borja Refoyo