プログラムでおかえしできるかな

Python、フェイジョア、日常のあれこれでお返し、元SEの隠居生活。

はてなブログ、スター、ブックマーク用APIの使い方【Python】

このエントリーをはてなブックマークに追加

はてなブログのスターの数とブックマークの数をカウントして CSV ファイルに出力するアプリをアプリを作成しました。

はてなブログ AtomPub、はてなスター取得APIはてなブックマーク件数取得API を使用しています。

機能的には次の特徴があります。
 ◎自分のはてなブログのURLを指定して各記事のスターの数とブックマークの数を出力
 ◎スターの数は色ごとに出力
 ◎結果は CSV ファイルに出力
 ◎出力する記事の数を指定可能

API の使い方をサンプルコードも交えて説明します。

アプリはお使いいただけます アプリ(バイナリ)を使ってみたい方は、別記事から取得できます。
 📄『はてなブログのスターとブックマークの数を取得するアプリ【フリー】🔗
目次

◆機能・特長

  • 自分のはてなブログのURLを指定して各記事のスターの数とブックマークの数を出力
  • スターの数は色ごとに出力
  • 結果は CSV ファイルに出力
  • 出力する記事の数を指定可能

◆考え方

基本的な考え方です。

  1. はてなブログ AtomPub を使用してブログのコレクションを取得する
  2. 取得したコレクションから記事の URL を取得する
    1. 取得した記事の URL から
      はてなスター取得 API を使用してスターの情報を取得する
    2. 取得した記事の URL から
      はてなブックマークのブックマーク数を取得する
    3. 取得した記事の URL から
      はてなブログのブログカードからアイキャッチ画像の URL を取得する追加:2022-08-02

認証には、WSSE を使用しました。

はてなブログ - AtomPub

はてなブログの記事の一覧を取得するために、はてなブログ AtomPub を使用します。

Atom Publishing Protocol とは

Atom Publishing Protocol(以下 AtomPub) はウェブリソースを公開、編集するためのアプリケーション・プロトコル仕様です。はてなブログのAtomPubと通じて、開発者ははてなブログのエントリを参照、投稿、編集、削除するようなオリジナルのアプリケーションを作成できます。 Hatena Developer Center

◇用語

はてなブログ AtomPub を使う上で出てくる用語を説明します。

  • コレクション:記事の集合(トップページの情報に近い)
  • メンバ:個々の記事➡多分コレクションでblog-idを取得してアクセスするみたい(未確認)
  • サービス文書:コレクションの一覧➡今は一つしか返らない
  • カテゴリ文書:コレクションで使用されるカテゴリを記述する文書

URIと操作

はてなブログによると、 URIURI Template の記法に基づいて以下のように表記しているようです。

https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/{entry_id}

各変数の意味と書式は次のようになります。

はてなブログProをお使いで独自ドメインをご利用の方は次のような注意事項があります。

はてなブログProの独自ドメイン機能をご利用の方は、独自ドメイン設定前のブログのドメインがブログのIDとなります。ブログの詳細設定のAtomPub項内のルートエンドポイントをご参照ください。 Hatena Developer Center

API がサポートしている URI

  • コレクションURIhttps://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry
  • メンバURIhttps://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/{entry_id}
  • サービス文書URIhttps://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom
    どのようなコレクションが存在するかを返すサービス
  • カテゴリ文書URIhttps://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/category
    コレクションで使用されるカテゴリを返す

API がサポートしている操作

  • ブログの操作 (コレクション)

    • ブログエントリ一覧の取得 (コレクションURI への GET) ⇐ 今回使用
    • ブログエントリの新規投稿 (コレクションURIへの POST)
  • ブログエントリ(記事)の操作 (メンバ)

    • ブログエントリの取得 (メンバURI への GET)
    • ブログエントリの更新 (メンバURI への PUT)
    • ブログエントリの削除 (メンバURI への DELETE)
  • サービスの操作 (サービス文書)

    • コレクション一覧の取得 (サービス文書URI への GET)
  • カテゴリの操作 (カテゴリ文書)

    • カテゴリ一覧の取得 (カテゴリ文書URI への GET)

◇WSSE認証

参照先 WSSE認証についてはこちらの記事を参照してください。
 📄はてなフォトライフへ画像をアップロードするアプリの作り方(AtomAPIの使い方)【Python】🔗

◇コレクションURI - ブログエントリの一覧取得

コレクション URI を GET することで、ブログエントリ一覧を取得できます。

  • 一度に複数のブログエントリを取得 ➡ トップページの表示数に依存
  • 次ページがある場合、次ページを示す要素が返る
    要素は属性 rel=next となる link タグで href 属性が次ページの URI となります。
  • 次ページのブログエントリを取得するには、page パラメータを URI に付与する

【リクエスト方法】

◇コレクションURIの応答

Pythonの場合、requestsパーケージの get() メソッドで要求を出し、戻り値(Responseオブジェクト)の text 属性に XML 文書が返ってきます。

コレクションの戻りXMLのタグの概要

  • link rel="next":href 属性が次ページのURL、これをたどってすべての記事をたどる
  • entry:記事の始まり、これが複数記事ごとにある
    • link rel="alternate":herf 属性が記事のURL
    • title:タイトル
    • updated:更新日(詳細は不明) 例2022-05-20T17:36:11+09:00
    • published:投稿日 例 同上
    • category:term 属性にカテゴリ名、複数あり
    • summary:記事のサマリー
    • content:記事の内容
    • hatena:formatted-content:フォーマットされた記事の内容

XML のサンプル

<?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="first" href="https://blog.hatena.ne.jp/{はてなID}}/{ブログID}/atom/entry" />
  <link rel="next" href="https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry?page=1377584217" />
  <title>ブログタイトル</title>
  <link rel="alternate" href="http://{ブログID}/"/>
  <updated>2013-08-27T15:17:06+09:00</updated>
  <author>
    <name>{はてなID}</name>
  </author>
  <generator uri="http://blog.hatena.ne.jp/" version="100000000">Hatena::Blog</generator>
  <id>hatenablog://blog/2000000000000</id>

  <entry>
    <id>tag:blog.hatena.ne.jp,2013:blog-{はてなID}-20000000000000-3000000000000000</id>
    <link rel="edit" href="https://blog.hatena.ne.jp/{はてなID}/
    ブログID}/atom/edit/2500000000"/>
    <link rel="alternate" type="text/html" href="http://{ブログID}/entry/2013/09/02/112823"/>
    <author><name>{はてなID}</name></author>
    <title>記事タイトル</title>
    <updated>2013-09-02T11:28:23+09:00</updated>
    <published>2013-09-02T11:28:23+09:00</published>
    <app:edited>2013-09-02T11:28:23+09:00</app:edited>
    <summary type="text"> 記事本文 リスト1 リスト2 内容 </summary>
    <content type="text/x-hatena-syntax">
      ** 記事本文
      - リスト1
      - リスト2
      内容
    </content>
    <hatena:formatted-content type="text/html" xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#">
      &lt;div class=&quot;section&quot;&gt;
      &lt;h4&gt;記事本文&lt;/h4&gt;
      &lt;ul&gt;
      &lt;li&gt;リスト1&lt;/li&gt;
      &lt;li&gt;リスト2&lt;/li&gt;
      &lt;/ul&gt;&lt;p&gt;内容&lt;/p&gt;
      &lt;/div&gt;
    </hatena:formatted-content>
    <app:control>
      <app:draft>no</app:draft>
    </app:control>
  </entry>
  <entry>
  ...
  </entry>
  ...
</feed>

Hatena Developer Center

Python で GET リクエス

headers, endpoint を指定して requests の get メソッドで電文を送ります。

import requests が必要です。

▶endpoint は、
コレクションURI - ブログエントリの一覧取得で示した通りトップページと次ページ以降の場合を扱えるように引数で受け取るようにします。

▶headers は、
WSSE 認証用の X-WSSE ヘッダを作成し、指定します。

▶get() メソッドの結果は戻り値で取得できます。 戻り値の text 属性は、XML です。

▶エラー処理
get() メソッド実行後に raise_for_status() メソッドを実行すると status_code が200番代以外の時に例外が発生します。

◎コード

▽GETリクエス

    def get_hatena(self, next_url:str = None) -> str:
        """
        はてなブログの記事取得
        Args:
            str:    None:ブログのトップ画面を取得する場合
                    url:前回の取得で得られた次のページのurl
        Returns:
            str:    xml
        """
        blog_id = settings_hatena_url.blog_id
        headers = {'X-WSSE': self.wsse(os.getenv("py_hatena_username"), os.getenv("py_hatena_api_key"))}
        if next_url:
            endpoint = next_url
        else:
            endpoint = f'https://blog.hatena.ne.jp/{os.getenv("py_hatena_username")}/{blog_id}/atom/entry'    # コレクション
            # endpoint = f'https://blog.hatena.ne.jp/{os.getenv("py_hatena_username")}/{blog_id}/atom'    # サービス文書
        
        try:
            r = requests.get(endpoint, headers=headers)

            print(f'--request result-- status code={r.status_code}')
            r.raise_for_status()    # 200番代以外は例外を発生
            # 例外がないのでxmlを返す
            result = r.text
        except requests.exceptions.ConnectionError:
            sys.stderr.write('Connection Error!')
            result = ""
        except:
            sys.stderr.write(f'Error!\nstatus_code: {r.status_code}\nmessage: {r.text}')
            result = ""
        return result

PythonXML 解析

ElementTree XML API を使用して XML を解析します。

import xml.etree.ElementTree as ET が必要です。

コレクションURIの応答で示した XML を解析して、目的のデータを取得します。

link タグは属性を見て解析する必要があるのでXPathを指定して解析します。
その他のタグは直接解析できるのでタグ名を指定して解析します。

◎デフォルト名前空間の指定方法

コレクションURIが返す XML名前空間が指定されています。
名前空間の中でも接頭語のない(デフォルト)名前空間が使用されています。

ElementTree で XML を解析する場合、find(), findall(), iter() メソッドを使用しますが、使い方の違いをまとめてみました。

メソッド 検索対象 名前空間の指定 検索範囲 戻り値(or None)
iter タグ 直接 子孫 エレメントのイテレータ
find タグ,パス 辞書 エレメント
findall タグ,パス 辞書 エレメントのリスト

find, findall メソッドを使用する場合、名前空間の指定に辞書を使います。
肝心のデフォルト名前空間の指定方法です。

XML内でのデフォルト名前空間xmlns="http://www.w3.org/2005/Atom"
findメソッドで使う辞書の指定ns={"":"http://www.w3.org/2005/Atom"}

xpathで検索

コレクションURIが返す XML のlinkタグは属性が異なるものが複数存在するので、属性も含めて解析します。
そのためには XPath を指定できる find() メソッドを使用します。

ElementTree の XPath サポートは、要素の位置決めのために必要な構文の一部のみサポートしています。
次の説明は、その中のさらに一部です。

XPathは属性もノードとみなされます。
ノードの区切りは / で指定します。

【主な構文】

  • tag:タグ名
  • *:すべての子供
  • .:現在のノード
  • //:すべての子孫
  • ..:親
  • [@attrib]:属性を持つ要素
  • [@attrib="value"]:指定した値を持つ属性を持つ要素
  • [@attrib!="value"]:指定した値を持たない属性を持つ要素

◎例

ns = {'': 'http://www.w3.org/2005/Atom'}    # デフォルト名前空間の指定  
next_el = root.find(".link/[@rel='next']", ns)  
  • xpathを使う find() メソッドは、辞書で名前空間を指定
  • link タグの rel 属性が nextのものを検索

◎コード

XMLの解析

    def get_article_info(self, xml:str) -> Tuple[str, dict]:
        """
        コレクション応答XMLから記事の情報を取得
        Args:
            str:    XML文書
        Returns:
            str:    次の記事のurl
            dict:   記事の情報の辞書(キーはURL、値は辞書)
        """
        root = ET.fromstring(xml)

        ns = {'': 'http://www.w3.org/2005/Atom'}    # デフォルト名前空間の指定

        # 次ページ用タグの取得
        # linkタグの属性relがnextのものを検索。無い場合を考慮
        next_el = root.find(".link/[@rel='next']", ns)
        if ET.iselement(next_el):       # エレメント自体を判定するとうまくいかない
            next_ = next_el.get("href") # urlの取得
        else:
            next_ = None

        article_info = {}

        entry_iter = root.iter("{http://www.w3.org/2005/Atom}entry")  # iterは名前空間辞書は使えない
        for entry_ in entry_iter:
            # entryタグの子から記事の属性を取得
            link_ = entry_.find(".link/[@rel='alternate']", ns).get("href") # 記事のURL
            title_ = entry_.find("title", ns).text                          # 記事のタイトル
            updated_ = entry_.find("updated", ns).text                      # 記事の更新日
            updated_ = updated_.replace("+09:00", "")                       # csvをエクセルで開いた時に見やすくするための置換
            updated_ = updated_.replace("T", " ")                           # yy:mm:ddThh:mm:ss+09:00⇒yy:mm:dd hh:mm:ss
            published_ = entry_.find("published", ns).text                  # 記事の投稿日
            published_ = published_.replace("+09:00", "")
            published_ = published_.replace("T", " ")
            category_list = entry_.findall("category", ns)                  # 記事のカテゴリ
            category_ = [element_.get("term") for element_ in category_list]    # カテゴリは複数あるのでまずリストに
            category_ = ",".join(category_)                                     # Notionで扱いやすいようにカンマ区切りの文字列に
            # 取得した情報を辞書に、後でcsv出力しやすいように、キーはURL
            article_info[link_] = {"title":title_, "published":published_
                                , "updated":updated_, "category":category_}
        return  next_, article_info

はてなスター

はてなスター取得 API の概要

はてなスター取得 API は HTTP の GET を特定の URL に対して行うことで、ある URL に対して付与されたスターを取得できる REST API です。 Hatena Developer Center

◇スター取得API

次のURIに対してGETリクエストします。
https://s.hatena.com/entry.json?uri=「記事URL」

◇応答

GETリクエストの結果として以下をJSON形式で取得できます。
[]という表現が正しいかどうかわかりませんが、配列を表現してみました

  • entries:以下を含む0個以上の配列
    • []
      • uri:URL
      • stars
        • []
          • name:スターをつけたユーザーのはてなID文字列
          • quote:スターの引用文 (無ければ空文字列)
          • count:スターの個数を表す数値 (1個のときは要素がないことがある)
      • colored_stars
        • []
          • color:スターの色 (green, red, blue など)
          • stars:同上

◇取得に失敗した場合

取得に成功した場合、応答は 200 です。

失敗した場合、以下のいずれかが返ります。

  • 400 (Bad Request)
  • 401 (Authorization Required)
  • 200 (OK)※ただし、エラーメッセージを含んだ JSON データが返る

Pythonでの実装

Pythonのrequestsパーケージのget()メソッドで要求を出した場合、応答が戻り値で返ってきます。
戻り値はResponseオブジェクトです。
戻り値のResponseオブジェクトのjson()メソッドを使用して JSON 形式の結果をエンコードしたオブジェクトを取得できます。

stars 要素は基本的にはスターの数だけ存在するようです。
ただし、仕様的には、count 要素がある場合にはその数を使います。
黄色以外のスターは別要素になっているので共通のメソッド(get_star_num)になるように対応しています。

◇コード

    def get_star_num(self, stars) -> int:
        """
        はてなスターのタグから数を集計。countタグがある時はその数、無い時は1
        タグは、name, quote, countのリスト。countはない場合がある
        Args:
            list:   スター情報のリスト
        Returns:
            int:    スターの数
        """
        item_num = len(stars)
        for star_ in stars:
            if "count" in star_:
                item_num = item_num + star_.get("count") - 1
        return item_num

    def get_hatena_stars(self, url:str) -> dict:
        """
        はてなスター情報をリクエストして取得。スターの色ごとに数を値にした辞書を作成
        情報は、uri, starts, colored_starsのリスト。colored_starsはない場合がある
        colored_starsはcolor, starsのリスト。
        Args:
            str:    スターを集計する記事のURL
        Returns:
            dict:   色ごとのスターの数の辞書
        """
        endpoint = f"https://s.hatena.com/entry.json?uri={url}"
        r = requests.get(endpoint)
        stars_num = {"yellow":0, "green":0, "red":0, "blue":0, "purple":0}
        if r.status_code == 200:
            for entry_ in r.json().get("entries"):
                item_num = self.get_star_num(entry_.get("stars"))   # 黄色スターの数を求める
                stars_num["yellow"] += item_num
                if entry_.get("colored_stars"):     # 黄色以外のスターの数を求める
                    for colored_star_ in entry_.get("colored_stars"):
                        n = self.get_star_num(colored_star_.get("stars"))
                        stars_num[colored_star_.get("color")] += n
        return stars_num

はてなブックマーク

はてなブックマーク件数取得API

GET リクエストでのシンプルな件数取得 API です。 Hatena Developer Center

はてなブックマーク件数取得API

  • GET リクエストでのシンプルなAPI
    https://bookmark.hatenaapis.com/count/entry?url=「記事URL」
    応答は ブックマーク数を値とした JSON 形式

  • GET リクエストでのシンプルな API(複数 URL 版)最大50個
    https://bookmark.hatenaapis.com/count/entries?url=「記事URL」&url=「記事URL」
    応答は URL をキー、ブックマーク数を値とした JSON 形式

Pythonでの実装

Pythonのrequestsパーケージのget()メソッドで要求を出した場合、応答が戻り値で返ってきます。
戻り値はResponseオブジェクトです。
戻り値のResponseオブジェクトのjson()メソッドを使用して JSON 形式の結果をエンコードしたオブジェクトを取得できます。

はてなブックマークの場合、ブックマーク数が数値で返ります。

◇コード

    def get_hatena_bookmark(self, url:str) -> int:
        """
        はてなブックマーク情報をリクエストして取得。ブックマークの数を返す
        Args:
            str:    ブックマークを集計する記事のURL
        Returns:
            int:    ブックマークの数
        """
        endpoint = f"https://bookmark.hatenaapis.com/count/entry?url={url}"
        r = requests.get(endpoint)
        j = None
        if r.status_code == 200:
            j = r.json()    # 個数が返る
        return j

アイキャッチ画像のURLの取得

別記事で紹介しています。

解説記事 📄はてなブログのアイキャッチ画像のURLの取り方【Python】🔗
更新:2022-08-02

CSV出力

今回、CSVファイルに出力するデータは、リストやタプルではなく辞書にしました。
辞書にすることでデータ作成時の順番を気にする必要が無くなります。
また、辞書の場合、csv.DictWriter() メソッドを使用することで簡単に CSV に出力することができます。
DictWriter() メソッドの第2引数にカラム定義を辞書のキーで指定すれば、その順番にカラムが出力されます。
また、今回、エクセルで CSV ファイルを読み込んだ時に日本語が文字化けしないようにエンコードをBOM付UTF8にしてあります。

◇コード

    def output_results2csv(self, blog_info:list):
        """
        XML出力 ファイルはカレントディレクトリに「hatenablog_sb_yymmddHHMM.xml」
        エンコードはBOM付UTF-8。BOM付だとエクセルで文字化けしない。
        Args:
            str:    xml string
        """
        logfile_name = f"hatenablog_sb_{datetime.now().strftime('%y%m%d%H%M')}.csv"
        try:
            with open(logfile_name, mode="w", encoding="utf_8_sig", newline="") as file_:
                # 辞書から出力するので辞書のキーをヘッダーとして定義する
                csv_fields = ["url", "title", "published", "updated", "bookmark", "yellow", "green", "red", "blue", "purple", "category", "eye_catch"]
                csv_writer = csv.DictWriter(file_, fieldnames=csv_fields)
                csv_writer.writeheader()
                csv_writer.writerows(blog_info)
        except Exception as e:
            print(f"書き込みエラー:{e}")

更新:2022-08-02

◆全体処理

【処理】

  1. ブログのURLを初期化する

  2. 指定したページ数以内なら以下を繰り返す

    1. ブログのコレクション情報(xml)を取得する
    2. ブログのコレクション情報(xml)から必要な情報を取得し、辞書で返す
      また、次画面がある場合、アドレスを返す
    3. スターの数とブックマークの数を取得し、辞書に追加する

    4. トップページ1ページ分の記事情報リストを蓄える

  3. リストをCSVに出力する

◇コード

▽全体の処理

    hatena_atom = HatenaBlogAtom()

    pages = settings_hatena_url.pages   # 取得するトップページのページ数
    page = 1                            # 処理しているページ
    next_blog_url = None
    articles_info = []
    print(f"Start from URl:{settings_hatena_url.blog_id}")

    # pagesが0ならずっと、指定されていればそのページまで処理を繰り返す
    while page <= pages or pages == 0:
        # DEBUG
        debug = False
        if debug:
            # 取得したxmlファイルで動作確認
            path = r'hatenaxml_220524.xml'
            with open(path, mode="r", encoding="utf-8") as f:
                result_xml = f.read()
        else:
            result_xml = hatena_atom.get_hatena(next_blog_url)  # はてなブログへリクエストと結果取得
            # hatena_atom.output_xml(result_xml)                # デバッグ、解析用にxml出力

        if not result_xml:
            print("\nエラー終了:URLが存在しないか、ユーザ ID か API key が誤っています")
            input("\n確認したらEnterキーを押してください")
            sys.exit(1)
        
        # はてなブログのコレクションを解析して記事情報を取得
        next_blog_url, article_info1 = hatena_atom.get_article_info(result_xml)
        print(f"Next URL:{next_blog_url}")

        # はてなスター、ブックマークの数を取得
        page_result_ = hatena_atom.get_star_and_bookmark_from_urls(article_info1)
        articles_info += page_result_  # リストにリストを追加

        # 次ページのURIが無くなったら終了
        if next_blog_url:
            page += 1
        else:
            break

    hatena_atom.output_results2csv(articles_info)
    print("Finished")
    input("\n確認したらEnterキーを押してください")

▽記事ごとの処理

    def get_star_and_bookmark_from_urls(self, blog_dict:dict) -> list:
        """
        はてなスターの数とはてなブックマークの数を取得して付加する
        Args:
            dict:   記事情報の辞書(キーはURL)取得した記事の数だけある
        Returns:
            list:   記事属性(辞書)のリスト
        """
        article_info = []
        for url, value in blog_dict.items():
            stars_dict = self.get_hatena_stars(url)         # はてなスター数の取得(色をキーにした辞書)
            bookmark_num = self.get_hatena_bookmark(url)    # はてなブックマーク数の取得
            stars_dict["url"] = url                         # スターの辞書にURLを追加
            stars_dict["bookmark"] = bookmark_num           # スターの辞書にブックマーク数を追加
            eye = self.get_hatena_eye_catch(url)            # アイキャッチ画像のURLを取得
            stars_dict["eye_catch"] = eye                   # スターの辞書にアイキャッチ画像のURLを追加
            stars_dict.update(value)                        # スターの辞書に記事情報(辞書)を追加
            article_info.append(stars_dict)                 # スターの辞書を記事属性の辞書としてリストに追加
        return article_info

更新:2022-08-02

◆必要なパッケージ

  • requests
    【インストール】pip install requests
    【インポート】 import requests

◆全体のソース

全体のソースはこちらから取得できます。
取得先:GitHub juu7g/Python-get-hatena-info

◆さいごに

はてなスターの数の取得は、返ってくるデータに癖があるので少し面倒でした。
この記事の公開準備をしている時にスターの数ではなく、スターを付けてくれた人の数を出してあげた方が需要があるかなと思い始めました。

需要という点では、先日、本アプリを公開しましたが、アプリはダウンロードされていません。
需要がないのか、記事に魅力がないのか、両方なのか、何か測るすべはないものか思案中です。

はてなブログの管理者の方が使えるようなツールをいくつか提供していますが、一つもダウンロードされていません。

なぜ使われないのか、難しいのか、需要がないのか、何かフィードバックがあると助けになるのですが・・・

期待してはいけませんね。

自分では使っているので、自分でフィードバックしていきます。

でも、誰かが使ってくれているのが分かると作り甲斐があるものですから・・・

あわせて読みたい 📄はてなスターの履歴(自分でスターを付けた記事が分かります)🔗

◇ご注意

本記事は次のバージョンの下で動作した内容を基に記述しています。

  • Python 3.8.5
  • Requests 2.25.1

ご利用に際しては、『免責事項』をご確認ください。
お気づきの点がございましたら『お問い合わせ』からお問い合わせください。

◆参考

投稿: 、更新: