C#でMecabがいきなり落ちる問題

ある程度の大きなファイルに対してMecabを連続してやろうとすると、前触れもなくソフトそのものが落ちるという現象が発生した。
今までは、それほど大きくないファイルを相手にしていたので、発生しなかったが、10M20Mというファイルをやると発生するようになった。

解決法は、ちゃんとNull判定をすること!製作者の工藤さんが書いてくださった仕様書をちゃんと読もう!
そしてNullになったら、再起動させること!

        [DllImport("libmecab")]
        private static extern IntPtr mecab_sparse_tostr(IntPtr ptrMecab, string str);
        protected string _mecab_sparse_tostr(string str)
        {
            IntPtr s = mecab_sparse_tostr(this.ptrMecab, str);
            if (s != null)
            {
                return Marshal.PtrToStringAnsi(s);
            }
            else
            {
                return null;
            }
        }

Rawler Framework C#用、webスクレイピングとテキスト処理のためのフレームワークを作ったよ。その1

webからこの情報がほしいとかいう需要はそこそこあるのだと思うのだけど、それに対してプログラミングで取ってこればいいというのは、プログラムのできる人の言うことであって、プログラムのできない人にそれを言うのは酷である。確かに、今は、ライブラりが整備されているので、わりとちょこちょこググれば、自分の使っているプログラミング言語でのやり方はあっさりわかる。でも、プログラミングができない人に、自分のほしいwebページの情報を取るために、プログラミング言語の習得から始めるのは、やりたいこととやれることの距離が遠すぎる。
また、実際にプログラミングを組む側でも、webから適切な情報を取ってくるという処理は、かなりだるい。そもそもデザインが変わると取れなくなってしまうし、やることが基本的に繰り返しで、そのパラメータとかをいちいちHTMLのソースと見比べながら、ちょこちょこ変えていくのだが 結局はそれだけなので、誰かに任せたくなる。コードの再利用性を高め、クロール部分とデータに書き込むロジックの分離を図り、いわゆるWEBサイトの開発などで言われる、デザインとロジックの分離と同等のことを達成したい。

そんなわけで、わりと気持ち初心者から、上級者にも使える感じで「Rawler Framework」を作ってみた。

ダウンロードはここhttp://web.sfc.keio.ac.jp/~kiichi/wiki/index.php?Rawler

基本的にこのフレームワークXAMLのみで記述できる。XAMLとは、XMLマイクロソフトの拡張で、C#でのオブジェクトの状態と関係をXML形式で書けるものである。テキストであるが故、コピペの使用感が素晴らしい。実際に、WPFでは画面のデザインに使われている。(画面のデザインもオブジェクトの状態と関係の記述だ)WPFでは、XAML部分で画面のデザイン、そのXAMLとくっついた形で、コードビハインドでロジックを記述できるのと同様に、Rawlerもコードビハインドに対応していて、イベントパンドラを追加したりして、拡張性を確保している。とはいっても、XAMLのみでも動き、サイトにあるZIPファイルの中に含まれているEXEを起動してできる、ツールにXAMLをコピペするとそれだけで動く。なんちゃってインタープリターである。C#コンパイルしないとだめな言語なのに!

まぁ、Rawlerを記述するためには、VS.NetのXAML編集機能を使うとインテリセンスが働くので圧倒的に書きやすい。記述制約が強いため、作る時は、VS.Netを使うといい。手書きは非奨励。

Rawlerの一番シンプルな例はこれである。

<Data Comment="" 
      xmlns="clr-namespace:Rawler.Tool;assembly=Rawler"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      >
    <Page Url="http://d.hatena.ne.jp/kiichi55/">
        <Link>
            <Report></Report>
        </Link>
    </Page>
</Data>

これをツールの上のテキストボックスに入れて、実行ボタンを押すと、http://d.hatena.ne.jp/kiichi55/ からページをダウンロードして、そのページにあるリンクをすべて下のテキストボックスに書き出す。

解説すると、 にある記述は、おまじないだ。今のところ、特に気にしないでくれ。
次のでUrlを指定すると指定した所から取ってくる。で囲まれたところには、そのダウンロードしたHTMLが入っていると思っていい。そのHTMLから、でリンクを判別し、さらに子に見つかった個数分のURLを渡す。そして、でそのURLを出力を繰り返す。

Rawlerでは、親から子へテキストを流していくという仕組みを取っており、親は子を複数持てるので、同じテキストに対して複数の処理をすることができる。そういうわけで、いくつかの命令を使って、テキストの範囲を狭めながら取っていくものというのが基本である。
ここでの売りは、変数の排除である。プログラミングを組む時、面倒なのはいちいち一時変数の名前を考えないといけないのだが、これの場合、考えなくてすむし、ツリー構造で示されるため、変数の変化の過程がわかりやすい。(このような構造だからこそ、二つ以上の動的な変数の扱いは難しくなっているのだが・・・)
余計なことはさておき、webスクレイピングのために作ったものなので、それ用に便利な機能がある。それはNextPage機能だ。これは、に次に読むべきURLを渡してがそのURLをダウンロードしてまた同じ手順を繰り返すのだ。これは同じ形式になっているサイトでの次へボタンがあるサイトで威力を発揮する。

こんな感じだ。実行して終了するまで少し待つ。

<Data Comment="" 
      xmlns="clr-namespace:Rawler.Tool;assembly=Rawler"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      >
    <Page Url="http://d.hatena.ne.jp/kiichi55/">
        <Link>
            <Report></Report>
        </Link>
        <Link TagFilter="prev" IsSingle="True">
            <NextPage></NextPage>
        </Link></span>
    </Page>
</Data>


LinkでのTagFilterを指定することでTagの中に指定した文字列を含むLinkだけを抽出する。次へに使うだけなので、IsSingleで一つだけを出力させる。これではてなのページならどんどんページ送りがなされる。でも出力には、サイドバーなどにあるいらないURLが出てきてしまう。
当然、ページ送りをするのだから、記事だけの情報がほしいだろう。次のように改造する。

<Data Comment="" 
      xmlns="clr-namespace:Rawler.Tool;assembly=Rawler"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      >
    <Page Url="http://d.hatena.ne.jp/kiichi55/">
        <TagExtraction Tag="div" ClassName="section">
            <Link>
                <Report></Report>
            </Link>
        </TagExtraction>
        <Link TagFilter="prev" IsSingle="True">
            <NextPage></NextPage>
        </Link>
    </Page>
</Data>

TagExtractionはTagで指定したタグを複数取ってくる。ClassNameを指定することとで、その中で、タグのClass名が"section"であるものを探してくる。そうするとはてなの場合、記事部分の取ってこれる(他のサイトの場合自分でHTMLの法則を見破ってくれ!)ので、記事部分にあるURLが書きだされる。

はシンプルなログ吐き機能でしかない。ほしいのは構造化されたデータである。そのための機構はちゃんと用意してある。の三つだ。はデータを貯めるところである。必ず上流になくてはならない。に属性(エクセルで言う列名だ)付きで書き込みに行く。そして、でそこまで貯めたのを一行分として次の行に進む。このようにして、構造化されたデータを貯めていく。
もし、を指定し忘れると、行送りはなされず、属性にリストとして値が溜まっていく。デフォルト設定では、属性に対して追加するのみで値を書きかえることはない。

リンクの中身とURLという二つの列をもつデータの取得。実行ボタンを押して完了を待って、ViewDataを押すと見れる。

<Data Comment="" 
      xmlns="clr-namespace:Rawler.Tool;assembly=Rawler"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      >
    <Page Url="http://d.hatena.ne.jp/kiichi55/">
        <TagExtraction Tag="div" ClassName="section">
            <Link VisbleType="Tag">
                <Link VisbleType="Label">
                    <DataWrite Attribute="label"></DataWrite>
                </Link>
                <Link VisbleType="Url">
                    <DataWrite Attribute="url"></DataWrite>
                </Link>
                <NextDataRow></NextDataRow>
            </Link>
        </TagExtraction>
        <Link TagFilter="prev" IsSingle="True">
            <NextPage></NextPage>
        </Link>
    </Page>
</Data>

はVisbleTypeを指定することで、そのタグそのもの、タグで挟まれたところ、URL部分をそれぞれ取得できる。このようにすることで、URLと中身のペアが取れる。はてななので、キーワードとの組み合わせがいっぱい出る。
が発動した時に発生するイベント(Commited)があるので、逐次的にデータベースに入れるなどの処理もできるようになっている。

あと書き忘れた重要な売りは、ページの連続読み込みに対応していること。

<Data Comment="" 
      xmlns="clr-namespace:Rawler.Tool;assembly=Rawler"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      >
    <Page Url="http://d.hatena.ne.jp/kiichi55/">
        <Link >
      <Page>
              処理・・・・
            </Page>
        </Link>
    </Page>
</Data>

という感じで書くと、読み込んだページにあるURLからさらにページに飛んでいくことができる。はURLが空の時、親にある文字列をURLとして読み込みにいくためだ。このような感じで複数のページに飛んでデータを取得するという処理もこんな感じに簡単に書ける。


まぁ、こんな感じに使えるようになっているフレームワークある。いろいろと他にも便利機能があって、これだけでかなり完結できるはず。
その説明は、力尽きたのでまた今度。

Civ5スレの風景

Civ5に新パッチが来た。

739 名前:名無しさんの野望[sage] 投稿日:2011/06/28(火) 09:54:05.72 ID:iKS7Wfy7 [1/2]
パッチきたか…!!

  ( ゚д゚ ) ガタッ
  .r   ヾ
__|_| / ̄ ̄ ̄/_
  \/    /

740 名前:名無しさんの野望[sage] 投稿日:2011/06/28(火) 10:59:06.06 ID:iWk4TMoA [1/2]

パッチ来たって?
俺はもう(Civで)ダメだから、俺のことは置いてお前らだけでも先に通学or出勤してくれ…

741 名前:名無しさんの野望[sage] 投稿日:2011/06/28(火) 11:24:40.84 ID:fY11uCPE
________________
    <○√   <○√ 
     ‖       ‖  お前、一人だけにイイ格好させるかよ
     くく       くく

742 名前:名無しさんの野望[sage] 投稿日:2011/06/28(火) 11:28:26.23 ID:DSbX29OJ [2/5]

かくて廃人がまた一人……南無阿弥陀仏。

相変わらずのこの風景にわろた。

C#でMecabを使う。もうやだ編

ここ最近、Mecabに振り回されまくりである。

.net freamework4.0に変更したら動かなくなった。

新機能を使いたいわーっと新しいバージョンにすると死にました。
.net freamework4.0以降の変更で、「http://wiki.sh4e.net/?Tips%2FOther%2FMeCab」 で書いてある通り、DllImportを細かく指定しないと動きません。それ以前のソースでは急に動かなくなります。
でも細かく指定しても挙動がおかしい。細かいことを忘れたが、うまく動いてくれなくなった。余計な仕様変更するなぁぁぁぁ

そのため、Mecabの処理を別のフレームワークのバージョンを落としたDLLでやらせるという戦略に変更。前のだったら動くのだから。

MeCabSharpを使おう!

そんなわけで、手抜きして、MecabSharpを使おうとソースコード変更。
VS,NETでのデバックモードでは順調に動く。さて、他の人に配布しようとした段階のチェックのために使ってみたら止まる・・・。
吐いているエラーを調べて、ググってみたら、自分の書いたページがトップでヒットする。壮大にずっこける。教えてほしいのはこっちだよ!
他にヒットしていたページをよむ。「MeCabのラッパークラスを使用して形態素解析を行う - DoboWiki」で自分のさきほどの記事が参考されていてちょいと感動。そして狭い世界だな・・。
同じ症状のことが書かれている。再コンパイルすればいいらしい。やる。
注意点としては、こっちの環境はwindows7の64ビット版だということ。そのため、参照を C:\Program Files (x86)にしなくてはならない。
64ビット版で思い出したけど、Mecabは32ビット版なので、ソフトのコンパイル設定を Any CPUではなく、x86設定にしないとだめ。これも落とし穴。64ビット版OSにした意味ないじゃん的な気分になるけど。
これだけやって、ようやくまともに動くようになりましたよっと。

一応、作成したDLLをUP。

C#で文字列の一度置換した語は置換しない置換クラス。

連続して大量の置換処理をしたいとき、前に置換した語をさらに置換したくない時がある。
たとえば、
「私はエヴァンゲリオンが好きです。」
「私はエヴァが好きです。」
このような文章があった時、意味的には、エヴァンゲリオンエヴァは同じなので同じものとして扱いたい。
このとき、エヴァエヴァンゲリオンで置換すればいいのだけど、ただの置換を連発すると、上の方は、
「私はエヴァンゲリオンンゲリオンが好きです。」
となってしまう。これを避けたい。


文字列が長いもの順に置換をしていくと期待した結果になるでしょう。置換の結果はToText()で取得する。

プログラミングの戦略としては、

  1. 連結リストを使い、文字列をリストで扱う。初めは一つの文字列だけがリストに入っている。
  2. 対象の文字列を見つけたら、その対象の前、対象、対象の後の三つの文字列に分け、元の文字列と差し替えてリストに登録。
  3. 置換したものの文字列なら、文字列を探す対象にしない。
  4. 次の文字列へいく。と繰り返す。

わりと、アルゴリズムの授業の課題に相応しいような簡単な例題だ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyLib.Text
{
    public class ReplaceText
    {
        public ReplaceText(string txt)
        {
            list.AddFirst(new TextPart(txt));
        }
        LinkedList<TextPart> list = new LinkedList<TextPart>();

        public void Replace(string txt, string replacedText)
        {
            LinkedListNode<TextPart> part = list.First;

            while (part != null)
            {
                if (part.Value.Replaced == false)
                {
                    int index = part.Value.Text.IndexOf(txt);
                    string tmpText = part.Value.Text;
                    List<TextPart> tmpList = new List<TextPart>();
                    if (index > -1)
                    {
                        if (index > 0)
                        {
                            TextPart t1 = new TextPart(tmpText.Substring(0, index ));
                            tmpList.Add(t1);
                        }
                        TextPart t2 = new TextPart(tmpText.Substring(index, txt.Length));
                        t2.Text = replacedText;
                        t2.Replaced = true;
                        tmpList.Add(t2);
                        if (tmpText.Length > index + txt.Length )
                        {
                            TextPart t3 = new TextPart(tmpText.Substring(index + txt.Length ));
                            tmpList.Add(t3);
                        }
                        LinkedListNode<TextPart> tmp = null;
                        foreach (var item in tmpList)
                        {
                            tmp = list.AddBefore(part, item);
                        }
                        list.Remove(part);
                        part = tmp;
                    }
                    else
                    {
                        part = part.Next;
                    }
                }
                else
                {
                    part = part.Next;
                }
            }
        }

        public string ToText()
        {
            StringBuilder str = new StringBuilder();
            foreach (var item in list)
            {
                str.Append(item.Text);
            }
            return str.ToString();
        }


        class TextPart
        {
            public TextPart(string txt)
            {
                this.Text = txt;
                this.Replaced = false;
            }
            public string Text { get; set; }
            public bool Replaced { get; set; }
        }
    }

}

XAMLいいよXAML

XMALとは、マイクロソフトの作ったマークアップ言語。ぐぐると、基本的にWPFsilverlightとセットで語られる。
多くはWPFなどで、インターフェース設計で使えるもので、多くのwebサイト開発のように、デザインとロジックの分離を可能とする技術である。
実際、WPFを使ってみると便利で、ボタンの配置などの情報を今までのデザイナソフトからのマウスでの指定や、一つ一つのコントロールをNewして、個々のクラスに細々とパラメータを代入するような開発と比べると、XMLで記述しているので、他のところで作ったものをコピペで簡単に移植できたりする。

連載:XAMLの基礎知識 - @IT
第2回 WPFとXAMLの関係とは? XAMLの基礎を学ぶ (1/3):連載:WPF入門 - @IT

しかし、これを読むと分かるけど、XAMLの本質は、インタフェースのデザインのためのものではない。
オブジェクトの依存関係を含む、設定を記述する言語である。
今までは、オブジェクトの永続化というと、バイナリにするか、制約の多い、XML記述にするかの選択であったけれど、もう一つ選択肢が出来たことになる。
また、WPFでの開発でも分かるとおり、XAMLは編集性が高い。そして、テキストのXAMLファイルを動的に読み込めて扱える。この二点により、プログラミングとして、手作業で動的に変化して欲しいところを記述するのに最適である。

そんなわけでXAMLいいよXAML
使い方も簡単。.Net Framework4.0から 「System.XAML名前空間が追加された。ここを使えばOK。
そして、難しいことを考えず、XamlServicesを使えばOKっぽい。
LoadとSaveが手軽に出来る。

http://msdn.microsoft.com/ja-jp/library/system.xaml.xamlservices.aspx

ここに書いてあるように、検索するとよく見つかる、system.windows.markup.xamlwriter はWPF専用なので、普通のクラスには使ってはいけない。

クラスの頭に、[ContentProperty("対象のプロパティ")]を指定すると、XAMLがすっきりする。
パブリックプロパティを記述するので、読み取り専用にするとか、メソッドにしてしまうとかすることで、不可視にするとさらにXAMLがすっきりする。

また、XamlServicesから出力したXAMLWPFのコントロールに貼り付け、ルートのクラスに「x:Class="Sample.MyGrid"」などの設定をちゃんとやると、普通のWPFのように、イベントパンドラを新たに付け加えることが出来る。こんな斜め上なやり方をやらなくても、直接XAMLファイルを扱えるテンプレートが欲しいものだ。

そういえば、XAMLが使えるという感覚が出てくると、XAMLを編集するために特化した、コントロールを欲しくなるのだが、検索しても無いようだ。
今のところ、VS.Netが最強という感じなのかな?

こんなことを調べまくったのは、これを作っていたためです。Webクロールするための、フレームワークです。XAML周りは作りかけですがw
Rawler

クロールの性格上、webページに合わせる必要があるので、それぞれのサイトに対して個別対応しなくてはならない。そのため、ユーザにクロールプログラムを作ってもらわなくてはいけない。しかし、webページの取得、解析のためのテキスト処理、データの書き込み、スレッド処理等を自分でやれと言っても、様々な分野にわたるので結構難易度が高い。これを肩代わりするためのフレームワークです。

こんな感じに書くとクロールプログラムとなって、ウェブページのここの要素をセマンティックに取得できる。

XAMLの形式に沿って親子関係をベースにしてプログラムが進むように作っている。たぶん、こういうことをするためにXAMLがあるのだと思う。


あんまりにもWPFがらみのXAMLの話は多いのだけど、XAML単体の話はなさ過ぎるので書いてみた。

C# カスタムコントロール

あるようでない、テキストの中身がないとき、灰色でヘルプを表示するTextBoxコントロール。文字が入ると消えます。

namespace MyLib.Controls
{
    public class TextBoxEx : System.Windows.Forms.TextBox
    {
        public TextBoxEx()
            :base()
        {
            SetUp();
            
        }

        private void SetUp()
        {
            helpLabel = new System.Windows.Forms.Label();
            helpLabel.AutoSize = true;

            helpLabel.ForeColor = System.Drawing.Color.Gray;
            this.Controls.Add(helpLabel);
            this.SizeChanged += new EventHandler(TextBoxEx_SizeChanged);
            helpLabel.Click += new EventHandler(helpLabel_Click);
            this.TextChanged += new EventHandler(TextBoxEx_TextChanged);
            helpLabel.Visible = true;
        }

        void TextBoxEx_TextChanged(object sender, EventArgs e)
        {
            if (this.TextLength == 0)
            {
                helpLabel.Visible = true;
            }
            else
            {
                helpLabel.Visible = false;
            }
        }

        void helpLabel_Click(object sender, EventArgs e)
        {
            this.Focus();
        }

        void TextBoxEx_SizeChanged(object sender, EventArgs e)
        {
            helpLabel.Left = this.ClientRectangle.Width / 2 - helpLabel.Width / 2;
            helpLabel.Top = this.ClientRectangle.Height / 2 - helpLabel.Height / 2;
        }



        public string HelpText
        {
            get
            {
                return helpLabel.Text;
            }
            set
            {
                helpLabel.Text = value;
                OnSizeChanged(new EventArgs());
            }
        }

        public System.Drawing.Font HelpFont
        {
            get
            {
                return helpLabel.Font;
            }
            set
            {
                helpLabel.Font = value;
                OnSizeChanged(new EventArgs());
            }
        }

        public System.Drawing.Color HelpColor
        {
            get
            {
                return helpLabel.ForeColor;
            }
            set
            {
                helpLabel.ForeColor = value;
            }
        }
        private System.Windows.Forms.Label helpLabel = new System.Windows.Forms.Label();

        public System.Windows.Forms.Label HelpLabel
        {
            get { return helpLabel; }
        }

      

    }
}