【python】WordPressの記事一覧を取得しようとして躓いた話。

python

こんばんは。ヤマモトです。

夜更かししているわけではない!! 究極の早起きをしただけだ!!!

なんか今日は疲れ果てて21時頃に寝てしまい、起きたら深夜の1時だったんよね。。。
う~ん。中途半端。

さてさて。今回の話題。

先日、WordPressの記事投稿を行った。

アイキャッチがイマイチな成功体験

ここでは投稿だけをやったのだが、python の wordpress_xmlrpc だと既存記事の一覧も取得できるとのこと。例えば投稿済みの記事を編集したいときとかに、以下の流れで作業する時とかに使える。

記事一覧取得 → 該当記事のIDを取得 → IDをキーにして記事を呼び出し → 編集

ということで、試しに使ってみよ~・・・っていう流れで掲題の通り躓いたという話である。

今日はその経緯と原因と対策についてお話しする。

以下の同じ悩みで詰まっている人は注目。

  • python 3.8 以上で python-wordpress-xmlrpc を使っている人
  • GetPosts() で失敗する人
  • GetPosts() で ” module 'collections' has no attribute 'Iterable' ” みたいなエラーが出ちゃう人。

記事一覧の取得失敗の経緯

記事取得のコードとエラーメッセージ

# 必要なモジュールを持ってくる
from wordpress_xmlrpc import Client, methods
from wordpress_xmlrpc.methods.posts import GetPosts

# client情報
url = 'https://arefukeblog.com/xmlrpc.php' # トップページのアドレス末尾に/xmlrpc.phpを付ける
user = 'yamatoXXXXXX' # WordPressのユーザ名
password = 'ABCDXXXXXX' # WordPressのパスワード
 
client = Client(url, user, password)

# 記事一覧の取得。出力はリスト型。
posts = client.call(GetPosts())

ただ、上記を実行してみると以下のエラーが出てきてしまった。

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [6], in <cell line: 1>()
----> 1 posts = client.call(GetPosts())

File ~\anaconda3\envs\py310\lib\site-packages\wordpress_xmlrpc\base.py:46, in Client.call(self, method)
     44     else:
     45         raise
---> 46 return method.process_result(raw_result)

File ~\anaconda3\envs\py310\lib\site-packages\wordpress_xmlrpc\base.py:128, in XmlrpcMethod.process_result(self, raw_result)
    126     if isinstance(raw_result, dict_type):
    127         return self.results_class(raw_result)
--> 128     elif isinstance(raw_result, collections.Iterable):
    129         return [self.results_class(result) for result in raw_result]
    131 return raw_result

AttributeError: module 'collections' has no attribute 'Iterable'

collections モジュールに Iterable って関数が無いよ!、ってことだと思うが。。。

あれ、でもこれってpythonに標準で装備されてるモジュールじゃね??

・・・(この後めちゃくちゃ検索した)・・・

エラー調査

エラーの箇所の調査

githubに python-wordpress-xmlrpc の中身が公開されていたのでそちらを追ってみる。

今回の場合、GetPosts メソッドでエラーが吐かれていたので、import した順に辿っていって、methods/posts.py の中にある class の GetPosts を発見。

from wordpress_xmlrpc.base import *
from wordpress_xmlrpc.wordpress import WordPressPost, WordPressPostType


class GetPosts(AuthenticatedMethod):
    """
    Retrieve posts from the blog.
    Parameters:
       `filter`: optional `dict` of filters:
            * `number`
            * `offset`
            * `orderby`
            * `order`: 'ASC' or 'DESC'
            * `post_type`: Defaults to 'post'
            * `post_status`
    Returns: `list` of :class:`WordPressPost` instances.
    """
    method_name = 'wp.getPosts'
    optional_args = ('filter', 'fields')
    results_class = WordPressPost

## 以下、略 ##

ただ、こいつはさらに base.py から AuthenticatedMethod XmlrpcMethod の順に呼び出しているようなので、そっちを追っていく。

import collections

## 中略 ##

class XmlrpcMethod(object):

## 中略 ##

    def process_result(self, raw_result):
        """
        Performs actions on the raw result from the XML-RPC response.
        If a `results_class` is defined, the response will be converted
        into one or more object instances of that class.
        """
        if self.results_class and raw_result:
            if isinstance(raw_result, dict_type):
                return self.results_class(raw_result)

            elif isinstance(raw_result, collections.Iterable):  ## ここが問題の箇所 !!!

                return [self.results_class(result) for result in raw_result]

        return raw_result

collections モジュール自体はコンテナデータ型と呼ばれる、list型みたいな配列処理の進化版らしい。(知らんかった)

いずれにせよ、ごく普通のモジュールらしい。現に↑の base.py でも冒頭で普通にimportして使ってる。

エラー原因の特定

前回、参考記事として載せた以下のサイトでは、python 3.7.9 の環境で実行しているようだった。

ヤマモトの仮想環境はpython 3.10.4 なのでそれかも??

ってことで、試しにpython 3.7の環境で実行してみる。

(base) C:\Users\ヤマモト>conda list python
# packages in environment at C:\Users\ヤマモト\anaconda3:
#
# Name                    Version                   Build  Channel
python                    3.7.6                h60c2a47_2

## baseのpythonのバージョンが3.7なので、こいつでjupyter labを起動する。

(base) C:\Users\ヤマモト>jupyter lab
## import とかは省略 ##

posts = client.call(GetPosts())

for post in posts:
    print(post.id, post.title)
250 【python】WordPressに自動投稿やってみた。
256 python投稿テスト
248 【気まま日記】今週を振り返りながら酒を飲む。
244 【python】Webスクレイピングで画像取得をやってみる。
238 【週刊少年ジャンプ】40号感想
231 トラックボールマウスを買ったった。
227 「スーパーの裏でヤニ吸うふたり」が面白かった件。
219 【python】Webスクレイピングをやってみる。
203 【python】Anacondaでpython環境を整える。

成功すんのかい 笑

ということで、バージョン違いが原因とわかったのでそれっぽい検索ワードで探してみると、以下のやり取りがヒット。

つまるところ、collectionscollections.abc にアップデートされたため、python 3.10 環境では使えなかったという話らしい。(正確にはpython 3.8からっぽい)

たしかに、モジュールの説明ページにもそんなことが書いてある。

New in version 3.3: Formerly, this module was part of the collections module.

Python 3.10.6 ドキュメント より

エラーの修正対応

残念ながら python-wordpress-xmlrpc のモジュールは数年以上更新されておらず(2022/9 現在)、言わずもがなpython 3.10への対応はされているわけがない。

ということで自分で修正する必要があるわけだが、修正方法としては以下が思いつく。

  • モジュールそのものを書き換える(恒久対応)
  • スクリプト内で動的に修正を加える(暫定対応)

それぞれメリット・デメリットはあるが、1個目はモジュールそのものを書き換えるので少しリスキーではある。おそらくこの問題自体は割と最近発生したであろうものだが、使えないとみんな困ると思うので、そのうちアップデートが入るだろう。

ということで、今回は暫定対応でリスクが少ない2個目の方法でトライしてみる。

コードの修正

つまるところ、呼び出すメソッドの中身を呼び出した後に書き換えれば良い。以下のページを参考にしてトライしてみる。

今回の場合は、base.py の中の XmlrpcMethod クラスの process_result を修正したいので、XmlrpcMethod をインポートして、process_result を自作の修正した関数に置き換える。

# 修正するモジュールをインポート
from wordpress_xmlrpc.base import XmlrpcMethod

# 修正するにあたり、必要な関連モジュールをインポート
from wordpress_xmlrpc.compat import xmlrpc_client, dict_type
import collections.abc

# 元の関数をコピペして、自作関数を作成
def my_process_result(self, raw_result):
    """
    Performs actions on the raw result from the XML-RPC response.
    If a `results_class` is defined, the response will be converted
    into one or more object instances of that class.
    """
    if self.results_class and raw_result:

     # dict_type は wordpress_xmlrpc.compat からインポートしてこないといけない。
        if isinstance(raw_result, dict_type):
            return self.results_class(raw_result)

     # collections => collections.abc に書き換え
        elif isinstance(raw_result, collections.abc.Iterable): ## ここを書き換え。
            return [self.results_class(result) for result in raw_result]

    return raw_result

# 自作のメソッドに置き換える。
XmlrpcMethod.process_result = my_process_result

記事一覧の再取得

ということでリトライ。

# python 3.10.4 で実行
from wordpress_xmlrpc import Client, methods, WordPressPost
from wordpress_xmlrpc.methods.posts import GetPosts, NewPost
from wordpress_xmlrpc.base import XmlrpcMethod
from wordpress_xmlrpc.compat import xmlrpc_client, dict_type
import collections.abc

# 元の関数をコピペして、自作関数を作成
def my_process_result(self, raw_result):
    """
    Performs actions on the raw result from the XML-RPC response.
    If a `results_class` is defined, the response will be converted
    into one or more object instances of that class.
    """
    if self.results_class and raw_result:
        if isinstance(raw_result, dict_type):
            return self.results_class(raw_result)
        elif isinstance(raw_result, collections.abc.Iterable): 
            return [self.results_class(result) for result in raw_result]
    
    return raw_result

# 自作のメソッドに置き換える。
XmlrpcMethod.process_result = my_process_result

# client情報
url = 'https://arefukeblog.com/xmlrpc.php' # トップページのアドレス末尾に/xmlrpc.phpを付ける
user = 'yamatoXXXXXX' # WordPressのユーザ名
password = 'ABCDXXXXXX' # WordPressのパスワード
 
client = Client(url, user, password)

# 記事一覧の取得。
posts = client.call(GetPosts())

for post in posts:
    print(post.id, post.title)
250 【python】WordPressに自動投稿やってみた。
256 python投稿テスト
248 【気まま日記】今週を振り返りながら酒を飲む。
244 【python】Webスクレイピングで画像取得をやってみる。
238 【週刊少年ジャンプ】40号感想
231 トラックボールマウスを買ったった。
227 「スーパーの裏でヤニ吸うふたり」が面白かった件。
219 【python】Webスクレイピングをやってみる。
203 【python】Anacondaでpython環境を整える。

いよっしゃ。成功。


ということでお疲れ様っす。

参考にした記事だと当たり前のように成功してて、自分だけできなくてめっちゃ焦ったw

ただ、検索してもなかなか日本語の記事が見つからず、割と最近のバグなのかな? と思った。

困っている人多そうだけどなー

はー とりあえず問題解決するとすっきり。

今日は気持ち良く寝れそう。→ もう寝ただろ。

コメント

タイトルとURLをコピーしました