はてなブログ自動投稿ツールの作成と活用
概要
ブログを書いていると、「エディタで書いた記事をそのまま投稿できたらいいのに」と思ったことはありませんか?
このツールは、Markdownファイルをコマンド一発で、はてなブログに下書きや公開状態で投稿できるPythonスクリプトです。
HTML変換だけでなく、はてな記法やマークダウン形式のまま投稿も選べるので、用途に合わせて柔軟に使えます。
実装方針
このツールは、以下の機能を持っています。
1. マークダウンファイルの読み込みとHTML/はてな記法への変換
2. ファイル先頭のフロントマター(メタデータ)の解析
3. はてなブログAPIを使った記事投稿
4. コマンドライン引数でタイトル・カテゴリ・公開/下書き・投稿形式を指定可能
API利用には、はてなユーザーID・APIキー・ブログIDが必要です。これらは.env
ファイルなどで環境変数として安全に管理します。
コードのポイント
1. はてなブログAPIクライアント
記事投稿のためのAPIクライアントです。
投稿形式(HTML/Markdown/はてな記法)を選んで記事を送信できます。
class HatenaClient: """はてなブログAPIクライアント""" def __init__(self, username, api_key, blog_id): self.username = username self.api_key = api_key self.blog_id = blog_id self.base_url = f"https://blog.hatena.ne.jp/{username}/{blog_id}/atom/entry" self.auth = (username, api_key) def post_entry(self, title, content, categories=None, draft=True, content_type="text/html"): # XMLテンプレート作成 xml = '<?xml version="1.0" encoding="utf-8"?>' xml += '<entry xmlns="http://www.w3.org/2005/Atom" ' xml += 'xmlns:app="http://www.w3.org/2007/app">' xml += f'<title>{escape_xml(title)}</title>' if categories and len(categories) > 0: for category in categories: xml += f'<category term="{escape_xml(category)}" />' xml += f'<content type="{content_type}">{escape_xml(content)}</content>' if draft: xml += '<app:control><app:draft>yes</app:draft></app:control>' xml += '</entry>' # APIリクエストの実行 headers = {'Content-Type': 'application/atom+xml; charset=utf-8'} response = requests.post(self.base_url, data=xml.encode('utf-8'), auth=self.auth, headers=headers) return response
2. マークダウン変換・はてな記法変換・フロントマター解析
マークダウンをHTMLやはてな記法に変換する関数、フロントマターを抽出する関数を用意しています。
def convert_markdown_to_html(markdown_content): """マークダウンをHTMLに変換""" html = markdown.markdown(markdown_content, extensions=['extra', 'codehilite']) return html def convert_markdown_to_hatena(markdown_content): """マークダウンをはてな記法に変換""" # 見出し (h1-h6) の変換 lines = markdown_content.split('\n') for i in range(len(lines)): if lines[i].startswith('# '): lines[i] = '*' + lines[i][2:] elif lines[i].startswith('## '): lines[i] = '**' + lines[i][3:] elif lines[i].startswith('### '): lines[i] = '***' + lines[i][4:] # 以下省略... content = '\n'.join(lines) # 太字 (<strong>text</strong> → [b]text[/b]) content = re.sub(r'\<em>\</em>(.<em>?)\</em>\<em>', r'<strong>\1</strong>', content) # 斜体 (</em>text<em> → [i]text[/i]) content = re.sub(r'(?<!\</em>)\<em>([^\</em>]+?)\<em>(?!\</em>)', r'<em>\1</em>', content) # コードブロック (``<code>lang → >|lang|) content = re.sub(r'</code>`<code>(\w<em>)\n(.</em>?)\n</code>`<code>', r'>|\1|\n\2\n||<', content, flags=re.DOTALL) # インラインコード (</code>code<code> → [code:inline]code[/code]) content = re.sub(r'</code>([^<code>]+?)</code>', r'<code>\1</code>', content) # リンク ([text](url) → [url:title=text]) content = re.sub(r'\[(.*?)\]\((https?://[^\)]+?)\)', r'[\2:title=\1]', content) return content def extract_front_matter(content): """フロントマターがある場合は抽出する""" metadata = {} main_content = content if content.startswith('---'): parts = content.split('---', 2) if len(parts) >= 3: front_matter = parts[1].strip() main_content = parts[2].strip() for line in front_matter.split('\n'): if ':' in line: key, value = line.split(':', 1) metadata[key.strip()] = value.strip() return metadata, main_content
3. メイン処理
コマンドライン引数で投稿形式やタイトル、カテゴリ、公開/下書きを指定できます。
--hatena
で「はてな記法」、--markdown
で「マークダウン形式のまま」、指定なしで「HTML」に変換して投稿します。
def main(): # コマンドライン引数のパース parser = argparse.ArgumentParser(description='はてなブログに記事を自動投稿するスクリプト') parser.add_argument('file_path', help='投稿するファイルのパス(Markdown形式)') parser.add_argument('--title', help='記事のタイトル(指定しない場合はファイル名またはfrontmatterから取得)') parser.add_argument('--publish', action='store_true', help='公開状態で投稿(指定しない場合は下書き)') parser.add_argument('--categories', help='カテゴリ(カンマ区切り)') parser.add_argument('--markdown', action='store_true', help='マークダウン形式のまま投稿') parser.add_argument('--hatena', action='store_true', help='はてな記法に変換して投稿') args = parser.parse_args() # .envファイルから環境変数を読み込む load_dotenv() # ファイルの読み込みとフロントマターの抽出 file_content = read_file(args.file_path) metadata, content = extract_front_matter(file_content) # タイトルとカテゴリの決定 title = args.title or metadata.get('title') or os.path.basename(args.file_path).split('.')[0] categories = [] if args.categories: categories = [c.strip() for c in args.categories.split(',')] elif 'categories' in metadata: categories = [c.strip() for c in metadata['categories'].split(',')] # コンテンツタイプとコンテンツの変換の決定 if args.hatena: content_type = "text/x-hatena-syntax" post_content = convert_markdown_to_hatena(content) elif args.markdown: content_type = "text/markdown" post_content = content else: content_type = "text/html" post_content = convert_markdown_to_html(content) # はてなブログに投稿 client = HatenaClient(username, api_key, blog_id) draft = not args.publish response = client.post_entry(title, post_content, categories, draft, content_type) if response.status_code == 201: print("記事の投稿に成功しました!") print(f"記事URL: {response.headers.get('Location', '不明')}") else: print(f"記事の投稿に失敗しました。ステータスコード: {response.status_code}")
セットアップ
まずは必要なライブラリをインストールしましょう。
pip install requests python-dotenv markdown beautifulsoup4
.env
ファイルにAPI情報を記載します。
HATENA_USERNAME=あなたのはてなID HATENA_API_KEY=あなたのAPIキー HATENA_BLOG_ID=ブログID(例:example.hatenablog.com)
記事の作成例
記事はマークダウン形式で作成できます。
タイトルやカテゴリなどはフロントマターで指定できます。
--- title: 記事のタイトル categories: カテゴリ1, カテゴリ2 --- *見出し1 これは記事の本文です。<strong>太字</strong>も使えます。 **見出し2 - リスト1 - リスト2 コードブロックも記述できます: def hello(): print("Hello, world!")
使い方
コマンド例をいくつか紹介します。
<em>下書き投稿(HTML変換) python auto_hatena_post.py my_article.md </em>タイトルを指定して公開投稿 python auto_hatena_post.py my_article.md --title "素晴らしい記事" --publish <em>カテゴリを指定して投稿 python auto_hatena_post.py my_article.md --categories "技術,Python,自動化" </em>はてな記法で投稿 python auto_hatena_post.py my_article.md --hatena *マークダウン形式のまま投稿 python auto_hatena_post.py my_article.md --markdown
このツールの良いところ
さらに便利にするアイデア
- 画像の自動アップロード
- 過去記事の取得・更新
- スケジュール投稿
- テンプレート機能
まとめ
「エディタで書いた記事を、もっと手軽にブログに投稿したい!」
そんな方にぴったりのツールです。
Pythonの勉強にもなりますし、ブログ運営の効率化にも役立ちます。
ぜひ一度使ってみて、あなたのブログライフをもっと快適にしてみてください!