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に移植してみた - とある誰かの覚え書き