※2019/8/12 書籍のリンクを最新版に更新
PyCon JP 2017で発表した野球×Pythonの分析ネタの詳細解説です.*1
プレゼンテーション:野球を科学する技術〜Pythonを用いた統計ライブラリ作成と分析基盤構築 | PyCon JP 2017 in TOKYO
時間および諸々の都合(察し)で公開できなかった*2,
「人とWebに優しい」Scrapyアプリのサンプル(なお野球)
を作って公開したのでその紹介と,PyConのプレゼンで発表しきれなかった部分を簡単に紹介します.
おしながき
対象の読者
- Pythonレベル初級〜中級
- 好きなエディタ・環境でPythonを読み書きできる(本を読みながら・人に聞きながらでもOK)
- データ分析とか機械学習やりたい!...けどデータ集めなきゃ!っていう方
- なお,特段野球好き・マニアである必要はありません.野球アレルギーが無ければ大丈夫.*3
参考文献
このエントリーを読む前でも読んだ後でも目を通しておくことをオススメします.
Pythonクローリング&スクレイピング[増補改訂版] -データ収集・解析のための実践開発ガイド
- 作者: 加藤耕太
- 出版社/メーカー: 技術評論社
- 発売日: 2019/08/10
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
Scrapyのみならず,スクレイピングとクローラーについて事細かかつわかりやすく解説している良著でホント参考にさせてもらいました.
Scrapyを用いた日本プロ野球データ取得Exampleアプリ
こちらに準備しました.
動作環境・使い方はREADME.mdを御覧ください.
大雑把に使い方を書くとこんな感じです.
- コードをcloneする
- Python 3.6およびscrapyをインストール
- 「scrapy crawl batter -a year=2017 -a league=1」とコマンドを叩くとsqlite3のDBができて打者の情報をドカドカ入れてくれる
- 「scrapy crawl pitcher -a year=2017 -a league=1」とコマンドを叩くと投手の情報を(上に同じく)
出来上がったDBは野球好き的にたまらないと思います.
ポイント
主にScrapyの解説となります...が,(アーキテクト図を除き)Scrapyそのものの解説は端折っているので詳細を知りたい方は先ほどの「Pythonクローリング&スクレイピング ―データ収集・解析のための実践開発ガイド―」を読むか,Scrapyの公式サイトを御覧ください.
Scrapy 1.7 documentation — Scrapy 1.7.3 documentation
全体像
Scrapyの全体アーキテクト図に今回の対象範囲を合わせてみました.
「人とWebに優しい」settings.pyの書き方
最低限守るべきポイントは以下の3つです.
- robots.txtに従う(Webクローリングする上での最低限のお作法)
- リクエストの発行回数を大幅に減らす.具体的には同一ドメインに対して並列数を絞る,ダウンロード間隔を60秒以上空ける
- キャッシュは必ず取る,再実行時はキャッシュを相手にする.
今回のサンプルアプリは,
- 一日に一回,野球選手の成績を取る
- 一覧ページを相手, 打撃成績は12球団分,投手成績も12球団分
- Spiderは打者もしくは投手を相手するので1分以上空けたとしても15分前後で全チームの打撃成績と投手成績が入手できる
という要件のもと,
- リクエスト数は2つもあれば十分
- 一日一回取るぐらいならキャッシュも24時間効かせてOK(デバッグ目的でたくさん叩いても相手サイトに迷惑かけない)
という方針で設定しました.
設定内容はこんな感じです(コメントも一緒に読んでもらえるとなお良い!)
# -*- coding: utf-8 -*- # Scrapy settings for baseball project # # For simplicity, this file contains only settings considered important or # commonly used. You can find more settings consulting the documentation: # # http://doc.scrapy.org/en/latest/topics/settings.html # http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html # http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html BOT_NAME = 'baseball' # botの名前 SPIDER_MODULES = ['baseball.spiders'] NEWSPIDER_MODULE = 'baseball.spiders' # Crawl responsibly by identifying yourself (and your website) on the user-agent #USER_AGENT = 'baseball (+http://www.yourdomain.com)' # Obey robots.txt rules ROBOTSTXT_OBEY = True # robots.txtに従うか否か # Configure maximum concurrent requests performed by Scrapy (default: 16) CONCURRENT_REQUESTS = 2 # リクエスト並行数,16もいらないので2 # Configure a delay for requests for the same website (default: 0) # See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay # See also autothrottle settings and docs DOWNLOAD_DELAY = 60 # ダウンロード間隔(s),60秒に設定 # The download delay setting will honor only one of: CONCURRENT_REQUESTS_PER_DOMAIN = 2 # 同一ドメインに対する並行リクエスト数. CONCURRENT_REQUESTSと同じ値でOK(サンプルは単一ドメインしか相手していない) CONCURRENT_REQUESTS_PER_IP = 0 # 同一IPに対する並行リクエスト数. ドメインで絞るので無効化してOK # Disable cookies (enabled by default) #COOKIES_ENABLED = False # Disable Telnet Console (enabled by default) #TELNETCONSOLE_ENABLED = False # Override the default request headers: #DEFAULT_REQUEST_HEADERS = { # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', # 'Accept-Language': 'en', #} # Enable or disable spider middlewares # See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html #SPIDER_MIDDLEWARES = { # 'baseball.middlewares.BaseballSpiderMiddleware': 543, #} # Enable or disable downloader middlewares # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html #DOWNLOADER_MIDDLEWARES = { # 'baseball.middlewares.MyCustomDownloaderMiddleware': 543, #} # Enable or disable extensions # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html #EXTENSIONS = { # 'scrapy.extensions.telnet.TelnetConsole': None, #} # Configure item pipelines # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'baseball.pipelines.BaseballPipeline': 100, # DB保存用のミドルウェア } # Enable and configure the AutoThrottle extension (disabled by default) # See http://doc.scrapy.org/en/latest/topics/autothrottle.html #AUTOTHROTTLE_ENABLED = True # The initial download delay #AUTOTHROTTLE_START_DELAY = 5 # The maximum download delay to be set in case of high latencies #AUTOTHROTTLE_MAX_DELAY = 60 # The average number of requests Scrapy should be sending in parallel to # each remote server #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 # Enable showing throttling stats for every response received: #AUTOTHROTTLE_DEBUG = False # Enable and configure HTTP caching (disabled by default) # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings HTTPCACHE_ENABLED = True # キャッシュの有無,勿論True HTTPCACHE_EXPIRATION_SECS = 60 * 60 * 24 # キャッシュのExpire期間. 24時間(秒数で指定) HTTPCACHE_DIR = 'httpcache' # キャッシュの保存先(どこでも良い) #HTTPCACHE_IGNORE_HTTP_CODES = [] #HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
リクエスト数やキャッシュ有効期限は要件・サイトに合わせて設定でOKですが,元々のScrapyが若干強気の設定なので注意しましょう.
「ひととWebにやさしく」しましょう!
Spider(クローラー本体)について〜Itemも添えて
取得先のURLルールを把握した上で作成しています.
なお,取得メソッドはxpathです.
Spider...の前にItem(構造体)
items.pyにあります.
投打共に野球用語の英語略称を用いてます,コメントともに見てもらえれば.*4
# -*- coding: utf-8 -*- # Define here the models for your scraped items # # See documentation in: # http://doc.scrapy.org/en/latest/topics/items.html from scrapy import Item, Field class BatterItem(Item): year = Field() # 年度 team = Field() # チーム name = Field() # 名前 bat = Field() # 右打ち or 左打ち or 両打ち games = Field() # 試合数 pa = Field() # 打席数 ab = Field() # 打数 r = Field() # 得点 h = Field() # 安打 double = Field() # 二塁打 triple = Field() # 三塁打 hr = Field() # 本塁打 tb = Field() # 塁打 rbi = Field() # 打点 sb = Field() # 盗塁 cs = Field() # 盗塁死 sh = Field() # 犠打(バント) sf = Field() # 犠飛(犠牲フライ) bb = Field() # 四球 ibb = Field() # 故意四球(敬遠) hbp = Field() # 死球(デットボール) so = Field() # 三振 dp = Field() # 併殺 ba = Field() # 打率 slg = Field() # 長打率 obp = Field() # 出塁率 class PitcherItem(Item): year = Field() # 年度 team = Field() # チーム name = Field() # 名前 throw = Field() # 右投げ or 左投げ games = Field() # 登板数 w = Field() # 勝利 l = Field() # 敗北 sv = Field() # セーブ hld = Field() # ホールド hp = Field() # HP(ホールドポイント cg = Field() # 完投 sho = Field() # 完封 non_bb = Field() # 無四球 w_per = Field() # 勝率 bf = Field() # 打者 ip = Field() # 投球回 h = Field() # 被安打 hr = Field() # 被本塁打 bb = Field() # 与四球 ibb = Field() # 故意四球(敬遠) hbp = Field() # 与死球 so = Field() # 三振 wp = Field() # 暴投 bk = Field() # ボーク r = Field() # 失点 er = Field() # 自責点 era = Field() # 防御率
batter.py(打者成績)
一つのtableタグに入ってるので愚直にtrとtdを得る感じでグイグイ回して取得しています.
# -*- coding: utf-8 -*- import scrapy from baseball.items import BatterItem from baseball.spiders import TEAMS, LEAGUE_TOP, BAT_RIGHT, BAT_LEFT, BAT_SWITCH from baseball.spiders import BaseballSpidersUtil as Util class BatterSpider(scrapy.Spider): name = 'batter' allowed_domains = ['npb.jp'] URL_TEMPLATE = 'http://npb.jp/bis/{year}/stats/idb{league}_{team}.html' def __init__(self, year=2017, league=LEAGUE_TOP): """ 初期処理(年度とURLの設定) :param year: シーズン年 :param league: 1 or 2(1軍もしくは2軍) """ self.year = year # URLリスト(12球団) self.start_urls = [self.URL_TEMPLATE.format(year=year, league=league, team=t) for t in TEAMS] def parse(self, response): """ 選手一人分の打撃成績 :param response: 取得した結果(Response) :return: 打撃成績 """ for tr in response.xpath('//*[@id="stdivmaintbl"]/table').xpath('tr'): item = BatterItem() if not tr.xpath('td[2]/text()').extract_first(): continue item['year'] = self.year item['team'] = Util.get_team(response.url) item['bat'] = self._get_bat(Util.get_text(tr.xpath('td[1]/text()').extract_first())) item['name'] = Util.get_text(tr.xpath('td[2]/text()').extract_first()) item['games'] = Util.text2digit(tr.xpath('td[3]/text()').extract_first(), digit_type=int) item['pa'] = Util.text2digit(tr.xpath('td[4]/text()').extract_first(), digit_type=int) item['ab'] = Util.text2digit(tr.xpath('td[5]/text()').extract_first(), digit_type=int) item['r'] = Util.text2digit(tr.xpath('td[6]/text()').extract_first(), digit_type=int) item['h'] = Util.text2digit(tr.xpath('td[7]/text()').extract_first(), digit_type=int) item['double'] = Util.text2digit(tr.xpath('td[8]/text()').extract_first(), digit_type=int) item['triple'] = Util.text2digit(tr.xpath('td[9]/text()').extract_first(), digit_type=int) item['hr'] = Util.text2digit(tr.xpath('td[10]/text()').extract_first(), digit_type=int) item['tb'] = Util.text2digit(tr.xpath('td[11]/text()').extract_first(), digit_type=int) item['rbi'] = Util.text2digit(tr.xpath('td[12]/text()').extract_first(), digit_type=int) item['sb'] = Util.text2digit(tr.xpath('td[13]/text()').extract_first(), digit_type=int) item['cs'] = Util.text2digit(tr.xpath('td[14]/text()').extract_first(), digit_type=int) item['sh'] = Util.text2digit(tr.xpath('td[15]/text()').extract_first(), digit_type=int) item['sf'] = Util.text2digit(tr.xpath('td[16]/text()').extract_first(), digit_type=int) item['bb'] = Util.text2digit(tr.xpath('td[17]/text()').extract_first(), digit_type=int) item['ibb'] = Util.text2digit(tr.xpath('td[18]/text()').extract_first(), digit_type=int) item['hbp'] = Util.text2digit(tr.xpath('td[19]/text()').extract_first(), digit_type=int) item['so'] = Util.text2digit(tr.xpath('td[20]/text()').extract_first(), digit_type=int) item['dp'] = Util.text2digit(tr.xpath('td[21]/text()').extract_first(), digit_type=int) item['ba'] = Util.text2digit(tr.xpath('td[22]/text()').extract_first(), digit_type=float) item['slg'] = Util.text2digit(tr.xpath('td[23]/text()').extract_first(), digit_type=float) item['obp'] = Util.text2digit(tr.xpath('td[24]/text()').extract_first(), digit_type=float) yield item def _get_bat(self, text): """ 右打ち or 左打ち or 両打ち :param text: テキスト :return: 右打ち or 左打ち or 両打ち """ if text == '*': return BAT_LEFT elif text == '+': return BAT_SWITCH return BAT_RIGHT
結構愚直に泥臭く書いています笑
なお,ところどころでリーグやチームの設定,数値変換などが登場するのでこれらについてはspiderパッケージ直下に便利ライブラリを作っています.
# baseball/spider/__init__.pyに書いてます # 打撃・投球で共通して使う定義 LEAGUE_TOP = 1 # 一軍 LEAGUE_MINOR = 2 # 二軍 THROW_RIGHT = 'R' # 右投げ THROW_LEFT = 'L' # 左投げ BAT_RIGHT = 'R' # 右打ち BAT_LEFT = 'L' # 左打ち BAT_SWITCH = 'T' # 両打ち TEAMS = { 'f': 'fighters', 'h': 'hawks', 'm': 'marines', 'l': 'lions', 'e': 'eagles', 'bs': 'buffalos', 'c': 'carp', 'g': 'giants', 'db': 'baystars', 's': 'swallows', 't': 'tigers', 'd': 'dragons', } class BaseballSpidersUtil: @classmethod def get_team(cls, url): try: return TEAMS.get(url.replace('.html', '').split('_')[-1], 'Unknown') except IndexError: return 'Unknown' @classmethod def get_text(cls, text): """ テキスト取得(ゴミは取り除く) :param text: text :return: str """ if not text: return '' return text.replace('\u3000', ' ') @classmethod def text2digit(cls, text, digit_type=int): """ 数値にキャストする(例外時はゼロを返す) :param text: text :param digit_type: digit type(default:int) :return: str """ if not text: return digit_type(0) try: return digit_type(text) except ValueError as e: print("変換に失敗しているよ:{}".format(e)) return digit_type(0)
pitcher.py(投手成績)
打撃成績とほぼ同じ.
なお,投球回(ip)は小数点以下でカラムが別れるという謎仕様だったので真ん中ら辺のコードが若干面白いことになっています笑
# -*- coding: utf-8 -*- import scrapy from baseball.items import PitcherItem from baseball.spiders import TEAMS, LEAGUE_TOP, THROW_RIGHT, THROW_LEFT from baseball.spiders import BaseballSpidersUtil as Util class PitcherSpider(scrapy.Spider): name = 'pitcher' allowed_domains = ['npb.jp'] allowed_domains = ['npb.jp'] URL_TEMPLATE = 'http://npb.jp/bis/{year}/stats/idp{league}_{team}.html' def __init__(self, year=2017, league=LEAGUE_TOP): """ 初期処理(年度とURLの設定) :param year: シーズン年 :param league: 1 or 2(1軍もしくは2軍) """ self.year = year # URLリスト(12球団) self.start_urls = [self.URL_TEMPLATE.format(year=year, league=league, team=t) for t in TEAMS] def parse(self, response): """ 選手一人分の投球成績 :param response: 取得した結果(Response) :return: 投球成績 """ for tr in response.xpath('//*[@id="stdivmaintbl"]/table').xpath('tr'): item = PitcherItem() if not tr.xpath('td[3]/text()').extract_first(): continue item['year'] = self.year item['team'] = Util.get_team(response.url) item['name'] = Util.get_text(tr.xpath('td[2]/text()').extract_first()) item['throw'] = self._get_throw(Util.get_text(tr.xpath('td[1]/text()').extract_first())) item['games'] = Util.text2digit(tr.xpath('td[3]/text()').extract_first(), digit_type=int) item['w'] = Util.text2digit(tr.xpath('td[4]/text()').extract_first(), digit_type=int) item['l'] = Util.text2digit(tr.xpath('td[5]/text()').extract_first(), digit_type=int) item['sv'] = Util.text2digit(tr.xpath('td[6]/text()').extract_first(), digit_type=int) item['hld'] = Util.text2digit(tr.xpath('td[7]/text()').extract_first(), digit_type=int) item['hp'] = Util.text2digit(tr.xpath('td[8]/text()').extract_first(), digit_type=int) item['cg'] = Util.text2digit(tr.xpath('td[9]/text()').extract_first(), digit_type=int) item['sho'] = Util.text2digit(tr.xpath('td[10]/text()').extract_first(), digit_type=int) item['non_bb'] = Util.text2digit(tr.xpath('td[11]/text()').extract_first(), digit_type=int) item['w_per'] = Util.text2digit(tr.xpath('td[12]/text()').extract_first(), digit_type=float) item['bf'] = Util.text2digit(tr.xpath('td[13]/text()').extract_first(), digit_type=int) # 小数点以下が別のカラムに入ってるのでこういう感じになります if tr.xpath('td[15]/text()').extract_first(): ip = tr.xpath('td[14]/text()').extract_first() + tr.xpath('td[15]/text()').extract_first() else: ip = tr.xpath('td[14]/text()').extract_first() item['ip'] = Util.text2digit(ip, digit_type=float) item['h'] = Util.text2digit(tr.xpath('td[16]/text()').extract_first(), digit_type=int) item['hr'] = Util.text2digit(tr.xpath('td[17]/text()').extract_first(), digit_type=int) item['bb'] = Util.text2digit(tr.xpath('td[18]/text()').extract_first(), digit_type=int) item['ibb'] = Util.text2digit(tr.xpath('td[19]/text()').extract_first(), digit_type=int) item['hbp'] = Util.text2digit(tr.xpath('td[20]/text()').extract_first(), digit_type=int) item['so'] = Util.text2digit(tr.xpath('td[21]/text()').extract_first(), digit_type=int) item['wp'] = Util.text2digit(tr.xpath('td[22]/text()').extract_first(), digit_type=int) item['bk'] = Util.text2digit(tr.xpath('td[23]/text()').extract_first(), digit_type=int) item['r'] = Util.text2digit(tr.xpath('td[24]/text()').extract_first(), digit_type=int) item['er'] = Util.text2digit(tr.xpath('td[25]/text()').extract_first(), digit_type=int) item['era'] = Util.text2digit(tr.xpath('td[26]/text()').extract_first(), digit_type=float) yield item def _get_throw(self, text): """ 右投げもしくは左投げか :param text: テキスト :return: 右投げ or 左投げ """ if text == '*': return THROW_LEFT return THROW_RIGHT
pipelines.py(データ保存)について
spiderが生成したItemをもらってデータとして保存する処理を記述しています.
SQLite3はPython標準で使えるので追加ライブラリは不要です.*5
実装のポイントは
- 初期処理でTable生成の有無を判断
- spiderの名前でInsert先のTableを振り分け
です.
本来的にはスキーマ定義(Create Table)は別のクラスに書くべきでしょうが,今回はサンプルとしての見通しを良くするためにあえて同じファイルに書いています.
# -*- coding: utf-8 -*- # Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html import sqlite3 from scrapy.exceptions import DropItem class BaseballPipeline(object): CREATE_TABLE_BATTER =""" CREATE TABLE batter ( id integer primary key, year integer, name text , team text , bat text , games integer , pa integer , ab integer , r integer , h integer , double integer , triple integer , hr integer , tb integer , rbi integer , so integer , bb integer , ibb integer , hbp integer , sh integer , sf integer , sb integer , cs integer , dp integer , ba real , slg real , obp real, create_date date, update_date date ) """ CREATE_TABLE_PITCHER =""" CREATE TABLE pitcher ( id integer primary key, year integer, name text , team text , throw text , games integer , w integer , l integer , sv integer , hld integer , hp integer , cg integer , sho integer , non_bb integer , w_per real , bf integer , ip real , h integer , hr integer , bb integer , ibb integer , hbp integer , so integer , wp integer , bk integer , r integer , er integer , era real , create_date date, update_date date ) """ INSERT_BATTER = """ insert into batter( year, name, team, bat, games, pa, ab, r, h, double, triple, hr, tb, rbi, so, bb, ibb, hbp, sh, sf, sb, cs, dp, ba, slg, obp, create_date, update_date ) values( ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, datetime('now', 'localtime'), datetime('now', 'localtime') ) """ INSERT_PITCHER = """ insert into pitcher( year, name, team, throw, games, w, l, sv, hld, hp, cg, sho, non_bb, w_per, bf, ip, h, hr, bb, ibb, hbp, so, wp, bk, r, er, era, create_date, update_date ) values( ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, datetime('now', 'localtime'), datetime('now', 'localtime') ) """ DATABASE_NAME = 'baseball.db' conn = None def __init__(self): """ Tableの有無をチェック,無ければ作る """ conn = sqlite3.connect(self.DATABASE_NAME) if conn.execute("select count(*) from sqlite_master where name='batter'").fetchone()[0] == 0: conn.execute(self.CREATE_TABLE_BATTER) if conn.execute("select count(*) from sqlite_master where name='pitcher'").fetchone()[0] == 0: conn.execute(self.CREATE_TABLE_PITCHER) conn.close() def open_spider(self, spider): """ 初期処理(DBを開く) :param spider: ScrapyのSpiderオブジェクト """ self.conn = sqlite3.connect(self.DATABASE_NAME) def process_item(self, item, spider): """ 成績をSQLite3に保存 :param item: Itemの名前 :param spider: ScrapyのSpiderオブジェクト :return: Item """ # Spiderの名前で投入先のテーブルを判断 if spider.name == 'batter': # 打者成績 self.conn.execute(self.INSERT_BATTER,( item['year'], item['name'], item['team'], item['bat'], item['games'], item['pa'], item['ab'], item['r'], item['h'], item['double'], item['triple'], item['hr'], item['tb'], item['rbi'], item['so'], item['bb'], item['ibb'], item['hbp'], item['sh'], item['sf'], item['sb'], item['cs'], item['dp'], item['ba'], item['slg'], item['obp'], )) elif spider.name == 'pitcher': # 投手成績 self.conn.execute(self.INSERT_PITCHER,( item['year'], item['name'], item['team'], item['throw'], item['games'], item['w'], item['l'], item['sv'], item['hld'], item['hp'], item['cg'], item['sho'], item['non_bb'], item['w_per'], item['bf'], item['ip'], item['h'], item['hr'], item['bb'], item['ibb'], item['hbp'], item['so'], item['wp'], item['bk'], item['r'], item['er'], item['era'], )) else: raise DropItem('spider not found') self.conn.commit() return item def close_spider(self, spider): """ 終了処理(DBを閉じる) :param spider: ScrapyのSpiderオブジェクト """ self.conn.close()
これからスクレイピングしてみよう!という方へ
まずは本やサンプルの写経がベストかなと思います.
今回紹介した野球スクレイピングについても自由にpullしたりダウンロードしたりforkしたりしてお試しで使って好きなサイトを(迷惑を書けない程度に)クローリングしてもらればと思います.
その上で,スクレイピングをやりたい方は是非,
- Webの基本的なルールを学ぶ(HTML・Javascript・http・サーバーのしくみetc...)
- スクレイピングの手段(今回はPythonとScrapy)をしっかり学んで自分の手足とする
- スクレイピングする目的とゴールを明確に
という観点でぜひぜひ学んでみては如何でしょうか?
...という提案でこの件は一旦終わります.
次回予告
実際に取得した野球データで何が出来るか?を,
- Jupyter
- pandas
- matplotlib
あたりでお料理してみたいと思います.
- 作者: 池内孝啓,片柳薫子,岩尾エマはるか,@driller
- 出版社/メーカー: 技術評論社
- 発売日: 2017/09/09
- メディア: 大型本
- この商品を含むブログ (1件) を見る
ついにこの本の出番かな...wkwk