グーバイクに販売中のバイク一覧をスクレイピングしてみた

流石に業務時間内で堂々とバイク販売サイトを見るのもあれですが、
仕事中の暇な時間を利用して、グーバイクに販売中のバイク一覧をスクレイピングしてみたよ。

https://github.com/MrYoshifumi/GoobikeScraping

今回の流れについて解説しましょう。

## なぜスクレイピング？
暇だから:joy:
主な原因は、KTM 390 Duke から 890 SMT に乗り換えようと考えています。

そのため、いつもサイトを見るより、コーディング画面のほうが上司から指摘されないから
（小声）



## まず調査を行おう
グーバイク側では、データベースから抽出された結果を動的にフロント側で同じdiv構成によってloopしてレンダリングするはずです

（多分。。。。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815020/4b9ba749-7c28-7973-1606-d95335852c9d.png)
パッと見たら、確かに一台ずつ同じ`bike_sec`との class の`div`で構成されていますね。

なので今回は、Python の`BeautifulSoup`モジュールの`find()`または`find_all()`を利用して、特定`div`の内容を抽出すればいいですよ。

## 画面構成
詳しく調査した結果、基本的に一台のバイクに対して、`<div class="bike_sec">`と`<div class="shop_info>`という2個のdiv要素が付与されていることが分かりました。

`bike_sec`はバイク情報、`shop_info`は販売店情報になっております。

```HTML
<div class="bike_sec">                  検索結果
    <div class="clear_fix">
        <div class="bike_img">          画像
        <div class="bike_info">         車体情報
            <div class="model_title">   モデル
            <div class="fav_btn">       検討ボタン
            <div class="detail_cont">   車体情報エリア
                <div class="cont01">
                    <table>             車両価格 + 支払総額
                    <ul>
                        <li> 0番
                            <span>      モデル年式
                            <b>         年式数字 + 年
                            
                        <li> 1番
                            <span>      初度登録年
                            <b>         新車（在庫あり）/ 年式数字 + 年
                            
                        <li> 2番
                            <span>      走行距離
                            <b>         ― / 距離数字 + Km
                            
                        <li> 3番
                            <span>      車検/自賠責保険
                            <b>         検 + yyyy/mm
                            
                        <li> 4番
                            <span>      修復歴
                            <b>        （不明）
                                        * 修復歴のあるバイク見かけたことない
                <div class="cont02">

    <div class="shop_info>
        <div class="bxl>
            <div class="shop_name">     販売店名前
                <dt>                    店名
                <dd>                    場所 ＋ 営業時間
            <div class="cus_voice">     販売店へのお客様の声
```

実際の画面は以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815020/3ea57296-61b1-e22e-0e26-cb36c6e2f1f1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815020/5b04467f-2332-854a-5874-efee1ab501bc.png)

メインに調べたい情報は、`<li>`の0番目から3番目の

`値段`、`年式`、`初度登録年`、`走行距離`、`車検`、`店名（場所）`です。

また、グーバイクの画面ではデフォルトで50個の結果しか表示しないため、ソース上で画面のページネーションの統計文から、最大ページ数を取得し、for文で繰り返します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815020/7987e716-1b54-63d6-4310-2a748d9d4687.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815020/f99aad2c-e50d-8478-0904-4b69be8955bc.png)

なので、`bxr` class の`<p>`要素の`全{num}ページ`の数値を抽出すればいいです。

## 使用例
```
$ python goobike_scraper.py
メーカー: ktm
モデル名: 390_duke
年式を指定するか (y/n): y
年式: 2020
支払総額を昇順で表示するか (y/n): y
```
メーカー名とモデル名を入力した後、スクレイピングが始めます。
その後、年式を指定するか、または昇順で表示かを聞いてくれます。

## 出力例
```
47万円, 2020年, 2021年, 14245Km, 検2026/01, Ｃ２０４ (大阪府守口市佐太中町３丁目９−１６ドミール佐太１Ｆ)
47万円, 2020年, 2021年, 17380Km, 検2024/08, バイク王　伊丹店 (兵庫県伊丹市北伊丹５−９６−１)
47万円, 2020年, 2021年, 14245Km, 検2026/01, Ｃ２０４ (大阪府守口市佐太中町３丁目９−１６ドミール佐太１Ｆ)
47万円, 2020年, 2021年, 17380Km, 検2024/08, バイク王　伊丹店 (兵庫県伊丹市北伊丹５−９６−１)
49万円, 2020年, 2020年, 19239Km, 検無し, バイク館富田林店 (大阪府富田林市喜志町５丁目４−３０)
50万円, 2020年, 2020年, 17565Km, 検2025/06, ＫＴＭ小山 (栃木県小山市雨ケ谷新田６９−１)
51万円, 2020年, 2020年, 7422Km, 検無し, ビジョギマンケーブ (埼玉県戸田市美女木４−１９−２７)
51万円, 2020年, 2021年, 2131Km, 検無し, バイクランド直販センター　環七鹿浜店 (東京都足立区椿２−２−２)
```
割と綺麗に抽出していますね。


## 分かったこと
グーバイク側では検索用APIが公開されていないため、あくまでもユーザー側がアクセスできる画面に対した調査結果となります。

また、コマンド引数の`メーカー`と`モデル名`によって自由に調べられるよう色々実装しましたが、なぜかグーバイク側で生成された固定URLが **規則的ではない** と分かりました。

## 不規則なURL
最初に実装するとき、URLのパラメータは全部
```
www.goobike.com/maker-{manufacturer}/car-{manufacturer}_{model}/index{page_number}.html
```
との解釈で大丈夫かと思いましたが、実際に一部のモデル名がアンダーバー付いてたり、付いていなかったりしています。

たまに
```python
maker-{manufacturer}/car-{model}/index.html
```
の場合もありますが...

以下は例です
```
https://www.goobike.com/maker-ktm/car-ktm_890smt/index.html
https://www.goobike.com/maker-ktm/car-ktm_890duke_r/index.html
https://www.goobike.com/maker-ktm/car-ktm_390_adventure/index.html
https://www.goobike.com/maker-bmw/car-bmw_f900xr/index.html
https://www.goobike.com/maker-honda/car-honda_cbr250rr/index2.html
```

### 外車
まず大好きな KTM のほうから、Dukeシリーズの場合でも
`390_duke`　`890duke`　`890duke_r`　`1290_super_duke_r`
のようなめちゃくちゃなネーミングルール、何なんだよ。

アドベンチャーシリーズの場合は結構いいです。
`790_adventure`　`890_adventure_r`　`1290_super_adventure_s`

他の外車勢も割とマシだろうと。
BMW は `f900xr`　`r1250gs_adventure`、MVアグスタは `f3_800`

### 日本車
次に日本車の場合、

Honda は `cbr250rr`　`crf1000l_africa_twin`、Yamaha は `yzf-r6`

***統一しろよおい（怒）***

単に `{num}_{seriesModel}` では無理のようです。

### 修正するなら？
外車の場合、改めてグーバイク側で表示するモデル名の辞書を用意して、アンダーなどを付くか付かないかを変換します。

日本車の場合、URLパラメータは `maker-{manufacturer}/car-{model}/` で解釈できそうな感じなので、if文で4大メーカーの場合だけURLを書き換えれば実現できるかと思います。

## 今後の課題
1. 抽出結果をSQLに保存して、新規追加した分のみ提示するように修正すること
1. モデル色は今回判別できていないため、画像認識とかを実装して、更にモデル色が分かるようにすること
1. 今回は類似csv形式で抽出したため、今度はjson形式で出力し、上記のモデル色など要素も追加し、結果分析を容易にすること
1. モデル名の入力規則を追加・修正する