←Pythonでスクレイピング(メモ7:色々なソースに対応する)
→Pythonでスクレイピング(メモ9:文字列の置換とリスト作成)
こんばんは。
Pythonでスクレイピング奮闘メモです。
今回は、ソースのタグが同一でそこからの絞り切りが出来ない事案に対してどう対応したかをまとめました。
ただし、各要素が循環している場合のみの対処法になります。
<この記事の目次>
・前回の問題点おさらい・・・ソースのタグが同一で区別が付けられない
・for文で数列っぽく対処・・・(循環していれば)for文を使って必要な情報のみ取り出せる
・len()でリストの長さを取得・・・「IndexError: list index out of range」への対処
・int()で小数値を整数値に変換・・・「TypeError: ‘float’ object cannot be interpreted as an integer」への対処
・.append()でリストに要素を追加・・・for文中で次々に要素をリストに追加したい
※動作環境
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ページ以降中心)
以下試行錯誤メモ。
前回の問題点おさらい
以下のソースにおいて、<tr>タグ内一番下の<td align=”center” nowrap class=”gray12“>のみスクレイプしたい。(元のページ:2017年1月5日開催 日刊スポーツ賞中山金杯:http://www.jra.go.jp/datafile/seiseki/replay/2017/001.html)
<tr bgcolor="#FFFFFF"> <td align="center" nowrap class="gray12"> 1</td> <td align="center"><img src="/JRADB/img/keiba/waku3.gif" alt="3" width="16" height="22"></td> <td align="center" class="gray12"> 3</td> <td align="left" class="gray12"> ツクバアズマオー</td> <td align="center" class="gray12">牡6</td> <td align="center" class="gray12">56.5</td> <td width="11" align="center" class="gray12"></td> <td width="75" align="left" class="gray12"> 吉田豊</td> <td align="center" class="gray12">2:00.6</td> <td align="left" class="gray12"> </td> <td align="center" class="gray12">35.6</td> <td width="39" align="center" class="gray12">480</td> <td width="40" align="center" class="gray12"> 0</td> <td align="left" class="gray13"> 尾形充弘</td> <td align="center" nowrap class="gray12">1</td> </tr>
そこで以下のようにalign=”center”を指定してみたが(今回はCSSを使用)・・・
In [5]: keiba = response.css('td[align="center"].gray12::text') In [6]: print(keiba) [・・・(不要な情報8つ)・・・, '1'(欲しい情報), ・・・(不要な情報8つ)・・・, '6'(欲しい情報), ・・・(以後同様)・・・, '13'(欲しい情報)]
直接指定する方法も調べて試したが解決には至らず、というよりよく分からず。
別の方法で解決の道へ進むことに。
自分は「同じことの繰り返し(循環)」になっている点に着目しました。
for文を使えば欲しい情報のみ得られるのではないか、と。
for文で数列っぽく対処
同じことが繰り返されているので、始めを0番目としてほしい方法は8→17→26→・・・番目にあるので、for文を用いて以下のコードでやってみました。
In [16]: for a in range(1,18): ←出馬数は最大18なのでとりあえず ...: b = a * 9 - 1 ←欲しい情報は(9a-1)番目にある ...: print(keiba[b].extract()) ←出力 1 6 4 9 11 12 5 8 2 3 10 7 13 --------------------------------------------------------------------------- IndexError ・・・(いろいろ)・・・ IndexError: list index out of range
途中までは良かったみたいですが、デバッグしてるページは出馬数が13頭で、その先が無いためにErrorをはじいたみたいです。
であれば、ある時だけ出力するようにif文を使えばどうだ!
In [17]: for a in range(1,18): ←出馬数は最大18なのでとりあえず ...: b = a * 9 - 1 ←欲しい情報は(9a-1)番目にある ...: if keiba[b]: ←あるときだけ以下を実行 ...: print(keiba[b].extract()) ←出力 1 6 4 9 11 12 5 8 2 3 10 7 13 --------------------------------------------------------------------------- IndexError ・・・(いろいろ)・・・ 1 for a in range(1,18): 2 b = a * 9 - 1 ----> 3 if keiba[b]: 4 print(keiba[b].extract()) ・・・(いろいろ)・・・ IndexError: list index out of range
と思いましたが、Error詳細をよく見るとkeiba[b]の時点でb番目が無いなら駄目みたいです。
len()でリストの長さを取得
そこでいつものGoogle検索。
「IndexError: list index out of range」とそのまま検索したら一番上に出てきたページ(http://blog.setunai.net/20120807/indexerror-list-index-out-of-range/)で速攻解決。
配列の長さを「len()」で取得すれば良いとのこと。
なるほど、ということで早速使ってみる。
In [18]: c = len(keiba)/9 ←「keiba」のリストの長さ117を9等分 ...: for a in range(1,c): ←出馬数は最大c ...: b = a * 9 - 1 ←欲しい情報は(9a-1)番目にある ...: print(keiba[b].extract()) ←出力 ・・・(いろいろ)・・・ TypeError: 'float' object cannot be interpreted as an integer
新たなErrorが出現。
うん何となく分かる。
確か’float’っていうのは小数のことだったはず。
だからrange()で使うには’c’の値を’integer’つまり整数値に直さなければならない。
int()で小数値を整数値に変換
いつものように、「TypeError: ‘float’ object cannot be interpreted as an integer」とそのまま検索。
トップで出てきたページ(https://ameblo.jp/taktak0/entry-12296943771.html)でこれまた解決。
「int()」を使いなさい、と。
はい分かりました。
In [19]: c = int(len(keiba)/9) ←「keiba」のリストの長さ117を9等分した数値を整数値に変換 ...: for a in range(1,c): ←出馬数は最大c ...: b = a * 9 - 1 ←欲しい情報は(9a-1)番目にある ...: print(keiba[b].extract()) ←出力 1 6 4 9 11 12 5 8 2 3 10 7
一つ足らないけど(’c’の値を+1すれば)完璧!
ということでクロールファイルのコードに挿入!
(クロール先でのスクレイプに関する記述部分の抜粋) keiba2 = response.css('td[align="center"].gray12::text') ←欲しい情報を含む場所 l = len(keiba2) ←長さを取得 a = l/9 + 1 ←繰り返しの上限を計算 for b in range(1,int(a)): ←上限値を整数値に変換 c = b * 9 - 1 ←欲しい情報は(9b-1)番目にある item['ninki'] = keiba2[c].extract() ←欲しい情報を'ninki'列へ
ところが実行してみると、Errorは出ずとも最後の項しか出力されない!
恐らくは上のような記述だとforによる繰り返しの中で絶えず上書きされているのだろう。
上書きされずに、またリストとして出力したいので、リストに繰り返し追加していけば良いはず。
.append()でリストに要素を追加
今までのもそうですが、参考文献に載ってた記憶があったので調べました。
普通に載ってました。
空のリスト(n = [])を用意して、n.append()でOK。
ってことで改良版。
(クロール先でのスクレイプに関する記述部分の抜粋) keiba2 = response.css('td[align="center"].gray12::text') l = len(keiba2) a = l/9 + 1 n = [] ←空のリストを用意 for b in range(1,int(a)): c = b * 9 - 1 n.append(keiba2[c].extract()) ←空のリストに欲しい情報を次々追加 item['ninki'] = n
これで見事成功しました!!
ようやく「単勝人気」の情報を手に入れる事ができました。
恐らく同じようにして「オッズ」の情報も手に入れられるはず。
ところがそこで新たに問題が発生するのでした・・・
それは次回のメモに回します。→Pythonでスクレイピング(メモ9:文字列の置換とリスト作成)
しかし今年からソースが変わっているとかイジメですよ・・・
以下はおまけ。
(おまけ)出力時の項目の順番の設定
項目が複数ある時のcsv出力時の順番がデフォルトだとアルファベット順っぽく、いじりたかったので「scrapy 出力 順番」で調べました。
するとトップではないがこのページ(https://qiita.com/iwostaq/items/afd6b2f77aed7fd6962b)に解決策がありました。
要はsettings.pyいじればOK。
FEED_EXPORT_FIELDS =['名前1','名前2','名前3',・・・]
(おまけ)連絡先の設定
色々調べている中で出てきたのがこのページ(https://sutaba-mac.site/scrapy-s2-settings-and-items/)
今更だが、もしもの時にスクレイピング先の管理者に向けて連絡先を設定する必要があるらしい。
USER_AGENT = “BOT_NAME (+youremailaddress@example.com)”
次回は新たな問題に挑戦します。