←前回「Pythonでスクレイピング(メモ4:スクレイプ試行)」
こんばんは。
Pythonでスクレイピングするまでの試行錯誤メモです。
今回は、あるページ内の全てのリンク先でスクレイピングを行う「クロール」奮闘メモです。
想定した通りに実行するようコードを書くのに大変苦労しました。
当初はクロールまでのメモのつもりが、下準備だけで大量になってしまいました笑
※動作環境
macOS High Sierra 10.13.6
プロセッサ 2.7 GHz Intel Core i5
メモリ 8 GB 1867 MHz DDR3
Python 3.6.0 :: Anaconda custom (x86_64)
コマンド実行は「ターミナル」、.py編集は「Xcode」を使用
※参考文献「Python エンジニア ファーストブック」(142ページ以降中心)
※参考URL
https://note.nkmk.me/python-scrapy-tutorial/
以下試行錯誤メモ。
リンク先でスクレイピング(クロール)させるコードを書く前に、そのリンク先でのスクレイピングコードをまずは確定させます。
つまりリンク先でのソースからhtmlのタグを指定します。
試行ページはhttp://www.jra.go.jp/datafile/seiseki/replay/2018/jyusyo.htmlのリンク先の一つであるhttp://www.jra.go.jp/datafile/seiseki/replay/2018/001.htmlでやってみました。
出走馬の単勝人気と最終オッズを知りたいので、そこに該当するソースを探す。
単勝人気に関する情報があるソース箇所は以下。
<table class="mainList"> ・・・ <tr class="oddRow"> <td class="chakuCol"> 1</td> <td class="wakuCol"><img src="/JRADB/img/keiba/waku3.gif" alt="枠3赤" class="iconWakuImage"></td> <td class="umabanCol"> 6</td> <td class="umameiCol">セダブリランテス</td> <td class="seireiCol">牡4</td> <td class="hutanCol">55.0</td> <td class="kigouCol"></td> <td class="jocCol"> 戸崎圭太</td> ・・・(時間などの情報) <td class="ninkiCol">1</td> </tr> <tr class="evenRow"> <td class="chakuCol"> 2</td> ・・・(先と同様の情報) <td class="ninkiCol">2</td> </tr> ・・・(3着以降の情報) </table>
とりあえず<td class=”ninkiCol“>に単勝人気がある。
(単勝人気だけなら)それを含むタグは<table class=”mainList“>で良いですかね。
最終オッズに関する情報があるソース箇所は以下。
<div class="haraimodoshiListTitle">払戻金</div> <div class="haraimodoshiOutDiv"> <div class="haraimodoshiCellDiv"> <table class="haraimodoshiList"> <tr> <th scope="row">単勝</th> <td class="umabanCol singleCell">6</td> <td class="haraimodoshiCol singleCell">250円</td> <td class="ninkiCol singleCell">1番人気</td> </tr> <tr> <th rowspan="3" scope="row">複勝</th> <td class="umabanCol compositeCellTop">6</td> <td class="haraimodoshiCol compositeCellTop">130円</td> <td class="ninkiCol compositeCellTop">1番人気</td> </tr> ・・・(他のオッズ情報) </table> </div> </div>
オッズは<td class=”haraimodoshiCol compositeCellTop“>の中にある。
(オッズだけなら)それを含むタグは<div class=”haraimodoshiOutDiv“>で良さそうです。
ただ人気もオッズも両方同時に欲しい。
人気の方は厳密には<tr class=”oddRow“>ですが、<tr>で一度試してみました。
お試しはscrapy shell <URL>でしたね!→メモ4:スクレイピング試行
In [1]: keiba = response.css('tr') In [2]: ninki = keiba.css('td.ninkiCol::text').extract_first() In [3]: odds = keiba.css('td.haraimodoshiCol::text').extract_first() In [4]: print([ninki, odds]) ['1', '250円']
あれ?駄目だったはずなんだが・・・
実はこのメモは事後で書いているので、記憶をたどってメモしてるんですが当時は出来なかったような・・・
.extract_first()のfirstは始めのやつのみなので、取り払って改めてやってみる。
In [5]: ninki = keiba.css('td.ninkiCol::text').extract() In [6]: odds = keiba.css('td.haraimodoshiCol::text').extract() In [7]: print([ninki, odds]) [['1', '2', '10', '4', '3', '9', '6', '14', '15', '5', '12', '7', '13', '8', '11', '17', '16', '1番人気', '1番人気', '2番人気', '10番人気', '1番人気', '1番人気', '1番人気', '13番人気', '19番人気', '1番人気', '13番人気', '38番人気'], ['250円', '130円', '170円', '540円', '540円', '680円', '310円', '1,380円', '1,750円', '1,100円', '5,270円', '18,170円']]
余計な情報があるけどまあいけるな・・・記憶違いだったか?
念の為以下のようなコードを書いた.pyでやってみる。
# -*- coding: utf-8 -*- import scrapy class Keiba2Spider(scrapy.Spider): name = 'keiba2' allowed_domains = ['jra.go.jp'] start_urls = ['http://www.jra.go.jp/datafile/seiseki/replay/2018/001.html'] def parse(self, response): for keiba2 in response.css('tr'): ninki = keiba2.css('td.ninkiCol::text').extract() odds = keiba2.css('td.haraimodoshiCol::text').extract() print([ninki, odds])
これを実行した結果・・・
[['38番人気'], ['18,170円']]
そうだ!確か最後しか得られなくて困ってたんだった!
ただ今更気づいてしまったんだが、この要因はprint([ninki, odds])の位置ではないのか?
printがfor文の中にないからでは?
ということで先程のコードのprintをtabキーで修正して再度実行した結果・・・
[[], []] [[], []] [[], []] [[], []] [['1'], []] [['2'], []] [['10'], []] [['4'], []] [['3'], []] [['9'], []] [['6'], []] [['14'], []] [['15'], []] [['5'], []] [['12'], []] [['7'], []] [['13'], []] [['8'], []] [['11'], []] [['17'], []] [['16'], []] [[], []] [[], []] [[], []] [[], []] [[], []] [[], []] [['1番人気'], ['250円']] [['1番人気'], ['130円']] [['2番人気'], ['170円']] [['10番人気'], ['540円']] [['1番人気'], ['540円']] [['1番人気'], ['680円']] [['1番人気'], ['310円']] [['13番人気'], ['1,380円']] [['19番人気'], ['1,750円']] [['1番人気'], ['1,100円']] [['13番人気'], ['5,270円']] [['38番人気'], ['18,170円']]
いらない情報も多いけどいけた笑
ただいらない情報は無駄なので改善しましょう。
要は欲しい場所意外にも<tr>タグがあるわけで、タグの指定の仕方によってはそもそもfor文でループする必要も無いんじゃないかと。
ならもっと大きく包含するタグ、<body>にしてはどうでしょう。
ってことで改善したコードが以下。
import scrapy from keiba_scraper.items2 import KeibaScraperItem class Keiba2Spider(scrapy.Spider): name = 'keiba2' allowed_domains = ['jra.go.jp'] start_urls = ['http://www.jra.go.jp/datafile/seiseki/replay/2018/001.html'] def parse(self, response): keiba2 = response.css('body') item = KeibaScraperItem() item['ninki'] = keiba2.css('td.ninkiCol::text').extract() item['odds'] = keiba2.css('td.haraimodoshiCol::text').extract() yield item
実行した結果・・・
{'ninki': ['1', '2', '10', '4', '3', '9', '6', '14', '15', '5', '12', '7', '13', '8', '11', '17', '16', '1番人気', '1番人気', '2番人気', '10番人気', '1番人気', '1番人気', '1番人気', '13番人気', '19番人気', '1番人気', '13番人気', '38番人気'], 'odds': ['250円', '130円', '170円', '540円', '540円', '680円', '310円', '1,380円', '1,750円', '1,100円', '5,270円', '18,170円']}
いけましたね。
さらに「〇〇番人気」はオッズの人気順なんですが要らないので更に改善を。
さすがに<body>タグは大きく取りすぎているのと、単勝人気とオッズを別々に分ければどうか。
先程のソースも踏まえて改善したコード(一部略)が以下。
(前は先と同様) def parse(self, response): keiba = response.css('table.mainList') keiba2 = response.css('div.haraimodoshiOutDiv') item = KeibaScraperItem() item['ninki'] = keiba.css('td.ninkiCol::text').extract() item['odds'] = keiba2.css('td.haraimodoshiCol::text').extract() yield item
実行した結果・・・成功!!
先ほどの結果から「〇〇番人気」だけ取り除けました。
これでようやく、リンク先でのスクレイピングコードが完成しました。
次回は(本来は今回メモする予定だった)このコードを各リンク先で実行する、つまりクロールするようコードを作っていきます。