BookoffオンラインのAPIをつくってみた。 #1  (データの取得) 

#はじめに
 何事も**手動より自動の方が良い**に決まっている。勿論本の検索も然り。すでに十分自動化されているが、もっと楽を追求するのが人間の業であると思う。今回は表題の通り、bookoffオンラインのAPIを少し作ってみた。もっと作る予定だが、とりあえず公開。^-^

#何をしたい
主にしたいのは、

- データの取得
- ~~データベースの作成~~
- 手元のリストから、bookoffオンラインのカートに追加

などなど。まずは上の3つを作りたい。「手元のリスト」をbookmeterやブクログから作れたら欲しい本をワンクリックで購入！なんてことも出来そうである。

#データの取得
 手始めにデータを取得する。サイトを触ると、一見バリバリのJavascriptなので、seleniumを使わないと厳しそうだが、ソースコードを見ると、普通のGETで取れるようである。
##テキストの取得
　まず、リクエストを送ってテキストを取得する。

```python:api.py
import requests
from bs4 import BeautifulSoup as bs
import urllib.parse as up
import sys
import re
import time

SEARCH_URL="https://www.bookoffonline.co.jp/disp/CSfSearch.jsp"

class SearchType:
        ALL=""
        BOOK=12
        PRATICAL_BOOK=1201
        MAGAZINE="13"
        CD=31
        DVD=71
        GAME=51
        ELSE=81
        SET="set"

def _search(query,page=1,type_=SearchType.BOOK,session=requests):
        query=query
        encoding="sjis"
        query=query.encode(encoding)
        params={
                "name":"search_form",
                "q":query,
                "st":"",
                "p":page,
                "bg":type_
        }
        headers={
            "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
"
        }
        res=session.get(SEARCH_URL,headers=headers,params=params)
        res.encoding=encoding
        return res.text
```

引数 |意味 |
---- |----|
query |検索する文字 |
page |何ページ目|
type_ |データの種類 |
session |セッション |

 sessionは内部のもの。type_の初期値はとりあえず本にしておく。

 少し工夫したのは、
` query=query.encode(encoding)`
のところで、これがないと文字コードの問題で、日本語のクエリがうまくいかない。

 SearchTypeは本当はもっとあるのだが、とりあえず使いそうなものだけ。~~以下が全て。~~  以下は本の全てとその他。

###ジャンルの一覧
ジャンル名 |値 |
---  |--- |
すべて | |
本・書籍 |12
実用書 |1201
健康・家庭医学 |1202
旅行・レジャー・スポーツ |1203
趣味・就職ガイド・資格 |1204
絵本・児童書 |1205
社会・文化 |1206
政治 |1207
法律・コンプライアンス |1208
ビジネス・経済 |1209
産業・労働 |1210
情報・通信・コンピュータ |1211
テクノロジー・環境 |1212
サイエンス |1213
メディカル |1214
哲学・心理学・宗教 |1215
歴史・地理 |1216
語学・会話 |1217
教育 |1218
芸術・芸能・エンタメ・アート |1219
文学・エッセイ・詩集 |1220
小説 |1221
外国の小説 |1222
ポルノグラフィティ |1223
教養・雑学（文庫） |1224
小説・エッセイ・ノンフィクション（新書） |1225
教養・雑学（文庫） |1226
小説・エッセイ・ノンフィクション（新書） |1227
グラビアアイドル・タレント写真集（女性） |1228
プライベートブランド |1299
漫画・コミック |11
雑誌 |13
CD |31
DVD |71
ゲーム |51
その他 |31
オトナ買い | "set"

##データに変換
 _search はテキストを返すだけなので、それをデータに変換する。

```python:api.py
def toData(l):
        ttl=l.select_one(".itemttl")
        title=ttl.text
        a=ttl.find("a")
        url=a.get("href")
        stocked=not l.select_one(".nostockbtn")
        mainprice=l.select_one(".mainprice")
        price=mainprice.text
        price=re.search("[\d,]+",price)
        price=price.group()
        price=int(price.replace(",",""))
        author=l.select_one(".author")
        author=author.text
        return {
                "title":title,
                "url":url,
                "author":author,
                "price":price,
                "stocked":stocked
        }

def getData(soup):
        for l in soup.select(".list_group"):
                yield toData(l)
```

####toDataの引数
引数 |意味 |
---- |----|
l |データのエレメント |

####toDataの返り値
引数 |意味 |
---- |----|
title |タイトル |
url |url 
author |著者名
price |価格
stocked |在庫の有無

####getDataの引数
引数 |意味 |
---- |----|
soup |スープ |

スープからデータを取って変換するだけ。toDataのときに、もっと色々なデータを取れそうだが、とりあえず欲しいものだけ取る。

そして、組み合わせて実用的にする。

```python:api.py
def search(query,page=1,type_=SearchType.BOOK,session=requests):
        text=_search(query,page,type_,session)
        soup=bs(text,"html.parser")
        for data in getData(soup):
                data["url"]=up.urljoin(SEARCH_URL,data["url"])
                yield data
```

###使ってみる

```python:main.py
import api
import json
import sys

query=sys.argv[1]
for data in api.search(query):
        print(json.dumps(data,indent=4,ensure_ascii=False))                                                        
```

```shell:
$ python main.py 安部公房

{
    "title": "  砂の女(新潮文庫)",
    "url": "https://www.bookoffonline.co.jp/old/0015580570",
    "author": "安部公房",
    "price": 200,
    "stocked": true
}
{
    "title": "  壁(新潮文庫)",
    "url": "https://www.bookoffonline.co.jp/old/0015580566",
    "author": "安部公房",
    "price": 200,
    "stocked": true
}
{
    "title": " 笑う月(新潮文庫)",
    "url": "https://www.bookoffonline.co.jp/old/0015580572",
    "author": "安部公房",
    "price": 200,
    "stocked": false
}

~~ 以下略 ~~
```

##何ページも取る

search は或る1ページしか取れないので、何ページも取れるようにする。

```python:api.py
def searchN(query,start=1,n=1,type_=SearchType.BOOK):
        session=requests.Session()
        for page in range(start,start+n):
                dataa=list(search(query,page,type_,session))
                if not dataa:
                        return
                for data in dataa:
                        yield data
        time.sleep(1)
```

####searchNの引数
引数 |意味 |
--- |--- |
query |検索する文字
start |最初のページ
n |ページ数
type_ |データの種類

只何ページも取る。一応 search の引数のsesison はこのためにつくった。

#試す

```python:main.py
import api
import json
import sys

query=sys.argv[1]
for data in  api.searchN(query,start=2,n=2):
        print(json.dumps(data,indent=4,ensure_ascii=False))
```

```shell
$ python main.py 安部公房

{
    "title": "  カンガルー・ノート(新潮文庫)",
    "url": "https://www.bookoffonline.co.jp/old/0012309902",
    "author": "安部公房",
    "price": 300,
    "stocked": true
}
{
    "title": " けものたちは故郷をめざす(新潮文庫)",
    "url": "https://www.bookoffonline.co.jp/old/0016232640",
    "author": "安部公房",
    "price": 600,
    "stocked": false
}

~~ 略 ~~

{
    "title": "  安部公房全集-１９７０．０２‐１９７３．０３(２３)(贋月報(本文挟み込み小冊子）付)",
    "url": "https://www.bookoffonline.co.jp/old/0012889510",
    "author": "安部公房",
    "price": 3350,
    "stocked": true
}
{
    "title": " 安部公房全集-１９７３．０３‐１９７４．０２(２４)(贋月報(本文挟み込み小冊子）付)",
    "url": "https://www.bookoffonline.co.jp/new/0012893518",
    "author": "安部公房",
    "price": 6270,
    "stocked": true
}
```



**以上!!**

[コードはこちら](https://github.com/tetsuya111/util/tree/main/bookoff)