環境:MacOS、Python、インターネット
用途:複数の企業コードリストCSVから、企業名、概要をリストにしたい
目的:株探サイトの企業概要の文字列を取得
参考サイト
https://kabutan.jp/stock/?code=3431
概要のHTML
Pythonスクリプト
>これから
環境:MacOS、Python、インターネット
用途:複数の企業コードリストCSVから、企業名、概要をリストにしたい
目的:株探サイトの企業概要の文字列を取得
参考サイト
https://kabutan.jp/stock/?code=3431
概要のHTML
Pythonスクリプト
>これから
結果、ほとんど当たらなかった
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#ボートレース公式サイトから情報を取得するスクレイピング #2021/07/07 #環境:Mac,python, anaconda, #使用クラス:boatRaceClass(), boatRaceclass.py #使用時間帯:ボートレースが開催される前の時間帯、AM8,9時ごろがよい。レースが始まると舟券が買えないから。 #レース開催時間中でも実行可能だけど、予想が無駄になる。 #レースが多いほど処理に時間がかかる。約10分は必要 import os import re import sys import time import datetime import glob#ファイル一覧 import shutil#move from selenium import webdriver import pandas as pd import requests#スクレイピング from bs4 import BeautifulSoup#スクレイピング import importlib#再読み込みのためのライブラリ import boatRaceclass#個人用メソッド pyファイル 外部ファイル。 作業はCodeを使った。 ###START importlib.reload(boatRaceclass) print("Get BoatRace COmpu information!") print("START#####################################") btObj=boatRaceclass.boatRaceClass() #今日の日付を取得 dt_now=datetime.datetime.now()#2021-07-08 08:27:05.439783 として出力される StockDate=dt_now.strftime('%Y%m%d')#20210708、2021/07/08としたければ%Y/%m/%dのようにスラッシュを入れる #todayの長さチェックを行う。また、今日の日付を取得する場合と任意の日付を設定する場合も入れておく ftoday=StockDate #today='20210707' print(ftoday) path="/Users/toshiromaseda/Documents/2021年/python_boatRace/csv/" filename="boatraceYosou_"+ftoday+'.csv' output=btObj.getIchiran(ftoday)#全場一覧ページを取得 time.sleep(1) if (btObj.checkFileExist(filename,path)): ##ファイル名を含んでいるファイル名の場合TRUEなので、似たようなファイル名だとゆるいチェックで検索されるかもしれない print("同じファイルが存在します。削除するか必要なら別の場所に移動してください。") exit() output=btObj.getCompuYosou(output) output_tuple=btObj.neraimeHantei(output)#狙い目6についてのみ出力 タプルを格納した配列オブジェクトを出力する print("neraime end") output=btObj.tupleBunkai(output_tuple)#タプルで出力されるのでそれを分解して結合する #CSV出力 次はここから if len(output)>0: btObj.outputCSV(output,filename,path) else: print("該当のデータがありませんでしたので、CSV出力できませんでした。") |
クラスコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
#class boatRaceclass # import os import re import sys import time import datetime import glob#ファイル一覧 import shutil#move from selenium import webdriver import pandas as pd import requests#スクレイピング from bs4 import BeautifulSoup#スクレイピング #場管理クラスを作成する #まだこれから class jouKanriClass(): def __init__(self): #dict3 = {'JP':'Tokyo','US':'Washington'} #要素に文字列を入れて初期化 self.jouCode=0 self.jouName="" #連想配列 print(dict[1])#大村と出力される びわこだけひらがな self.dict={1:'桐生',2:'戸田',3:'江戸川',4:'平和島',5:'多摩川',6:'浜名湖',7:'蒲郡',8:'常滑',\ 9:'津',10:'三国',11:'びわこ',12:'住之江',13:'尼崎',14:'鳴門',15:'丸亀',16:'児島',17:'宮島',\ 18:'徳山',19:'下関',20:'若松',21:'芦屋',22:'福岡',23:'唐津',24:'大村'} def getJouCode(self,jouName):#場名から数字コードを出力する for key, value in self.dict.items(): if jouName == value: return key return "There is no such Key" #個別レースクラス 1レースのコンピュータ予想情報を管理する。選手情報を管理するためのものではない class raceCompuKanriClass(): def __init__(self,jouCode,raceNum,starttime,cdate,neraime,url): self.jouCode=jouCode self.raceNum=raceNum self.time=starttime#時刻 self.date=cdate#日付 self.neraime=neraime#6を狙い目としている self.kekka=""#レース結果,結果が表示されていれば。時間帯によって結果が表示されている #以降のデータは随時取得してから挿入する self.url=url self.star=""#コンピュータ予想の自信度の星、1-5 self.nirentanyosou=[]#コンピュータ予想2連単予想 self.sanrentanyosou=[]#コンピュータ予想3連単1-6 #個別場クラス、個別レースクラスをオブジェクト配列として管理する class jouClass(): def __init__(self,jouCode): self.jouArray=[]#初期化 raceCompuKanriClassオブジェクトを追加する self.jouCode=jouCode def getJouCodeHikisuNashi(self): return self.jouCode #レース情報クラス、個別の場のレース情報をまとめて管理する class boatRaceClass(): def __init__(self): self.tableCount=0#テーブルのカウンター self.totalJouCount=0 self.zenjouArrayPositionCount=0#全部の場の配列の位置把握のためのカウンター self.zenjouArray=[]#全部の場の配列 self.neraime=6#狙い目 self.baseurl='https://www.boatrace.jp' #self.jouCode=jouCode def seleniumReadPage(self,furl):#selenium版、requestでうまく取得できないClassがあった # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい # driver.get(furl) soup=driver return soup def readPage(self,furl): #URL読み込み 2021/07/07 r = requests.get(furl)#requestsを使って、webから取得 soup = BeautifulSoup(r.text, 'lxml') #要素を抽出 return soup def checkFileExist(self,filename,path): #他にもimport os os.listdir(path) というのもある if path[-1]=='/':#文字列の最後の1文字がスラッシュならなにもしない。 pass else: path+='/' files=glob.glob(path+'*.csv') count=0 for file in files: #print(file) if filename in file:#ファイル名を含んでいればTRUE print('ファイルは存在します') print(file) count+=1 return True#見つかれがすぐに抜ける #ループで探し終わってから判断もいれる if count>0: return True else: print('ファイルはありません') return False #end def def outputCSV(self,columArray,filename,path):#def checkFileExist()を事前にチェックすると良い #CSV出力用 #TableColumクラス専用のCSV出力、通常の配列は使用できない。 #path='/Users/toshiromaseda/Documents/2021年/2021年株/yahoofinance_data/' if filename.find('.csv')==-1:#拡張子.csvがないときは付記する filename+='.csv' os.chdir(path)#ディレクトリ変更 print(os.getcwd())#ディレクトリ確認 try: ofile=open(filename,'tw') except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません", e) sys._exit()#ファイルがなければ終了#tryのときは_exit()が良いらしい except Exception as e: # Exceptionは、それ以外の例外が発生した場合 print(e) for i in columArray: ofile.write(i+'\n')#カンマで区切られている配列を出力する ofile.close() def tupleBunkai(self,tuple):#タプルtupleを処理するためのものtupleはタプルで配列ではない tmp=[] stockStr="" for inside in tuple: for m in inside: stockStr+=str(m)+',' tmp.append(stockStr) stockStr="" output=tmp return output def neraimeHantei(self,obj): #6かどうかを判定する。イコール=があった場合も処理する #今後複数もしくは別の狙い目についてもどうするか考える。 #class raceCompuKanriClass() #self.star=""#コンピュータ予想の自信度の星、1-5 #self.nirentanyosou=[]#コンピュータ予想2連単予想 #self.sanrentanyosou=[]#コンピュータ予想3連単1-6 tmp=[] tuple=()#こっちはタプルでタプルをtmp配列に入れる if self.neraime=="": print("self.neraimeに値がありません。設定していないと探せません") exit() for i in range(0,len(obj)): print("狙い目作業中------------") print("場コード:"+str(obj[i].jouArray[0].jouCode)) for m in range(0,len(obj[i].jouArray)): #class jouKanriClass():これに場名があるprint(dict[1])#大村と出力される jouNameObj=jouKanriClass() jouName=jouNameObj.dict[obj[i].jouArray[m].jouCode] print(jouName,str(obj[i].jouArray[m].raceNum),str(obj[i].jouArray[m].kekka),str(obj[i].jouArray[m].url)) #配列 obj[i].jouArray[m].nirentanyosou stockStrNirentan="" stockStrSanrentan="" for f in range(0,len(obj[i].jouArray[m].nirentanyosou)): stockStrNirentan=obj[i].jouArray[m].nirentanyosou[f] if stockStrNirentan=="": pass else: print("stockStrNirentan2連単:"+stockStrNirentan) if (str(self.neraime) in stockStrNirentan): #タプルで追加する jouCodeを場名に変える tuple=(obj[i].jouArray[m].date,obj[i].jouArray[m].time,obj[i].jouArray[m].jouCode,jouName,obj[i].jouArray[m].raceNum,obj[i].jouArray[m].kekka, stockStrNirentan) tmp.append(tuple) # #該当のオブジェクトをtmpに入れる。もしかすると該当するレースだけを入れてもよい。 #もし、1-12全部に狙い目があると全部同じ場が入ることになるのでそれを避ける必要がある。 #やっぱりレースごとにするか考える。 tuple=() #配列 obj[i].jouArray[m].sanrentanyosou for f in range(0,len(obj[i].jouArray[m].sanrentanyosou)): stockStrSanrentan=obj[i].jouArray[m].sanrentanyosou[f] if stockStrSanrentan=="": pass else: print("stockStrSanrentan3連単:"+stockStrSanrentan) if(str(self.neraime) in stockStrSanrentan): tuple=(obj[i].jouArray[m].date,obj[i].jouArray[m].time,obj[i].jouArray[m].jouCode,jouName,obj[i].jouArray[m].raceNum,obj[i].jouArray[m].kekka, stockStrSanrentan) tmp.append(tuple) #tmp=obj if len(tmp)==0: print("狙い目はありませんでした。") output_tuple=tmp return output_tuple def getIchiran(self,ftoday): #「本日の払戻金一覧」から各場のレースのURLを取得する #https://www.boatrace.jp/owpc/pc/race/pay?hd=20210707 jouname=""#場名 jouCount=0#場コードではなくて、左上から数えた番号 self.totalJouCount=0#総合的な場数 #場名とコードクラスオブジェクト jouKanri=jouKanriClass() print("getIchiran_START") print("処理中") if ftoday=='': print("注意!!日付が設定されていませんのでダミーの日付で処理します") ftoday='20210707'#ダミーデータ target_url='https://www.boatrace.jp/owpc/pc/race/pay?hd=' mysoup=self.readPage(target_url+ftoday)#class内のメソッド main=mysoup.find(class_='l-main') mainInner=main.find(class_='l-mainInner') contentsFrame1=mainInner.find(class_='contentsFrame1') contentsFrame1_inner=contentsFrame1.find(class_='contentsFrame1_inner') #table1クラスは複数存在する。そして列が5個ある.その1個が1場分 table1_class=contentsFrame1_inner.find_all(class_='table1')#こっちはクラスのテーブル print(str(len(table1_class))+"テーブル分、1テーブル5場") for i in range(0,len(table1_class)): table1_tag=table1_class[i].find("table")#ここにタグのTableタグ以降の情報が入る if table1_tag==None:#'該当なし、1件もない場合、中身がない' print("table情報を取得できませんでした。") else: jouCount=0 self.tableCount+=1 thead=table1_tag.find("thead") #trが2行あるけど1行目のclass指定はない trs=thead.find_all("tr") for m in range(0,len(trs)): if m==0:#TR1行目だけ処理 ths=trs[m].find_all("th") for l in range(0,len(ths)): if l>0: area_type1=ths[l].find('div', class_=lambda s:s.startswith("table1_area type1"))#クラスの空白の場合はこのようにする #.find_all('div', class_=lambda s:s.startswith('foo bar ')) if area_type1==None: print("area_type1が見つかりませんでした") else: areaName=area_type1.find(class_="table1_areaName")#classのときはclass_=が必要でタグのときは不要 if areaName==None: print("areaNameが見つかりませんでした")#2021/07/07 else: jouCount+=1 self.totalJouCount+=1 imgAlt=areaName.find("img") jouName=imgAlt.get('alt', '') #ここは配列にして格納する。クラスオブジェクトにカプセルにする jouCode=jouKanri.getJouCode(jouName)#コードを出力する。びわこはひらがなで登録されている print(str(jouCount)+jouName+str(jouCode)) tmp=jouClass(jouCode) self.zenjouArray.append(tmp)#次はraceCompuKanriClassオブジェクトにレース情報を与えて、zenjouArrayに入れる tmp="" #次へtbodyレース時刻と個別レースのURLを取得する。ただし、レース後は時刻ではなくて結果が挿入される tbodys=table1_tag.find_all("tbody") rCount=0 for n in range(0,len(tbodys)): if rCount==12:#12レースでリセット rCount=0 rCount+=1 tbody_tr=tbodys[n].find("tr") r_th=tbody_tr.find("th")#レース番号が入る #a.rstrip("B")末尾のBを削除 print(r_th.get_text().rstrip("R"))#tag内のテキストを取得、1とか12とかが出力される tbody_tds=tbodys[n].find_all("td")#tdは複数存在する。なお列ごとに場が異なる。いわゆるエクセルのセルの列情報みたいな感じ #tdの数を数えつつ、必要な場にデータを与える感じになる。 tdCount=1#ここは1から #raceTdCount=0#セル3つごとのためのカウンター inTableJouCount=0#1-5を数えるためのもの for f in range(0,len(tbody_tds)): #if tdCount==jouCount:#Max5件、jouCountはテーブルごとに場の数が変わるのでここで調整 # tdCount=0 #raceTdCount+=1 #tdCount+=1 #if raceTdCount==4: # raceTdCount=1 #tdCount+=1 if tdCount==4: tdCount=1 #レース結果の場合はnumberSet1_rowが存在する。朝のレース開始前は時刻が表示される numberSet1_row=tbody_tds[f].find(class_="numberSet1_row") #次はここから starttime="" url="" if tdCount==1:#1個目だけ処理をする inTableJouCount+=1 if inTableJouCount>5: print("inTableJouCountが5を越えたのでインクリメントが間違っています") #全部の場の配列の位置把握のためのカウンター、テーブルごとに場の情報を入れるので、テーブルループで位置を把握できるようにする。つまり配列の要素数 self.zenjouArrayPositionCount=((self.tableCount-1)*5+inTableJouCount)-1#配列は0からなので1を引いておく #print("場位置配列の要素位置:"+str(self.zenjouArrayPositionCount)) if self.zenjouArrayPositionCount<0: print("self.zenjouArrayPositionCountの値が間違っています。マイナスです") if numberSet1_row==None: #print("レースは開始前でまだ結果が表示されていないのでセルが違います") #次はここから2021/07/08 午前10時12分 atag=tbody_tds[f].find("a") if atag==None: #print("atag がありません") pass else: starttime=atag.get_text() tmp="" tmp=atag.get('href') #URLは、そのままのリンクでは、コンピュータ予想のURLになっていないので置換が必要replace(before,after) #racelistをpcexpectに置換する url=tmp.replace('racelist','pcexpect') #レースオブジェクト作成 raceCompuKanriClass(jouCode,raceNum,starttime,cdate,neraime,url) #print(starttime.strip())#.strip()で前後の空白文字を削除 tmpjouCode=self.zenjouArray[self.zenjouArrayPositionCount].getJouCodeHikisuNashi() raceObj=raceCompuKanriClass(tmpjouCode,rCount,starttime.strip(),ftoday,self.neraime,self.baseurl+url) self.zenjouArray[self.zenjouArrayPositionCount].jouArray.append(raceObj) else: #レース結果 spans=numberSet1_row.find_all("span") kekkaStr="" for x in range(0,len(spans)): kekkaStr+=spans[x].get_text() #print(str(tdCount)+"結果"+kekkaStr) tmpjouCode=self.zenjouArray[self.zenjouArrayPositionCount].getJouCodeHikisuNashi() raceObj=raceCompuKanriClass(tmpjouCode,rCount,"",ftoday,self.neraime,"") raceObj.kekka=kekkaStr self.zenjouArray[self.zenjouArrayPositionCount].jouArray.append(raceObj) print("結果"+kekkaStr) tdCount+=1 #theadを使うか、それともTRタグでいいのかどうか print(str(self.totalJouCount)+"場数") output=self.zenjouArray return output def getCompuYosou(self,arrayObj):#arrayObjはgetIchiran()の出力配列オブジェクトのURLを使用する #レースのコンピュータ予想を取得する tmp=arrayObj raceYosou=[]#レース予想の配列 for i in range(0,len(tmp)): print("場コード:"+str(tmp[i].jouArray[0].jouCode)) for m in range(0,len(tmp[i].jouArray)): #print(str(output[i].jouArray[m].raceNum)) #print(str(output[i].jouArray[m].raceNum),str(output[i].jouArray[m].kekka),str(output[i].jouArray[m].url)) tmpurl=tmp[i].jouArray[m].url if tmpurl=='': print("結果を参照してください。") else: #self.getCompuYosouSeleniumSub(tmpurl) raceYosou=self.getCompuYosouSub(tmpurl)#1レース分のコンピュータ予想を登録する。この中で秒待ち設定あり time.sleep(2) if len(raceYosou)>0: #コンピュータ予想を取得して、tmpオブジェクトに入れる #star #tmp[i].jouArray[m].star=mstar for f in range(0,len(raceYosou)): #次はここから、2021/07/09 if len(raceYosou[f])==3: #2連単でクラスメソッドに分ける tmp[i].jouArray[m].nirentanyosou.append(raceYosou[f]) else:#3連 tmp[i].jouArray[m].sanrentanyosou.append(raceYosou[f]) else: print("コンピュータ予想が取得できませんでした。") if len(tmp)==0: print("情報がありません。どこかでエラーが発生したと思われますのでチェックしてください") output=tmp#全場オブジェクト配列を出力する。次はCSV出力 return output #requestで正常にHTMLが取得できないClassがあったのでSeleniumにしてみた. しかしうまくいかず。結論は、URLが違ってたのと、findの指定が間違ってた def getCompuYosouSeleniumSub(self,url): #selenium版 mysoup=self.seleniumReadPage(url)#selenium #https://www.boatrace.jp/owpc/pc/race/racelist?rno=10&jcd=05&hd=20210708 print("作業中") time.sleep(2) main=mysoup.find_element_by_class_name('l-main') mainInner=main.find_element_by_class_name('l-mainInner') contentsFrame1=mainInner.find_element_by_class_name('contentsFrame1') contentsFrame1_inner=contentsFrame1.find_element_by_class_name('contentsFrame1_inner') if contentsFrame1_inner==None: print("contentsFrame1_innerが見つかりません") else: grid_is_type3_h_clear=contentsFrame1_inner.find_elements_by_css_selector(".grid.is-type3.h-clear") #grid_is_type3_h_clear=contentsFrame1_inner.find('div', class_=lambda c:"grid" in c and "is-type3" in c and "h-clear" in c) #test=contentsFrame1_inner.find_all('div', class_=lambda c:"grid" in c and "is-type3" in c and "h-clear" in c) #print(test) #grid is-type3 h-clear grid is-type3 h-clear if grid_is_type3_h_clear==None: print("grid_is_type3_h_clearが見つかりませんでした code112") else: contentsFrame1_inner.text grid_units=contentsFrame1_inner.find_elements_by_class_name('grid_unit') print("grid_units:"+str(len(grid_units)))#ここが0だ for f in range(0,len(grid_units)): if f==0:#1番目だけあればOK numberSet2_hclear=grid_units[f].find_elements_by_css_selector('.numberSet2.h-clear') #numberSet2_hclear=grid_units[f].find('div', class_=lambda s:s.startswith("numberSet2 h-clear")) if numberSet2_hclear==None: print("numberSet2 h-clearが見つかりませんでした") else: numberSet2_units=numberSet2_hclear.find_elements_by_class_name('numberSet2_unit')#複数numberSet2_unit for i in range(0,len(numberSet2_units)): numberSet2_rows=numberSet2_units[i].find_elements_by_class_name('numberSet2_row') strSpan=numberSet2_rows.text #spanにはされた文字 print(strSpan) for m in range(0,len(numberSet2_rows)):#2単も3単も同じクラス名 spans=numberSet2_rows[m].find_elements_by_tag_name('span') yosouNumStr="" for x in range(0,len(spans)): yosouNumStr+=spans[x].text print(strSpan,yosouNumStr) state2=grid_units[f].find_element_by_class_name('state2') spans=state2.find_elements_by_tag_name('span') for k in range(0,len(spans)): if k==1: print(spans[k]) print("end") output="" return output def getCompuYosouSub(self,url): mysoup=self.readPage(url)#class内のメソッド #https://www.boatrace.jp/owpc/pc/race/racelist?rno=10&jcd=05&hd=20210708 #上記を取得するので #racelistをpcexpectに置換する。このメソッドでは置換しない予定 #https://www.boatrace.jp/owpc/pc/race/pcexpect?rno=10&jcd=05&hd=20210708 こっちだ #自信度の星数は、クラス名の総当りでNONEでなければありと判断する print(url) print("コンピュータ予想ページ読み込み、解析中") raceYosou=[]#初期化 1レース分のコンピュータ予想を格納する配列 print("秒待ち設定少々待ってください。連続取り込みしません。") time.sleep(3) main=mysoup.find(class_='l-main') mainInner=main.find(class_='l-mainInner') contentsFrame1=mainInner.find(class_='contentsFrame1') contentsFrame1_inner=contentsFrame1.find(class_='contentsFrame1_inner') if contentsFrame1_inner==None: print("contentsFrame1_innerが見つかりません") else: #Classに空白が含まれていた場合の対応 grid_is_type3_h_clear=contentsFrame1_inner.find('div', class_=lambda c:"grid" in c and "is-type3" in c and "h-clear" in c) #test=contentsFrame1_inner.find_all('div', class_=lambda c:"grid" in c and "is-type3" in c and "h-clear" in c) #print(test) #grid is-type3 h-clear grid is-type3 h-clear if grid_is_type3_h_clear==None: print("grid_is_type3_h_clearが見つかりませんでした code112") else: grid_units=contentsFrame1_inner.find_all(class_='grid_unit') print("grid_unitの数:"+str(len(grid_units)))#ここが0だとだめ for f in range(0,len(grid_units)): if f==0:#1番目だけあればOK numberSet2_hclear=grid_units[f].find('div', class_=lambda s:s.startswith("numberSet2 h-clear")) #tmpCount=grid_units[f].find_all(class_='numberSet2_unit') #print('numberSet2_unitのCount'+str(len(tmpCount))) if numberSet2_hclear==None: print("numberSet2 h-clearが見つかりませんでした") else: numberSet2_units=numberSet2_hclear.find_all(class_='numberSet2_unit')#複数 print("2連単をチェック"+str(len(numberSet2_units))) for i in range(0,len(numberSet2_units)): numberSet2_rows=numberSet2_units[i].find_all(class_='numberSet2_row') #yosouNumStr="" for m in range(0,len(numberSet2_rows)):#2単も3単も同じクラス名 spans=numberSet2_rows[m].find_all('span') divStr=numberSet2_rows[m].get_text()#spanにはされた文字を取得 tmpstr=divStr.strip().replace('\t','') pStr=tmpstr.replace('\r\n','')#文字化けしてた理由がわかったCRLFの改行になっているから\nだけを消すと文字化けする print("spanの間の文字"+pStr) raceYosou.append(pStr) #配列に入れてoutputすればOK #星についてはタプルで出力できるか、まだ print("end") output=raceYosou#コンピュータ予想配列を出力する return output |
その後、改良したクラスコードはこちら
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 |
#class boatRaceclass #ボートレース公式サイトから情報を取得するスクレイピング #2021/07/07 #環境:OS:Mac, 言語python, anaconda, 作業をするときはanaconda jupterのエディタよりMS Codeのほうがやりやすい。 #boatRaceCompuYosou.ipynbで使用 ''' ・仕様 ボートレース公式https://www.boatrace.jp/ から「本日の払戻金一覧」のところを https://www.boatrace.jp/owpc/pc/race/pay?hd=20210707 クリックして、本日開催の場の開催時間をクリックする 出走表から結果までの情報を掲載したページが表示される コンピュータ予想 https://www.boatrace.jp/owpc/pc/race/racelist? その中のコンピュータ予想タブをクリック 「予想フォーカス」という項目に予想が表示される。 例 蒲郡 jcd=07 hd=20210707 1R rno=1 https://www.boatrace.jp/owpc/pc/race/racelist?rno=1&jcd=07&hd=20210707 ・目的:予想するレースの当選番号が出現するかもしれないレースを探す。 例えば6-全-全が出現するレースを探したい。その場合コンピュータ予想の予想フォーカスで予想されている番号に 6-全-全に該当しそうな予想が表示されていたら、そのレースを出力したい。 なお、コンピュータ予想は当然ながら、当たるとは限らず確率は低い すべてのレースの予想を手動で探すのは面倒なので、自動で探したい。 ・機能:該当のページから必要な情報を取得して、該当の情報(ここでは予想番号)を取得して CSV出力する。 作業内容 1.「本日の払戻金一覧」 https://www.boatrace.jp/owpc/pc/race/pay?hd=20210707 から開催される全レースのURLを取得 2.取得したURLを場ごとに取得。ループ 3.レースごとにループする 41。「予想フォーカス」という項目のところの予想番号を取得する 5.予想する番号、例えば6,1の数字があればリストに追加する 6.CSV出力する。該当する予想がなければ出力しない。 ※使用上の注意点 一度のスクレイピングとCSV出力だけに対応していて、前回作成したCSVを読み込んで処理を追記する仕様はない。 一度作成したファイルに、レース結果を追記変更することはしない。 ようするに、朝実行して、夜に実行しても朝作成したファイルを読み込んで処理をするものではない。 朝実行すると、朝のファイルを作成し、その後実行すればそのWebサイトの情報をCSVとして出力する。 >>なぜこのように書くかというと、レースが開催されるとレース結果が払戻金一覧ページに表示される。 レースが開催されていない場合は時刻が記載されていて、出力されるCSVの記載が異なるので、後日CSVの内容を 確認すると、「レース結果が後で追記されるのか?」と勘違いするかもしれない。 現時点では、払戻金一覧のレース前の状態で、コンピュータ予想を知りたいだけの機能である。 よってコンピュータ予想を取得してCSV出力して、その後の処理は何もしない。 ''' import os import re import sys import time import datetime import glob#ファイル一覧 import shutil#move from selenium import webdriver import pandas as pd import requests#スクレイピング from bs4 import BeautifulSoup#スクレイピング #場管理クラスを作成する #まだこれから class jouKanriClass(): def __init__(self): #dict3 = {'JP':'Tokyo','US':'Washington'} #要素に文字列を入れて初期化 self.jouCode=0 self.jouName="" #連想配列 print(dict[1])#大村と出力される びわこだけひらがな self.dict={1:'桐生',2:'戸田',3:'江戸川',4:'平和島',5:'多摩川',6:'浜名湖',7:'蒲郡',8:'常滑',\ 9:'津',10:'三国',11:'びわこ',12:'住之江',13:'尼崎',14:'鳴門',15:'丸亀',16:'児島',17:'宮島',\ 18:'徳山',19:'下関',20:'若松',21:'芦屋',22:'福岡',23:'唐津',24:'大村'} def getJouCode(self,jouName):#場名から数字コードを出力する for key, value in self.dict.items(): if jouName == value: return key return "There is no such Key" #個別レースクラス 1レースのコンピュータ予想情報を管理する。選手情報を管理するためのものではない class raceCompuKanriClass(): def __init__(self,jouCode,raceNum,starttime,cdate,neraime,url): self.jouCode=jouCode self.raceNum=raceNum self.time=starttime#時刻 self.date=cdate#日付 self.neraime=neraime#6を狙い目としている self.kekka=""#レース結果,結果が表示されていれば。時間帯によって結果が表示されている #以降のデータは随時取得してから挿入する self.url=url self.star=""#コンピュータ予想の自信度の星、1-5 self.nirentanyosou=[]#コンピュータ予想2連単予想 self.sanrentanyosou=[]#コンピュータ予想3連単1-6 self.playerNumAndClass=[]#6人分の入れる #個別場クラス、個別レースクラスをオブジェクト配列として管理する class jouClass(): def __init__(self,jouCode): self.jouArray=[]#初期化 raceCompuKanriClassオブジェクトを追加する self.jouCode=jouCode def getJouCodeHikisuNashi(self): return self.jouCode #レース情報クラス、個別の場のレース情報をまとめて管理する class boatRaceClass(): def __init__(self): self.tableCount=0#テーブルのカウンター self.totalJouCount=0 self.zenjouArrayPositionCount=0#全部の場の配列の位置把握のためのカウンター self.zenjouArray=[]#全部の場の配列 self.neraime=6#狙い目 self.baseurl='https://www.boatrace.jp' #self.jouCode=jouCode def seleniumReadPage(self,furl):#selenium版、requestでうまく取得できないClassがあった # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい # driver.get(furl) soup=driver return soup def readPage(self,furl): #URL読み込み 2021/07/07 r = requests.get(furl)#requestsを使って、webから取得 soup = BeautifulSoup(r.text, 'lxml') #要素を抽出 return soup def checkFileExist(self,filename,path): #他にもimport os os.listdir(path) というのもある if path[-1]=='/':#文字列の最後の1文字がスラッシュならなにもしない。 pass else: path+='/' files=glob.glob(path+'*.csv') count=0 for file in files: #print(file) if filename in file:#ファイル名を含んでいればTRUE print('ファイルは存在します') print(file) count+=1 return True#見つかれがすぐに抜ける #ループで探し終わってから判断もいれる if count>0: return True else: print('ファイルはありません') return False #end def def outputCSV(self,columArray,filename,path):#def checkFileExist()を事前にチェックすると良い #CSV出力用 #TableColumクラス専用のCSV出力、通常の配列は使用できない。 #path='/Users/toshiromaseda/Documents/2021年/2021年株/yahoofinance_data/' if filename.find('.csv')==-1:#拡張子.csvがないときは付記する filename+='.csv' os.chdir(path)#ディレクトリ変更 print(os.getcwd())#ディレクトリ確認 try: ofile=open(filename,'tw') except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません", e) sys._exit()#ファイルがなければ終了#tryのときは_exit()が良いらしい except Exception as e: # Exceptionは、それ以外の例外が発生した場合 print(e) for i in columArray: ofile.write(i+'\n') ofile.close() def tupleBunkai(self,tuple):#タプルtupleを処理するためのものtupleはタプルで配列ではない tmp=[] stockStr="" for inside in tuple: for m in inside: stockStr+=str(m)+',' tmp.append(stockStr) stockStr="" output=tmp return output def neraimeHantei(self,obj): #6かどうかを判定する。イコール=があった場合も処理する #今後複数もしくは別の狙い目についてもどうするか考える。 #class raceCompuKanriClass() #self.star=""#コンピュータ予想の自信度の星、1-5 #self.nirentanyosou=[]#コンピュータ予想2連単予想 #self.sanrentanyosou=[]#コンピュータ予想3連単1-6 tmp=[] tuple=()#こっちはタプルでタプルをtmp配列に入れる #見出しを与える 時刻は締め切り時刻、「結果」はレース終了となったときWebに更新されるので実行する時刻によっては空欄になる tuple=('日付','時刻','場コード','場名','レース番号','結果','予想') tmp.append(tuple) if self.neraime=="": print("self.neraimeに値がありません。設定していないと探せません") exit() for i in range(0,len(obj)): print("狙い目作業中------------") print("場コード:"+str(obj[i].jouArray[0].jouCode)) for m in range(0,len(obj[i].jouArray)): #class jouKanriClass():これに場名があるprint(dict[1])#大村と出力される jouNameObj=jouKanriClass() jouName=jouNameObj.dict[obj[i].jouArray[m].jouCode] #print(jouName,str(obj[i].jouArray[m].raceNum),str(obj[i].jouArray[m].kekka),str(obj[i].jouArray[m].url)) #配列 obj[i].jouArray[m].nirentanyosou stockStrNirentan="" stockStrSanrentan="" for f in range(0,len(obj[i].jouArray[m].nirentanyosou)): stockStrNirentan=obj[i].jouArray[m].nirentanyosou[f] if stockStrNirentan=="": pass else: #print("stockStrNirentan2連単:"+stockStrNirentan) if (str(self.neraime) in stockStrNirentan): #タプルで追加する jouCodeを場名に変える tuple=(obj[i].jouArray[m].date,obj[i].jouArray[m].time,obj[i].jouArray[m].jouCode,jouName,'R'+str(obj[i].jouArray[m].raceNum),obj[i].jouArray[m].kekka, stockStrNirentan) tmp.append(tuple) # #該当のオブジェクトをtmpに入れる。もしかすると該当するレースだけを入れてもよい。 #もし、1-12全部に狙い目があると全部同じ場が入ることになるのでそれを避ける必要がある。 #やっぱりレースごとにするか考える。 tuple=() #配列 obj[i].jouArray[m].sanrentanyosou for f in range(0,len(obj[i].jouArray[m].sanrentanyosou)): stockStrSanrentan=obj[i].jouArray[m].sanrentanyosou[f] if stockStrSanrentan=="": pass else: #print("stockStrSanrentan3連単:"+stockStrSanrentan) if(str(self.neraime) in stockStrSanrentan): tuple=(obj[i].jouArray[m].date,obj[i].jouArray[m].time,obj[i].jouArray[m].jouCode,jouName,'R'+str(obj[i].jouArray[m].raceNum),obj[i].jouArray[m].kekka, stockStrSanrentan) tmp.append(tuple) #tmp=obj if len(tmp)==1:#見出し行のみなら狙い目はない print("狙い目はありませんでした。") output_tuple=tmp return output_tuple def getIchiran(self,ftoday): #「本日の払戻金一覧」から各場のレースのURLを取得する #https://www.boatrace.jp/owpc/pc/race/pay?hd=20210707 jouname=""#場名 jouCount=0#場コードではなくて、左上から数えた番号 self.totalJouCount=0#総合的な場数 #場名とコードクラスオブジェクト jouKanri=jouKanriClass() print("getIchiran_START") print("処理中") if ftoday=='': print("注意!!日付が設定されていませんのでダミーの日付で処理します") ftoday='20210707'#ダミーデータ target_url='https://www.boatrace.jp/owpc/pc/race/pay?hd=' mysoup=self.readPage(target_url+ftoday)#class内のメソッド main=mysoup.find(class_='l-main') mainInner=main.find(class_='l-mainInner') contentsFrame1=mainInner.find(class_='contentsFrame1') contentsFrame1_inner=contentsFrame1.find(class_='contentsFrame1_inner') #table1クラスは複数存在する。そして列が5個ある.その1個が1場分 table1_class=contentsFrame1_inner.find_all(class_='table1')#こっちはクラスのテーブル print(str(len(table1_class))+"テーブル分、1テーブル5場") for i in range(0,len(table1_class)): table1_tag=table1_class[i].find("table")#ここにタグのTableタグ以降の情報が入る if table1_tag==None:#'該当なし、1件もない場合、中身がない' print("table情報を取得できませんでした。") else: jouCount=0 self.tableCount+=1 thead=table1_tag.find("thead") #trが2行あるけど1行目のclass指定はない trs=thead.find_all("tr") for m in range(0,len(trs)): if m==0:#TR1行目だけ処理 ths=trs[m].find_all("th") for l in range(0,len(ths)): if l>0: area_type1=ths[l].find('div', class_=lambda s:s.startswith("table1_area type1"))#クラスの空白の場合はこのようにする #.find_all('div', class_=lambda s:s.startswith('foo bar ')) if area_type1==None: print("area_type1が見つかりませんでした") else: areaName=area_type1.find(class_="table1_areaName")#classのときはclass_=が必要でタグのときは不要 if areaName==None: print("areaNameが見つかりませんでした")#2021/07/07 else: jouCount+=1 self.totalJouCount+=1 imgAlt=areaName.find("img") jouName=imgAlt.get('alt', '') #ここは配列にして格納する。クラスオブジェクトにカプセルにする jouCode=jouKanri.getJouCode(jouName)#コードを出力する。びわこはひらがなで登録されている #print(str(jouCount)+jouName+str(jouCode)) tmp=jouClass(jouCode) self.zenjouArray.append(tmp)#次はraceCompuKanriClassオブジェクトにレース情報を与えて、zenjouArrayに入れる tmp="" #次へtbodyレース時刻と個別レースのURLを取得する。ただし、レース後は時刻ではなくて結果が挿入される tbodys=table1_tag.find_all("tbody") rCount=0 for n in range(0,len(tbodys)): if rCount==12:#12レースでリセット rCount=0 rCount+=1 tbody_tr=tbodys[n].find("tr") r_th=tbody_tr.find("th")#レース番号が入る #a.rstrip("B")末尾のBを削除 print(r_th.get_text().rstrip("R"))#tag内のテキストを取得、1とか12とかが出力される tbody_tds=tbodys[n].find_all("td")#tdは複数存在する。なお列ごとに場が異なる。いわゆるエクセルのセルの列情報みたいな感じ #tdの数を数えつつ、必要な場にデータを与える感じになる。 tdCount=1#ここは1から #raceTdCount=0#セル3つごとのためのカウンター inTableJouCount=0#1-5を数えるためのもの for f in range(0,len(tbody_tds)): #if tdCount==jouCount:#Max5件、jouCountはテーブルごとに場の数が変わるのでここで調整 if tdCount==4: tdCount=1 #レース結果の場合はnumberSet1_rowが存在する。朝のレース開始前は時刻が表示される numberSet1_row=tbody_tds[f].find(class_="numberSet1_row") #次はここから starttime="" url="" if tdCount==1:#1個目だけ処理をする inTableJouCount+=1 if inTableJouCount>5: print("inTableJouCountが5を越えたのでインクリメントが間違っています") #全部の場の配列の位置把握のためのカウンター、テーブルごとに場の情報を入れるので、テーブルループで位置を把握できるようにする。つまり配列の要素数 self.zenjouArrayPositionCount=((self.tableCount-1)*5+inTableJouCount)-1#配列は0からなので1を引いておく #print("場位置配列の要素位置:"+str(self.zenjouArrayPositionCount)) if self.zenjouArrayPositionCount<0: print("self.zenjouArrayPositionCountの値が間違っています。マイナスです") if numberSet1_row==None: #print("レースは開始前でまだ結果が表示されていないのでセルが違います") #次はここから2021/07/08 午前10時12分 atag=tbody_tds[f].find("a") if atag==None: #print("atag がありません") pass else: starttime=atag.get_text() tmp="" tmp=atag.get('href') #URLは、そのままのリンクでは、コンピュータ予想のURLになっていないので置換が必要replace(before,after) #racelistをpcexpectに置換する url=tmp.replace('racelist','pcexpect') #レースオブジェクト作成 raceCompuKanriClass(jouCode,raceNum,starttime,cdate,neraime,url) #print(starttime.strip())#.strip()で前後の空白文字を削除 tmpjouCode=self.zenjouArray[self.zenjouArrayPositionCount].getJouCodeHikisuNashi() raceObj=raceCompuKanriClass(tmpjouCode,rCount,starttime.strip(),ftoday,self.neraime,self.baseurl+url) self.zenjouArray[self.zenjouArrayPositionCount].jouArray.append(raceObj) else: #レース結果raceresult td_url=tbody_tds[f].get('data-href') if td_url=="": pass else: url=td_url.replace('raceresult','pcexpect') spans=numberSet1_row.find_all("span") kekkaStr="" for x in range(0,len(spans)): kekkaStr+=spans[x].get_text() #print(str(tdCount)+"結果"+kekkaStr) tmpjouCode=self.zenjouArray[self.zenjouArrayPositionCount].getJouCodeHikisuNashi() if url=="": raceObj=raceCompuKanriClass(tmpjouCode,rCount,"",ftoday,self.neraime,"") else: raceObj=raceCompuKanriClass(tmpjouCode,rCount,"",ftoday,self.neraime,self.baseurl+url) raceObj.kekka=kekkaStr self.zenjouArray[self.zenjouArrayPositionCount].jouArray.append(raceObj) print("結果"+kekkaStr) tdCount+=1 #theadを使うか、それともTRタグでいいのかどうか print(str(self.totalJouCount)+"場数") output=self.zenjouArray return output def getCompuYosou(self,arrayObj):#arrayObjはgetIchiran()の出力配列オブジェクトのURLを使用する #レースのコンピュータ予想を取得する tmp=arrayObj raceYosou=[]#レース予想の配列 for i in range(0,len(tmp)): print("残り:"+str(len(tmp)-i)) print("場コード:"+str(tmp[i].jouArray[0].jouCode)) for m in range(0,len(tmp[i].jouArray)): #print(str(output[i].jouArray[m].raceNum)) #print(str(output[i].jouArray[m].raceNum),str(output[i].jouArray[m].kekka),str(output[i].jouArray[m].url)) tmpurl=tmp[i].jouArray[m].url if tmpurl=='': print("結果を参照してください。") continue else: #self.getCompuYosouSeleniumSub(tmpurl) raceYosou=self.getCompuYosouSub(tmpurl)#1レース分のコンピュータ予想を登録する。この中で秒待ち設定あり time.sleep(2) if len(raceYosou)>0: #コンピュータ予想を取得して、tmpオブジェクトに入れる #star #tmp[i].jouArray[m].star=mstar for f in range(0,len(raceYosou)): #次はここから、2021/07/09 if len(raceYosou[f])==3: #2連単でクラスメソッドに分ける tmp[i].jouArray[m].nirentanyosou.append(raceYosou[f]) else:#3連 tmp[i].jouArray[m].sanrentanyosou.append(raceYosou[f]) else: print("コンピュータ予想が取得できませんでした。") if len(tmp)==0: print("情報がありません。どこかでエラーが発生したと思われますのでチェックしてください") output=tmp#全場オブジェクト配列を出力する。次はCSV出力 return output #requestで正常にHTMLが取得できないClassがあったのでSeleniumにしてみた. しかしうまくいかず。結論は、URLが違ってたのと、findの指定が間違ってた def getCompuYosouSeleniumSub(self,url): #selenium版 mysoup=self.seleniumReadPage(url)#selenium #https://www.boatrace.jp/owpc/pc/race/racelist?rno=10&jcd=05&hd=20210708 print("作業中") time.sleep(2) main=mysoup.find_element_by_class_name('l-main') mainInner=main.find_element_by_class_name('l-mainInner') contentsFrame1=mainInner.find_element_by_class_name('contentsFrame1') contentsFrame1_inner=contentsFrame1.find_element_by_class_name('contentsFrame1_inner') if contentsFrame1_inner==None: print("contentsFrame1_innerが見つかりません") else: grid_is_type3_h_clear=contentsFrame1_inner.find_elements_by_css_selector(".grid.is-type3.h-clear") #grid_is_type3_h_clear=contentsFrame1_inner.find('div', class_=lambda c:"grid" in c and "is-type3" in c and "h-clear" in c) #test=contentsFrame1_inner.find_all('div', class_=lambda c:"grid" in c and "is-type3" in c and "h-clear" in c) #print(test) #grid is-type3 h-clear grid is-type3 h-clear if grid_is_type3_h_clear==None: print("grid_is_type3_h_clearが見つかりませんでした code112") else: contentsFrame1_inner.text grid_units=contentsFrame1_inner.find_elements_by_class_name('grid_unit') print("grid_units:"+str(len(grid_units)))#ここが0だ for f in range(0,len(grid_units)): if f==0:#1番目だけあればOK numberSet2_hclear=grid_units[f].find_elements_by_css_selector('.numberSet2.h-clear') #numberSet2_hclear=grid_units[f].find('div', class_=lambda s:s.startswith("numberSet2 h-clear")) if numberSet2_hclear==None: print("numberSet2 h-clearが見つかりませんでした") else: numberSet2_units=numberSet2_hclear.find_elements_by_class_name('numberSet2_unit')#複数numberSet2_unit for i in range(0,len(numberSet2_units)): numberSet2_rows=numberSet2_units[i].find_elements_by_class_name('numberSet2_row') strSpan=numberSet2_rows.text #spanにはされた文字 print(strSpan) for m in range(0,len(numberSet2_rows)):#2単も3単も同じクラス名 spans=numberSet2_rows[m].find_elements_by_tag_name('span') yosouNumStr="" for x in range(0,len(spans)): yosouNumStr+=spans[x].text print(strSpan,yosouNumStr) state2=grid_units[f].find_element_by_class_name('state2') spans=state2.find_elements_by_tag_name('span') for k in range(0,len(spans)): if k==1: print(spans[k]) print("end") output="" return output def getCompuYosouSub(self,url): mysoup=self.readPage(url)#class内のメソッド #https://www.boatrace.jp/owpc/pc/race/racelist?rno=10&jcd=05&hd=20210708 #上記を取得するので #racelistをpcexpectに置換する。このメソッドでは置換しない予定 #https://www.boatrace.jp/owpc/pc/race/pcexpect?rno=10&jcd=05&hd=20210708 こっちだ #自信度の星数は、クラス名の総当りでNONEでなければありと判断する #print(url) #print("コンピュータ予想ページ読み込み、解析中") raceYosou=[]#初期化 1レース分のコンピュータ予想を格納する配列 #print("秒待ち設定少々待ってください。連続取り込みしません。") time.sleep(3) main=mysoup.find(class_='l-main') mainInner=main.find(class_='l-mainInner') contentsFrame1=mainInner.find(class_='contentsFrame1') contentsFrame1_inner=contentsFrame1.find(class_='contentsFrame1_inner') if contentsFrame1_inner==None: print("contentsFrame1_innerが見つかりません") else: #Classに空白が含まれていた場合の対応 grid_is_type3_h_clear=contentsFrame1_inner.find('div', class_=lambda c:"grid" in c and "is-type3" in c and "h-clear" in c) #test=contentsFrame1_inner.find_all('div', class_=lambda c:"grid" in c and "is-type3" in c and "h-clear" in c) #print(test) #grid is-type3 h-clear grid is-type3 h-clear if grid_is_type3_h_clear==None: print("grid_is_type3_h_clearが見つかりませんでした code112") else: grid_units=contentsFrame1_inner.find_all(class_='grid_unit') #print("grid_unitの数:"+str(len(grid_units)))#ここが0だとだめ for f in range(0,len(grid_units)): if f==0:#1番目だけあればOK numberSet2_hclear=grid_units[f].find('div', class_=lambda s:s.startswith("numberSet2 h-clear")) #tmpCount=grid_units[f].find_all(class_='numberSet2_unit') #print('numberSet2_unitのCount'+str(len(tmpCount))) if numberSet2_hclear==None: print("numberSet2 h-clearが見つかりませんでした") else: numberSet2_units=numberSet2_hclear.find_all(class_='numberSet2_unit')#複数 #print("2連単をチェック"+str(len(numberSet2_units))) for i in range(0,len(numberSet2_units)): numberSet2_rows=numberSet2_units[i].find_all(class_='numberSet2_row') #yosouNumStr="" for m in range(0,len(numberSet2_rows)):#2単も3単も同じクラス名 spans=numberSet2_rows[m].find_all('span') divStr=numberSet2_rows[m].get_text()#spanにはされた文字を取得 tmpstr=divStr.strip().replace('\t','') pStr=tmpstr.replace('\r\n','')#文字化けしてた理由がわかったCRLFの改行になっているから\nだけを消すと文字化けする #print("spanの間の文字"+pStr) raceYosou.append(pStr) #配列に入れてoutputすればOK #星についてはタプルで出力できるか、まだ #選手番号と級別の取得 print("end") output=raceYosou#コンピュータ予想配列を出力する、級別のオブジェクトをタプルで出力する return output #-------------------------------------------------------------------- #-------------------------------------------------------------------- #-------------------------------------------------------------------- #-------------------------------------------------------------------- #-------------------------------------------------------------------- #---ここは継承クラスboatRacePlayerClass() 選手クラスを追加するためのバージョン #-------------------------------------------------------------------- #-------------------------------------------------------------------- #-------------------------------------------------------------------- #-------------------------------------------------------------------- #-------------------------------------------------------------------- #継承クラスを作る利点は、すでに動いているものは壊さないために別クラスを作成する。手間を省いて、追加したい機能だけを効率よく作りたい #使用するときはクラス名を変えるだけでメソッドは極力そのままとする。(機能追加や大幅変更は、メソッド名を変更したほうがわかりやすい) class boatRacePlayerClass(boatRaceClass): #基底クラスのboatRaceClassと異なる点は、級別(選手番号)を追加したこと。級別取得スクレイピングとCSVに出力する部分を書き直す #オーバーライドの記載は一部変数が追加のときにsuper()を使うと基底クラスのメソッドの一部をそのまま使えて、その分を記載する必要がない #今回はsuperを使う必要はないかも def allA1Reject(self,tupleObj): #1-6コース全員がA1は除外する。タプルが配列に入っているオブジェクト print("1-6コース全員がA1は除外するallA1Reject()") tmpObj=[]#タプルの入った配列オブジェクトを再編する for getstr in tupleObj: #今の所、A1という文字が存在するのは選手クラスだけなのでタプル全部のセルを検索してA1があるかをチェックする。 count=0 for m in range(0,len(getstr)): if ('A1' in str(getstr[m])): count+=1 if count>=6: pass #print("みんなA1だったのでリストに入れない:"+getstr) else: tmpObj.append(getstr) output_tuple=tmpObj return output_tuple def playerClassGet(self,soup): #ここは継承クラス boatRacePlayerClass #選手番号と級別の取得 getCompuYosouSub()の中で使用する playerClassObj=[] classTables=soup.find_all(class_='table1')#class名で[tabel1]と[table1 aaa]が存在するとtable1は複数あるとみなされる #print(len(classTables)) for x in range(0,len(classTables)): table=classTables[x].find("table") tbodys=table.find_all("tbody") #print("playerClassGet処理中") for i in range(0,len(tbodys)): trs=tbodys[i].find_all("tr") #print("tr取得") for f in range(0,len(trs)): if f==0:#f=0のときだけ tds=trs[f].find_all("td") for m in range(0,len(tds)): #print("td処理中")OK is_fs11=tds[m].find_all(class_='is-fs11') #ここで正常にis-fs11が取得できていない。2021/07/11、12:13、なんで? table1クラスが実は同名を含む複数が存在したから。 #print(str(m)+"番目"+str(len(is_fs11))) for l in range(0,len(is_fs11)): #print("is_fs_112処理中") if l==0:#選手クラスは0要素の1番目 #単体テスト用の確認用メソッドを作る必要があるな。統合して実行すると15分以上かかるから。2021/07/11 #print(is_fs11[l].get_text().strip().replace('\t','').replace('\r\n','')) #左から順に処理が進む、stripしてreplaceしてまたreplaceして、、、 playerClassObj.append(is_fs11[l].get_text().strip().replace('\t','').replace('\r\n','')) if len(playerClassObj)==0: print("playerClassObjの数が0です。") output=playerClassObj return output #オーバーライド def neraimeHantei(self,obj): #6かどうかを判定する。イコール=があった場合も処理する #今後複数もしくは別の狙い目についてもどうするか考える。 #class raceCompuKanriClass() #self.star=""#コンピュータ予想の自信度の星、1-5 #self.nirentanyosou=[]#コンピュータ予想2連単予想 #self.sanrentanyosou=[]#コンピュータ予想3連単1-6 #選手クラスを追加した。 tmp=[] tuple=()#こっちはタプルでタプルをtmp配列に入れる #見出しを与える 時刻は締め切り時刻、「結果」はレース終了となったときWebに更新されるので実行する時刻によっては空欄になる tuple=('日付','時刻','場コード','場名','レース番号','結果','予想','枠1','枠2','枠3','枠4','枠5','枠6') tmp.append(tuple) if self.neraime=="": print("self.neraimeに値がありません。設定していないと探せません") exit() for i in range(0,len(obj)): print("狙い目作業中------------") print("場コード:"+str(obj[i].jouArray[0].jouCode)) for m in range(0,len(obj[i].jouArray)): #class jouKanriClass():これに場名があるprint(dict[1])#大村と出力される jouNameObj=jouKanriClass() jouName=jouNameObj.dict[obj[i].jouArray[m].jouCode] #print(jouName,str(obj[i].jouArray[m].raceNum),str(obj[i].jouArray[m].kekka),str(obj[i].jouArray[m].url)) #配列 obj[i].jouArray[m].nirentanyosou stockStrNirentan="" stockStrSanrentan="" for f in range(0,len(obj[i].jouArray[m].nirentanyosou)): stockStrNirentan=obj[i].jouArray[m].nirentanyosou[f] if stockStrNirentan=="": pass else: #print("stockStrNirentan2連単:"+stockStrNirentan) if (str(self.neraime) in stockStrNirentan): tuple=(obj[i].jouArray[m].date,obj[i].jouArray[m].time,obj[i].jouArray[m].jouCode,jouName,\ 'R'+str(obj[i].jouArray[m].raceNum),obj[i].jouArray[m].kekka, stockStrNirentan,\ obj[i].jouArray[m].playerNumAndClass[0],obj[i].jouArray[m].playerNumAndClass[1],\ obj[i].jouArray[m].playerNumAndClass[2],obj[i].jouArray[m].playerNumAndClass[3],\ obj[i].jouArray[m].playerNumAndClass[4],obj[i].jouArray[m].playerNumAndClass[5]) tmp.append(tuple) tuple=() #配列 obj[i].jouArray[m].sanrentanyosou for f in range(0,len(obj[i].jouArray[m].sanrentanyosou)): stockStrSanrentan=obj[i].jouArray[m].sanrentanyosou[f] if stockStrSanrentan=="": pass else: #print("stockStrSanrentan3連単:"+stockStrSanrentan) if(str(self.neraime) in stockStrSanrentan): tuple=(obj[i].jouArray[m].date,obj[i].jouArray[m].time,obj[i].jouArray[m].jouCode,jouName,\ 'R'+str(obj[i].jouArray[m].raceNum),obj[i].jouArray[m].kekka, stockStrSanrentan,\ obj[i].jouArray[m].playerNumAndClass[0],obj[i].jouArray[m].playerNumAndClass[1],\ obj[i].jouArray[m].playerNumAndClass[2],obj[i].jouArray[m].playerNumAndClass[3],\ obj[i].jouArray[m].playerNumAndClass[4],obj[i].jouArray[m].playerNumAndClass[5]) tmp.append(tuple) #tmp=obj if len(tmp)==1:#見出し行のみなら狙い目はない print("狙い目はありませんでした。") output_tuple=tmp return output_tuple #getCompuYosouはオーバーライド def getCompuYosou(self,arrayObj):#arrayObjを入れる。getIchiran()の出力配列オブジェクトのURLを使用する #ここは継承クラス boatRacePlayerClass #レースのコンピュータ予想を取得する #追加機能、選手クラス追加 tmp=arrayObj raceYosou=[]#レース予想の配列 for i in range(0,len(tmp)): print("場コード:"+str(tmp[i].jouArray[0].jouCode)) for m in range(0,len(tmp[i].jouArray)): #print(str(output[i].jouArray[m].raceNum),str(output[i].jouArray[m].kekka),str(output[i].jouArray[m].url)) tmpurl=tmp[i].jouArray[m].url if tmpurl=='': print("結果を参照してください。") continue else: #ここは継承クラス boatRacePlayerClass #タプルの配列オブジェクトを受け取る raceYosou=self.getCompuYosouSub(tmpurl)#1レース分のコンピュータ予想、選手クラスを登録する。この中で秒待ち設定あり #0番目タプルと1番目タプルで処理を行う。2021/07/11 time.sleep(2) if len(raceYosou)>0: #コンピュータ予想を取得して、tmpオブジェクトに入れる #star #tmp[i].jouArray[m].star=mstar #0番目タプル、予想 for f in range(0,len(raceYosou[0])): #次はここから、2021/07/09 if len(raceYosou[0][f])==3: #2連単でクラスメソッドに分ける tmp[i].jouArray[m].nirentanyosou.append(raceYosou[0][f]) else:#3連 tmp[i].jouArray[m].sanrentanyosou.append(raceYosou[0][f]) #1番目タプル、選手クラス for x in range(0,len(raceYosou[1])):#6人分が入っている。「選手番号/クラス」となっている。 tmp[i].jouArray[m].playerNumAndClass.append(raceYosou[1][x].split('/')[1])#1234/B1のときにsplitして、2番めのB1を取得する else: print("コンピュータ予想が取得できませんでした。") if len(tmp)==0: print("情報がありません。どこかでエラーが発生したと思われますのでチェックしてください") output=tmp#全場オブジェクト配列を出力する。次はCSV出力 return output #オーバーライド def getCompuYosouSub(self,url):#スクレイピング部分 #ここは継承クラス boatRacePlayerClass、基底クラスはboatRaceClass() #基底クラスとの違いは選手クラスを追加したこと mysoup=self.readPage(url)#class内のメソッド #https://www.boatrace.jp/owpc/pc/race/racelist?rno=10&jcd=05&hd=20210708 #上記を取得するので #racelistをpcexpectに置換する。このメソッドでは置換しない予定 #https://www.boatrace.jp/owpc/pc/race/pcexpect?rno=10&jcd=05&hd=20210708 こっちだ #自信度の星数は、クラス名の総当りでNONEでなければありと判断する #print(url) #print("コンピュータ予想ページ読み込み、解析中") raceYosou=[]#初期化 1レース分のコンピュータ予想を格納する配列 playerObj=[] #print("秒待ち設定少々待ってください。連続取り込みしません。") time.sleep(3) main=mysoup.find(class_='l-main') mainInner=main.find(class_='l-mainInner') contentsFrame1=mainInner.find(class_='contentsFrame1') contentsFrame1_inner=contentsFrame1.find(class_='contentsFrame1_inner') if contentsFrame1_inner==None: print("contentsFrame1_innerが見つかりません") else: #Classに空白が含まれていた場合の対応 grid_is_type3_h_clear=contentsFrame1_inner.find('div', class_=lambda c:"grid" in c and "is-type3" in c and "h-clear" in c) #grid is-type3 h-clear grid is-type3 h-clear if grid_is_type3_h_clear==None: print("grid_is_type3_h_clearが見つかりませんでした code112") else: grid_units=contentsFrame1_inner.find_all(class_='grid_unit') #print("grid_unitの数:"+str(len(grid_units)))#ここが0だとだめ for f in range(0,len(grid_units)): if f==0:#1番目だけあればOK numberSet2_hclear=grid_units[f].find('div', class_=lambda s:s.startswith("numberSet2 h-clear")) #tmpCount=grid_units[f].find_all(class_='numberSet2_unit') #print('numberSet2_unitのCount'+str(len(tmpCount))) if numberSet2_hclear==None: print("numberSet2 h-clearが見つかりませんでした") else: numberSet2_units=numberSet2_hclear.find_all(class_='numberSet2_unit')#複数 #print("2連単をチェック"+str(len(numberSet2_units))) for i in range(0,len(numberSet2_units)): numberSet2_rows=numberSet2_units[i].find_all(class_='numberSet2_row') #yosouNumStr="" for m in range(0,len(numberSet2_rows)):#2単も3単も同じクラス名 spans=numberSet2_rows[m].find_all('span') divStr=numberSet2_rows[m].get_text()#spanにはされた文字を取得 tmpstr=divStr.strip().replace('\t','') pStr=tmpstr.replace('\r\n','')#文字化けしてた理由がわかったCRLFの改行になっているから\nだけを消すと文字化けする #print("spanの間の文字"+pStr) raceYosou.append(pStr) #配列に入れてoutputすればOK #星についてはタプルで出力できるか、まだ #選手番号と級別の取得 #ここは継承クラス boatRacePlayerClass #これから2021/07/11 #playerClassGet()メソッドを作成してコードの視認性を上げる #table1はクラス名として複数存在している[table1 aaa]みたいなだからこっちを見てるなので, playerObj=self.playerClassGet(contentsFrame1_inner)#レースごとの出場選手のクラス情報を追加する。1レース6名となる #print("end") output=(raceYosou,playerObj)#コンピュータ予想配列を出力する、級別のオブジェクトをタプルで出力する return output |
#株探先物オプションのスクレイピング。取得した文字列をコピペしてTEXTにして、
#CSVに出力してMysqlにインサートするコード
#URLと日付と月限は手動で毎回入力する必要がある。
#以前はファイル作成まで手動で作業していた。
#WEBページを開き
#先物オプションの出来高データを取得して
#テキストファイルにする
#所定のフォルダにコピー、移動する
#個人用クラスメソッドを使う。
いままでWebdriverライブラリを使用していたが、空白部分が自動で削除されてしまった。
削除してほしくなかったので、BeautifulSoupライブラリを使用した。こちらのほうはクロムは起動しないし純粋にhtmlの文字を空白を含めて取得してくれた。
以下は今回作ったコードの一部。class cGetOption()
なおdef returnDriver(self,lurl)はwebdriverを使用したもので実際には採用しなかった。
株探の先物オプションのURLを取得して
そのページの文字列を取得。このとき、ある文字列Aと文字列Bの間の文字を取得(findFirstStrのところ)
そしてタプルで、文字列とファイル名文字列を出力する
https://github.com/maseda1030/yahoofinance_python.git
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
import MySQLdb import os import re import sys import time import datetime import glob#ファイル一覧 import shutil#move from selenium import webdriver import importlib#再読み込みのためのライブラリ import requests#スクレイピング from bs4 import BeautifulSoup#スクレイピング import methodMysql#個人用メソッド pyファイル ###START importlib.reload(methodMysql) ##以下は毎回手動で確認すること #株探の市況の先物オプション、市場が開いている当日のPM4:06分頃と、翌日の朝AM6:30以降に更新される。 #土日祝は更新されないが、前日が市場が開いていると更新される。 optionUrl='https://kabutan.jp/news/marketnews/?b=n202103010692'#都度修正、毎日 useDate='2021/3/1'#オプション取り込みの日付 #都度修正、毎日 useMonth=3#月限 12月の作業はたいてい翌月である1月 #都度修正、月 useTable='Table_StockOption'#テーブルは固定 # print('START') print('chrome起動') obj=methodMysql.cGetOption() #このURLは手動でコピペする #BeautifulSoup版 tupleArray=obj.getOptionDataByBeautifulSoup(optionUrl) #webdriver版 #tupleArray=obj.getOptionData(optionUrl) a,b=tupleArray print(a)#text print(b)#年月日時分 path='/Users/toshiromaseda/Documents/2020年株関連/kabu_python/' obj.outputText(path,tupleArray) #file名は、年月日時分.txtになって保存される。 #ファイルの存在チェックで拡張子をつけてチェックする if b.find('.txt')>=0: pass else: ifile=b+'.txt' ##-- checkFlag='nashi' files=glob.glob(path+'*.txt') for file in files: if ifile in file:#ファイル名を含んでいればTRUE print(file) checkFlag='ari' break else: #print(file) checkFlag='nashi' if checkFlag=='nashi': print('ファイルが存在しないか、ファイル名が間違っています') sys.exit() csvfile='c_'+ifile.split('.')[0]+'.csv' methodMysql.checkFile(ifile)#check 取り込み済みのファイルかどうかを調べる try: ofile=open(csvfile,'tw') except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません", e) #print("強制終了") sys._exit()#ファイルがなければ終了#tryのときは_exit()が良いらしい except Exception as e: # Exceptionは、それ以外の例外が発生した場合 print(e) try:#ファイルが存在しないときのエラー処理try with open(ifile,'tr') as fin: for iline in fin: #ilineに、”コール”、”出来高”の文字がある行は無視するようにする #countで探すか 1行目、2行目は不要なのでDBにインサートしないための処理 if iline.count('コール')>=1 or iline.count('出来高')>=1: continue#以下は実行されない if len(iline)==1:#1文字は改行コードだろうから何もしない continue try: ofile.write(methodMysql.outMojiMKIII(iline)+'\n')#MKIII改良版2020/12/18 #次はMysqlに直接インサートするバージョンを作業する except Exception: print("do not") ofile.close() except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません。パス、ファイル名を確認してください", e) ofile.close() print("強制終了") sys._exit()#ファイルがなければ終了 #tryのときは_exit()が良いらしい except Exception as e: # Exceptionは、それ以外の例外が発生した場合 print(e) #CSVからMysqlへ CSVファイルがないと実行できない。重複チェックなし #csvToMysql(difileはCSVファイル名,dtablename、doptionDate日付、dmonth月限) #テスト用テーブルTest_Table_StockOption print("csvtoMysql") methodMysql.csvToMysql(csvfile,useTable,useDate,useMonth) #Testが終わったら試す。 単独実行可能 但し、ファイルの移動先チェックがないので、他で移動がないことをチェックする必要あり methodMysql.fileMove(ifile,'./option_python_execute') methodMysql.fileMove(csvfile,'./option_python_execute') #表、グラフを出力する 単独実行可能 ##methodMysql.selectMysql('Table_StockOption') print("終わりました。") print('+++++++') print('chrome終了') print('END') ''' mysql> mysql> SELECT SUM(Volume1) as callVolume1, ExercisePrice, SUM(Volume2) as putVolume2 FROM Table_StockOption WHERE Month=3 GROUP BY ExercisePrice ORDER BY Exerce Desc; ''' |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
#先物オプションテキストコピペ 2021/02/28 class cGetOption(): def __init__(self): pass def returnDriver(self,lurl): # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい url=lurl driver.get(url) mydriver=driver return mydriver #BeautifulSoup版はクロムは起動しない def getOptionDataByBeautifulSoup(self,lurl):#BeautifulSoup対応版、別途Importが必要、2021/03/01 target_url = lurl r = requests.get(target_url) #requestsを使って、webから取得 soup = BeautifulSoup(r.text, 'lxml') #要素を抽出 shijyounews=soup.find(id="shijyounews")#id article=shijyounews.find("article")#tag tagTime=article.find("time")#tag mono=article.find(class_="mono")#class print("BeautifulSoup") #print(mono.text) outputFileName=tagTime.text#年月日時分 #ここに処理を入れる。 #2021/03/01 findFirstStr='コール プット'#を探して findSecondStr='株探ニュース'#の前までを取得する findFirstPosition=mono.text.find(findFirstStr) findSecondPosition=mono.text.find(findSecondStr) if(findFirstPosition>=0 and findSecondPosition>=0): print('search success.') outputText=mono.text[findFirstPosition:findSecondPosition] else: print('先物オプションが見つかりませんでした。URLが正しいか確認してください。') sys.exit() output=()#初期化 output=(outputText,outputFileName)#タプルで出力する,この時点で拡張子.txtは付記していない。 time.sleep(1) return output #webdriver版は、クロムが起動する。こっちは文字の先頭に空白を自動で削除する。この処理がよいときもあるが、今回は空白が必要だったのでB4にした def getOptionData(self,lurl): driver=self.returnDriver(lurl) time.sleep(1) #tag,id,css main = driver.find_element_by_id("wrapper_main") container=main.find_element_by_id("container") container_main=container.find_element_by_id("main") shijyounews=container_main.find_element_by_id("shijyounews") article=shijyounews.find_element_by_tag_name("article") tagTime=article.find_element_by_tag_name("time") mono=article.find_element_by_class_name("mono") #brs=mono.find_elements_by_tag_name("br") print(tagTime.text) outputFileName=tagTime.text#年月日時分 #tagTimePosition=tagTime.text.find('日') #print(tagTime.text[0:tagTimePosition+1])#年月日 #print(mono.text) findFirstStr='コール プット'#を探して findSecondStr='株探ニュース'#の前までを取得する findFirstPosition=mono.text.find(findFirstStr) findSecondPosition=mono.text.find(findSecondStr) if(findFirstPosition>=0 and findSecondPosition>=0): print('search success.') outputText=mono.text[findFirstPosition:findSecondPosition] else: print('先物オプションが見つかりませんでした。URLが正しいか確認してください。') sys.exit() output=()#初期化 output=(outputText,outputFileName)#タプルで出力する,この時点で拡張子.txtは付記していない。 time.sleep(1) driver.close()#起動したウィンドウを閉じる return output def outputText(self,path,tupleArray):#ここで受け取るタプルは2つ #getOptionData()からテキストとファイル名をタプルを受け取る outputText,filename = tupleArray if outputText=='' or filename=='' or path=='': print("パスかファイル名かテキストがありませんでした。filename,outputText") sys.exit() os.chdir(path)#ディレクトリ変更 print(os.getcwd())#ディレクトリ確認 if filename.find('.txt')>=0: pass else: filename+='.txt' try: ofile=open(filename,'tw') except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません", e) sys._exit()#ファイルがなければ終了#tryのときは_exit()が良いらしい except Exception as e: # Exceptionは、それ以外の例外が発生した場合 print(e) ofile.write(outputText) ofile.close() |
>追記、2022/02/19、ヤフーファイナンスのデザイン変更により、Pythonスクリプト変更。変更内容は姉妹ブログへ
Python を使ってヤフーファイナンス 日経平均株価の時系列を取得、スクレイピング
#https://stocks.finance.yahoo.co.jp/stocks/history/?code=998407.O
#日経平均株価の時系列のテーブル表から該当の文字列を取得する、日付 始値 高値 安値 終値
https://github.com/maseda1030/yahoofinance_python.git
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
import time from selenium import webdriver import csv import datetime import os class jikeiretsu: def __init__(self,day,kopen,khigh,klow,kclose): self.day=day self.kopen=kopen self.khigh=khigh self.klow=klow self.kclose=kclose class yahooFinanceTimeline: def __init__(self): self.hello="yahoo Finace" def getTimeline(self): #print("カプセルにして取得") #動作確認のためのメソッド # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい url='https://stocks.finance.yahoo.co.jp/stocks/history/?code=998407.O' driver.get(url) time.sleep(1) #title = driver.title #print(title) main = driver.find_element_by_id("main") el = main.find_element_by_css_selector(".padT12.marB10.clearFix") tableElem = el.find_element_by_tag_name("table") trs = tableElem.find_elements_by_tag_name("tr") ths = trs[0].find_elements_by_tag_name("th") kabuka="" #thタグは1行目のTRにだけ存在する。 for f in range(0,len(ths)):#日付 始値 高値 安値 終値 ths[f].text kabuka+=ths[f].text+" " print(kabuka) jikeiretsuArray=[] for i in range(0,len(trs)): tds = trs[i].find_elements_by_tag_name("td") if len(tds)>=5: getJikeiretsu=jikeiretsu(tds[0].text,tds[1].text,tds[2].text,tds[3].text,tds[4].text) jikeiretsuArray.append(getJikeiretsu) else: print("tdsの数が足りない行があるので無視") time.sleep(1) driver.close()#起動したウィンドウを閉じる print("通常出力日付 始値 高値 安値 終値") for f in range(0,len(jikeiretsuArray)): print(jikeiretsuArray[f].day,jikeiretsuArray[f].kopen,jikeiretsuArray[f].khigh,jikeiretsuArray[f].klow,jikeiretsuArray[f].kclose) print('+----------------------+') print("個人的出力順、CSVカンマ区切りで 桁区切りのカンマは削除、昇順day,High,Low,Open,Close,AdjClose")#("個人的な利用の出力順、昇順day,High,Low,Open,Close,AdjClose") count=len(jikeiretsuArray) tmpstr="" for n in reversed(range(count)):#).replace(',','') カンマを削除 tmpstr=jikeiretsuArray[n].day+","+jikeiretsuArray[n].khigh.replace(',','')+","+jikeiretsuArray[n].klow.replace(',','')+","+\ jikeiretsuArray[n].kopen.replace(',','')+","+jikeiretsuArray[n].kclose.replace(',','')+","+\ jikeiretsuArray[n].kclose.replace(',','') print(tmpstr) #テスト用のメソッドなので実際は不要なメソッド def getTimelineTest(self): print("test") #動作確認のためのメソッド # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい url='https://stocks.finance.yahoo.co.jp/stocks/history/?code=998407.O' #driver.get(url+str(code)+'.T') driver.get(url) time.sleep(1) #title = driver.title #print(title) main = driver.find_element_by_id("main") #el = main.find_element_by_id("stockinf") #nextClass = el.find_element_by_css_selector(".stocksDtl.clearFix") el = main.find_element_by_css_selector(".padT12.marB10.clearFix") tableElem = el.find_element_by_tag_name("table") trs = tableElem.find_elements_by_tag_name("tr") ths = trs[0].find_elements_by_tag_name("th") kabuka="" #thタグは1行目のTRにだけ存在する。 for f in range(0,len(ths)):#日付 始値 高値 安値 終値 ths[f].text kabuka+=ths[f].text+" " print(kabuka) kabuka="" for i in range(0,len(trs)): tds = trs[i].find_elements_by_tag_name("td") for m in range(0,len(tds)): kabuka+=tds[m].text+" "#ここはカプセルにしてクラスオブジェクトを配列に入れたほうがいいかも #print(kabuka) print(kabuka) kabuka="" time.sleep(1) driver.close()#起動したウィンドウを閉じる ###START print("START#####################################") print("Japan yahoo financeの日経平均株価の時系列") getstock=yahooFinanceTimeline() getstock.getTimeline() print("End#####################################") |
出力例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
START##################################### Japan yahoo financeの日経平均株価の時系列 カプセルにして取得 日付 始値 高値 安値 終値 tdsの数が足りない行があるので無視 通常出力日付 始値 高値 安値 終値 2021年2月16日 30,229.46 30,714.52 30,191.65 30,467.75 2021年2月15日 29,662.41 30,092.34 29,662.41 30,084.15 2021年2月12日 29,635.88 29,650.51 29,417.32 29,520.07 2021年2月10日 29,412.55 29,562.93 29,368.18 29,562.93 2021年2月9日 29,435.61 29,585.75 29,350.48 29,505.93 2021年2月8日 28,831.58 29,400.56 28,817.6 29,388.5 2021年2月5日 28,631.46 28,785.71 28,548.27 28,779.19 2021年2月4日 28,557.46 28,600.22 28,325.89 28,341.95 2021年2月3日 28,482.71 28,669.95 28,402.3 28,646.5 2021年2月2日 28,207.48 28,379.31 28,089.12 28,362.17 2021年2月1日 27,649.07 28,107.1 27,649.07 28,091.05 2021年1月29日 28,320.72 28,320.72 27,629.8 27,663.39 2021年1月28日 28,169.27 28,360.48 27,975.85 28,197.42 2021年1月27日 28,665.34 28,754.99 28,542 28,635.21 2021年1月26日 28,696.3 28,740.71 28,527.81 28,546.18 2021年1月25日 28,698.89 28,822.29 28,566.85 28,822.29 2021年1月22日 28,580.2 28,698.18 28,527.16 28,631.45 2021年1月21日 28,710.41 28,846.15 28,677.61 28,756.86 2021年1月20日 28,798.74 28,801.19 28,402.11 28,523.26 2021年1月19日 28,405.49 28,720.91 28,373.34 28,633.46 +----------------------+ 個人的出力順、カンマを削除、昇順day,High,Low,Open,Close,AdjClose 2021年1月19日,28720.91,28373.34,28405.49,28633.46,28633.46 2021年1月20日,28801.19,28402.11,28798.74,28523.26,28523.26 2021年1月21日,28846.15,28677.61,28710.41,28756.86,28756.86 2021年1月22日,28698.18,28527.16,28580.2,28631.45,28631.45 2021年1月25日,28822.29,28566.85,28698.89,28822.29,28822.29 2021年1月26日,28740.71,28527.81,28696.3,28546.18,28546.18 2021年1月27日,28754.99,28542,28665.34,28635.21,28635.21 2021年1月28日,28360.48,27975.85,28169.27,28197.42,28197.42 2021年1月29日,28320.72,27629.8,28320.72,27663.39,27663.39 2021年2月1日,28107.1,27649.07,27649.07,28091.05,28091.05 2021年2月2日,28379.31,28089.12,28207.48,28362.17,28362.17 2021年2月3日,28669.95,28402.3,28482.71,28646.5,28646.5 2021年2月4日,28600.22,28325.89,28557.46,28341.95,28341.95 2021年2月5日,28785.71,28548.27,28631.46,28779.19,28779.19 2021年2月8日,29400.56,28817.6,28831.58,29388.5,29388.5 2021年2月9日,29585.75,29350.48,29435.61,29505.93,29505.93 2021年2月10日,29562.93,29368.18,29412.55,29562.93,29562.93 2021年2月12日,29650.51,29417.32,29635.88,29520.07,29520.07 2021年2月15日,30092.34,29662.41,29662.41,30084.15,30084.15 2021年2月16日,30714.52,30191.65,30229.46,30467.75,30467.75 End##################################### |
#複数企業コード対応版 企業コードから企業名と株価を取得する
#Anaconda JupyterLabによる動作 Shift+Enterで実行になる
動作環境:MacOS 、Python、Anaconda JupyterLab
https://github.com/maseda1030/yahoofinance_python.git
面倒だった点は、複数タグが存在するタグ名tr,td,ddの扱い。複数の場合forで回して配列みたいにしてデータを取得する必要があるし、スペースが含まれるクラス名は別のプロパティ?で読み込む必要がある。
find_element_by_class_nameでスペースを含むクラス名はエラーとなるので、find_element_by_css_selectorとしてドット.で接続する。
誤り:el.find_element_by_class_name(‘stocksDtl clearFix’)
正:el.find_element_by_css_selector(“.stocksDtl.clearFix”)
以下はサンプルコード。使い方は、codelistに企業コードを入力すると、ヤフーファイナンスからスクレイピングして「コード、企業名、終値株価、前日比、日付」を半角スペース区切りで出力するだけ。ヤフーファイナンスに接続するときは、取得間隔を1,2秒程度空けている。連続で取得するとサーバ負荷が増えるから強制切断とかアクセスをブロックされてしまうかもしれない。
>>コード修正、Spanタグが存在しない時間帯があるみたいなので、存在するかどうかのチェックを追記した。2021年2月8日、12時06分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
import time from selenium import webdriver import csv import datetime import os codelist=[] codelist=[6315,2207,6928,5724,2702]#企業コード、複数 def getName(code): # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい url='https://stocks.finance.yahoo.co.jp/stocks/detail/?code=' driver.get(url+str(code)+'.T') #time.sleep(1) #title = driver.title #print(title) main = driver.find_element_by_id("main") el = main.find_element_by_id("stockinf") #エラー、nextClass = el.find_element_by_class_name('stocksDtl clearFix') Classにスペースが含まれるとエラーになるので別のメソッドにする nextClass = el.find_element_by_css_selector(".stocksDtl.clearFix") subNextClass = nextClass.find_element_by_class_name("forAddPortfolio") tableElem = subNextClass.find_element_by_class_name("stocksTable") dlElem = subNextClass.find_element_by_css_selector(".stocksInfo.clearFix") dds = dlElem.find_elements_by_tag_name("dd") trs = tableElem.find_element_by_tag_name("tr") symbol = trs.find_element_by_class_name("symbol") name = symbol.find_element_by_tag_name("h1") stockinfo = [] stockinfo.append(str(code)) stockinfo.append(name.text) tds = trs.find_elements_by_tag_name("td") for j in range(0,len(tds)): if tds[j].text != "": stockinfo.append(tds[j].text) ##ddタグの中に spanタグがあって、他に文字がある。spanタグのテキストだけが取得したかった。 ##>>追記 2021年2月8日エラー処理を追加 if len(dds[1].find_elements_by_tag_name("span"))>0:#存在チェック elementでなくてelementsにする stockinfo.append(dds[1].find_element_by_tag_name("span").text)#日付 output=stockinfo driver.close()#起動したウィンドウを閉じる return output ###START print("START#####################################") tmpStr="" f=0 for i in codelist: outputStr=getName(i) #コード、企業名、終値株価,前日比、日付 for m in outputStr: if f==2: tmpStr+=m+'円'+' ' else: tmpStr+=m+' ' f+=1 #print(outputStr[0],outputStr[1],outputStr[2]+"円",outputStr[3],outputStr[4]) print(tmpStr) tmpStr="" f=0 time.sleep(2) print("End#####################################") |
更に修正した。メソッドをクラスにした。また始値、高値、安値を入れた。2021/02/12
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
#Python を使って#ヤフーファイナンス 情報を取得、#スクレイピング #複数企業コード対応版 企業コードから企業名と株価を取得する #Anaconda JupyterLabによる動作 Shift+Enterで実行になる #修正、クラスに変更 #修正2021/02/12、始値、高値、安値を入れた #手作業はcodelistにコードを入れる import time from selenium import webdriver import csv import datetime import os codelist=[] codelist=[6134,7952,6462 ]#企業コード、複数 #これからは、どんなに小さなプログラムでもクラスを使用する。そのほうが汎用性がある。 #同じメソッド名でもクラス名が異なればコピペして使用できる class yahooFinanceClass(): def __init__(self): self.hello="yahoo Finace" def getName(self,code): # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする & |