iPhoneやiPadのSafariとかでクソ見づらいULOGの記事をまともに読む方法


注意: 下記方法で有料記事を読むことはできません。


問題点: iPhoneiPadSafariだと、ULOGの記事を途中までしか読めない
解決策1: 2本指フリックでスクロールする
解決策2: Instapaperを使う


本記事では、2の「Instapaper」を使ってULOGをiPhone/iPadでも見やすくする方法を解説します。
試してませんがAndroidでもだいたい同じだと思います。

問題点: iPhoneSafariだと、ULOGの記事を途中までしか読めない

以下のようになんか見切れます。ファック!


解決策1: 2本指フリックでスクロールする

WebKitの仕様らしいんですが、フレームの中は2本指フリックでスクロールできます。



けど字が小さくて視力が破壊されるので、解決策2を使いましょう。

解決策2: Instapaperを使う

Instapaperはいわゆる「あとで読む」系のiPhoneアプリです。
が、「単に読み易くする機能」みたいのもあって、Safariだけでも使えます。

1. SafariでInstapaper Mobilizerを使って見やすくする

ULOGのページを見ているときに、あるブックマークを選択すると、見やすく変換してくれる機能です。(ブックマークレットといいます)


まずブックマークレットを登録します。
iPhoneSafariで下記のURLを開いてください。


iPhone/iPod touch用ブックマークレット登録支援


下のようなページに行きますね。そしたらブックマークに追加します。



タイトルはとりあえず「Read Now」にしておきましょう。



追加したら、今度はいま追加したブックマークを編集します。
ブックマーク一覧で「編集」をタップして、「Read Now」を選びます。



編集画面で、URLの先頭から # までを削除します。



以上で登録完了です。


では使ってみましょう。読みたいULOG記事に移動してください。
今回は下記の記事を読んでみます。



この状態で、ブックマークから「Read Now」を選ぶと、読みやすく変換されたページが表示されます。



お疲れ様でした!

2. SafariからInstapaperアプリに送ってアプリで読む

上で書いた方法ですが、画像が見切れたりとかすることあります。
アプリを使うと、オフラインでも読めるのはもちろん、文字サイズを調整できたり、画像を個別に表示できたり、さらに色々捗ります。


こっちの始め方の解説はいろんな記事が出ているのでそちらに譲ります。
こことか。
http://netafull.net/lifehack/034678.html

使わせていただいたもの

ブックマークレットの登録に使用するページは、むぅもぉ.jpにて提供されているブックマークレット登録支援機能を使わせていただきました。ありがとうございました。
むぅもぉ.jp

よく使うシェルスクリプト基本構文とサンプル

シェルスクリプト書くときだいたいいつも必要になるけど、たまにしか書かないので忘れがちなやつをメモ。随時更新予定。
なお、全てMac OS X/bin/shを想定している。

絶対パスを得る

fullpath=$(cd $(dirname $0) && pwd)

$0はシェルスクリプト自身を表すので、シェルスクリプトファイルの相対パスが得られる。
そのディレクトリでpwdすると、絶対パスが得られる。
$0のかわりに相対パスを与えると、そのファイルの絶対パスが得られる。
なお、括弧の中でcdしているが、その後の処理には影響しない。

配列

配列を作成
arr=(a b c)
echo ${arr[0]}  # => a

${配列変数名[index]}と、"{}"で囲む必要あり
インデックスはshでは0始まりだが、bashzshだと違うみたい?なので注意

配列の要素数を得る
$#arr   # => 3
${#arr} # => 3
配列に要素を追加
arr=(1 2 3)
arr=(${arr[*]} 4)
echo ${arr[*]}      # => 1 2 3 4

なお、上記では、変数$arrが存在しない場合、4という1つの要素をもつ配列になる。

分岐(よく使うパターン)

  • パス存在チェック
if [ -e $path ]; then
    (パスが存在する場合実行される)
fi
  • ファイル存在チェック
if [ -f $file ]; then
    (パスが存在し、かつファイルである場合実行される)
fi
if [ -d $dir ]; then
    (パスが存在し、かつディレクトリである場合実行される)
fi
  • 複数ファイルの存在チェック
if [ "$(ls $dir | grep '.html$')" != '' ]; then
    (拡張子がhtmlのファイルが存在する場合実行される)
fi

[ -e *.html ] などとすると、複数存在する場合は [ コマンドのエラーになり、存在しない場合はシェルのグラブ展開エラーになるため、上記のようにした。(他にもっといい方法があるかも)

終了ステータス

  • 正常時
exit 0
  • 異常時
exit 1

LDRのpinをInstapaperにポストするpythonスクリプト


LDRAPIを使うにはログインが必要だが、python版mechanizeを使うとcookieなどの処理を意識せず簡単にできる。

#! /usr/bin/env python
# coding: utf-8

import mechanize
import json
import urllib

def account(service_name):
  import sys, os
  import yaml
  # スクリプトのディレクトリ
  dir = os.path.abspath(os.path.dirname(__file__))
  return yaml.load(open(dir + '/accounts.yaml'))[service_name]

class LDRbrowser(object):
  LD_LOGIN_URL = 'https://member.livedoor.com/login/index'
  LD_READER_URL = 'http://reader.livedoor.com/'
  LD_GET_PIN_URL = 'http://reader.livedoor.com/api/pin/all'
  LD_REMOVE_PIN_URL = 'http://reader.livedoor.com/api/pin/remove'

  def __init__(self, username, password):
    ''' インスタンス生成時にログイン '''
    self.client = mechanize.Browser()
    self.client.open(self.LD_LOGIN_URL)
    self.client.select_form('loginForm')
    self.client['livedoor_id'] = username
    self.client['password'] = password
    self.client.submit()
    # ApiKeyを取得するため一度LDRにアクセスし、クッキーを解析
    info = str(self.client.open(self.LD_READER_URL).info())
    for line in info.split('\n'):
      key = 'reader_sid='
      if key in line:
        self.apikey = line[line.find(key) + len(key):line.find(';')]

  def get_pin(self):
    # 第二引数Dataを与えるとPOSTメソッドになる
    self.client.open(self.LD_GET_PIN_URL, '')
    # JSON形式がかえってくる
    pin_json = self.client.response().read()
    # JSON形式のデータをdictに変換
    list = json.loads(pin_json)
    return list

  def remove_pin(self, entry_list):
    '''
    entry_listはキー link を含む辞書のリスト
    entry_list = [{'link': 'http://...'}, {...}, ...]
    戻り値はURLとレスポンスコードの辞書
    '''
    result = {}
    pd = {'ApiKey': self.apikey}
    for entry in entry_list:
      pd['link'] = entry['link']
      params = urllib.urlencode(pd)
      response = self.client.open(self.LD_REMOVE_PIN_URL, params)
      result[entry['link']] = response.code
    return result

def add_to_instapaper(entry_list, username, password):
  '''
  entry_list はキー link, title を含む辞書のリスト
  entry_list = [{'link': 'http://...', 'title': '...'}, {...}, ...]
  戻り値はURLとレスポンスコードの辞書
  '''
  INSTAPAPER_API_URL = 'https://www.instapaper.com/api/add'

  pd = {'username':username, 'password':password}
  result = {}
  for entry in entry_list:
    pd['url'] = entry['link']
    pd['title'] = entry['title'].encode('UTF-8')
    params = urllib.urlencode(pd)
    response = urllib.urlopen(INSTAPAPER_API_URL, params)
    result[entry['link']] = response.code
  return result

if __name__ == '__main__':
  from datetime import datetime

  # Livedoorにログイン
  ldac = account('livedoor')
  br = LDRbrowser(ldac['username'], ldac['password'])

  # ピン一覧を取得
  entries = br.get_pin()

  # Instapaperにpost
  # post成功したものをピンから削除
  ipac = account('instapaper')
  ip_result = add_to_instapaper(entries, ipac['username'], ipac['password'])
  remove_urls = []
  log = []
  for url, code in ip_result.iteritems():
    if 200 <= code and code < 300:
      remove_urls.append({'link': url})
    log.append('%s Post to Instapaper: %s %s' % (datetime.now(), url, code))

  pin_result = br.remove_pin(remove_urls)
  for url, code in pin_result.iteritems():
    log.append('%s Remove from Pin: %s %s' % (datetime.now(), url, code))
  f = open('/var/log/ldrpin_to_instapaper_log.txt', 'a')
  if log:
    f.write('\n'.join(log) + '\n')
  f.close()


インスタンス生成時にログインし、セッションとapikeyを使い回すようにした。
が、ログイン失敗時などの処理をしておくべきかもしれない。今後の課題か。


スクリプトと同じディレクトリにLivedoorとInstapaperのID, Passwordを書いたYAMLファイルを置いておく


accounts.yaml

livedoor:
  username: xxxx
  password: xxxx

instapaper:
  username: xxxx
  password: xxxx


このスクリプトを10分くらいの間隔で実行するようcronに登録しておくと、iPhone上で

  • LDRをチェック -> pin登録 -> 軽量かつオフライン閲覧可能なInstapaperアプリで読む

ことができる。
Livedoorの二重ログインが問題になるかと思ったが、数日使ってみたところ特に問題なく使えている。


LDRAPIについては以下のエントリが参考になる。
Livedoor ReaderのAPIを探して全部まとめてみた。 - ガジェカツ~在宅SEのガジェット活動ブログ~


python版mechanizeの使い方は以下のエントリの、はてなにログインするスクリプトを参考にした。
はてなのバックアップスクリプトをPythonに移植してみた - とある誰かの覚え書き

pythonのfeedparserを使う


インストール

progdmac% sudo easy_install feedparser


スクリプト

#! /usr/bin/env python
# coding: utf-8

import feedparser

def get_links(feed_url, log_file):
  """
  指定したURLのフィードからlinkを抜き出して、そのリストを返す。
  ログファイルに既に存在するlinkはリストに含めない。
  抜き出したlinkはログファイルに保存する。
  """  

  file = open(log_file, 'r')
  log_list = file.readlines()
  file.close()

  feed_list = feedparser.parse(feed_url)
  link_list = []
  for feed in feed_list.entries:
    if (feed['link'] + '\n') not in log_list:
      link_list.append(feed['link'])

  file = open(log_file, 'a')
  file.write('%s\n' % '\n'.join(link_list))
  file.close()

  return link_list

if __name__ == '__main__':
  feed_url = 'http://d.hatena.ne.jp/progd/rss'
  print get_links(feed_url, '/var/log/hoge.log')


適当だけどとりあえず使える。

FLACをMP3に変換→タグのコピー

http://d.hatena.ne.jp/progd/20091227/1261914093 の方法だとMP3のタグが無い状態になってしまう。
以下のPythonスクリプトFLACファイルからタグを移植する。

#!/usr/bin/env python2.6
#coding: utf-8

import sys
from mutagen.flac import FLAC
from mutagen.easyid3 import EasyID3, EasyID3KeyError

# コマンドライン引数を取得
argvs = sys.argv
if len(argvs) != 2:
    print "usage: python %s flacfile" % argvs[0]
    quit()

flac = FLAC(argvs[1])
mp3 = EasyID3(argvs[1] + ".mp3")

for key, value in flac.iteritems():
  try:
    mp3[key] = value
  except EasyID3KeyError:
    print "%s is passed" % key

mp3.save()
print argvs[1] + ".mp3 writed"


注意点

  • easy_installでmutagenをインストールしておくこと
  • 普通にLAMEでMP3を作るとタグが無い状態のファイルができる。mutagenではタグの無いMP3ファイルは読み込めないので、lameコマンドに--add-id3v2オプションを付加すること
  • mutagen.easyid3では処理できるキーに制約がある。処理可能なキーの一覧はEasyID3.valid_keys.keys()で見られる。mutagen.id3ならば自由に編集できるようだが、ID3タグの仕様を理解していないと使えない


使い方

ubuntu:progd% cat flac2mp3.sh
#!/bin/sh

echo decoding: $1
flac --decode --stdout "$1" | lame --add-id3v2 - "$1".mp3
python tag.py "$1"

ubuntu:progd% find . -name "*.flac" -exec ./flac2mp3.sh {} \;
ubuntu:progd% find . -name "*.mp3" | perl -lne '$o=$_; s/\.flac//; rename $o,$_;'

指定したサイトから文字列を正規表現で抜き出すpythonスクリプト

#!/usr/bin/env python2.6
#coding: utf-8

import urllib2
import chardet
import sys
import re

# コマンドライン引数を取得
argvs = sys.argv
if len(argvs) != 3:
  print "usage: python %s url regex" % argvs[0]
  quit()

url = argvs[1].decode('utf-8')
regex = argvs[2].decode('utf-8')
# 引数の正規表現文字列を( )で囲んで、正規表現オブジェクトを生成
reg_patt = re.compile(u"(%s)" % regex)

# WebページのHTMLを取得
html = urllib2.urlopen(url).read()
# ページのエンコードを取得
encoding = chardet.detect(html)['encoding'].decode()
# HTMLをUnicodeに変換
html = unicode(html, encoding, 'ignore')

try:
  match_list = []
  while 1:
    m = reg_patt.search(html)     # マッチしないと m = None
    match_list.append(m.group())  # マッチ部分の文字列をリストに追加
    html = html[m.end():]         # 検索した部分を取り除く
except AttributeError:
  pass

for s in match_list:
  print s


メソッドにまとめる

#!/usr/bin/env python2.6
#coding: utf-8

import urllib2
import chardet
import sys
import re

def get_page(url):
  url = unicode(url)
  # WebページのHTMLを取得
  html = urllib2.urlopen(url).read()
  # ページのエンコードを取得
  encoding = chardet.detect(html)['encoding'].decode()
  # HTMLをUnicodeに変換
  html = unicode(html, encoding, 'ignore')
  return html

def search_all(str, reg_patt): 
  try:
    match_list = []
    while 1:
      m = reg_patt.search(str)      # マッチしないと m = None
      match_list.append(m.group())  # マッチ部分の文字列をリストに追加
      str = str[m.end():]           # 検索した部分を取り除く
  except AttributeError:
    pass
  return match_list

if __name__ == "__main__":
  # コマンドライン引数を取得
  argvs = sys.argv
  if len(argvs) != 3:
    print "usage: python %s url regex" % argvs[0]
    quit()
  
  html = get_page(argvs[1])
  
  regex = argvs[2].decode('utf-8')
  # 引数の正規表現文字列を( )で囲んで、正規表現オブジェクトを生成
  reg_patt = re.compile(u"(%s)" % regex)
  
  for s in search_all(html, reg_patt):
    print s


ちなみに、URLなどを取り出すときに「&」が「&amp;」などになってしまうのが困る場合、cgi.unescapeを使えばよい。

sudo easy_install cgi.unescape

でインストールして、スクリプトに以下を追記。

from cgi import unescape
    print unescape(s)