技術書典 応援祭の無料本一覧を取得する
2月29日~3月1日に開催されるはずだった技術書典8が新型コロナウイルス感染症の影響により中止となり、代わりに3月7日~4月5日までの約1ヶ月間オンラインで技術書典 応援祭が開催されています。
記事投稿点で700種類以上の技術書が掲載されており、多くは電子版と製本版が有料で販売されているのですが、中には電子版が無料で提供されているものがあります。それらはログインすれば.pdfや.epubの形式でダウンロードが可能です。
しかし残念ながらサイトには価格順でソート表示する機能が見当たりません。なんとかすべての無料本を入手したいと思い、スクレイピングで一覧を作成しましたので参考までに掲載させていただきます。※もし不都合あればコメント欄などから連絡いただければ取り下げます
技術書典 応援祭の無料本一覧
以下の51冊が見つかりました。ありがたく読ませていただきます。末尾から順に取得したので、サイトの表示とは順番が逆になっています。漏れがあったらすみません。なお以下は記事投稿時点のものであり、期間中に増える可能性があります。
取得に使ったスクリプト
当初GoogleスプレッドシートのIMPORTXMLでやろうとしたところ、どうもJavaScriptで動的にページ内容が作られているようでうまくいきませんでした。サイトのソースを見てもパッと見でよくわからなかったので、PythonからHeadless Chromeを使う形で実現しました。
作成したスクリプトは全書籍の電子版価格一覧を出力するものです。約5秒の間隔を開けてアクセスするようにしているので、完了まで1時間くらいかかります。Ctrl+Cで中断した場合や、新たに書籍が追された場合は、既存取得部分をスキップして必要な分だけアクセスするようになっています。
import time import re import traceback from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import chromedriver_binary from bs4 import BeautifulSoup options = webdriver.ChromeOptions() options.add_argument("--headless") options.add_argument("--log-level=3") driver = webdriver.Chrome(options=options) try: exist_id_list = [] try: with open('./output.txt', mode='r', encoding='utf-8') as f: exist_id_list = list(map(lambda x: re.sub(r'^(\d+).*', r'\1', x), f.read().splitlines()[1:])) print("既存のoutput.txtを読み込みました。") except Exception as e: with open("./output.txt", mode='w', encoding='utf-8') as f: f.write("id, title, author, price\n") print("output.txtを新規作成しました。") print("全productのリンクを取得中・・・") products = [] driver.get("https://techbookfest.org/market/all") WebDriverWait(driver, 30).until(EC.presence_of_all_elements_located) seconds = 0 prev_product_count = 0 wait_flag = True while wait_flag and seconds < 180: time.sleep(5) products = BeautifulSoup(driver.page_source,"lxml").find_all(class_="css-1dbjc4n r-18u37iz r-1w6e6rj r-1qhn6m8")[0].find_all("a") if prev_product_count < len(products): prev_product_count = len(products) seconds += 5 print(str(seconds) + "秒 : " + str(len(products)) + "件") wait_flag = True else: wait_flag = False print("各productの電子版価格を取得中・・・") for product in reversed(products): product_id = re.sub(r'/product/(\d+)', r'\1', product['href']) if product_id in exist_id_list: print(product_id + "をスキップ") else: driver.get("https://techbookfest.org/product/" + product_id) WebDriverWait(driver, 30).until(EC.presence_of_all_elements_located) time.sleep(5) title = BeautifulSoup(driver.page_source,"lxml").find("h2").text author = BeautifulSoup(driver.page_source,"lxml").find("h3").text price = re.sub(r'.*電子版¥(\d+).*', r'\1', BeautifulSoup(driver.page_source,"lxml").find(class_="css-1dbjc4n r-18u37iz r-1w6e6rj r-1v1z2uz").text) result_line = ", ".join([product_id, title, author, price]) print(result_line) with open("./output.txt", mode='a', encoding='utf-8') as f: f.write(result_line + "\n") except: traceback.print_exc() finally: driver.quit()
動かすためには以下の準備が必要です。「<Chromeに近いバージョン数>」はChromeのヘルプを確認してからchromedriver-binary · PyPIの一番近いものを使います。
>pip install selenium >pip install chromedriver-binary==<Chromeに近いバージョン数> >pip install beautifulsoup4 >pip install lxml
出力結果から0円のものに絞り込んだり、ログインして購入→ダウンロードする部分はいまのところ手動でやっています。前述のスクリプトは改変含め自由に使っていただいて構いませんが、動作など一切保証しません。使い方についての問い合わせなどもご遠慮ください。技術書典のサーバに負荷を掛けないようにくれぐれもご注意ください。
更新履歴
2020/03/31
- 追加されていた6冊を追記しました。
- ページ構成が変わっていたのでスクリプトを修正しました。