1
4

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-10-24

はじめに

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

何をしたい

主にしたいのは、

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

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

データの取得

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

テキストの取得

 まず、リクエストを送ってテキストを取得する。

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 はテキストを返すだけなので、それをデータに変換する。

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のときに、もっと色々なデータを取れそうだが、とりあえず欲しいものだけ取る。

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

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

使ってみる

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))                                                        
$ 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ページしか取れないので、何ページも取れるようにする。

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 はこのためにつくった。

試す

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))
$ 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": "  安部公房全集-1970.02‐1973.03(23)(贋月報(本文挟み込み小冊子)付)",
    "url": "https://www.bookoffonline.co.jp/old/0012889510",
    "author": "安部公房",
    "price": 3350,
    "stocked": true
}
{
    "title": " 安部公房全集-1973.03‐1974.02(24)(贋月報(本文挟み込み小冊子)付)",
    "url": "https://www.bookoffonline.co.jp/new/0012893518",
    "author": "安部公房",
    "price": 6270,
    "stocked": true
}

以上!!

コードはこちら

1
4
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
1
4