0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

BookoffオンラインのAPIをつくってみた。 #2 (ブックマーク、カートに追加 )

Last updated at Posted at 2021-10-24

#今回の目的
前回の記事はこちら
前回はデータを取得する関数をつくった。勿論データを取るのも良いが、今回はもう少し自作プログラムっぽいこと(?)をしたい。つまり、ブックマークやカートに本を追加したい... ちょっとややこしそうだが、前回同様、bookoffオンラインのサイトの構造はかなり単純なので、スイスイと進んでいく。

#ログインする
まずはログインする。しなくてもやり方はあるか?... そんな高度そうなことはどうせわからないから、男は黙って selenium の webriver を ... しかしそれは最終手段と言う感じがして使いたくない。ここは流石にbookoffオンラインなので、もっと原始的にイケそうである。

api.py
HOST="www.bookoffonline.co.jp"
URL="https://"+HOST
SEARCH_URL=URL+"/disp/CSfSearch.jsp"
LOGIN_URL=URL+"/common/CSfLogin.jsp"

HEADERS={ "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/5377.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"}

def isloginurl(text):
        return "MemberForm" in text

def login(session,mail,pw):
        params={
                "name":"MemberForm",
                "ID":mail,
                "PWD":pw
        }
        res=session.get(LOGIN_URL,headers=HEADERS,params=params)
        return not isloginurl(res.text)

####loginの引数

引数 意味
session セッション
mail メールアドレス
pw パスワード

今回の session は重要である。もし session=requests にすると、ログイン情報はメモリの彼方に消えてしまう。
isloginurl はテキストによってそれが LOGIN_URL のものか調べる。 metaタグに、urlが見つからなかったので、こんな有様に ... (笑)
要するに、ログインして、ページが変わったら成功、変わらなかったら失敗である。
##試してみる

パスワードは getpass で入力してみる。

main.py
import api
import getpass
import requests

session=requests.Session()
mail="tetsuya1729@icloud.com"
pw=getpass.getpass()
logined=api.login(session,mail,pw)
print(logined)

正しいのを入力すれば、

$ python main.py

Password:
True

間違えれば

$ python main.py

Password:
False

よしよし。

#ブックマーク、カートに追加する

ログインできたら、もう7合目である。(早い(笑)) 次は、本をブックマークに追加し、カートにも追加する。

api.py
ADD_BM_URL=URL+"/member/BPmAddBookMark.jsp"
ADD_CART_URL=URL+"/disp/CSfAddSession_001.jsp"

def addCart(session,bookid):
        params={
                "iscd":bookid,
                "st":1
        }
        return session.get(ADD_CART_URL,params=params)

def addBM(session,bookid):
        params={
                "iscd":bookid,
                "st":1
        }
        return session.get(ADD_BM_URL,params=params)
引数 意味
bookid 本のID
session セッション

bookid とは本のIDで、url で言えば、

https://www.bookoffonline.co.jp/old/<bookid>

のように末尾にあたる。砂の女なら、

https://www.bookoffonline.co.jp/old/0015580570

bookidを渡すのは実用的に見えないので、後でラップする。

実演は難しいので割愛。

#まとめる

api.py
class Client:
        def __init__(self,mail="",pw=""):
                self.session=requests.Session()
                self.__logined=False
                if mail and pw:
                        self.login(mail,pw)
        def __getattr__(self,attr):
                return getattr(self.session,attr)
        @property
        def logined(self):
                return self.__logined
        def login(self,mail,pw):
                self.__logined=login(self.session,mail,pw)
                return self.logined
        def addCart(self,bookid):
                if not self.logined:
                        return False
                addCart(self.session,bookid)
        def addBM(self,bookid):
                if not self.logined:
                        return False
                addBM(self.session,bookid)

#実用的に

次に、上のコードをもう少し実用的にしたい。
これは、すこし「原始的」ではない感じなので、別のファイルに書いてみる。

##一致した本を返す
前回の search 関数は、クエリからデータのリストを返したが、今回はぴったり一致するデータを返す。しかし厳密に完全一致なら、クエリを入力する方が大変なので、そこはうまくやる。

util.py
from . import api
import re

ALL_LEFT_KAKKO='\xef\xbc\x88'   #"(" of all
ALL_SPACE='\xe3\x80\x80'        #" " of all
U_SPACE="\u3000"
NBSP="\xa0"     #&nbsp
GET_TITLE_F_2="{0}"+"[\({0} {1}{2}{3}].*".format(ALL_LEFT_KAKKO,NBSP,ALL_SPACE,U_SPACE)
def get(title,author,type_=api.SearchType.BOOK,n=3):
        title=re.escape(title)
        def matchtitle(data_title):
                if re.fullmatch(title,data_title):
                        return True
                return re.fullmatch(GET_TITLE_F_2.format(title),data_title)
        for data in api.searchN(title,start=1,n=n,type_=type_):
                if matchtitle(data["title"]) and re.fullmatch(author,data["author"]):
                        return data
        return None
引数 意味
title タイトル
author 著者名
type データの型
n ページ数

まず引数は、基本的に searchN と同じである。start がないのは、一致するものは、最初の方に来るやろう決め打ちしてるからで、本当はページ数 n も不要な気がするが、一応。しかし偏見か?(笑)
まずデータを取って、タイトルと著者名が一致するか調べる。 matchtitle と言いながら fullmatch を使ってるが、細かいことは気にしない。
matchtitle は少し複雑になってしまった。見ての通り fullmatch が2つあって、一つ目は普通の完全一致で、2つ目がやや複雑なのは、以下ののような対応を一致してると見なしたかったせい。

title data_title
砂の女 砂の女(新潮文庫)
熱砂の女たち 熱砂の女たち アラブの心
ダレン・シャン 奇怪なサーカス ダレン・シャン 奇怪なサーカス(1)

###使ってみる

パッケージとしてつかう。

main.py
import bookoff.util as bfu
import sys

title=sys.argv[1]
author=".+"

data=bfu.get(title,author)
print(data)
$ python main.py 砂の
None

$ python main.py 砂の女
{'title': '砂の女(新潮文庫)', 'url': 'https://www.bookoffonline.co.jp/old/0015580570', 'author': '安部公房', 'price': 200, 'stocked': True}    

##実用的にまとめる

先ほど、ブックマークやカートに追加するのをまとめたものを、もう少し実用的にする。

util.py
def getidInUrl(url):
        id_=re.search("/(?P<id>[0-9]+)$",url)
        return id_.group("id") if id_ else None

def getid(title,author=".*"):
        data=get(title,author)
        if not data:
                return None
        return getidInUrl(data["url"])

class Client(api.Client):
        def addCart(self,title,author=".*"):
                bookid=getid(title,author)
                return super().addCart(bookid)
        def addBM(self,title,author=".*"):
                bookid=getid(title,author)
                return super().addBM(bookid)
                                              

####getidInUrl

引数 意味
url url

####getid

引数 意味
title タイトル
author 著者名

ミソは getid で、本と著者名から、その本の ID を取る。単純だが、なんか気持ち良い。(笑) それを使って、Client をラップする。

###実演?
実演は難しいのでコードだけ

a.txt
砂の女
ロシア的人間
狩猟と遊牧の世界
伊豆の踊子
坂の上の雲
main.py
import bookoff.util as bfu
import sys
import getpass


fname=sys.argv[1]

mail="tetsuya1729@icloud.com"
pw=getpass.getpass()
client=bfu.Client(mail,pw)

with open(fname,"r") as f:
        for title in f:
                title=title.rstrip()
                print(title)
                client.addBM(title)

時間はかかるが、一応使える。

#終わりに
もう API は良いかな。やるなら次はシェルを作りたい。それより bookmeter と連携させたいか。しかしサイトの構造が単純で驚いた。YoutubeやPornhubもこれくらい単純なら嬉しいが、やはり動画は守られてしまうか。。。 しかし、そういう大手のものは、もっと頭の良い方が解析してソフトを作ってくれるので、ボーっと待っておこう。youtube-dl なんて使われてるが、正規なら動画のダウンロードで月1500円取られるわけやから、グレー寄りのブラックと言う感じがする。 しかしそれでも商品やプレミア配信は流石に取れないか。頑張ったら取れるんかな。。。(笑)

全然関係ない話でした、すみません。^-^

コードはこちら

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?