技術書典の本を検索できるアプリを作ってみた~サーバーサイド 編(Python, Go)~

# はじめに
この記事は、時間ができた学生が暇つぶしに**「技術書典の本を検索できるアプリを作ってみた」**というものです。
技術書典に参加する前にある程度購入する本を決めると思うのですが、その際に欲しい技術について書いてある本を探したいと思うはずです。サークル一覧から本を探すのはとても大変で時間が足りません。そこでスクレーピングしてデータ収集し、検索できるアプリを開発してみました（自己満）。
この記事ではサーバーサイドについて書きます。次の記事で「Androidアプリを作ってみた」を書きたいと思います。

# サーバーサイドの流れ
1. Pythonでデータを収集する  
I.[技術書典のサークルリスト](https://techbookfest.org/event/tbf07/circle) からURL情報を収集する  
II. 集めたURLにアクセスしてそれぞれのサークルが出している本の情報を収集する
2. 収集したデータをDBに保存する
3. Goでサーバーを立てる

# 1. Pythonでデータ収集
### I. 技術書典のサークルリスト からURL情報を収集する

[技術書典のサークルリスト](https://techbookfest.org/event/tbf07/circle)の\<a>タグをひたすら保存するコード

```collect_url.py
# coding: UTF-8
from bs4 import BeautifulSoup
from selenium import webdriver
import chromedriver_binary
from selenium.webdriver.chrome.options import Options

# ブラウザのオプションを格納する変数
options = Options()

# Headlessモードを有効にする
options.set_headless(True)

# ブラウザを起動する
driver = webdriver.Chrome(chrome_options=options)

# ブラウザでアクセスする
driver.get("https://techbookfest.org/event/tbf07/circle")

# HTMLを文字コードをUTF-8に変換してから取得
html = driver.page_source.encode('utf-8')

# BeautifulSoupで扱えるようにパース
soup = BeautifulSoup(html, "html.parser")

# ファイル出力
file_text = open("url_data.txt", "w")
elems = soup.find_all("a")
for elem in elems:
    print(elem.get('href'),file=file_text)
file_text.close()
```

### II. 集めたURLにアクセスしてそれぞれのサークルが出している本の情報を収集する
1.で集めたURLにアクセスし、
出店しているサークル名、配置場所、ジャンル、ジャンル詳細、サークル画像、出品している本の名前、内容
のデータを収集する。（残念ながらペンネームは集められなかった。~~html にclass or idを貼って無いから面倒~~）

```collect_data.py
# coding: UTF-8
from bs4 import BeautifulSoup
from selenium import webdriver
import chromedriver_binary
from selenium.webdriver.chrome.options import Options
import pickle
import sys

# データを保存する
i = 0
sys.setrecursionlimit(10000)
with open('../data/getData.txt', 'wb') as ff:

    # 保存するデータ
    save_datas = []

    # ブラウザのオプションを格納する変数
    options = Options()

    # Headlessモードを有効にする
    options.set_headless(True)

    # ブラウザを起動する
    driver = webdriver.Chrome(chrome_options=options)

    urlHeader = "https://techbookfest.org"
    pathfile = "../data/url_data.txt"

    with open(pathfile) as f:
        for _path in f:
            i += 1
            url = urlHeader + _path
            print(i,url)
            # ブラウザでアクセスする
            driver.get(url)

            # HTMLを文字コードをUTF-8に変換してから取得
            html = driver.page_source.encode('utf-8')

            # BeautifulSoupで扱えるようにパース
            soup = BeautifulSoup(html, "html.parser")

            circle = soup.find(class_="circle-name").string
            arrange = soup.find(class_="circle-space-label").string
            genre = soup.find(class_="circle-genre-label").string
            keyword = soup.find(class_="circle-genre-free-format").string

            circle_image = soup.find(class_="circle-detail-image").find(class_="ng-star-inserted")

            book_title = []
            for a in soup.find_all(class_="mat-card-title"):
                book_title.append(a.string)

            book_content = []
            for a in soup.find_all(class_="products-description"):
                book_content.append(a.string)

            for title, content in zip(book_title, book_content):
                data = [circle,circle_image['src'],arrange,genre,keyword,title,content,url]
                save_datas.append(data)

    pickle.dump(save_datas,ff)
```

# 2. 収集したデータをDBに保存する
ファイルに保存したデータを取得し、MySQLにInsertするだけのプログラムです。Insertするだけなので適当なプログラムになってしまった。

```insertDB.py
# coding: UTF-8
import MySQLdb
import pickle
 
# データベースへの接続とカーソルの生成
connection = MySQLdb.connect(
    host='0.0.0.0',
    user='user',
    passwd='password',
    db='techBook')
cursor = connection.cursor()

# id, circle, circle_image, arr, genere, keyword, title, content
with open('../data/getData.txt','rb') as f:
    load_datas = pickle.load(f)
    for load_data in load_datas:
        data = []
        if load_data[6] == None:
            for dd in load_data:
                if dd == None:
                    continue
                dd = dd.replace('\'','')
                data.append(dd)
            sql = "INSERT INTO circle (circle, circle_image, arr, genere, keyword, title, circle_url) values ('" + data[0] + "','" + data[1] + "','" + data[2]+"','" + data[3]+"','" + data[4]+"','" + data[5]+"','" + data[6]+"')"
        else:
            for dd in load_data:
                dd = dd.replace('\'','')
                data.append(dd)
            sql = "INSERT INTO circle (circle, circle_image, arr, genere, keyword, title, content, circle_url) values ('" + data[0] + "','" + data[1] + "','" + data[2]+"','" + data[3]+"','" + data[4]+"','" + data[5]+"','" + data[6]+"','" + data[7]+"')"
            print(sql)
        cursor.execute(sql)

# 保存を実行
connection.commit()
 
# 接続を閉じる
connection.close()
```

#3. Goでサーバーを立てる
Pythonで書いてもよかったのですが、気分的にGoで書きました。
[レイアードアーキテクチャ](https://qiita.com/hmarf/items/7f4d39c48775c205b99b)で書いています。~~(宣伝)~~
ファイル数が多いので全部載せることはできませんでした。[Github](https://github.com/hmarf/TechBookFest_serverside)を参照ください。
検索はSQLの部分一致検索で行います。

```
SELECT * FROM circle where content like '%swift%';
```
取得したデータをjson形式で返して終わりです！！

```
[request] 
{
"keyword":"..."
}

[response]
{
    "result": [
        {
            "CircleURL": "...",
            "Circle": "...",
            "CircleImage": "...",
            "arr": "...",
            "Genere": "...",
            "Keyword": "...",
            "Title": "...",
            "Content": "..."
        },
    ]
}
```

# 終わりに
久々にPythonを書いた気がします。全体的に書いてて楽しかったです。今回書いたコードは[こちら](https://github.com/hmarf/TechBookFest_serverside)。
読んでいただきありがとうございました。次回のAndroid編をお楽しみに！！