#今回の目的
前回の記事はこちら
前回はデータを取得する関数をつくった。勿論データを取るのも良いが、今回はもう少し自作プログラムっぽいこと(?)をしたい。つまり、ブックマークやカートに本を追加したい... ちょっとややこしそうだが、前回同様、bookoffオンラインのサイトの構造はかなり単純なので、スイスイと進んでいく。
#ログインする
まずはログインする。しなくてもやり方はあるか?... そんな高度そうなことはどうせわからないから、男は黙って selenium の webriver を ... しかしそれは最終手段と言う感じがして使いたくない。ここは流石にbookoffオンラインなので、もっと原始的にイケそうである。
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 | セッション |
メールアドレス | |
pw | パスワード |
今回の session は重要である。もし session=requests にすると、ログイン情報はメモリの彼方に消えてしまう。
isloginurl はテキストによってそれが LOGIN_URL のものか調べる。 metaタグに、urlが見つからなかったので、こんな有様に ... (笑)
要するに、ログインして、ページが変わったら成功、変わらなかったら失敗である。
##試してみる
パスワードは getpass で入力してみる。
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合目である。(早い(笑)) 次は、本をブックマークに追加し、カートにも追加する。
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を渡すのは実用的に見えないので、後でラップする。
実演は難しいので割愛。
#まとめる
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 関数は、クエリからデータのリストを返したが、今回はぴったり一致するデータを返す。しかし厳密に完全一致なら、クエリを入力する方が大変なので、そこはうまくやる。
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" # 
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) |
###使ってみる
パッケージとしてつかう。
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}
##実用的にまとめる
先ほど、ブックマークやカートに追加するのをまとめたものを、もう少し実用的にする。
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 をラップする。
###実演?
実演は難しいのでコードだけ
砂の女
ロシア的人間
狩猟と遊牧の世界
伊豆の踊子
坂の上の雲
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円取られるわけやから、グレー寄りのブラックと言う感じがする。 しかしそれでも商品やプレミア配信は流石に取れないか。頑張ったら取れるんかな。。。(笑)
全然関係ない話でした、すみません。^-^