Pythonでスクレイピング(メモ10:n番目指定・思わぬ落とし穴)

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&#8546;</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=”centerBGCOLOR=”#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年途中以前の結果ページに対処していきます。

Pythonでスクレイピング(メモ11:タグの属性表記の違い色々)

シェアする

  • このエントリーをはてなブックマークに追加

フォローする