はてなブログAPIで記事一覧を取得
はてなブログAPIで記事一覧を取得
はてなブログAPI(はてなブログAtomPub)で記事一覧取得のメモ. 「サービス文書URIを使って, コレクション操作の一覧の取得」と 「コレクションURIを使って,ブログエントリ一覧を取得」のメモ.
今回は,PythonでrequestsモジュールとBeautifulSoup4を使う.
ドキュメントはここです.はてなブログAtomPub - Hatena Developer Center
環境
- Windows10
- python 3.5.4
- requests (2.18.4)
- beautifulsoup4 (4.6.0)
- python 3.5.4
Atom Publishing Protocol とは
Atom Publishing Protocol(以下 AtomPub) はウェブリソースを公開、編集するためのアプリケーション・プロトコル仕様です。はてなブログのAtomPubと通じて、開発者ははてなブログのエントリを参照、投稿、編集、削除するようなオリジナルのアプリケーションを作成できます。
はてなブログAtomPub APIを使うことで,ブログの記事の取得,投稿,編集,削除ができる.
主に4つある.
- サービスの操作 (サービス文書URIを使う)
- コレクション操作の一覧の取得
- ブログの操作 (コレクションURIを使う)
- ブログエントリ一覧の取得
- ブログエントリの新規投稿
- ブログエントリの操作 (メンバURIを使う)
- ブログエントリの取得
- ブログエントリの更新
- ブログエントリの削除
- カテゴリの操作 (カテゴリ文書URIを使う)
- カテゴリ一覧の取得
今回は,以下の2つを行う.
はてなAtomPubAPIの認証
はてなブログAtomPub を利用するために、クライアントは
OAuth 認証
WSSE認証、
のいずれかを行う必要があります。
書いてあるように,はてなAtomPubAPIでは,Basic認証とWSSE認証とOAuth認証が使える.
- Basic認証:Basic認証が簡単なので,今回では,これを利用する.
- WSSE認証: WSSE認証についての実装例は,次の記事で解説されている. py3.hateblo.jp
- OAuth認証:OAuth認証は,ほかのはてなブログユーザも利用するアプリケーションを作成する場合に使える.
Basic認証のためのIDとパスワード
APIキーは,はてなブログの「詳細設定 > AtomPub」から確認できるのでコピペしておく.
URIの表記
- はてなID: 意味:あなたのはてなid
- ブログID:ブログのドメイン (例: hoge.hatenablog.com)
- entry_id: 意味:ブログエントリのID, カテゴリURIの記事一覧から取得できる.
サービス文書URIを使って, コレクション操作の一覧の取得
はてなブログ AtomPub で操作できるコレクションの一覧を含むサービス文書を取得できます。
コレクション操作(URI)の一覧を取得できる. 現在,コレクションURIは1つだけなので,その1つが返る.
レスポンスが正しく返れば,認証が正しくできているか確認もできる.
リクエスト
GET https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom
requestsモジュールを利用したBasic認証
省略表記では,引数にauth=(<user_name>,<password>)
を指定する.
requests.get(URI, auth=(<user_name>,<password>))
参考:Authentication — Requests 2.18.4 documentation
requestsモジュールでResponse確認
import requests hatena_id = "<hatena id>" blog_id = "<blog_id>" password = "<api key>" service_doc_uri = "https://blog.hatena.ne.jp/{hatena_id:}/{blog_id:}/atom".format(hatena_id=hatena_id, blog_id=blog_id) res_service_doc = requests.get(url=service_doc_uri, auth=(hatena_id, password)) print(res_service_doc.text)
<?xml version="1.0" encoding="utf-8"?> <service xmlns="http://www.w3.org/2007/app"> <workspace> <atom:title xmlns:atom="http://www.w3.org/2005/Atom">はしくれエンジニアもどきのメモ</atom:title> <collection href="https://blog.hatena.ne.jp/cartman0/cartman0.hatenablog.com/atom/entry"> <atom:title xmlns:atom="http://www.w3.org/2005/Atom">はしくれエンジニアもどきのメモ - 記事一覧</atom:title> <accept>application/atom+xml;type=entry</accept> </collection> </workspace> </service>
サービス文書URIのレスポンスから,コレクションURIを取得する
サービス文書URIのレスポンスには,コレクションURIが含まれている.
<collection href="https://blog.hatena.ne.jp/cartman0/cartman0.hatenablog.com/atom/entry">
これをBeautifulSoup4 を使って抽出する.
BeautifulSoup4でXMLを扱う場合,
bs4.BeautifulSoup(XMLtext, features="xml")
で扱える.
import requests import bs4 hatena_id = "<hatena id>" blog_id = "<blog_id>" password = "<api key>" def get_collection_uri(hatena_id, blog_id, password): service_doc_uri = "https://blog.hatena.ne.jp/{hatena_id:}/{blog_id:}/atom".format(hatena_id=hatena_id, blog_id=blog_id) res_service_doc = requests.get(url=service_doc_uri, auth=(hatena_id, password)) if res_service_doc.ok: soup_servicedoc_xml = bs4.BeautifulSoup(res_service_doc.content, features="xml") collection_uri = soup_servicedoc_xml.collection.get("href") return collection_uri return False get_collection_uri(hatena_id, blog_id, password)
results:
'https://blog.hatena.ne.jp/cartman0/cartman0.hatenablog.com/atom/entry'
gist: はてなブログAPI サービス文書URIのレスポンスからコレクションURIを取得 · GitHub
コレクションURIを使って,ブログエントリ一覧を取得
はてなブログのエントリを操作するためのコレクションです。ブログエントリの一覧取得、新規ブログエントリの投稿を行うことができます。
ブログエントリの一覧取得
コレクション URI を GET することで、ブログエントリ一覧を取得できます。一度に7件のブログエントリを取得できます。また、取得したブログエントリ一覧が、コレクションの部分的リストである場合には、 page パラメータを付与する事で、7件目以降のブログエントリも取得出来ます。続きについては、AtomPub の仕様に基づき、部分的リストの続きは rel=next となる atom:link の href 属性がその URI となります。page パラメータを付与しない場合には、最新の7件を取得します。
どうやら現在では,最新7件でなく10件の記事を取得できる.
リクエスト
GET https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry
最新10件以降の取得
GET https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry?page=0123456789 // => GET <link rel="next">のhref属性
記事一覧の取得
import requests import bs4 def get_collection_uri(hatena_id, blog_id, password): service_doc_uri = "https://blog.hatena.ne.jp/{hatena_id:}/{blog_id:}/atom".format(hatena_id=hatena_id, blog_id=blog_id) res_service_doc = requests.get(url=service_doc_uri, auth=(hatena_id, password)) if res_service_doc.ok: soup_servicedoc_xml = bs4.BeautifulSoup(res_service_doc.content, features="xml") collection_uri = soup_servicedoc_xml.collection.get("href") return collection_uri return False collection_uri = get_collection_uri(hatena_id, blog_id, password) res_collection = requests.get(collection_uri, auth=(hatena_id, password)) print(res_collection.text)
results:
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app"> <link rel="next" href="https://blog.hatena.ne.jp/cartman0/cartman0.hatenablog.com/atom/entry?page=1504592364" /> <title>はしくれエンジニアもどきのメモ</title> <subtitle>情報・Web系技術・Englishの勉強メモ・備忘録です。</subtitle> <link rel="alternate" href="http://cartman0.hatenablog.com/"/> <updated>2017-10-17T01:45:48+09:00</updated> <author> <name>cartman0</name> </author> <entry> <id>tag:blog.hatena.ne.jp,2013:blog-cartman0-12921228815722929243-8599973812308668215</id> <link rel="edit" href="https://blog.hatena.ne.jp/cartman0/cartman0.hatenablog.com/atom/entry/8599973812308668215"/> <link rel="alternate" type="text/html" href="http://cartman0.hatenablog.com/entry/2017/10/17/Slack_RSS_integrations_%E3%81%AEfetching%28polling%29%E3%81%AE%E4%BB%95%E6%A7%98_%28About_specifications_of_fetching%28polling%29_in_Slack_RSS_integrations%29"/> <author><name>cartman0</name></author> <title>Slack RSS integrations のfetching(polling)の仕様 (About specifications of fetching(polling) in Slack RSS integrations)</title> <updated>2017-10-17T01:45:48+09:00</updated> <published>2017-10-17T01:45:48+09:00</published> <app:edited>2017-10-17T01:53:53+09:00</app:edited> <summary type="text">Slack RSS integrations のfetching(polling)の仕様 (About specifications of fetching(polling) in Slack RSS integrations) 2017年9月時点の情報です. SlackのRSS…</summary> <content type="text/x-markdown"># Slack RSS integrations のfetching(polling)の仕様 (About specifications of fetching(polling) in Slack RSS integrations) ... </content> ... <category term="Slack" /> <category term="RSS" /> <app:control> <app:draft>no</app:draft> </app:control> </entry>
<link rel="next" href="https://blog.hatena.ne.jp/cartman0/cartman0.hatenablog.com/atom/entry?page=1504592364" />
が次の10件のエントリがあるコレクションURIになる.<entry>' > '<title>
: 記事のタイトル<entry>
><summary type="text">
:記事のサマリー<content type="text/x-markdown">
: 実際の投稿した記事の内容(はてな記法もそのまま)<entry>
><id>tag:blog.hatena.ne.jp,2013:blog-cartman0-12921228815722929243-8599973812308668215</id>
:idの最後のハイフンからの値8599973812308668215
がentry idになる.<entry>
><app:control><app:draft>no</app:draft></app:control>
:は下書きかどうかを示している.yes
の場合下書きの記事になる.
全記事のentry IDを取得
下書きの記事は今回無視して,全記事のentry id を取得する.
- 無限ループを防ぐためにforループ使用.
- サーバの負荷を減らすためにループの最後にsleepを入れる.
- entry idは,
<id>
から正規表現で抽出
import requests import bs4 import re import time def get_collection_uri(hatena_id, blog_id, password): service_doc_uri = "https://blog.hatena.ne.jp/{hatena_id:}/{blog_id:}/atom".format(hatena_id=hatena_id, blog_id=blog_id) res_service_doc = requests.get(url=service_doc_uri, auth=(hatena_id, password)) if res_service_doc.ok: soup_servicedoc_xml = bs4.BeautifulSoup(res_service_doc.content, features="xml") collection_uri = soup_servicedoc_xml.collection.get("href") return collection_uri return False collection_uri = get_collection_uri(hatena_id, blog_id, password) entry_id_list = [] MAX_ITERATER_NUM = 50 for i in range(MAX_ITERATER_NUM): print(collection_uri) # Basic認証で記事一覧を取得 res_collection = requests.get(collection_uri, auth=(hatena_id, password)) if not res_collection.ok: print("faild") continue # Beatifulsoup4でDOM化 soup_collectino_xml = bs4.BeautifulSoup(res_collection.content, features="xml") # entry elementのlistを取得 entries = soup_collectino_xml.find_all("entry") # 下書きを無視 pub_entry_list = list(filter(lambda e: e.find("app:draft").string != "yes", entries)) # entry idを取得 entry_id_list.extend([re.search(r"-(\d+)$", string=e.id.string).group(1) for e in pub_entry_list]) # next link_next = soup_collectino_xml.find("link", rel="next") if not link_next: break collection_uri = link_next.get("href") if not collection_uri: break time.sleep(0.01)# 10ms entry_id_list
results:
https://blog.hatena.ne.jp/cartman0/cartman0.hatenablog.com/atom/entry https://blog.hatena.ne.jp/cartman0/cartman0.hatenablog.com/atom/entry?page=1504592364 ... Out[]: ['8599973812308668215', '8599973812308570873', ...
関数版は,gistに置いておく.はてなブログAPI 全記事のentry idを取得 · GitHub