質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Python 2.7

Python 2.7は2.xシリーズでは最後のメジャーバージョンです。Python3.1にある機能の多くが含まれています。

Q&A

解決済

2回答

2938閲覧

pythonとBeautifulsoupで競馬情報をスクレイピングする中で、連続して情報を取れず、苦慮しております

akakage13

総合スコア89

Python 2.7

Python 2.7は2.xシリーズでは最後のメジャーバージョンです。Python3.1にある機能の多くが含まれています。

0グッド

0クリップ

投稿2017/05/07 19:51

python2.7を用いて、netkeiba.comから競馬情報をスクレイピングしております。

python2.7

1# -*- coding:utf-8 -*- 2 3import urllib2 4import codecs 5import time 6from bs4 import BeautifulSoup 7 8f = codecs.open('hiyoko.csv', 'w', 'utf-8') 9 10url='http://db.netkeiba.com/horse/2013100690/' 11 12soup = BeautifulSoup(urllib2.urlopen(url).read(),"lxml") 13 14horse_name_tag=soup.find('div',{'class':'horse_title'}).find('h1') 15horse_name="".join([x for x in horse_name_tag.text if not x == u'\xa0' and not x == u'\n']) 16 17print horse_name.strip() 18cols = [horse_name] 19f.write(",".join(cols) + "\n") 20 21f.close()

上記のソースコードはうまく動きます。2013100690の馬の名前がプリントされます。

問題は、ここからさらにソースコードを改変して、2013100690から例えば、2013100699、

あわよくば、2013100000から2013199999までの馬の名前を入手出来ればと思っております。

そこで、下記のようなソースコードに改変したところ、うまく動きません。

python2.7

1 2# -*- coding:utf-8 -*- 3 4import urllib2 5import codecs 6import time 7from bs4 import BeautifulSoup 8 9f = codecs.open('hiyoko.csv', 'w', 'utf-8') 10 11url='http://db.netkeiba.com/horse/201310069{0}/' 12 13for i in xrange(1,9): 14 url = url.format( i ) 15 soup = BeautifulSoup(urllib2.urlopen(url).read(),"lxml") 16 time.sleep(1) 17 horse_name_tag=soup.find('div',{'class':'horse_title'}).find(('div',{'class':'horse_title'}).find('h1')) 18 horse_name="".join([x for x in horse_name_tag.text if not x == u'\xa0' and not x == u'\n']) 19 20 print horse_name.strip() 21 cols = [horse_name] 22 f.write(",".join(cols) + "\n") 23 24f.close()
url='http://db.netkeiba.com/horse/201310069{0}/'

上記のforの回し方の考え方、そのものが不適切ではないかと思い、いろいろ調べましたが、上手く動きません。

urlに変数を挿入して回すこと自体が、間違いなのでしょうか。苦慮しております。

改善方法も含めまして、先輩方の御教示、よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

長文になったのでまとめを最初に書いときます

  • findにタプルを渡してるのでエラーになってた

  • urlの変数を上書きしない

  • DOM要素の指定ミス(最初のdivタグの指定を2回行っていたため、h1が見つからずNoneが返ってた)

  • ページが変だったとき(馬の情報が空だったとき)はDOM要素が見つからずNoneが返るので、そのときはスキップ

以下、試行の変遷記録(笑)

最初に言っておくと、Python3しか書けないのでPython3で書きました。
連番をurlに埋め込んで使うのは良いと思いますが、ページが変なページで取得したものがNoneだったらエラーになってしまいます。
なのでifで回避すれば動くと思います。
以下のコードは取得のみなのでフォーマットを整えてcsv出力はしてません。

python

1# -*- coding:utf-8 -*- 2 3from urllib.request import urlopen 4import time 5from bs4 import BeautifulSoup 6 7url = 'http://db.netkeiba.com/horse/201310069{0}/' 8horse_name_list = [] 9 10for i in range(1, 99999): 11 soup = BeautifulSoup(urlopen(url.format(i)), "lxml") 12 time.sleep(5) 13 div = soup.find('div', class_='horse_title') 14 15 # divがNoneTypeObjectだったらcontinue 16 if div != None: 17 horse_name = div.h1.text 18 if horse_name != None: 19 horse_name_list.append(horse_name) 20 else: 21 continue 22 else: 23 print("取得できないから飛ばすよ") 24 continue 25 26print(horse_name_list) 27

求めているものと違ったら申し訳ありません。

追記:

2.7で質問にあるコードでいろいろやってみたのですが、なんかxrangeで返るイテレータをforで回してurlに入れ込んでもurlが変わらないみたいです。
ずっとhttp://db.netkeiba.com/horse/2013100691/のままなので同じものしか取得できませんでした。
2.7の仕様はよく分かってませんので、きっとなにかがPython3と違うのでしょう。
print i をするとちゃんと順番にたされた数字が返ってきてるのですが・・・
※このへんは変数urlを上書きしてるのが原因でした。一番下で言及します

なのでいろいろやってこうなりました。

python

1# -*- coding:utf-8 -*- 2 3import urllib2 4import codecs 5import time 6from bs4 import BeautifulSoup 7 8f = codecs.open('hiyoko.csv', 'w', 'utf-8') 9horse_name = "" 10 11for i in xrange(1, 10): 12 url = 'http://db.netkeiba.com/horse/201310069%d/' % i 13 soup = BeautifulSoup(urllib2.urlopen(url).read(),"lxml") 14 time.sleep(5) 15 horse_name_tag = soup.find('div',{'class':'horse_title'}) 16 if horse_name_tag != None: 17 horse_name_tag.find('h1') 18 horse_name = "".join([x for x in horse_name_tag.text if not x == u'\xa0' and not x == u'\n']) 19 print horse_name.strip() 20 cols = [horse_name] 21 f.write(",".join(cols) + "\n") 22 else: 23 continue 24 25f.close()

追記:

質問者様のコードでは

python

1 horse_name_tag=soup.find('div',{'class':'horse_title'}).find(('div',{'class':'horse_title'}).find('h1'))

の部分で、find関数にタプルを渡してしまっています。(丸括弧が多い)
この部分ですね。

python

1soup.find('div',{'class':'horse_title'}).find( ('div',{'class':'horse_title'}).find('h1') )

このまま実行すると

AttributeError: 'tuple' object has no attribute 'find'

というエラーになります。なので、

python

1soup.find('div',{'class':'horse_title'}).find('div',{'class':'horse_title'}).find('h1')

これでOK。(違う理由でこれだとエラーになります。以下参照)

おそらくただのタイプミスだとは思いますが一応。

上記のままだとエラーになる理由がこれ。

python

1horse_name_tag=soup.find('div',{'class':'horse_title'}).find('div',{'class':'horse_title'}).find('h1')

ここでclass="horse_title"divの下でまたclass="horse_title"divを探しちゃってるのでNoneになります。
サイト内で確認したらclass="horse_title"divの直下にh1があるので

python

1horse_name_tag=soup.find('div',{'class':'horse_title'}).find('h1')

これで取得できます。
あとはページが変だったときにNoneが返るのをifで分岐してエラー回避して・・・

python

1# -*- coding:utf-8 -*- 2 3import urllib2 4import codecs 5import time 6from bs4 import BeautifulSoup 7 8f = codecs.open('hiyoko.csv', 'w', 'utf-8') 9horse_name = "" 10start_url = 'http://db.netkeiba.com/horse/201310069{0}/' 11 12for i in xrange(1, 10): 13 url = start_url.format(i) 14 soup = BeautifulSoup(urllib2.urlopen(url).read(), "lxml") 15 time.sleep(1) 16 horse_name_tag = soup.find('div', {'class': 'horse_title'}) 17 18 if horse_name_tag != None: 19 20 if horse_name_tag.find('h1') != None: 21 horse_name = horse_name_tag.find('h1').text 22 horse_name = "".join( 23 [x for x in horse_name_tag.text if not x == u'\xa0' and not x == u'\n']) 24 print horse_name.strip() 25 cols = [horse_name] 26 f.write(",".join(cols) + "\n") 27 28 else: 29 continue 30 31f.close()

これで動くはずです。

最初コード書いたときに、最初に定義したurlという変数をfor内で上書きしちゃってました。

format関数は文字列内に{0}などがなくてもエラー吐かないんですね・・・

確か同スコープ内なので、最初のurlはstart_urlという変数にいれて、for内でフォーマットして使うものはurlとしました。

最後にもう一度まとめ

  • findにタプルを渡してるのでエラーになってた

  • urlの変数を上書きしない

  • DOM要素の指定ミス(最初のdivタグの指定を2回行っていたため、h1が見つからずNoneが返ってた)

  • ページが変だったとき(馬の情報が空だったとき)はDOM要素が見つからずNoneが返るので、そのときはスキップ

あとはxrange(1, 100000)にすれば2013100000から2013199999までの情報がとれます。

sleep(1)だとちょっと不安なのでもっと増やしたほうが個人的にはいいですが・・・
import randomをしてtime.sleep(random.uniform(5, 10))とかにすると5sec - 10secの間でランダムな秒数待てます。

ただこれだけ待ってると99999件取得した場合に尋常じゃない時間がかかります。

普通1秒に1アクセスでも問題にはならないようですが、岡崎市図書館事件のように、相手側のサーバーでの通信方式に不備があると障害を引き起こすこともあります。

一番は相手側にスクレイピングの許可をもらえることですね。

長文で申し訳ないです・・・

投稿2017/05/08 04:17

編集2017/05/08 06:22
kurosuke___

総合スコア217

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kurosuke___

2017/05/08 04:22 編集

厳密にするならh1タグ(コード中ではhorse_name)もNoneじゃないか判定した方がいいですね。
akakage13

2017/05/08 10:01

懇切丁寧な御教示、本当にありがとうございました! うまく動いて感激しております!!!
guest

0

Ruby

1# encoding: utf-8 2 3require'kconv' 4require'open-uri' 5require'certified' 6 7url = 'http://db.netkeiba.com/horse/20131' 8 999_999.times do |num| 10 data = open(url + format('%05d', num), &:read).toutf8 11 puts data.scan(%r{<title>(.+?) \| 競走馬データ - netkeiba.com</title>}) 12 sleep(4) 13end

実行結果例

コスモカナディアン バトルゾーン アイリーアイリー マイネルアーベント ナンベーサン クエリ ブレイズガール ビッグダディ ヘビデューティー アリュエット

私はRubyしか分からない人間ですが作ってみました。
20131を固定して、下五桁をtimesで増やしながらループを回すことにしました。
馬の名前はタイトルからとることにしました。
「2013100000から2013199999まで」をところどころ確認してみると情報が
登録されていないページがありました。
ループで回して情報をとることはできるようです。

投稿2017/05/08 01:50

編集2017/05/08 03:25
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

akakage13

2017/05/08 10:02

Rubyを用いた御教示、ありがとうございました!!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問