←Pythonでスクレイピング(メモ9:文字列の置換とリスト作成)
→Pythonでスクレイピング(メモ11:タグの属性表記の違い色々)
こんばんは。
Pythonでスクレイピング奮闘メモです。
今回は.replace()を使用時のError対応と、タグのn番目指定についてまとめました。
<この記事の目次>
・新たな問題が発生・・・古いページはソースが微妙に違う
・タグのn番目指定・・・:nth-child(n)(nは任意の正の整数)を使う
・タグ指定はなるべく絞る・・・思わぬ箇所に指定タグがあるかもしれない
・Noneに対して.replace()は無効・・・if文で対処すればOK
※動作環境
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ページ以降中心)
以下試行錯誤メモ。
新たな問題が発生
JRAの重賞一覧ページから各重賞結果ページにクロールして、単勝人気順と最終オッズをスクレイピングするコードを作成していました。
結果ページのソースが2017年の途中から変わっていたため、それの対応を頑張りました。
前回ようやく完了したんですが、念の為さらに遡って調べると・・・
①2015年以前の重賞一覧ページ
②2006年途中以前の結果ページ
以上のソースが違いました。
①2015年以前の重賞一覧ページ
重賞一覧ページでスクレイピングしたい情報は「日付・レース名・競馬場・リンク先」の4つです。
2018年の重賞一覧ページのソース(一部)。
<tr> <td class="date">1月6日<span class="sat">土曜</span></td> <td class="race"><span class="grade_icon g3">GⅢ</span>中山金杯</td> <td class="place">中山</td> ・・・(距離などの情報)・・・ <td class="result"><a href="/datafile/seiseki/replay/2018/001.html" class="btn-def btn-xs"><i class="fa fa-chevron-circle-right" aria-hidden="true"></i>レース結果</a></td> </tr>
データごとに分かりやすいclass設定(太字)がされていてとても楽でした。
2015年の重賞一覧ページのソース(一部)。
<TR> <TD ALIGN="center" BGCOLOR="#e4e4db" CLASS="gray12">1/4<BR>(<FONT COLOR="#ff0000">日</FONT>)</TD> <TD BGCOLOR="#e4e4db" CLASS="gray12"><SPAN CLASS="kaisaiBtn"><IMG SRC="../../img/g3_pict.gif" WIDTH="27" HEIGHT="11" ALIGN="absmiddle"></SPAN><B>中山金杯</B></TD> <TD ALIGN="center" BGCOLOR="#e4e4db" CLASS="gray12">中山</TD> ・・・(距離などの情報)・・・ <TD ALIGN="center" BGCOLOR="#e4e4db" CLASS="gray12"><A HREF="001.html"><IMG SRC="../../img/replay.gif" WIDTH="18" HEIGHT="18" BORDER="0"></A></TD> </TR>
タグの属性(ALIGN=”center” BGCOLOR=”#e4e4db“)もclass(CLASS=”gray12“)も同じ。
特に「日付」と「競馬場」の2つは完全一致。
とはいえ「日付」は1番目なので、.extract_first()で問題なし。
「競馬場」はn番目指定できればOK(知らないので調べる必要あり)。
また「リンク先」は<a>タグを含む<td>タグは一つしか無いので問題なし。
「レース名」は一部\u3000が混じっていたので、.replace()を用いて取り除く必要がありました。
この作業に若干苦戦しました。
今回は「競馬場」と「レース名」解決までのメモがメインです。
日付とリンク先に関しては、「メモ7:色々なソースに対応する」での属性等の指定方法が活きました。
②2006年途中以前の結果ページ
こちらは次回メモします。
→Pythonでスクレイピング(メモ11:タグの属性表記の違い色々)
タグのn番目指定
一旦デバッグで確かめます。
In [1]: keiba = response.css('tr') In [2]: place = keiba.css('td[align="center"].gray12::text').extract() In [3]: print(place) ['1/4', '\r\n(', ')', '中山', '4歳以上', '芝2,000m', '1/4', '\r\n(', ')', '京都', '4歳以上', '芝1,600m', '1/11', ・・・(以後繰り返し)]
「日付→(→)→競馬場→性齢→コース」の繰り返しになっています。
ソース的には<tr>タグ内の3番目の<td>タグに競馬場の情報があります。
グーグル先生に聞いて一番分かりやすかったページ:https://qiita.com/ituki_b/items/62a752389385de7ba4a2
:nth-child(n)で良いそう。一度試すと・・・
In [31]: place = keiba.css('td[align="center"].gray12:nth-child(3)::text').extract() In [32]: print(place) ['中山', '京都', '京都', '中山', '中山', '京都', ・・・]
成功!念の為、for文でも通用するか確認。
In [74]: for keiba in response.css('tr'): ...: place = keiba.css('td[align="center"].gray12:nth-of-type(3)::text') ...: .extract_first() ...: print(place) 中山 None None ・・・謎のNoneの連続・・・ 中山 京都 京都 中山 中山 京都 京都 中山 中京 ・・・(欲しい情報)・・・
初めの方が謎ですが、当時はよく分からなかったので無視していました笑
.pyにコードを書いてスクレイピングしたら上手くいったので笑
タグ指定はなるべく絞る
このメモは思い出しながら書いているので、もう一度考え直しました。
そもそも初めの「中山」ってなんだ・・・
とりあえず<tr>タグをソースの中から探してみることに。
すると初めの方にありました、ほぼページ全体を含む<tr>タグが笑
なるほど.css(‘tr’)では大雑把過ぎたわけです。
ソースを改めて確認すると、重賞に関する情報が載っている<tr>タグは全て、<TABLE CELLSPACING=1 CELLPADDING=3 WIDTH=”620“>タグに含まれています。
そこでコードを以下のように改善。
※数字は必ず「”」で挟む
In [81]: jusho = response.css('table[cellspacing="1"][cellpadding="3"][width="620"]') ...: for keiba in jusho.css('tr'): ...: place = keiba.css('td[align="center"].gray12:nth-of-type(3)::text').extract_first() ...: print(place) None 中山 京都 京都 中山 ・・・(以後続く)
上手くいきました。
初めのNoneは表の説明欄の<tr>タグがあるためで、ほっといても良いし、if文を使えば無くせます(次の話題にもあります)。
このように絞れるのなら可能な限り絞るべきですね。
Noneに対して.replace()は無効
当時は先程の対処が出来なかったため、レース名でも同様の現象が起きていました。
In [83]: for keiba in response.css('tr'): ...: race = keiba.css('td.gray12 b::text').extract_first() ...: print(race) 中山金杯 None None ・・・(謎のNone)・・・ 中山金杯 京都金杯 シンザン記念 フェアリーS ・・・(以後続く)
ですが当時はそちらの問題よりも\u3000による空白を消す(レース名の左)ことに夢中でした。
取り除き方は前回学んでいたので、早速.replace()を使ってみると・・・
※リストに対しては.replace()は通用しないので、.extract_first()でなければならないことに注意。
In [86]: for keiba in response.css('tr'): ...: race = keiba.css('td.gray12 b::text').extract_first() ...: race = race.replace('\u3000','') ...: print(race) 中山金杯 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-86-198ffc4d0d4b> in <module>() 1 for keiba in response.css('tr'): 2 race = keiba.css('td.gray12 b::text').extract_first() ----> 3 race = race.replace('\u3000','') 4 print(race) AttributeError: 'NoneType' object has no attribute 'replace'
Noneに対しては.replace()は使えないそうです。残念。
ならif文で対処しましょう。
In [87]: for keiba in response.css('tr'): ...: race = keiba.css('td.gray12 b::text').extract_first() ...: if race: ...: race = race.replace('\u3000','') ...: print(race) 中山金杯 中山金杯 中山金杯 京都金杯 シンザン記念 フェアリーS ・・・(以後続く)
これでOK。
厳密にはGIの箇所だけ空白(タグが微妙に違う)だったんですが、ここでもやはりif文を用いれば簡単に解決できることなので省略します。
ちなみにレース名も、先程と同じように<TABLE CELLSPACING=1 CELLPADDING=3 WIDTH=”620“>タグ内の<tr>タグに絞れば完璧です。
In [88]: jusho = response.css('table[cellspacing="1"][cellpadding="3"][width="620"]') ...: for keiba in jusho.css('tr'): ...: race = keiba.css('td.gray12 b::text').extract_first() ...: if race: ...: race = race.replace('\u3000','') ...: print(race)
まあ後の祭りというか、いまさら過ぎるんですが苦笑
以上で①2015年以前の重賞一覧ページの対処が完了。
次回は②2006年途中以前の結果ページに対処していきます。