2009/04/16


はてなブックマークをdeliciousに同期する場合、どうやってますか?
  • 同時ポストツール?
  • Plagger?
  • まさか、手作業?
同時ポストツールの場合、確かにその場で同時にポスト出来て便利ですね。でも携帯ではてなブックマークから登録した場合、同期されませんよね。 Plaggerだと、cronで動き続けるPCが要りますよね。家に24時間稼動可能なPC無いよ!なんて人いるかもしれません。
手作業?問題外!

先日、Google App Engineにcronが導入されました。
Google App Engine Blog: Seriously this time, the new language on App Engine: Java™

Cron support: schedule tasks like report generation or DB clean-up at an interval of your choosing.

http://googleappengine.blogspot.com/2009/04/seriously-this-time-new-language-on-app.html
つまり...
  1. cronでイベント発生
  2. はてなブックマークからRSS取得
  3. 内部のデータベースから既にポスト済みでないか確認
  4. deliciousにポストする
  5. モテモテ
って事で作ってみました。
mattn's hatenabookmark-meets-delicious at master - GitHub

post hatenabookmark to delicious periodically using google app engine

http://github.com/mattn/hatenabookmark-meets-delicious/tree/master
Google App Engineのアカウントを作成後アプリケーションを作成します。
app.yamlの application: your_app_name
version: 1
runtime: python
api_version: 1

handlers:
  - url: /tasks/sbm-sync
    script: sbm-sync.py

  - url: /
    script: sbm-sync.py

  - url: /favicon.ico
    static_files: static/images/favicon.ico
    upload: static/images/favicon.ico
    mime_type: image/x-icon

  - url: /static
    static_dir: static
your_app_nameの部分を作成したアプリケーションIDに書き換えて下さい。次にmy-config.yaml.exampleをmy-config.yamlにコピーし hatena_user : your_hatena_user
delicious_user : your_delicious_user
delicious_pass : your_delicious_password
timezone: JST
timeoffset: 9
あなたの、はてなブックマークIDとdeliciousユーザID/パスワードに書き換えて下さい。
あとはサーバにアップロードすれば10分毎に、はてなブックマークのRSSを取得してdeliciousに同期されます。
gae-sbm-sync

よろしければ使ってみて下さい。ソースはgithubにあるのでpatch welcomeです。
Posted at by



2009/04/14


JSONって大体のサーバレスポンスだと、改行もなくエディタで開いてちまちま改行入れて内容確認...なんて事やってる人もいるかと思います。
結構前に作った物ですが、もしかしたら便利と思ってくれる人もいるんじゃないか...と思ったの晒しておきます。
mattn's gtkjsonviewer at master - GitHub

A simple json viewer written in GTK.

http://github.com/mattn/gtkjsonviewer/tree/master
早い話が、JSONをGUIで見ようという話です。中身は簡単なpythonのコードで、GTKを使っています。
# curl http://twitter.com/public_timeline/?format=json | gtkjsonview.py
の様に使います。Windowsの場合はpython.exeがちゃんと標準入力を読み取ってくれないので # curl http://twitter.com/statuses/user_timeline/mattn_jp.json | python gtkjsonview.py
とする必要があります。起動すると以下の様な画面が表示されます。
gtkjsonview
不正なJSON(例えば最終カンマあり)等の場合はパースエラーが出ます。
Posted at by



2009/03/05


結果から言うと実に使いにくい!
PyAWS - A Python wrapper for Amazon Web Service

PyAWS is a Python wrapper for the latest Amazon Web Service. It is designed to pave the way for Python developers to interactivate AWS. This project is forked from the code base of pyamazon. The Amazone E-Commerce Services is supported.

http://pyaws.sourceforge.net/
配列なら配列で、存在したり存在しない場合があるプロパティならばそれ用のアクセサを作ってほしい...
Djangoテンプレートで配列かどうかを判断してループで回してってのはつらいです。
あと、Django Template Engineの最新版でないと {% for key, value in values %}
こういう書き方が出来ないので、結局明示的な名前の付いたdictに作り変える必要があった。まぁこれはpyawsのせいではないけれど。
aws.py
#!-*- coding:utf-8 -*-
import os
import cgi
import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.api import urlfetch
import logging
import yaml
from pyaws import ecs

def to_dict_array(array):
  ret = []
  for item in array:
    ret.append({'key': item.keys()[0], 'value':item.values()[0] })
  return ret

class MainPage(webapp.RequestHandler):
  config = yaml.safe_load(open(os.path.join(os.path.dirname(__file__), 'aws.yaml'), 'r'))
  def get(self):
    kind = self.request.get('kind')
    keyword = self.request.get('keyword')
    template_values = {}
    template_values = {
      'asoid'    : self.config['asoid'],
      'kinds'    : to_dict_array(self.config['kinds']),
      'kind'     : kind,
      'keyword'  : keyword,
      'books'    : [],
    }
    if keyword:
      asoid = self.config['asoid']
      devkey = self.config['devkey']
      kinds = self.config['kinds']
      ecs.setLicenseKey(devkey)
      ecs.setLocale(self.config['locale'])
      books = ecs.ItemSearch(keyword, SearchIndex=kind, ResponseGroup='Medium,Offers')
      count = 0
      max = 5
      for book in books:
        if 'Author' in book.__dict__ and not isinstance(book.Author, list):
          book.Author = [book.Author]
        if 'Offer' in book.Offers.__dict__:
          if isinstance(book.Offers.Offer, list):
            book.Availability = book.Offers.Offer[0].OfferListing.Availability
          elif 'Availability' in book.Offers.Offer.OfferListing.__dict__:
            book.Availability = book.Offers.Offer.OfferListing.Availability
        template_values['books'].append(book)
        count += 1
        if count > max: break
    path = os.path.join(os.path.dirname(__file__), 'aws.html')
    self.response.out.write(template.render(path, template_values))

def main():
  application = webapp.WSGIApplication([('/pyaws/', MainPage)], debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
  main()
aws.html
<html>
<head>
<title>AWS商品検索</title>
<style tyle="text/css"><!--
body {
    font-family: 'メイリオ', 'Osaka'
}
#content {
    margin-left: 50px;
}
#error {
    color: red;
}
.awsxom {
    background: #eeeeee;
    padding: 0.5em;
}
--></style>
</head>
<body>
    <h1>AWS商品検索</h1><img src="http://b.hatena.ne.jp/entry/image/http://mattn.appspot.com/pyaws/" title="はてなブックマーク" />
    <div id="content">
        <p align="right"><a href="/">目次</a></p>
        <form method="get">
            <label for="kind">種類</label>
            <select id="kind" name="kind" value="{{ kind }}">
                {% for item in kinds %}<option value="{{ item.key }}"{% ifequal item.key kind %} selected="selected"{% endifequal %}>{{ item.value }}</option>
                {% endfor %}
            </select>
            <label for="keyword">キーワード</label><input id="keyword" name="keyword" type="text" value="{{ keyword|escape }}" />
            <input type="submit" />
        </form>
        <hr />
        {% for book in books %}
        <div class="awsxom">
            <a href="http://www.amazon.co.jp/exec/obidos/ASIN/{{ book.ASIN }}/ref=nosim/{{ asoid }}">
                <img src="{{ book.SmallImage.URL }}" align="left" hspace="5" border="0" alt="{{ book.Title }}" class="image" />
                <strong>{{ book.Title }}</strong></a><br />
            {{ book.Author|join:", " }}<br />
            {{ book.Manufacturer}} / {{ book.ListPrice.FormattedPrice }} ({% if book.PublicationDate %}{{ book.PublicationDate }}{% else %}{{ book.ReleaseDate }}{% endif %})<br />
            &nbsp;<br />
            発送可能時間:{{ book.Availability }}<br />
            <br clear="all" />
        </div><br />
        {% endfor %}
    </div>
</body>
</html>
あと、設定用のyaml
aws.yaml
asoid : xxxxxxxxx
devkey : 1XXXXXXXXXXXXXXXXXXX
locale: jp
kinds:
 - Blended: Blended すべての商品
 - Books: 本
 - Classical: クラシック音楽
 - DVD: DVD
 - Electronics: エレクトロニクス
 - ForeignBooks: 洋書
 - Hobbies: ホビー
 - Kitchen: ホーム&キッチン
 - Music: 音楽
 - MusicTracks: 曲名
 - Software: ソフトウェア
 - SportingGoods: スポーツ
 - Toys: おもちゃ
 - VHS: VHSビデオ
 - Video: DVD&ビデオ
 - VideoGames: ゲーム
 - HealthPersonalCare: ヘルス&ビューティー
これ、AuthorとかAvailabilityなんかのアクセサが最初から決まった型で扱えればコードは2/3くらいになりそう。
いっそBeautifulSoupとかで作った方が作り手側としては納得が行くのかも。
動いてる物は以下
AWS商品検索
追記
パッチを付けるのを忘れてました。
pyaws.diff
--- pyaws/ecs.py.orig   Mon Apr 09 07:38:57 2007
+++ pyaws/ecs.py    Wed May 07 18:12:21 2008
@@ -19,7 +19,8 @@
 
 
 import os, urllib, string, inspect
-from xml.dom import minidom
+from google.appengine.api import urlfetch
+import pxdom
 
 __author__ = "Kun Xi < kunxi@kunxi.org >"
 __version__ = "0.2.0"
@@ -164,10 +165,7 @@
    """Send the query url and return the DOM
    
    Exception is raised if there is errors"""
-   u = urllib.FancyURLopener(HTTP_PROXY)
-   usock = u.open(url)
-   dom = minidom.parse(usock)
-   usock.close()
+   dom = pxdom.parseString(urlfetch.fetch(url).content)
 
    errors = dom.getElementsByTagName('Error')
    if errors:
@@ -282,7 +280,7 @@
    if(plugins == None):
        plugins = {}
 
-   childElements = [e for e in element.childNodes if isinstance(e, minidom.Element)]
+   childElements = [e for e in element.childNodes if isinstance(e, pxdom.Element)]
 
    if childElements:
        for child in childElements:
@@ -291,7 +289,7 @@
                if type(getattr(rc, key)) <> type([]):
                    setattr(rc, key, [getattr(rc, key)])
                setattr(rc, key, getattr(rc, key) + [unmarshal(child, plugins)])
-           elif isinstance(child, minidom.Element):
+           elif isinstance(child, pxdom.Element):
                if plugins.has_key('isPivoted') and plugins['isPivoted'](child.tagName):
                        unmarshal(child, plugins, rc)
                elif plugins.has_key('isBypassed') and plugins['isBypassed'](child.tagName):
@@ -303,7 +301,7 @@
                else:
                    setattr(rc, key, unmarshal(child, plugins))
    else:
-       rc = "".join([e.data for e in element.childNodes if isinstance(e, minidom.Text)])
+       rc = "".join([e.data for e in element.childNodes if isinstance(e, pxdom.Text)])
    return rc
 
 
pxdomはDOM Level 3をpythonで実装しているすばらしいライブラリです。ココから取得して下さい。
Posted at by