Python Blogger API を使った記事の操作について

前に書いたGoogleBloggerを操作するスクリプトがあったので情報共有を兼ねて公開してみようと思います。

gdataライブラリを使用するので予めpipなどでインストールしておく必要があります。あと、操作対象のBloggerのブログも用意してください。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# -*- coding: utf-8 -*-
 
from gdata.blogger import service, atom, BlogPostEntry
 
import functools
 
def auth(f):
    @functools.wraps(f)
    def wrapper(self, *args, **kwargs):
        #if not self.cli.current_token:
        if hasattr(self.cli, 'current_token') and not self.cli.current_token:
            self.login()
 
        return f(self, *args, **kwargs)
 
    return wrapper
 
 
class Blogger(object):
    def __init__(self, email, password, blog_id):
        self.cli = service.BloggerService()
        self.cli.email = email
        self.cli.password = password
        self.cli.ssl = True
        self.blog_id = blog_id
 
    def login(self):
        self.cli.ProgrammaticLogin()
 
    @auth
    def get_entries(self, force=False):
        if not hasattr(self, 'feed') or force:
            self.feed = self.cli.GetBlogPostFeed(self.blog_id)
        return self.feed.entry
 
    @auth
    def get_entry(self, post_id):
        return self.cli.GetEntry('http://www.blogger.com/feeds/%s/posts/default/%s' % (
            self.blog_id,
            post_id
        ))
 
    def generate_label(self, labelname):
        label = atom.Category()
        label.term = labelname
        label.scheme = u'http://www.blogger.com/atom/ns#'
        return label
 
    def set_draft(self, entry, is_draft=True):
        if not hasattr(entry, 'control'):
            entry.control = atom.Control()
 
        if is_draft:
            entry.control.draft = atom.Draft(text='yes')
        else:
            entry.control.draft = None
 
    @auth
    def post_entry(self, title, body, labels=[], is_draft=False, content_type=u'html'):
        entry = BlogPostEntry()
        entry.title   = atom.Title(text=title)
        entry.content = atom.Content(text=body)
        entry.content.type = content_type
 
        self.set_draft(entry, is_draft)
        for label in labels:
            entry.category.append(self.generate_label(label))
 
        return self.cli.AddPost(entry, self.blog_id)
 
 
    @auth
    def update_entry(self, entry, **field):
        if not isinstance(entry, BlogPostEntry):
            entry = self.get_entry(entry)
 
        if u'title' in field:
            entry.title.text = field[u'title']
 
        if u'body' in field:
            entry.content.text = field[u'body']
 
        if u'type' in field:
            entry.content.type = field[u'type']
 
        if u'is_draft' in field:
            self.set_draft(entry, field[u'is_draft'])
 
        if u'labels' in field:
            entry.category = []
            for label in field[u'labels']:
                entry.category.append(self.generate_label(label))
 
        return self.cli.UpdatePost(entry)
 
    @auth
    def delete_entry(self, post_id):
        entry = self.get_entry(post_id)
        return self.cli.DeletePost(entry)

公開するために少しだけいじったけど多分うごくはず…

使い方

blogger = Blogger('メールアドレス', 'パスワード', 'ブログID')
# 記事一覧を取得する場合
entries = blogger.get_entries()
# 特定の記事を取得する場合
entry = blogger.get_entry('記事ID')
# 記事のタイトルを変更する
blogger.update_entry(entry, title='テストのタイトル')

解説

メールアドレスとパスワードは普段ログインに使用しているものと同じです。ただし、2段階認証を有効にしている場合はアプリパスワードが必要です。

ブログIDはURLのパラメータ「blogID」としてよく出てくるんですが、わからない方はブログのHTMLソースに出力されているので確認してみてください。テンプレートから意図的に削除していない限り以下のようになっているはずです。

<meta content='ブログID' itemprop='blogId'/>

また、Bloggerではタグはラベルと同じ意味です。記事のラベルはentry.categoryリストで管理されています。リスト型ですがラベルの順番は維持されないことに注意してください。

全ての変更に言えることですが保存しなければ記事には反映されません。新規の記事を保存する場合はpost_entry、既存の記事を保存する場合はupdate_entryを使ってください。

意外と簡単ですよね。

[おまけ]APIでできないこと

すごく便利なBlogger APIですができないこともたくさんあります。
例えばテンプレートの変更やブログ名変更などのメタ的な変更はAPIによって操作することができません。基本的にAPIを使ってできることは記事の操作に限られます。
また、記事の操作でもパーマリンクに使用されるスラッグ的なものはAPIからは指定できませんでした。途中から追加された機能だからかな?