ブログトップ 記事一覧 ログイン 無料ブログ開設

それはそれ。これはこれ。 このページをアンテナに追加 RSSフィード

2003 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2004 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 03 | 04 | 05 | 06 | 07 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 11 | 12 |
2013 | 01 | 02 | 04 | 05 | 07 | 08 | 10 | 11 | 12 |
2014 | 02 | 03 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 05 | 06 | 07 | 11 |
2016 | 07 |
2017 | 01 | 03 | 06 | 07 | 11 |
2018 | 05 | 07 | 08 | 09 | 10 |

2009-05-26(火)

[]いくらなんでも看板に偽りあり いくらなんでも看板に偽りありを含むブックマーク いくらなんでも看板に偽りありのブックマークコメント

はてなブックマークの注目エントリーで、「バグを生まないコーディング法、10個の規則でソフト開発を効率化」という200以上のブックマークを集めた記事を発見タイトルに惹かれて読んでみる。

http://eetimes.jp/article/23004/


出だしに、「コーディング規則でバグを減らせる」と書いてあるので、「えー、それは無理」と。まあ、レビューしやすくなれば結果的にバグが減るかもと言うのはあるか。で、規則を見てみると、orz

これって、コーディング規則と言うより、「C言語入門」じゃないの?

そりゃCを知らないCプログラマの書いたプログラムには、Cを知っているCプログラマの書いたプログラムより、バグは確かに多いです。

トラックバック - http://d.hatena.ne.jp/otn/20090526

2009-05-22(金)

[]Linuxソース表示時に4タブになっていて欲しいのだが Linuxでソース表示時に4タブになっていて欲しいのだがを含むブックマーク Linuxでソース表示時に4タブになっていて欲しいのだがのブックマークコメント

最近プログラムを書くときは4タブにしているのだが、cat や less でソースを表示させると8タブなのでインデントが乱れる

expand -4 すればいいのだが面倒。ということで端末のタブを変更することを考える。


タブの展開は確か tty でやってたような記憶があったので、man stty してみたがそれらしいオプションはない。念のためBSDの man stty を見ると、oxtabs というオプションでタブのスペース展開を制御できることがわかった。しかし、Linuxにはそんなオプションはない上に、BSDでもいくつのスペースに展開するのかは指定できないみたい。


ということで、これは端末側で何とかするしかないのか。使っているのは Teratermだ。端末タイプは vt100。


man terminfoで、タブクリアとタブ設定のcapnameを調べる。全タブストックリアtbc で、該当位置へのタブストップセットが hts。

tput tbc|hexdump -c と tput hts|hexdump -c で、シーケンスを調べる。あとは、.bash_profile に書くだけ。

echo -e '\e[3g'
for((i=0; i<20; ++i)) do echo -e '    \eH\c'; done

タブ間隔を引数にした関数にしたいところだ。と、よく考えればtputのままでもいいのか。

tabset(){
    cols=$(tput cols)
    hts=$(tput hts)
    len=$(expr length "$hts")
    tput tbc
    for((i=0; i<$cols/$1; ++i)) do printf %$(($1+$len))s "$hts"; done
    printf '\r'
    unset cols hts len
}
tabset 4

2009-05-25追記:

くっ! less は自前でタブ展開してるのか。-x4 オプションでOK。

トラックバック - http://d.hatena.ne.jp/otn/20090522

2009-05-09(土)

[]スクレイピングのためのNokogiri利用メモ スクレイピングのためのNokogiri利用メモを含むブックマーク スクレイピングのためのNokogiri利用メモのブックマークコメント

スクレイピングチュートリアルを書いてみた。

参考:http://nokogiri.rubyforge.org/nokogiri/Nokogiri.html

まだまだたくさんのクラスメソッドがあるが(読んでない)、HTMLスクレイピングに限定すれば多分これくらいで十分。

2014-02-16追記

なんかたくさんブックマークされていることに気づいたので、サンプルコードRuby1.9/2対応アップデート

Mechanize周りも修正。WWW::Mechanize → Mechanize 等


(0) 前提知識

RubyHTMLDOMCSSセレクタまたはXPath


(1) クラス構造理解

Nokogiri::HTML::Document < Nokogiri::XML::Document < Nokogiri::XML::Node < Object

Nokogiri::XML::Element < Nokogiri::XML::Node < Object

Nogogiri::XML::NodeSet < Enumerable < Object


(2) HTMLドキュメントオブジェクトを得る

まずは最初に、解析したいページの Nokogiri::HTML::Document オブジェクトを得る。

Nokogiri::HTML::Document は、Nokogiri::XML::Document のさらに、Nokogiri::XML::Node のサブクラスなので、

Node のメソッドが使える。

ログイン処理など、Cookieを使ったページ遷移が必要場合
Mechanize を利用して、page = agent.get(url) で Mechanize::Page オブジェクトが返り、page.parser またはその alias の page.root で Nokogiri::HTML::Document オブジェクトが返る。ページの文字コードutf-8 以外だとうまく処理できないケースもあるので、id:otn:20090429 参照
単純なページ取得の場合
open-uriを使って、doc = Nokogiri.HTML(open(url)[,url,[jcode] ]) で Nokogiri::HTML::Document オブジェクトが返る。HTMLメソッドの第三引数でページの文字コードを指定する

(3) 基本的な処理パターン

Document 等の Nokogiri::XML::Node または Nokogiri::XML::NodeSet オブジェクトに対して、

CSSセレクタXPath検索を行い、検索結果として Nokogiri::XML::NodeSet オブジェクトを得る。

NodeSetはArrayのようなもので、eachや[]でNodeのサブクラスのElementを得て、情報を得る。

tds=doc.xpath("//td") # => tdタグの検索(NodeSetオブジェクト)
tds.size              # => tdタグの個数
tds[0]                # => 最初のtdタグ(Elementオブジェクト)
tds[0]["class"]       # => 最初のtdタグのclass名(String)
tds[0].xpath(".//a")  # => さらにその中のaタグを探す(NodeSetオブジェクト)

よく使うと思われるメソッドは、xpathまたはcss、NodeSetからElementを取り出す[]やeach、Elementの属性値を取る[]、テキストの取り出しtext、あとは近くのノードをたどるparentやchild・children・previous・nextくらいでしょうか。あ、NodeSetに対するempty?とかsizeも。


(4) Nodeの参照系メソッド

検索
at("検索")
XPathCSSセレクタ検索し、結果の最初のノード(Element)。無ければnil
css("CSSセレクタ")
引数は複数やArrayも可能。CSSセレクタ検索し、NodeSetを返す。無ければ空のNodeSet
xpath("XPath")
引数は複数やArrayも可能。XPath検索し、NodeSetを返す。無ければ空のNodeSet
search("検索")、/ "検索"
引数は複数やArrayも可能。XPathCSSセレクタ検索し、NodeSetを返す。無ければ空のNodeSet
○自ノード情報
node_name、name
ノード名(String)
css_path
このノードのCSSセレクタ(String)
path
ノードのXPath(String)
node_type、type
ノードタイプ(Fixnum)
blank?
白文字のみのテキストノードか?
cdata?
CDATAノードか?
text?
テキストノードか?
commnet?
コメントノードか?
element?、elem?
エレメントノードか?
属性情報

属性値(String)を返すものと、属性(Attr < Node)を返すものがある。

["属性名"]、get_attribute("属性名")
属性値(String)。無ければnil
key?("属性名")、has_attribute?("属性名")
属性があるか?
keys
属性名(String)の一覧(Array)
values
属性値(String)の一覧(Array)
attributes
属性名(String)と属性オブジェクト(Attr)のハッシュ(Hash)
attribute("属性名")、attribute_nodes
属性オブジェクト(Attr)やそのリスト(Array)
each { |k,v| 。。。}
属性名(String)と属性値(String)でブロック呼び出し
○子ノード情報
child
最初の子ノード(Element)
children
子ノード(Element)のリスト(Array)
content、text、inner_text、to_str
テキスト子孫ノードの内容をつなぎ合わせたもの(String)。IEFirefoxJavaScriptのinnerTEXT、textContent相当
inner_html
子孫ノードのHTMLをつなぎ合わせたもの(String)。IEFirefoxJavaScriptのinnerHTML相当
兄弟ノード情報
previous_sibling、previous
兄ノード(Element)
next_sibling、next
弟ノード(Element)
○親ノード情報
parent
親ノード(Element)
ancestors
親、祖父・・・ノード(Element)のリスト(Array)
document
そのノードを含むDocumentオブジェクトを得る。
テキスト
to_html、to_html("エンコード")
ノード全体をHTMLテキストに(String)。IEJavaScriptのouterHTML相当
to_xhtml、to_xhtml("エンコード")
ノード全体をXHTMLテキストに(String)
write_html_to(io,"エンコード")
ノード全体をHTMLテキスト(String)にして書き出す(io)

(5) NodeSetの参照系メソッド

検索
at("検索")
css("CSSセレクタ")
xpath("XPath")
search("検索")、/ "検索"
○Enumerator、Arrayもどき
length、size
[添え字]
empty?
first
last
each { |x| 。。。}
push(node)、<< node
to_a、to_ary
テキスト
inner_text、text
すべてのエレメントに適用してつなぎ合わせ
inner_html
すべてのエレメントに適用してつなぎ合わせ
to_html(*arg)
すべてのエレメントに適用してつなぎ合わせ
to_xhtml(*arg)
すべてのエレメントに適用してつなぎ合わせ

(6) サンプル:各地の今日の天気

XPashとCSSを混ぜてみた。

Windowsで動かしたので、出力はSJIS。-Kは念のためeuc-jpに。

#!/usr/bin/ruby -Ke
require "rubygems"
require "nokogiri"
require "open-uri"
require "kconv"

doc = Nokogiri.HTML(open("http://weather.asahi.com"))

doc.search("//table[@class='font12' and @bgcolor]//tr[position()>1]").each do |tr|
	place   = tr.search("td[1]").text
	weather = tr.search("td[2] > img").map{|img| img["alt"]}.join("|")
	puts "#{place}\t#{weather}".tosjis
end
2014-02-16追記

エンコードをRuby1.9or2らしく書くとこんな感じか。

#!/usr/bin/ruby
require "rubygems"
require "nokogiri"
require "open-uri"

Encoding.default_external = "Windows-31J"

doc = Nokogiri.HTML(open("http://weather.asahi.com","r:euc-jp"),nil,"euc-jp")

doc.search("//table[@class='font12' and @bgcolor]//tr[position()>1]").each do |tr|
	place   = tr.search("td[1]").text
	weather = tr.search("td[2] > img").map{|img| img["alt"]}.join("|")
	puts "#{place}\t#{weather}"
end

(7) サンプル:マイミク最新日記

Windowsで動かしたので、出力はSJIS。-Kは念のためutf-8に。

2014-02-16修正

ページ内容(HTML)が変わっていたので、現状の物に対応

#!/usr/bin/ruby -Ku
MAIL="foo@example.jp"
PASS="password"

require "rubygems"
require "mechanize"
require "kconv"

agent = Mechanize.new
agent.user_agent_alias = "Windows IE 9"

agent.get("http://mixi.jp")
agent.page.form("login_form").field("email").value=MAIL
agent.page.form("login_form").field("password").value=PASS
agent.page.form("login_form").submit

agent.get("http://mixi.jp/home.pl")
agent.page.root.search("ul.homeFeedList > li.diary").each do |node|
	puts sprintf("%s\t\%s\t%s", node.search("li.date")[0].text,
		node.search("p.name>a")[0].text, node.search("p.title")[0].text).tosjis
end
2014-02-16追記

エンコードをRuby1.9or2らしく書くとこんな感じか。

#!/usr/bin/ruby
MAIL="foo@example.jp"
PASS="password"

require "rubygems"
require "mechanize"

Encoding.default_external = "Windows-31J"

agent = Mechanize.new
agent.user_agent_alias = "Windows IE 9"

agent.get("http://mixi.jp")
agent.page.form("login_form").field("email").value=MAIL
agent.page.form("login_form").field("password").value=PASS
agent.page.form("login_form").submit

agent.get("http://mixi.jp/home.pl")
agent.page.root.search("ul.homeFeedList > li.diary").each do |node|
agent.page.root.search("ul.homeFeedList > li.diary").each do |node|
	printf "%s\t\%s\t%s\n", node.search("li.date")[0].text,
		node.search("p.name>a")[0].text,
		node.search("p.title")[0].text
end

(以下おまけ)

(8) Nodeの更新メソッド

○自ノード処理
node_name="名前"、name="名前"
ノード名の差し替え
dup
ノードのコピーを作る。dup(0)だと子ノードはコピーしない
unlink、remove
ノードの削除
replace(node)
ノードを置き換え
swap("テキスト")
ノードをテキストHTMLから作ったノードと置き換えて元のノードを返す(Element)
属性書き換え
["属性名"]="値"、set_attribute("属性名","値")
属性値をセット
delete("属性名")、remove_attribute("属性名")
属性の削除。属性値(String)を返す。無ければnil
○子ノードの処理
add_child(node)、<< node
nodeを子ノードとして追加(self)
content="テキスト"
子孫ノードすべてをテキストノードで置き換え
inner_html="文字列"
子孫ノードすべてを文字列HTMLタグとして置き換え
兄弟ノードの処理
add_previous_sibling(node)
nodeを兄ノードとして追加(self)
add_next_sibling(node)
nodeを弟ノードとして追加(self)
before("テキスト")
テキストを兄テキストノードとして追加(self)
after("テキスト")
テキストを弟テキストノードとして追加(self)
○親ノードの処理
parent=node
親ノードから切り離して別ノードの最後の子ノードに
○その他
encode_special_chars("文字列")
< > & " をエンコードする
fragment("文字列")
文字列HTMLタグとして DocumentFragmentオブジェクトを作る

(9) NodeSetの更新メソッド

add_class("クラス名")
すべてのエレメントにクラス追加
remove_class("クラス名")
すべてのエレメントからクラス削除
attr("属性名","属性値",&blk)、set("属性名","属性値",&blk)
すべてのエレメントに属性セット
remove_attr("属性名")
すべてのエレメントから属性削除
after("テキスト")
最後のエレメントに弟としてテキストノード追加
before("テキスト")
最初のエレメントに兄としてテキストノード追加
dup
ノードセットの複製
unlink、remove
すべてのエレメントをそれぞれの親から削除

2009-05-04(月)

[]新手の検索スパム新手の検索スパム?を含むブックマーク 新手の検索スパム?のブックマークコメント

検索していると、意味のないページが少なからず引っかかる。その検索語を検索した結果ページだ。

どういうことかというと、例えば「ORIG_PATH_INFO」で検索すると、「ORIG_PATH_INFO ;の求人情報 | ジョブエンジン : 総合転職支援サイト ...」というサイトが引っかかる。このサイトで「ORIG_PATH_INFO」というのを検索した結果のページな訳だが、当然ながら何の情報もない。


いったいどういう理由でこのようなページがクローラーに引っかかるのか?引っかかると言うことは、

・誰かがこのページでこの検索語で検索した

・そのリンクを別のページから張った

というのが普通の理由だろうけど、それはとても考えられない。

ということは、こういったジャンクページを系統的に生成してリンクをはり回る仕組みがあるということではないか?意図的とすれば目的が不明。まあ、この手の検索サイトSEOなのかもしれないけど、SEOが必要ないようなメジャー検索サイトでもよく見かける。うーん、いざ探すとなかなか見つからないけど、普通検索するとよく出くわす。


これらを検索結果から省くようなことをしてくれればいいのだけど、判断が難しそう。

トラックバック - http://d.hatena.ne.jp/otn/20090504

2009-05-03(日)

[]Pukiwikiの使用PHPをPHP5に(さくらのレンタルサーバー) Pukiwikiの使用PHPをPHP5に(さくらのレンタルサーバー)を含むブックマーク Pukiwikiの使用PHPをPHP5に(さくらのレンタルサーバー)のブックマークコメント

id:otn:20080130 に、さくらのレンタルサーバーで使用しているPukiwikiのPHP5への切り替えについて、

一般ユーザでのページ表示は問題ないが、私がパッチを当てた管理者ユーザ機能だとFrontPageしか表示できない。他のページへのリンクをクリックしてもFrontPageが表示されてしまう。

と書いたが、原因を調べて対応した。


URLを整形しているのだが、「http://otnx.jp/CMD/バグ」と書くとOKで、「http://otnx.jp/CMD/edit.php/バグ」だと「バグ」のページじゃなくてFrontPageが表示されると言うことだ。

問題切り分けのために、「http://otnx.jp/CMD/index.php/バグ」を試してみると、これもだめ。と言うことで、PATH_INFOが取れないと言うことのようだ。


id:otn:20060603に下記のように書いた。三年前だ。

cgiのため、直接にPATH_INFOは参照できず、php.iniに、

cgi.fix_pathinfo=1

を指定した上で、ORIG_PATH_INFOを参照する。

デバッグコーディングを入れて、$_SERVER['ORIG_PATH_INFO'] を見ると空だ。その代わりに、$_SERVER['PATH_INFO'] に値が入っている。どうもこの点の仕様がPHP4からPHP5になったことで変わっているようだが、ぐぐってもそのあたりの情報がない。現実がそうなので、調査はほどほどにして、「ORIG_PATH_INFO」の部分を「PATH_INFO」に書き換えてOK。


もしかしてと思って、cgi.fix_pathinfo=0 にしてみたがエラーになるので、やっぱりこの設定は必要そう。

[][] Pukiwiki スパムコメントよけ  Pukiwiki スパムコメントよけを含むブックマーク  Pukiwiki スパムコメントよけのブックマークコメント

スパムコメントがたくさん書かれていたので、対策を少し考える。おそらくは自動処理だと思うので、JavaScriptで確認ダイアログを出してそれを通さないと投稿できないようにする。

プログラムでhtmlを解釈してポストすると、example.comに行くように。これでどの程度減るかしばらく様子を見る。htmlを読まないで決めうちでポストしてくるようなものだと駄目だけど、項目を見ると、refererとかdigestとかあるのでそういうのは弾いてくれていると期待する(読んでないけど)。


<form action="fooBar" method="post"><form baz="fooBar" action="http://example.com" method="post" onsubmit="return check('baz')">
に書き換え
function check(x){
  if(window.confirm('Are You Sure ?')){
    var fs=document.getElementsByTagName('form');
    for(var i=0; i<fs.length; ++i){
      var a=fs[i].getAttribute(x);
      if(a) fs[i].setAttribute('action',a);
    }
    return true;
  }
  else return false;
}

Tatsuya(レンタルサーバー好き)Tatsuya(レンタルサーバー好き) 2010/07/23 02:18 参考になります。
ありがとうございます。

トラックバック - http://d.hatena.ne.jp/otn/20090503