2011年6月19日日曜日

PHPの開発を受けたり、お守り(運用)をしたりしているとcakePHP等のMVCモデルまではいかなくとも SmartyのViewだけでもあった方が良いなと思う事が多いです。直接PHPファイルに書いたり preg_replace を使って自前のテンプレートエンジンを使われたりすると、肝のソースコードに到達するまでに余計な時間が掛かります。

デザイン(マークアップ言語)は外に出そうよと強く思います。V(View) が別管理されているだけでも違います。

さて、Smartyの継承です。Smartyは各プロダクトで共通の設定をする方が設定が散逸せずに管理が楽です。

継承して使いたいと考え 先人を探して googleすると下記が引っかかりました。

Smartyの継承クラスを用意する

require_once("Smarty.class.php"); // smarty.class.phpの指定。環境によって異なります。
class xxxxSmarty extends Smarty{ // Smartyクラスを継承したxxxxSmaryクラスを定義します。
public function __construct(){    // __construct()はPH5以上です。
$this->Smarty();
$this->left_delimiter = "{!";    // xoopsと同じにする。(xoopsに慣れてるので)
$this->right_delimiter = "}";
$this->template_dir = "xxxxxxxx/templates";
$this->compile_dir = "xxxxxxxx/templates_c";
}
}

Uncaught exception 'SmartyException' with message 'Please use parent::__construct() to call parent constuctor'

のエラーが出た。parent::__construct() 親のコンストラクタを先頭で呼んで下さい。

色々あったのねという事で 下記に修正しました。(抜粋)

class SpnsSmarty extends Smarty{ 
    public function __construct(){
        parent::__construct();
        //$this->Smarty();
        $this->left_delimiter = "{{";    
        $this->right_delimiter = "}}";

テンプレートの継承も奇麗にヘッダーやフッターを分割出来るので是非機会があれば使ってみて下さい。本家のサイトが詳しいです。

テンプレートの継承

2011年4月18日月曜日

1. シェルスクリプト 方式


(1) ftp -n

-n ユーザ名とパスワードを聞かれないようにする。
⇒ シェルスクリプトで実行出来るようにする。

$ vi ftp.sh
下記を入力する

---------
ftp -n hostname << ECHO
user userid password
bin
cd www
ls
ascii
bye
ECHO
---------
$ chmod 755 ftp.sh (2) echo "" | ftp -n
#!/bin/sh
echo "user userid password
ls
" | ftp -n hostname

2. wget (ダウンロードのみ) 方式

$ wget ftp://userid:password@ hostname/pass

3. ncftp 方式

ncftpget ncftp + get : ダウンロード ncftpget ncftp + put : アップロード インストール: yum install ncftp (1) ncftpget Usages:
  ncftpget [flags] remote-host local-dir remote-path-names...      (mode 1)
  ncftpget -f login.cfg [flags] local-dir remote-path-names...     (mode 2) ← 推奨?
  ncftpget [flags] ftp://url.style.host/path/name                  (mode 3)
  ncftpget -c [flags] remote-host remote-path-name > stdout        (mode 4)
  ncftpget -C [flags] remote-host remote-path-name local-path-name (mode 5)
  ncftpget -c [flags] ftp://url.style.host/path/name > stdout      (mode 6)
(2) ncftpput Usages:
  ncftpput [flags] remote-host remote-dir local-files...   (mode 1)
  ncftpput -f login.cfg [flags] remote-dir local-files...  (mode 2) ← 推奨?
  ncftpput -c remote-host remote-path-name < stdin         (mode 3)
  ncftpput -C remote-host local-path-name remote-path-name (mode 4)

注意:flags の -u -p はセキュリティ的に使用は推奨されない

(3) login.cfg

2011年4月11日月曜日

mysqlドライバのインストール


$ gem install ruby-mysql

接続テスト


require 'rubygems'
require 'mysql'


#db = Mysql.new("localhost", "username", "password","db")
db = Mysql.connect("localhost", "username", "password","db")

⇒ エラーが出なければOK。

undefined method `connect' for Mysql:Class (NoMethodError)
もしくは `initialize': wrong number of arguments (3 for 0) (ArgumentError)
が出る場合


- 確認

1. Mysql.methods でメソッドを確認

["private_class_method", "inspect", "yaml_as", "to_yaml_style", "name", "tap", "clone", "public_methods", "object_id", "__send__", "method_defined?", "instance_variable_defined?", "equal?", "freeze", "extend", "send", "const_defined?", "methods", "ancestors", "module_eval", "instance_method", "hash", "autoload?", "dup", "to_enum", "yaml_tag_read_class", "instance_methods", "public_method_defined?", "instance_variables", "class_variable_defined?", "eql?", "constants", "id", "instance_eval", "singleton_methods", "module_exec", "to_yaml", "yaml_tag_class_name", "const_missing", "taint", "instance_variable_get", "frozen?", "enum_for", "private_method_defined?", "public_instance_methods", "display", "instance_of?", "superclass", "to_yaml_properties", "method", "to_a", "included_modules", "const_get", "instance_exec", "type", "<", "protected_methods", "<=>", "class_eval", "==", "yaml_tag_subclasses?", "class_variables", ">", "===", "instance_variable_set", "protected_instance_methods", "protected_method_defined?", "respond_to?", "kind_of?", ">=", "taguri", "public_class_method", "to_s", "<=", "const_set", "allocate", "class", "new", "private_methods", "=~", "tainted?", "__id__", "class_exec", "taguri=", "autoload", "untaint", "nil?", "private_instance_methods", "include?", "is_a?"] connect メソッドがない

2. gem list |grep mysql を確認。
mysql (2.8.1)がある。

- 対処方法

1. mysql をアンインストール

$ gem uninstall mysql

2. ruby-mysqlのインストール
⇒既にインストールしている場合はこの手順は必要ありません。

$ gem install ruby-mysql

Mac OS X環境で invalid packet: sequence number mismatch のエラーが出る場合


$gem unistall ruby-mysql
$gem install ruby-mysql -v 2.9.3

2011年3月31日木曜日

はじめに


XXMLサイトマップ(sitemap.xml)をトップページに配置する事で検索エンジン(google等)にページの情報を通知する事が出来ます。
必ずクロールされるとは限りませんが検索エンジンのインデックスに登録するURLの候補にはなります。
Webクローラ(Anemone)を利用して 簡易 simap ジェネレータを作ってみました。ページが多いサイトでは実行する時にはサーバに負荷が掛かりますので注意が必要です。

ソースコード


pythonで書いた場合

Anemone(Webクローラフレームワーク)から取得したURLの結果を使用してpythonでsitemap.xmlに加工しています。
コマンドを組み合わせる shell scriptに近い作り方かもしれません。

import datetime
import os

URLSET = {
          "xsi":"http://www.w3.org/2001/XMLSchema-instance" ,
          "schema":'''http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd''',
          "xmlns":"http://www.sitemaps.org/schemas/sitemap/0.9"
           }



def update_date():
    """
    更新日付を取得 ⇒ 現在日付で更新
    """
    return datetime.date.today().isoformat()

def create_sitemap_xml(url_list_path,sitemap_path):
    """
    サイトのurlのリストから サイトマップ(sitemap.xml)を作成します
    """
    sitemap_file = open(sitemap_path,'w')
    url_list_file = open(url_list_path,'r')
    
    #header 
    sitemap_file.write( '''
''' % (URLSET))
    sitemap_file.write(os.linesep)
    #body
    for url in url_list_file:
        sitemap_file.write('    %s' % (os.linesep))
        sitemap_file.write(        
        '''        %s
        %s
        daily
        0.5
''' % (url.strip(),update_date())
                           )
        sitemap_file.write('    %s' % (os.linesep))    
    #footer
    sitemap_file.write('')

if __name__ == "__main__":
    #sitemap.xmlを作成
    create_sitemap_xml("url_list.txt","sitemap_from_list.xml")

Rubyで書いた場合

依存)

anemone
builder XMLの作成の記述がシンプルに出来る。

Rubyではライブラリを使用している分シンプルで分かりやすいかもしれません。

ソース)

require 'rubygems'
require 'anemone'
require 'builder'


opts = {
  :storage => Anemone::Storage.MongoDB,
  :skip_query_strings => true,
}

sitemap = ""
xml = Builder::XmlMarkup.new(:target => sitemap, :indent=>2)

xml.instruct!
xml.urlset(:xmlns=>'http://www.sitemaps.org/schemas/sitemap/0.9',
           "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
           "xsi:schemaLocation" => "http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
) {

  Anemone.crawl("http://work.blog.eggplant.org.uk",opts) do |anemone|
    anemone.on_every_page do |page|
      xml.url {
        xml.loc page.url
        xml.lastmod Time.now.strftime("%Y-%m-%d")
        xml.changefreq "daily"
        xml.priority 0.5
      }
    end
  end
 }
File.open('sitemap.xml', 'w') do |f|
  f.write sitemap
end

参考サイト

※ サイトマッププロトコル ⇒ sitemaps.org
Generate sitemap.xml using a ruby script

2011年3月28日月曜日

はじめに


Anemoneはrubyで作られたWebクローラフレームワークです。実行した環境は Max OS X 10.6 です。

特徴は

  • The multi-threaded design makes Anemone fast.
  • The API makes it simple. And the expressiveness of Ruby makes it powerful

です。
意訳するとマルチスレッド対応でAPIがシンプルなとことが売りという感じでしょうか。
クライアントのプログラムがシンプルに記述出来てCLIで実行出来るので他の(言語の)プログラムとの連携が取りやすい所が気に入り試してみる事にしました。

紹介記事:クローラーを作るためのフレームワーク「Anemone」

Anemoneのインストール


$ gem install anemone

lxml,lxslt2 関連でエラーが出た場合

libxml2 is missing.
========
ERROR: Error installing nokogiri:
ERROR: Failed to build gem native extension.

/usr/local/bin/ruby extconf.rb
checking for libxml/parser.h... no
-----
libxml2 is missing. please visit http://nokogiri.org/tutorials/installing_nokogiri.html for help with installing dependencies.
-----

上記のエラーが出た場合は nokogiri のサイトを参考に します。

私のサーバのOSは Cent OSですので下記を実行しました。

$ yum install -y libxml2 libxml2-devel libxslt libxslt-devel
$ gem install anemone

問題点


最初にサンプルページにある下記のプログラムで実行しました。

require 'anemone'

Anemone.crawl("http://www.example.com/") do |anemone|
  anemone.on_every_page do |page|
      puts page.url
  end
end

Anemoneはメモリーにデータを保存するため、ページが多い時に負荷が掛かりパソコン自体の動作にも影響が出ました。
方法がないかと探すとストレージをサポートしているとの記述があります。説明は本家のサイトの下記の辺りです。

By default Anemone stores all the pages it crawls in an in-memory hash. This is fine for small sites, and is very fast. How many pages it can handle will depend on the amount of RAM in your machine, but after a certain point you will want to start storing the pages on disk during the crawl.

ストレージの選択


Anemone がサポートしているストレージは下記の通りです。

  • Redis (>= 2.0.0)
  • MongoDB
  • TokyoCabinet
  • PStore

メモリ上のデータを書き込むという目的だけなので自分の興味で MongoDB を選択しました。

mongoのインストール・起動


(1) mongoのインストール

$ wget http://fastdl.mongodb.org/osx/mongodb-osx-x86_64-1.8.0.tgz
$ tar xvfz mongodb-osx-x86_64-1.8.0.tgz
$mv mongodb-osx-x86_64-1.8.0 /usr/local/mongodb

(2) mongoの起動

バックグラウンドで起動

$ mkdir ~/tmp/mongodb
$ sudo /usr/local/mongodb/bin/mongod --dbpath ~/tmp/mongodb &

(3) Ruby ドライバのインストール

$ sudo gem install mongo
$ sudo gem install bson_ext

bson_ext は ruby 1.8.5 ではインストール出来ないようです。
CentOSのruby(1.8.5)で実行した時に下記サイトと同じ症状が発生しました。

gem install mongo_ext はruby-1.8.5 じゃダメでruby-1.8.7でおkだった

プログラム


require 'rubygems'
require 'anemone'

Anemone.crawl("http://www.example.com/") do |anemone|
  anemone.storage = Anemone::Storage.MongoDB
  anemone.on_every_page do |page|
    puts page.url
  end
end
追加したのは
anemone.storage = Anemone::Storage.MongoDB
だけ。

要望

クローラの処理で毎回全部のページを走査するのは無駄だし重いので、一回取得したサイトは一定期間処理しないとかの処理が欲しいです。折角ストレージに保存しているからあるはずと思ったのですが見当たらないようです。

crawlに指定出来るオプション


DEFAULT_OPTS = {
# run 4 Tentacle threads to fetch pages
:threads => 4,
# disable verbose output
:verbose => false,
# don't throw away the page response body after scanning it for links
:discard_page_bodies => false,
# identify self as Anemone/VERSION
:user_agent => "Anemone/#{Anemone::VERSION}",
# no delay between requests
:delay => 0,
# don't obey the robots exclusion protocol
:obey_robots_txt => false,
# by default, don't limit the depth of the crawl
:depth_limit => false,
# number of times HTTP redirects will be followed
:redirect_limit => 5,
# storage engine defaults to Hash in +process_options+ if none specified
:storage => nil,
# Hash of cookie name => value to send with HTTP requests
:cookies => nil,
# accept cookies from the server and send them back?
:accept_cookies => false,
# skip any link with a query string? e.g. http://foo.com/?u=user
:skip_query_strings => false,
# proxy server hostname
:proxy_host => nil,
# proxy server port number
:proxy_port => false,
# HTTP read timeout in seconds
:read_timeout => nil
}

を見ても無さそうです。storageはオプションで指定した方が良かったかも。。。

という事で、今回はクエリーストリングは対象には含めないのでその条件も含めて、

opts = {
:storage => Anemone::Storage.MongoDB,
:skip_query_strings => true,
}

Anemone.crawl("http://www.example.com/",opts) do |anemone|
#anemone.storage = Anemone::Storage.MongoDB
anemone.on_every_page do |page|
puts page.url
end
end

としました。

2011年2月25日金曜日

スクリプトの実行


CORE SERVERはクライアントのIPアドレスが変わるたびにSSHやFTPでログインする時に許可が必要(下記の画面)になります。ブラウザにアクセスしなくても良いようにスクリプトを作成しました。言語はpython です。OSは Mac OS  X 10.6 です。


1.  設定ファイル(settings.py)を 更新する。

   (1) HOST_URL を設定します。

     HOST_URL = "www.s**.coreserver.jp"

   (2) コントロールパネルにログインするユーザ名とパスワードを設定します。

     USER_INFO = {
       "id": "***",
       "pass": "*******",
     }

2. スクリプトを実行します。

     python host.py -p ssh
     python host.py --protocol ssh

ソースコード



ソースファイルはsettings.py(設定ファイル)、host.py(Mainプログラム)の二ファイルになります。

settings.py
# -*- coding:utf-8 -*-
'''
Created on 2011/02/25

@author: nasu
'''

#クライアントのIPアドレスを取得する為に使用するURL
VALUE_DOMAIN_URL = "http://dyn.value-domain.com/cgi-bin/dyn.fcg?ip"
HOST_URL = "www.s**.coreserver.jp"

USER_INFO = {
  "id": "***",
  "pass": "******",
}

host.py
# -*- coding:utf-8 -*-
'''
Created on 2011/02/25

@author: nasu
'''
import getopt
import copy
import sys
import urllib2,urllib
from settings import HOST_URL,USER_INFO,VALUE_DOMAIN_URL

def usage():
    print("host.py: python host.py [-p|--protocol] protocol\n")
    print(" -h/--help: 使用方法を表示する")
    print(" -p/--protocol: ssh もしくは ftp を指定する");
    """
    使用方法を記述する。
    """
    sys.exit(1) # 使用方法を説明した後はプログラムを終了する。

def client_regist():
    '''
    CORE SERVER に ホスト情報を登録する。
    '''
    try :
        opts, args = getopt.getopt(sys.argv[1:], "hp:", ["help", "protocol="])
        #引数が指定されていない時の処理を書く。
        if not opts:
            raise getopt.GetoptError("引数が指定されていません")
        query = copy.deepcopy(USER_INFO)
        for opt, arg in opts:
            if opt in ("-h", "--help"):
                usage()
            elif opt in ("-p", "--protocol"):
                if arg == 'ssh':
                    #SSH -s --ssh
                    query['ssh2'] = "SSH登録".encode("utf-8")
                elif arg == 'ftp':
                    #FTP -f --ftp
                    query['telnet2'] = "FTP登録".encode("utf-8")
                else:
                    usage()
            else:
                usage()
        
        value_domain_response = urllib2.urlopen(VALUE_DOMAIN_URL)
        query['remote_host'] = value_domain_response.read() #クライアントのIPアドレスを取得
        #使わないけどデバッグ用に変数を定義
        #coreserver へポスト
        coreserver_admin_response = urllib2.urlopen(
                        "https://ss1.coressl.jp/%s/jp/admin.cgi" % (HOST_URL), 
                        urllib.urlencode(query)
                        )
    except (getopt.GetoptError,IOError), error:
        print str(error)
        usage()

if __name__ == '__main__':
    client_regist()