ブログの記事が多くなってくると、複数の記事に対してある文字を置き換えるなど一括編集したくなる場面があります。前の記事でブログ村のバナー貼りを各記事に人力コピペしていたところをテンプレートに取り込みました。こうするとこれまで人力で貼ったバナーとテンプレートのバナーが2つ表示されてしまうため、人力コピペバナーを削除したくなりました。しかしながら、Bloggerの記事編集画面にはそういった機能はなさそうです。
APIを使うことで記事を更新できるため、Pythonスクリプトで記事を取得し、投稿の内容を変更してから更新する方法を調べやってみました。
準備
Google Cloudの認証情報取得
Blogger APIを使って記事を更新するには、Google CloudでBlogger APIを有効にし、 Blogger API ガイド から、またはGoogle Cloud ConsoleからOAuth 2.0 クライアント IDを作成する必要があります。
OAuth 2.0 クライアント IDが作成できたらクライアント シークレットをjsonファイルにダウンロードしておきます。
lifetechia.com さんにこのあたりの手順が詳しく載っています。
Pythonの準備
$ pip install google-api-python-client oauth2client httplib2 bs4
スクリプト
モジュールのインポートとグローバル変数
import os
import re
import json
import httplib2
from bs4 import BeautifulSoup
from googleapiclient.discovery import build
from oauth2client import file, client, tools
# グローバル変数
client_id = "XXXXXXXXXXXXXXX.apps.googleusercontent.com"
client_secret = "XXXXXXXXXXXXXX"
blog_id = "626605194105590####"
OAuth2.0認証
oauth2clientの認証フローです。
def blogger_auth(blog_id, client_id, client_secret):
# OAuth 2.0 flow
flow = client.OAuth2WebServerFlow(
client_id,
client_secret,
"https://www.googleapis.com/auth/blogger",
redirect_uri="urn:ietf:wg:oauth:2.0:oob",
)
storage = file.Storage(__file__ + "dat")
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = tools.run_flow(flow, storage)
http = credentials.authorize(httplib2.Http())
service = build("blogger", "v3", http=http)
return service
プログラムの初回実行時にブラウザが立ち上がり、認証画面が出ます。認証情報はファイルに保存され、トークンの期限切れまでは認証画面なしで実行できます。
認証後、Blogger APIのサービスを返します。
投稿の取得とバックアップ
投稿の取得は先程の認証で取得したサービスに対し、APIのメソッド(posts, list)を使います。APIの詳細は Blogger APIリファレンス や PyDocリファレンス を参照ください。
def blogger_get_posts(
service, blog_id, end_date, start_date, max_results, order_by, sort_option
):
try:
post_list = (
service.posts()
.list(
blogId=blog_id,
endDate=end_date,
startDate=start_date,
maxResults=max_results,
orderBy=order_by,
sortOption=sort_option,
fetchBodies=True,
fetchImages=True,
)
.execute()
)
print("リストを取得しました:", len(post_list["items"]))
return post_list
except Exception as e:
print("Errror occured:", str(e))
return False
これで取得した投稿リストの各投稿に対して変更を行っていくわけですが、その前に念の為バックアップをしておきましょう。listメソッドで返ってきたオブジェクトをjson.dumpsして保存すればよいのですが、各記事をローカルでブラウザから見られるように1記事ごとHTMLの1ファイルに書き出す機能も付けました。
def blogger_backup_local(posts, split=False):
if split:
for i, p in enumerate(posts["items"]):
fname = re.sub(
r'[&()\\/:*?"<>|\s]+',
"_",
f'{i:03}_{p["id"]}_{p["title"]}.html',
)
with open("archive/" + fname, mode="w") as f:
f.write(p["content"])
return True
else:
print(json.dumps(post, ensure_ascii=False))
return True
これでいったんバックアップを保存します。
if __name__ == "__main__":
service = blogger_auth(blog_id, client_id, client_secret)
posts = blogger_get_posts(
service,
blog_id,
"2025-12-31T00:00:00.000Z",
"2020-01-01T00:00:00.000Z",
500,
"PUBLISHED",
"ASCENDING",
)
blogger_backup_local(posts, split=True)
blogger_backup_local(posts, split=False)
投稿の内容を変更
投稿の内容を変更する部分です。今回は、
<div class="blogmura"> ... </div>
の部分を削除するため、BeautifulSoupを使いました。lxmlパーサは余計なhtml,
head,
bodyタグを付けるので、divを削除後、これらを外しています。簡単な編集であれば
replace()
や
re.gsub()
で文字列置換してもいいでしょう。
def modify_content(content):
soup = BeautifulSoup(content, "lxml")
tags = soup.select("div.blogmura")
for tag in tags:
tag.extract()
if soup.html is not None:
soup.html.unwrap()
if soup.head is not None:
soup.head.unwrap()
if soup.body is not None:
soup.body.unwrap()
print(len(tags), "個のdiv.blogmuraタグを削除しました。")
return len(tags), soup.prettify()
変更が無ければ記事のアップデートはしないようにするため、削除したタグの個数を戻り値に含めています。
投稿を更新
まずは特定の投稿を更新する関数です。記事の更新は、updateとpatchがあるようです。今回はpatchを使い、部分的な更新(記事の中身、contentのみ)を行います。
def blogger_patch(service, blog_id, post_id, body):
if body is None:
print("No body")
return None
try:
new_post = (
service.posts()
.patch(blogId=blog_id, postId=post_id, body=body, publish=None)
.execute()
)
print("投稿を更新しました。id:", new_post["id"], " ,url:", new_post["url"])
return True
except Exception as e:
print("Error occured:...", str(e))
return False
メインの中で取得した投稿リストに対し、各記事をループで回し、変更があれば更新するようにしました。
if __name__ == "__main__":
service = blogger_auth(blog_id, client_id, client_secret)
posts = blogger_get_posts(
service,
blog_id,
"2026-01-01T00:00:00.000Z",
"2025-01-12T00:00:00.000Z",
500,
"PUBLISHED",
"ASCENDING",
)
# update
for p in posts["items"]:
print("投稿id: ", p["id"], "を変更します。")
replaced, p["content"] = modify_content(p["content"])
if replaced > 0:
blogger_patch(
service,
blog_id,
p["id"],
p,
)
else:
print("更新はありませんでした。")
メタタグ 検索向け説明 が消えてしまう
blogger_get_posts
の期間を1〜2記事に限定し試し、大丈夫そうなので全記事を更新していました。5分程度で300記事ちょっとが更新され大成功!!
と思いきや、大きな課題が出ました。
Bloggerの設定で「メタタグ 検索向け説明を有効にする」をONにすると、各記事に150字以内の説明文が記入でき、meta description タグが挿入される仕様になっています。
しかし更新後、となんと各記事の検索向け説明が消えてしまいました😭
綺麗サッパリきえました、ええ。「検索向け説明」は、バックアップでも取れていないし、APIからの更新方法についても全く記載がありません。ネット検索すると同じような質問がありましたが、解決策は無いようです。
やってしまった。。。これからは「検索向け説明」OFFでやっていきます。
もう1点、失敗したことがあります。上の例ではpatchで更新しましたが、はじめupdateで更新し、必要な部分(content)のみ送信したところ、記事のタイトルが全部消えてしまいました😭 これはバックアップから復元できましたがupdateを使う際は基本的にlistで取得した記事のすべての情報を入れて送信してあげる必要があります。
まとめ
Blogger APIを使い、記事を更新することができました。大量の記事を自動で更新できるのは非常に役に立ちますが、検索向け説明が消える問題はいかんともしがたく、解決策ご存知の方はコメント欄で教えてください🙇
関連記事
Bloggerテンプレートカスタマイズ ブログ村のバナーを入れる
ブログを始めたときに集客の第一歩としてブログ村に登録してバナーを貼りました。当時はBlogger ...
Bloggerで記事のラベル(タグ)ごとに表示をカスタマイズする
このブログは、Googleのブログサービス、Bloggerを使っています。Bloggerは、テン ...
アマゾンPA-API FaaSを使いリアルタイムで商品情報を取得
アマゾンアソシエイトの画像リンクが廃止になり、API経由で商品画像、価格などの情報を取得必要にな ...
Bloggerのフィードからブログカードを一気に作成 - Python編
以前に JavaScript を使い Blogger のフィードから各記事のブログカードを作成する方 ...
Blogger: モバイルで画像の解像度が低いときの対処法
このブログはGoogleのブログサービス、Bloggerを使っています。無料で広告が出ず、アドセンス ...
Amazonアソシエイト リンク作成ツール
AmazonアソシエイトのPA-APIを使ったリンク作成ツールを作ってみました。 ...
0 件のコメント:
コメントを投稿