ヤフーファイナンスの株ランキングデザイン変更になったのでWebスクレイピングのPythonスクリプトを変更する。現在使用中のWebスクレイピングのスクリプトを実行するとクラス名が見つからない等のエラーが発生する。
例、新しい年初来高値のランキング表
CSV出力したものをエクセルで開いた例
select文で出力した
>スクリプト 株探の企業概要のスクレイピング クラス
このクラスをヤフーファイナンスのランキングと合わせて情報を取得する。
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 |
import os import re import sys import time import datetime import glob import shutil from typing import Container import pandas as pd import requests from bs4 import BeautifulSoup #2022.1.9 kabutan #今回はpythonの命名規則にできるだけ準じるように記載する。以前はアバウトだった。 #クラス、例外 命名規則 # CapWords方式 (先頭だけ大文字の単語を繋げる、アンダースコアは使わない) # 複数の単語を使う場合は2番目以降の単語も大文字始まり #メソッド 命名規則 # 関数、メソッド 小文字のみ、必要に応じて単語をアンダースコアで区切る #変数、引数 # 小文字のみ、必要に応じて単語をアンダースコアで区切る #やりたいことは #ヤフーファイナンスから年初来高値銘柄を取得してCSVにして #株探から企業情報概要を取得して #Mysqlに登録したい #株探から取得した概要をCSVに結合して、再度CSVに出力する #CSV出力は、都度結合かまとめて結合する。 class CgetWebScraping(): def __init__(self,filename) -> None: self.filename=filename #csv filename #csvファイルから文字列を1行ずつ取得するループ処理 def kabutan_loop(self)->str: output="" outputArray=[] try: with open(self.filename,'tr') as fin: for iline in fin: output=self.kabutan_get_webscraping(iline) #return #print(output) #ここでCSVの行に結合するか、別途配列に入れてCSVに出力するかを検討する #メイン側に記載してCSVと企業概要を結合するかどうか仕様を決める #outputArrayはいままでのCSVデータに概要を結合したもの outputArray.append(output) print(".wait 2s") time.sleep(2) #一気に読み込むと負荷がかかるので2秒待ち # outputCSVForTableColum()を参考にして概要をyobiとして結合する #その場合hは、外出しに関数を作成する。 except FileNotFoundError as e: print("not found file",e) print("end.") sys._exit() except Exception as e: print(e) return outputArray #1行の中身はカンマ区切りなのでそれを配列かループで処理する def kabutan_get_webscraping(self,strmoji) -> str: output="" tmp=[] tmp=strmoji.strip().split(",") #tmp=strmoji.strip().split(",")#tmp配列に企業コード、企業名の順で入っているはず if len(tmp)==0: print("not array line string. Please check csv file. ") else: #print(tmp[0],tmp[1]) #here 2022/01/12 kabutan web scraping #tmp[0] is code. exp 9201 #2022/01/26 #取得した概要をstrにカンマを付けて結合すればよい #2022/02/06 #行末の改行を削除して、結合してから改行を付ける #strmojiはreplace( '\n' , '' )改行は削除、なしにして、行末に改行を付けた output=strmoji.replace( '\n' , '' )+','+self.getPage(tmp[0]) + '\n'#return return output def getPage(self,marketCode) -> str: output="" #企業概要を文字列として返す url='https://kabutan.jp/stock/?code=' #kabutan https://kabutan.jp/stock/?code=3431 if marketCode == '': print('not exist code') sys.exit() target_url=url+str(marketCode) r=requests.get(target_url) soup=BeautifulSoup(r.text,'lxml') # #find id or class 2022/01/13 div_container=soup.find(id="container") if div_container==None: print("該当するid,classが見つかりませんでした。") output="" return output else: main=div_container.find(id="main") kobetsu_right=main.find(id="kobetsu_right") company_block=kobetsu_right.find(class_="company_block") table=company_block.find("table") tbody=table.find("tbody") trs=tbody.find_all("tr")#複数TR for i in range(0,len(trs)): ths=trs[i].find_all("th") if len(ths)>0: if ths[0].text=="概要": #print("概要あり") tds=trs[i].find_all("td") if len(tds)>0: #カンマが混ぜってたら困るので置換する output=tds[0].text.replace(","," ")#カンマをスペースに置換 #2022/02/06 #print (output) return output #outputCSVForTableColum()を参考にして、そのままファイル出力する def outputFromCSV(self,strArray,filename,path): #strArray,文字列の配列でカンマを含む1行単位で配列に入れているだけで改行あり if filename.find('.csv')==-1:#拡張子.csvがないときは付記する filename+='.csv' os.chdir(path)#ディレクトリ変更 print(os.getcwd())#ディレクトリ確認 try: ofile=open(filename,'tw') # w:上書き、a:追記、t:テキスト except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません", e) sys._exit()#ファイルがなければ終了#tryのときは_exit()が良いらしい except Exception as e: # Exceptionは、それ以外の例外が発生した場合 print(e) for i in strArray:#tableColumクラスのメンバ変数code,market,name,time,price,value,preValue,percent ofile.write(i) ofile.close() ## ## #Start program #CSVはフルパスなので注意して # /Users/toshiromaseda/Documents/2021年/2021年株/yahoofinance_data/yearHighPrice20220126.csv # 以下を実行環境に記載して実行する。 ''' #/Users/toshiromaseda/Documents/2020年株関連/kabu_python内 import cgetwebscraping#個人用メソッド pyファイルcgetwebscraping ###START importlib.reload(cgetwebscraping) print("スクレイピングSTART#####################################") csvfile="/Users/toshiromaseda/Documents/2021年/2021年株/yahoofinance_data/testyearHighPrice20220126.csv" obj=cgetwebscraping.CgetWebScraping(csvfile)#import名.クラス名になる output="" output=obj.kabutan_loop() print(output) ここまでOKナノで、CSV出力する。そしてあとはMysqlに登録できればOK ''' |
>スクリプト ヤフーファイナンスの出来高増加率、年初来高値更新ランキングを取得するスクリプト
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 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 |
#Anaconda Navigator pythonUseMysql2.ipynbを使用 #Chrome上で実行する。Webフレームワークではない #DB(Mysql)を使用する理由 #利点:データの集計、検索はSQL文が非常に楽で便利、条件Whereを追加すれば細かく範囲を出力できる #テーブル同士をリレーショナルに管理できる #・エクセルでマクロを使ってデータを使用するのはどうか?マクロは苦手 #マクロを使ってセルを集計するのにマクロを組む手間がかかる。行が増えたり列が増減すると都度マクロの修正が必要 #シート同士でリレーショナルに集計できる方法がわからない。 #/Users/toshiromaseda/Documents/2020年株関連/kabu_python import MySQLdb#古い更新されないライブラリらしいのでmysql-connector-pythonに今後切り替え import mysql.connector#mysql-connector-python 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#スクレイピング #2022/02/06、改修、ヤフーファイナンスのデザイン変更による、htmlのクラス名テーブル名の変更対応 #URLの記載等も変わっている。なおhtmlのクラス名、テーブル名は共通で使用されている。 #2022/02/07 #次の作業 次はここから next JupiterLab側でclass yahooFinanceDekidaka()のgetPageCount2()をテスト実行 #まだ確認作業をしていない。 # #株探の先物オプション記事について、特定文字列について空白をカンマに置換処理するためのメソッド #def latterHalf(text):数字を後ろから探す #def outMojiMKIII(text):空白をカンマに置換処理 #def csvToMysql(difile,dtablename,doptionDate,dmonth): #def checkFile(filename):fileが存在するかチェック。パスは固定 #def fileMove(file,path):file移動 #def selectMysql(tablename):select #ADD 2021/02/18 #yahooFinanceJapanから出来高率のデータを取得してMysqlのテーブルに登録する #URL https://info.finance.yahoo.co.jp/ranking/ #kd= ランキング種別項目koumoku 、mk= 市場market #kd=33 出来高、kd=50 一株当期利益EPS #mk=3 東証一部、 mk=4東証二部 #株式ランキング、出来高率、東証一部 #https://info.finance.yahoo.co.jp/ranking/?kd=33&mk=3&tm=d&vl=a #https://info.finance.yahoo.co.jp/ranking/?kd=33&tm=d&vl=a&mk=3&p=1 #https://info.finance.yahoo.co.jp/ranking/?kd=33&tm=d&vl=a&mk=3&p=2 #株式ランキング、出来高率、東証二部 #https://info.finance.yahoo.co.jp/ranking/?kd=33&mk=4&tm=d&vl=a #一株当期利益 東証一部 #https://info.finance.yahoo.co.jp/ranking/?kd=50&mk=3&tm=d&vl=a #一株当期利益東証二部 #https://info.finance.yahoo.co.jp/ranking/?kd=50&mk=4&tm=d&vl=a class kamokuClass(): def __init__(self): self.kamoku='' def returnKamoku(self,kamokuCode): if kamokuCode==9:#高PER self.kamoku='高PER' elif kamokuCode==29:#yearHigh self.kamoku='年初来高値' elif kamokuCode==33:#出来高 self.kamoku='出来高' elif kamokuCode==50:#一株当期利益EPS self.kamoku='一株当期利益EPS' else: self.kamoku='kamokuClassに登録なし,' kamoku=self.kamoku return kamoku class tableColum(): def __init__(self,num,code,market,name,time,price,value,preValue,percent): self.num=num#たぶん使わないけど先頭なのでいれておく self.code=code self.market=market self.name=name self.time=time self.price=price self.value=value self.preValue=preValue self.percent=percent class epsTableColum(): def __init__(self,num,code,market,name,time,price,eps,kessanDate): #使用しない変数num,timeがあるのは、ループするときに挿入する順番を間違えないようにするためとコードを見やすくするため #使用するときは変数名を指定するので、不使用でも問題はない。 self.num=num#たぶん使わないけど先頭なのでいれておく self.code=code self.market=market self.name=name self.time=time#使用せず、単に配列の要素数を合わせるために使っている self.price=price self.eps=eps self.kessanDate=kessanDate self.kessanSyubetsu=''#連結(連)、単体(単)を格納するための変数 def changeEpsKessanSyubetsuWord(self): #「(連)」が含まれたら削除したい もしかすると「(単)」もあるので単、連結のテーブルカラムを用意するかな self.kessanSyubetsu='' tmpStr='' tmpStr=self.eps#一旦文字列を移してから文字列置換をする.replaceは元のデータは書き換えられない。 if tmpStr.find('(連)')>=0: self.kessanSyubetsu='(連)' self.eps=tmpStr.replace('(連)','') elif tmpStr.find('(単)')>=0: self.kessanSyubetsu='(単)' self.eps=tmpStr.replace('(単)','') def changeKessanDate(self): #kessanDateの日付は年月2021/3とかのようになっていて日がないので日をダミーで付記する。 #2021/3を2021/3/01みたいに日を付ける #なので、4桁2桁2桁か/を基準にして数を数えて日が足りていないことを判別する #splitで配列にしてその数で判別したほうが早いかもしれない。 tmpArray=self.kessanDate.split('/') if len(tmpArray)==2:#yyyy,mmのときは2つしかないので、dayがないので付記する self.kessanDate+='/01' #高PER会社予想 class perTableColum(epsTableColum): def __init__(self,num,code,market,name,time,price,kessanDate,eps,per): self.num=num#たぶん使わないけど先頭なのでいれておく self.code=code self.market=market self.name=name self.time=time#使用せず、単に配列の要素数を合わせるために使っている self.price=price self.kessanDate=kessanDate self.eps=eps self.per=per self.kessanSyubetsu=''#連結(連)、単体(単)を格納するための変数 self.PerKessanSyubetsu='' #高PER用の(連)、単体(単)の変換処理 def changePerKessanSyubetsuWord(self): #PERの(連)、単体(単)種別用 #「(連)」が含まれたら削除したい もしかすると「(単)」もあるので単、連結のテーブルカラムを用意するかな self.PerKessanSyubetsu='' tmpStr='' tmpStr=self.per#一旦文字列を移してから文字列置換をする.replaceは元のデータは書き換えられない。 if tmpStr.find('(連)')>=0: self.PerKessanSyubetsu='(連)' self.per=tmpStr.replace('(連)','') elif tmpStr.find('(単)')>=0: self.PerKessanSyubetsu='(単)' self.per=tmpStr.replace('(単)','') #年初来高値クラス、BeautifulSoap版 class yearHighTableColum(epsTableColum): def __init__(self,code,market,name,torihikiDay,price,previousYearHighDay,previousYearHighPrice,highPrice): #self.num=num#順位は年初来高値にはなかった。たぶん使わないけど先頭なのでいれておく self.code=code self.market=market self.name=name self.torihikiDay=torihikiDay#ここが場中で時刻のみに変わるので、日付+時刻に変える self.price=price self.previousYearHighDay=previousYearHighDay self.previousYearHighPrice=previousYearHighPrice self.highPrice=highPrice def changeTimeAndDate(self,cdate): #self.torihikiDay="" #cdateを変更する#更新日、時刻 「最終更新日時:」年、月、日 時 分>>年月日に変更する tmpStr="" tmpStr=cdate.replace('最終更新日時:','') #年、月、日を取得して、2021/03/03にする。 nenPosition=tmpStr.find('年') tsukiPosition=tmpStr.find('月') hiPosition=tmpStr.find('日') updateDate=tmpStr[0:nenPosition]+'/' tmpYear=updateDate#get year updateDate+=tmpStr[nenPosition+1:tsukiPosition]+'/' updateDate+=tmpStr[tsukiPosition+1:hiPosition] if self.torihikiDay.find(':')>=0:#場中は時刻になり、市場が終わると日付になっている。 self.torihikiDay=updateDate+' '+self.torihikiDay else: self.torihikiDay=tmpYear+self.torihikiDay+' 00:00'#2021/11/14 翌日にスクレイピングすると日付が異なるので対応 #新ヤフーファイナンスのランキング 「出来高率」が、「出来高増加率」「出来高減少率」の2つに別れた #2022/02/06 #新しい 「出来高増加率」 に修正する #出来高増加率、東証一部 #https://finance.yahoo.co.jp/stocks/ranking/volumeIncrease?market=tokyo1&term=previous #https://finance.yahoo.co.jp/stocks/ranking/volumeIncrease?market=tokyo1&term=previous&page=1 #https://finance.yahoo.co.jp/stocks/ranking/volumeIncrease?market=tokyo1&term=previous&page=2 #2部 #https://finance.yahoo.co.jp/stocks/ranking/volumeIncrease?market=tokyo2&term=previous #マザーズ #https://finance.yahoo.co.jp/stocks/ranking/volumeIncrease?market=tokyoM&term=previous #ジャスダックすべて #https://finance.yahoo.co.jp/stocks/ranking/volumeIncrease?market=jasdaqAll&term=previous ''' 空白がある場合のfind elementsは.でつなげる find_elements_by_css_selector(".grid.is-type3.h-clear") >次へ リンクのID,クラス構成 2022/02/06 id=wrapper id=root class=_2xTIZru5 class=_6Py7qVbv class=rMa89KwT _34OEfNCy _2STjqM5M class=XuqDlHPN class=_1IdtoV3i paF0-B-R _3qa3JjJ- ----共通ここまで↑ class=ux3Ged_S _3x6-dc8h class=_1IdtoV3i _3WzzJnld class=_1GMw3s8h ul class=KkN9Pygd 複数li class=_36peHlF5 出来高、ID,クラス構成 id=wrapper id=root class=_2xTIZru5 class=_6Py7Vbv class=rMa89KwT _34OEfNCy _2STjqM5M class=XuqDlHPN class=_1IdtoV3i paF0-B-R _3qa3JjJ- ----共通ここまで↑ id=item, class=_3Xekfsw9 class=_1IdtoV3i _3WzzJnld >銘柄がありませんになる table class=_2JCHU1fr tbody non 複数tr class=WRru9z7J 複数td class=_3C-qA9GA a href:企業名 複数ul class=_23uoD1_R 複数li class=_2AbqlM50: コード、1部、? ---年初来高値もここまで同じ↑ 年初来高値 出来高と同じ table class=_2JCHU1frまでを共通コードで取得すればよさそう tdの構成が、それぞれのランキングで異なる構成に、数、リスト数等で変わると思う 新しく作成または修正したメソッド2022年2月10日 class yahooFinanceDekidaka():内、以下にメソッド def getrankingUrl(self,koumokuStr,marketStr,pageNum): def commonTag(self,soup): def commonNextTag(self,soupTag): def commonDekidakaUntilTableTag(self,soupTableTag): def getDekidakaNewType(self,koumokuStr,marketStr,pageNum): def getPageCount2(self,koumokuStr,marketStr,pageNum): def mysqlConnectorInsertFuncDekidaka(self,inputStr,cursor): 年初来高値、追加、修正箇所 1115行くらいから作業 2022/02/10 次はここから 新規OR不要 def commonDekidakaUntilTableTag(self,soupTableTag): 新規 def getDekidakaNewType(self,koumokuStr,marketStr,pageNum): 不要多分 def getPageCount2(self,koumokuStr,marketStr,pageNum): 修正済 def mysqlConnectorInsertFuncDekidaka(self,inputStr,cursor): ''' #以下は、古い出来高率の仕様 #ヤフーファイナンスのランキングから出来高率を開く,EPS1株利益クラスで継承してる #今後、出来高以外の条件のデータを取得することもあるので汎用性を考慮する #市場ごとに必要なページ数を取得する。 #ループが2個 URLの引数を調整 #市場を番号で管理する #注意 ページ数がいくつまであるかのチェックは行っていない。10Pくらいはいつでも存在する前提。ページ数が10を越えるとそのページが存在しないこともあり得る。 #東証一部で約5ページを取得 #東証二部で約5ページを取得 #一旦CSVに出力する(テスト、動作確認のため) #CSVを読み込みMYsqlの所定のテーブルに追加する #SELECT文で必要な情報を出力する、TEXTかCSV class yahooFinanceDekidaka(): def __init__(self): #print() pass def getrankingUrl(self,koumokuStr,marketStr,pageNum): #koumokuStr:volumeIncrease,yearToDateHigh #marketStr:tokyo1,tokyo2,tokyoM,jasdaqAll #2022/02/06 url='https://finance.yahoo.co.jp/stocks/ranking/' fuzokuUrl='?market=' # 次はここから、 できるだけ汎用性でスクリプト作成してください term='&term=' if koumokuStr == 'volumeIncrease' or koumokuStr == 'volumeincrease': #出来高増加率,前日比 この期間を変更したい場合は、別のtermをif追加する。 #現在はpreviousで固定にしている term+="previous" elif koumokuStr == 'yearToDateHigh' or koumokuStr == 'yeartodatehigh': #年初来高値 term+="daily" elif koumokuStr == '': pass else: pass output=url + koumokuStr + fuzokuUrl + marketStr + term + '&page=' + str(pageNum) return output def commonTag(self,soup): #始まりはここから、 次はcommonNextTagか「commonDekidakaUntilTableTag」に移る #共通タグ beautifulSoupなのでfind find_allを使用する #要素名クラスなので複数ありの場合もあるが、今回のデザイン変更でクラス名といえどユニークな要素名だから #直接一発でFindで見つかるかもしれないが、一個ずつ探している。idはユニークなので一発でも良さそうだが、 #クラス直下にごとに複数有るかもしれないので、結局一個ずつ探している output="" wrapper=soup.find(id="wrapper") child1=wrapper.find(id="root") child2=child1.find(class_="_2xTIZru5") child3=child2.find('div',class_="_6Py7qVbv")#ここでrMa89KwTを探したら見つからず if child3==None or child3=="": print("child3:_6Py7qVbvが見つかりませんでした") child4=[] child4=child2.find_all('div',class_="rMa89KwT")#1階層上から探したら2個見つかった if child4==None or child4=="": print("rMa89KwTが見つかりませんでした") child5="" #child4=child3.find_all(class_="rMa89KwT")#再起検索なし,recursive=False #child4=child3.find_all(class_="rMa89KwT _34OEfNCy _2STjqM5M") この記述は見つからず、3つの別のクラスと認識される #child4=child3.find_all(class_="rMa89KwT._34OEfNCy._2STjqM5M") ドットを入れてもだめ。 #rMa89KwT _34OEfNCy _2STjqM5M #ドットで結合したけどだめだったので、入れ子で探すことにした。 #child4=child3.find(class_="rMa89KwT _34OEfNCy _2STjqM5M")#スペースを含む要素はドットで結合 if len(child4)==0: output="" print("rMa89,,,がありませんでした。") return output else: #print("rMa89の個数:"+str(len(child4))) for i in range(0,len(child4)): #print ("class:"+str(child4[i].get("class"))) #class:['rMa89KwT'] こんな感じで取得できた #class:['rMa89KwT', '_34OEfNCy', '_2STjqM5M'] 3つ存在する #数が3このときのsoupを取得すればよい #print ( "class個数:"+ str(len((child4[i].get("class")))) ) if len(child4[i].get("class")) == 3: #print("rMa89KwT _34OEfNCy _2STjqM5Mを取得") if child4[i].find(class_="XuqDlHPN")!=None: child5=child4[i].find(class_="XuqDlHPN") #ここから、次のXuqDlHPN に移る #以下も入れ子で探す #output=child5.find(class_="_1IdtoV3i paF0-B-R _3qa3JjJ-")この記述だとクラスが3つ存在となる。1つのクラス名とは認識されない if len(child5.find_all(class_="_1IdtoV3i"))==0: print("_1IdtoV3iが見つかりませんでした。") else: child6=child5.find_all(class_="_1IdtoV3i") for m in range(0,len(child6)): #print ("class2:"+str(child6[m].get("class"))) if len(child6[m].get("class"))==3:#_1IdtoV3i paF0-B-R _3qa3JjJ- #print("_1IdtoV3i paF0-B-R _3qa3JjJ-を取得") output=child6[m] return output else: print("XuqDlHPNが見つかりませんでした。") return output def commonNextTag(self,soupTag): #次へを取得 共通タグからSoupを取得する output=0 if soupTag=="": print("soupが空です。") return output counter=0 #child1=soupTag.find(class_="") #2つのワードのうちの最初の1つで検索する child1=[] child2=[] child1=soupTag.find_all(class_="ux3Ged_S")#[ux3Ged_S _3x6-dc8h]を探す if len(child1)==0: print("ux3Ged_Sが見つかりませんでした。") return output else: for i in range(0,len(child1)): #print("commonNextTag class:"+str(child1[i].get("class"))) if len(child1[i].get("class")) == 2: #次のクラス要素を取得する #child2=child1.find("_1IdtoV3i _3WzzJnld") child2=child1[i].find_all(class_="_1IdtoV3i")#次のクラス「_1IdtoV3i _3WzzJnld」 if len(child2)==0: print("_1IdtoV3iが見つかりませんでした。") return output else: for m in range(0,len(child2)): #print("commonNextTag class2:"+str(child2[m].get("class"))) if len(child2[m].get("class")) == 2:#2個あるものは今の所ない if child2[m].find(class_="_1GMw3s8h")==None: print("_1GMw3s8hが見つかりませんでした。") output=0 return output else: child3=child2[m].find(class_="_1GMw3s8h") if child3==None: print("_1GMw3s8hが見つかりませんでした。") else: if child3.find("ul")==None:#,_class="KkN9Pygd" print("Ulタグ、KkN9Pygdが見つかりませんでした。") return output else: child4=child3.find("ul") #複数のliがあるので数える lis=child4.find_all("li") #print(len(lis)) if len(lis) <= 3: print("1ページだけ") #1ページでもLIタグは3つ存在する output=1 return output else: #print(" 2ページ以上あり") output=len(lis)-2#(前へ、次への2つだけ引く) return output #Liタグが4つ以上ある場合の処理 #for f in range(0,len(lis)): # if lis[f].find("button")!=None: #数字だけを取り込む #print("Page") # print(lis[f].find("button").text) # if lis[f].find("button").text.isdigit() : # counter=lis[f].find("button").text # output=int(counter) # return output # else: # print("buttonがない") #何もしないけど、インデントを知りたいのでPASSがある # pass return output #override ただし、もしかすると年初来高値も同じかもしれない。そのときはこのまま使用する def commonDekidakaUntilTableTag(self,soupTableTag): #共通タグを受け取り、テーブルTagのところまで output="" #child1=soupTableTag.find(class_="_3Xekfsw9") child1=soupTableTag.find(id="item") if child1==None: print("commonDekidakaUntilTableTag()内、_3Xekfsw9が取得できませんでした。") return output child11=child1.find_all(class_="_1IdtoV3i")#_1IdtoV3i _3WzzJnld if len(child11)==0: print("commonDekidakaUntilTableTag()内、_1IdtoV3iが見つかりませんでした") return output else: for i in range(0,len(child11)): if len(child11[i].get('class'))==2:#2個あるはず output=child11[i].find("table")#tag if output==None: print("commonDekidakaUntilTableTag()内、table が見つかりませんでした") output="" return output #tableタグまで取得できた return output #override 出来高 def getDekidakaNewType(self,koumokuStr,marketStr,pageNum): #ここから個別ランキングのデータ取得になる 2022/02/06 #テーブルタグを受け取り、TRをループで回す output="" #1件もないときの処理を入れる。この関数を使用する場所で、0件のときの処理を #入れるので、この関数に1件もないときの処理を入れる必要はない target_url=self.getrankingUrl(koumokuStr,marketStr,pageNum) r=requests.get(target_url) soup=BeautifulSoup(r.text,'lxml') common=self.commonTag(soup) soupDekidaka=self.commonDekidakaUntilTableTag(common) if soupDekidaka=="": print("getDekidakaNewType()の戻り値を取得できませんでした。") return output #commonDekidakaUntilTableTag()から受け取り、出来高用の内容に変える。 outputArray=[] tbody=soupDekidaka.find("tbody") trs=tbody.find_all("tr") #loop for i in range(0,len(trs)): tds=trs[i].find_all("td") #tdは4個 next liに対応させる companyName=tds[0].find("a").text lis_parent=tds[0].find("ul") lis=lis_parent.find_all("li",recursive=False)#再起検索なし,recursive=False #liは3個あるがlis[2]は不要 code=lis[0].text market=lis[1].text #lis[2] 掲示板へのリンクなので不要 spans=tds[1].find_all("span",recursive=False)#spanがあるか調べる #print("getDekidakaNewType():spans:") #print(spans) #print(spans[0].find("span").text)#クラスを指定しないでやってみた_class="_3rXWJKZF" #kabuka=spans[0].find("span",_class="_3rXWJKZF").text クラス名なしでテキストを取得できた。 kabuka=spans[0].find("span").text #時刻のみの場合は、日付をつける。 hidukeTime=spans[1].text#日付02/04 or time if hidukeTime.find(":")!=None:#時刻の場合は、日付にする #今日の年月日を取得する。 dt_now=datetime.datetime.now() StockDate=dt_now.strftime('%Y/%m/%d')# hidukeTime=StockDate else: dt_now=datetime.datetime.now() StockDate=dt_now.strftime('%Y') tmpstr=hidukeTime hidukeTime=StockDate+'/'+tmpstr#年と月日を結合する #2022/02/08 #クラス名を指定しないで取得できるかも spans2=tds[2].find_all("span",recursive=False)#spanがあるか調べる #print(spans2)#<span class="_1fofaCjs _2aohzPlv _3uM9p7Zj"><span class="_1-yujUee"><span class="_3rXWJKZF">235,900</span><span class="_2SD5_rym">株</span></span></span> #print(spans2[0].find("span").text)#[1111株」 となり1111 と株が一緒になる。できればわけたい spans2Child=spans2[0].find("span",recursive=False) #print(spans2Child)#<span class="_1-yujUee"><span class="_3rXWJKZF">235,900</span><span class="_2SD5_rym">株</span></span> #print(spans2Child.find("span").text)#235,900 が出力された #volume=_class="_3rXWJKZF" volume=spans2Child.find("span").text#1個め #print(volume) #単位は今の所使用しない #print(spans2Child.find_all("span"))#2個とも <span class="_3rXWJKZF">235,900</span>, <span class="_2SD5_rym">株</span> spans2Child2=spans2Child.find_all("span") tanni2=spans2Child2[1].text #_class="2SD5_rym"#株 が出力される。ここではfind("span")は不要 #print(tanni2) #理屈がこんがらかっているので整理したほうが良さそう。時間をとってサンプルを作って記事にして spans3=tds[3].find_all("span",recursive=False)#spanがあるか調べる spans3Child=spans3[0].find("span",recursive=False) zenjitsuhi=spans3Child.find("span").text #print(zenjitsuhi) spans3Child2=spans3Child.find_all("span") #単位は今の所使用しない tanni3=spans3Child2[1].text#株 #print(tanni3) spans4=tds[4].find_all("span",recursive=False)#spanがあるか調べる spans4Child=spans4[0].find("span",recursive=False) bairitsu=spans4Child.find("span").text #print(bairitsu) spans4Child2=spans4Child.find_all("span") #単位は今の所使用しない tanni4=spans4Child2[1].text#倍 #print(tanni4) #tableColum classのnuは使用していないので、0にしておく obj=tableColum(0,code,market,companyName,hidukeTime,kabuka,volume,zenjitsuhi,bairitsu) outputArray.append(obj) output=outputArray return output #add override ヤフーファイナンスのデザイン変更による改修 2022/02/06 def getPageCount2(self,koumokuStr,marketStr,pageNum): #改修内容が大きいので、別メソッドにした。 #import requestsを使用することにした2022/02/06 #引数koumokuCodeからkoumokuStrに変更、以前は出来高「33」だったのが「volumeIncrease」の文字列になった #url='https://finance.yahoo.co.jp/stocks/ranking/' #fuzokuUrl='?market=' #term='&term=' if pageNum==0: #pageNumは1以上なので0だったら抜ける output=0 return output target_url=self.getrankingUrl(koumokuStr,marketStr,pageNum) r=requests.get(target_url) soup=BeautifulSoup(r.text,'lxml') commonTmp=self.commonTag(soup)#共通タグ output=0 #ページ数を出力する output=self.commonNextTag(commonTmp)#次へタグを取得 #ページ数を出力する。基本は1ページだが、該当なしもある。その場合は「0」を出力 if output=="": output=0 return output # これは旧デザインなので使用せず # #add override def getPageCount(self,koumokuCode,marketCode,pageNum): #ページ総数を取得する # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい url='https://info.finance.yahoo.co.jp/ranking/?kd='#株式ランキング if koumokuCode=='' or marketCode=='': print('ランキング種別か市場の引数が設定されていません') sys.exit() if pageNum=='' or pageNum==0:#ページ番号の指定がないときは強制的に1にしておく pageNum=1 #注意 ページ数がいくつまであるかのチェックは行っていない。10Pくらいはいつでも存在する前提。ページ数が10を越えるとそのページが存在しないこともあり得る。 driver.get(url+str(koumokuCode)+'&mk='+str(marketCode)+'&tm=d&vl=a'+'&p='+str(pageNum))#&p=1,&p=2みたいにページ番号を追記する。 time.sleep(1) #shijyounews=soup.find(id="shijyounews")#id contentsBodyBottom=driver.find_element_by_id("contents-body-bottom")#id rankData=contentsBodyBottom.find_element_by_class_name("rankdata") paging=rankData.find_elements_by_tag_name("ul")#1個しかないけどこれは、存在確認のためだけ #if len(paging)==0: if paging!=True: totalCount=1 else: #if len(paging.find_elements_by_tag_name("a"))==0: atags=paging.find_elements_by_tag_name("a") if atags:#存在する if len(atags)>=1: totalCount=len(atags) else: totalCount=1#リンクがないので1Pしかない else: totalCount=1 count=totalCount #print(count) driver.close() return count 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 #MYSQL def selectDekidakaMysql(self): # コネクションの作成 conn = mysql.connector.connect( host='localhost', port='3306', user='maseda', password='Test1030',#知られてもよいパスワード database='YahooFinance' ) #今日の日付か、CSVのファイル名から取得した日付または、直近のテーブルの日付 sql="SELECT * FROM Table_Dekidaka;" df_dekidaka = pd.read_sql(sql,conn) print(df_dekidaka.head())#出力 print("pd, head出力したけど表示されているかい>されていないなら修正して") conn.close() #add #これで動作するか試して、まだ動作確認していない。2021/02/26 def returnDriver(self,koumokuCode,marketCode): # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい url='https://info.finance.yahoo.co.jp/ranking/?kd='#株式ランキング if koumokuCode=='' or marketCode=='': print('ランキング種別か市場の引数が設定されていません') sys.exit() if pageNum=='' or pageNum==0:#ページ番号の指定がないときは強制的に1にしておく pageNum=1 #注意 ページ数がいくつまであるかのチェックは行っていない。10Pくらいはいつでも存在する前提。ページ数が10を越えるとそのページが存在しないこともあり得る。 driver.get(url+str(koumokuCode)+'&mk='+str(marketCode)+'&tm=d&vl=a'+'&p='+str(pageNum))#&p=1,&p=2みたいにページ番号を追記する。 mydriver=driver return mydriver #スクレイピングコード testだったのに、本番になった def getDekidakaTest(self,koumokuCode,marketCode,pageNum): #print() #URL 項目koumokuと市場marketの引数をそれぞれ分解しておく # # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい url='https://info.finance.yahoo.co.jp/ranking/?kd='#株式ランキング if koumokuCode=='' or marketCode=='': print('ランキング種別か市場の引数が設定されていません') sys.exit() if pageNum=='' or pageNum==0:#ページ番号の指定がないときは強制的に1にしておく pageNum=1 #注意 ページ数がいくつまであるかのチェックは行っていない。10Pくらいはいつでも存在する前提。ページ数が10を越えるとそのページが存在しないこともあり得る。 driver.get(url+str(koumokuCode)+'&mk='+str(marketCode)+'&tm=d&vl=a'+'&p='+str(pageNum))#&p=1,&p=2みたいにページ番号を追記する。 time.sleep(1) contentsBodyBottom = driver.find_element_by_id("contents-body-bottom") rankdata = contentsBodyBottom.find_element_by_class_name("rankdata") rankingTableWrapper=rankdata.find_element_by_class_name("rankingTableWrapper") tableTag=rankingTableWrapper.find_element_by_tag_name("table") #thead tr thead=tableTag.find_element_by_tag_name("thead") trtag=thead.find_element_by_tag_name("tr")#1個しかないので単数 tdsThead=trtag.find_elements_by_tag_name("td")#複数 for m in range(0,len(tdsThead)): tdsThead[m].text print(tdsThead[m].text) #array outputArray=[] #tbody trs tbody=tableTag.find_element_by_tag_name("tbody") trs=tbody.find_elements_by_tag_name("tr")#"複数" for i in range(0,len(trs)):#tr tds=trs[i].find_elements_by_tag_name("td") #a link に挟まれたのが企業コード #倍を削除する obj=tableColum(tds[0].text,tds[1].find_element_by_tag_name("a").text,tds[2].text,tds[3].text,tds[4].text,tds[5].text,\ tds[6].text,tds[7].text,tds[8].text.replace('倍','')) outputArray.append(obj) driver.close()#起動したウィンドウを閉じる output=outputArray return output def outputCSVForTableColum(self,columArray,filename,path): #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') # w:上書き、a:追記、t:テキスト except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません", e) sys._exit()#ファイルがなければ終了#tryのときは_exit()が良いらしい except Exception as e: # Exceptionは、それ以外の例外が発生した場合 print(e) for i in columArray:#tableColumクラスのメンバ変数code,market,name,time,price,value,preValue,percent ofile.write(i.code+','+i.market+','+i.name+','+i.price.replace(',','')+','+i.value.replace(',','')+','+\ i.preValue.replace(',','')+','+i.percent.replace(',','')+'\n') ofile.close() #こっちは古い def mysqlInsertFuncDekidaka(self,inputStr,cursor): #MySQLdbを使用 #古い更新されないライブラリらしいのでmysql-connector-pythonに今後切り替え #mysqlの接続はすでに行われた前提 tablename='Table_Dekidaka' rowname="(Code,Market,Name,Price,Volume,PreviousVolume,VolumePer,StockDate,CreateDate,Yobi)" tmp=[] tmp=inputStr.strip().split(",") dt_now=datetime.datetime.now() StockDate=dt_now.strftime('%Y/%m/%d')# createDate=dt_now.strftime('%Y/%m/%d %H:%M:%S')#今日の日付時刻を入れて、取り込み実行の日付で良い。後で削除操作するときの目安くらいだから厳密ではない yobi='' try: #%dにするとエラーになるのでINT型は%sにしておくとエラーにならない。なのでINTなのに%sとして記述してある。 cursor.execute("INSERT INTO " + tablename + " " + rowname + " VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",\ (tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5],tmp[6],StockDate,createDate,yobi)) except MySQLdb.Error as e: print('MySQLdb.Error: ', e) def mysqlConnectorInsertFuncDekidaka(self,inputStr,cursor): #MysqlConnector版 今後はこちらで作業 #MySQLdb#古い更新されないライブラリらしいのでmysql-connector-pythonに今後切り替え #mysqlの接続はすでに行われた前提 tablename='Table_Dekidaka' rowname="(Code,Market,Name,Price,Volume,PreviousVolume,VolumePer,StockDate,CreateDate,Yobi)" tmp=[] tmp=inputStr.strip().split(",") #print(tablename) dt_now=datetime.datetime.now() StockDate=dt_now.strftime('%Y/%m/%d')# createDate=dt_now.strftime('%Y/%m/%d %H:%M:%S')#今日の日付時刻を入れて、取り込み実行の日付で良い。後で削除操作するときの目安くらいだから厳密ではない #次は、ここから、配列の数を数えて10個ないときはエラー処理を入れたほうよよい if len(tmp)>=8: yobi=tmp[7]#CSVは8個ある。tmp[7] is Yobi 2022/02/10 else: yobi="" #try: #%dにするとエラーになるのでINT型は%sにしておくとエラーにならない。なのでINTなのに%sとして記述してある。 cursor.execute("INSERT INTO " + tablename + " " + rowname + " VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",\ (tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5],tmp[6],StockDate,createDate,yobi)) #except MySQLdb.Error as e: #print('MySQLdb.Error: ', e) def mysqlConnectorInsertFuncDekidakaTest(self,inputStr,cursor): #接続確認用メソッド #MysqlConnector版 今後はこちらで作業 #MySQLdb#古い更新されないライブラリらしいのでmysql-connector-pythonに今後切り替え #mysqlの接続はすでに行われた前提 tablename='Table_Dekidaka' rowname="(Code,Market,Name,Price,Volume,PreviousVolume,VolumePer,StockDate,CreateDate,Yobi)" tmp=[] if inputStr!='': tmp=inputStr.strip().split(",") dt_now=datetime.datetime.now() StockDate=dt_now.strftime('%Y/%m/%d')# createDate=dt_now.strftime('%Y/%m/%d %H:%M:%S')#今日の日付時刻を入れて、取り込み実行の日付で良い。後で削除操作するときの目安くらいだから厳密ではない yobi='' try: #%dにするとエラーになるのでINT型は%sにしておくとエラーにならない。なのでINTなのに%sとして記述してある。 cursor.execute("SELECT * FROM " + tablename) rows = cursor.fetchall() print("SELECT処理") for row in rows: print(row) except MySQLdb.Error as e: print('MySQLdb.Error: ', e) def CSVtoMysqlConnectorTest(self,filename,path): #Test #MysqlConnector版 今後はこちらで作業 # コネクションの作成 conn = mysql.connector.connect( host='localhost', port='3306', user='maseda', password='Test1030',#知られてもよいパスワード database='YahooFinance' ) cursor=conn.cursor() try: #test iline="" self.mysqlConnectorInsertFuncDekidakaTest(iline,cursor) except MySQLdb.Error as e: print('MySQLdb.Error: ', e) conn.rollback()#失敗したらもとに戻す。これだと途中で成功してもコミットされるので、1回でもエラーのときはBREAKのほうがいいかも。 print("強制終了MYSQL") conn.close() return #conn.commit() conn.rollback() cursor.close() conn.close() def CSVtoMysqlConnector(self,filename,path): #MysqlConnector版 今後はこちらで作業 # コネクションの作成 conn = mysql.connector.connect( host='localhost', port='3306', user='maseda', password='Test1030',#知られてもよいパスワード database='YahooFinance' ) cursor=conn.cursor() print("Trueなら接続OK") print(conn.is_connected())#True,False os.chdir(path)#ディレクトリ変更 print(os.getcwd())#ディレクトリ確認 try:#ファイルが存在しないときのエラー処理try with open(filename,'tr') as fin: for iline in fin: #try: self.mysqlConnectorInsertFuncDekidaka(iline,cursor) #except MySQLdb.Error as e: # print('MySQLdb.Error: ', e) # conn.rollback()#失敗したらもとに戻す。これだと途中で成功してもコミットされるので、1回でもエラーのときはBREAKのほうがいいかも。 # print("強制終了MYSQL") # cursor.close() # conn.close() # return except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません。パス、ファイル名を確認してください", e) print("強制終了") sys._exit()#ファイルがなければ終了 #tryのときは_exit()が良いらしい except Exception as e: # Exceptionは、それ以外の例外が発生した場合 print(e) conn.commit() #テストロールバック #conn.rollback() #print('現在テスト中なのでrollbackしてます') #print('commit') cursor.close() conn.close() print('DB 処理終了。。。') #古いコネクターを使っているのでこのメソッドは使わない def CSVtoMysql(self,filename,path):##MySQLdb#古い #MySQLdb#古い更新されないライブラリらしいのでmysql-connector-pythonに今後切り替え #mysql接続 #Mysqlに接続メソッドを入れる # データベースへの接続とカーソルの生成 #DBはすでに作成済みとする。mysql-connector-pythonに今後切り替え connection = MySQLdb.connect( host='localhost', user='maseda', passwd='Test1030',#知られても問題ないパスワード db='YahooFinance') cursor = connection.cursor() os.chdir(path)#ディレクトリ変更 print(os.getcwd())#ディレクトリ確認 try:#ファイルが存在しないときのエラー処理try with open(filename,'tr') as fin: for iline in fin: try: #カンマ区切りなのでSplitする self.mysqlInsertFuncDekidaka(iline,cursor) except MySQLdb.Error as e: print('MySQLdb.Error: ', e) connection.rollback()#失敗したらもとに戻す。これだと途中で成功してもコミットされるので、1回でもエラーのときはBREAKのほうがいいかも。 print("強制終了MYSQL") connection.close() return connection.commit() except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません。パス、ファイル名を確認してください", e) print("強制終了") sys._exit()#ファイルがなければ終了 #tryのときは_exit()が良いらしい except Exception as e: # Exceptionは、それ以外の例外が発生した場合 print(e) #ヤフーファイナンスランキングの1株あたり利益クラス。 Dekidakaクラスを継承する。といってもそれほど継承するメソッドはない class yahooFinanceEps(yahooFinanceDekidaka): def __init__(self): pass #print() #テーブルが存在するという前提Table_eps #override def selectDekidakaMysql(self): # コネクションの作成 conn = mysql.connector.connect( host='localhost', port='3306', user='maseda', password='Test1030', database='YahooFinance' ) #今日の日付か、CSVのファイル名から取得した日付または、直近のテーブルの日付 sql="SELECT * FROM Table_Eps;" df_dekidaka = pd.read_sql(sql,conn) print(df_dekidaka.head())#出力 print("pd, head出力したけど表示されているかい>されていないなら修正して") conn.close() #オーバーライド def mysqlConnectorInsertFuncDekidaka(self,inputStr,cursor): #MysqlConnector版 今後はこちらで作業 #MySQLdb#古い更新されないライブラリらしいのでmysql-connector-pythonに今後切り替え #mysqlの接続はすでに行われた前提 tablename='Table_Eps' rowname="(Code,Market,Name,Price,KessanSyubetsu,Eps,KessanDate,StockDate,CreateDate,Yobi)" tmp=[] tmp=inputStr.strip().split(",") dt_now=datetime.datetime.now() StockDate=dt_now.strftime('%Y/%m/%d')# createDate=dt_now.strftime('%Y/%m/%d %H:%M:%S')#今日の日付時刻を入れて、取り込み実行の日付で良い。後で削除操作するときの目安くらいだから厳密ではない yobi='' #try: #%dにするとエラーになるのでINT型は%sにしておくとエラーにならない。なのでINTなのに%sとして記述してある。 cursor.execute("INSERT INTO " + tablename + " " + rowname + " VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",\ (tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5],tmp[6],StockDate,createDate,yobi)) #except MySQLdb.Error as e: #print('MySQLdb.Error: ', e) #override def outputCSVForTableColum(self,columArray,filename,path): #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:#tableColumクラスのメンバ変数code,market,name,time,price,value,preValue,percent ofile.write(i.code+','+i.market+','+i.name+','+i.price.replace(',','')+','+i.kessanSyubetsu+','+\ i.eps.replace(',','')+','+i.kessanDate+'\n') ofile.close() #add def getEps(self,koumokuCode,marketCode,pageNum): # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい url='https://info.finance.yahoo.co.jp/ranking/?kd='#株式ランキング if koumokuCode=='' or marketCode=='': print('ランキング種別か市場の引数が設定されていません') sys.exit() if pageNum=='' or pageNum==0:#ページ番号の指定がないときは強制的に1にしておく pageNum=1 #注意 ページ数がいくつまであるかのチェックは行っていない。10Pくらいはいつでも存在する前提。ページ数が10を越えるとそのページが存在しないこともあり得る。 #Eps1株利益用に修正するkd=50になるので、「50」が異なるだけで他の書式は同じだ driver.get(url+str(koumokuCode)+'&mk='+str(marketCode)+'&tm=d&vl=a'+'&p='+str(pageNum))#&p=1,&p=2みたいにページ番号を追記する。 time.sleep(1) #URLはほぼ同じだけど、CSSのクラスやIDが異なるので、EPS用に個別に変更が必要となる contentsBodyBottom = driver.find_element_by_id("contents-body-bottom") rankdata = contentsBodyBottom.find_element_by_class_name("rankdata") rankingTableWrapper=rankdata.find_element_by_class_name("rankingTableWrapper") tableTag=rankingTableWrapper.find_element_by_tag_name("table") #thead tr thead=tableTag.find_element_by_tag_name("thead") trtag=thead.find_element_by_tag_name("tr")#1個しかないので単数 tdsThead=trtag.find_elements_by_tag_name("th")#複数 for m in range(0,len(tdsThead)): tdsThead[m].text #print(tdsThead[m].text) #array outputArray=[] #tbody trs tbody=tableTag.find_element_by_tag_name("tbody") trs=tbody.find_elements_by_tag_name("tr")#"複数" for i in range(0,len(trs)):#tr tds=trs[i].find_elements_by_tag_name("td") #a link に挟まれたのが企業コード #num,code,market,name,price,eps,kessan #epsに「(連)3,358」「(連)」が含まれたら削除したい obj=epsTableColum(tds[0].text,tds[1].find_element_by_tag_name("a").text,tds[2].text,tds[3].text,tds[4].text,tds[5].text,\ tds[6].text,tds[7].text) obj.changeEpsKessanSyubetsuWord()#連、単の文字を調整する obj.changeKessanDate()#決算の日付を調整 outputArray.append(obj) driver.close()#起動したウィンドウを閉じる output=outputArray return output class yahooFinancePer(yahooFinanceDekidaka): #CSVtoMysqlConnector(でロールバックを手動コメントで作業している。 #できればもっとわかりやすい方法があると思うので、調査して、2021/02/26 def __init__(self): pass #override def selectDekidakaMysql(self): pass #override def mysqlConnectorInsertFuncDekidaka(self,inputStr,cursor): #MysqlConnector版 今後はこちらで作業 #MySQLdb#古い更新されないライブラリらしいのでmysql-connector-pythonに今後切り替え #mysqlの接続はすでに行われた前提 tablename='Table_highPer' rowname="(Code,Market,Name,Price,KessanSyubetsu,Eps,KessanDate,PerKessanSyubetsu,Per,StockDate,CreateDate,Yobi)" tmp=[] tmp=inputStr.strip().split(",") dt_now=datetime.datetime.now() StockDate=dt_now.strftime('%Y/%m/%d')# createDate=dt_now.strftime('%Y/%m/%d %H:%M:%S')#今日の日付時刻を入れて、取り込み実行の日付で良い。後で削除操作するときの目安くらいだから厳密ではない yobi='' #try: #%dにするとエラーになるのでINT型は%sにしておくとエラーにならない。なのでINTなのに%sとして記述してある。 cursor.execute("INSERT INTO " + tablename + " " + rowname + " VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",\ (tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5],tmp[6],tmp[7],tmp[8],StockDate,createDate,yobi)) #except MySQLdb.Error as e: #print('MySQLdb.Error: ', e) #override def outputCSVForTableColum(self,columArray,filename,path): #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:#テーブルのカラム順が良さそう、まだ num,code,market,name,time,price,KessanSyubetsu,eps,kessanDate,PerKessanSyubetsu,per ofile.write(i.code+','+i.market+','+i.name+','+i.price.replace(',','')+','+i.kessanSyubetsu+','+\ i.eps.replace(',','')+','+i.kessanDate+','+i.PerKessanSyubetsu+','+i.per.replace(',','')+'\n') ofile.close() #add def getPer(self,koumokuCode,marketCode,pageNum): # 仮想ブラウザ起動、URL先のサイトにアクセス driver = webdriver.Chrome('/usr/local/bin/chromedriver')#ここでエラーになったらパスが違うかchromedriverをインストールする #他のエラーで、「unexpectedly exited. Status code was:-9」だったら、Macの場合はシステム環境設定 → セキュリティとプライバシー で許可すればよい url='https://info.finance.yahoo.co.jp/ranking/?kd='#株式ランキング if koumokuCode=='' or marketCode=='': print('ランキング種別か市場の引数が設定されていません') sys.exit() if pageNum=='' or pageNum==0:#ページ番号の指定がないときは強制的に1にしておく pageNum=1 #注意 ページ数がいくつまであるかのチェックは行っていない。10Pくらいはいつでも存在する前提。ページ数が10を越えるとそのページが存在しないこともあり得る。 #Eps1株利益用に修正するkd=50になるので、「50」が異なるだけで他の書式は同じだ driver.get(url+str(koumokuCode)+'&mk='+str(marketCode)+'&tm=d&vl=a'+'&p='+str(pageNum))#&p=1,&p=2みたいにページ番号を追記する。 time.sleep(1) #URLはほぼ同じだけど、CSSのクラスやIDが異なるので、EPS用に個別に変更が必要となる contentsBodyBottom = driver.find_element_by_id("contents-body-bottom") rankdata = contentsBodyBottom.find_element_by_class_name("rankdata") rankingTableWrapper=rankdata.find_element_by_class_name("rankingTableWrapper") tableTag=rankingTableWrapper.find_element_by_tag_name("table") #thead tr thead=tableTag.find_element_by_tag_name("thead") trtag=thead.find_element_by_tag_name("tr")#1個しかないので単数 tdsThead=trtag.find_elements_by_tag_name("th")#複数 for m in range(0,len(tdsThead)): tdsThead[m].text #print(tdsThead[m].text) #array outputArray=[] #tbody trs tbody=tableTag.find_element_by_tag_name("tbody") trs=tbody.find_elements_by_tag_name("tr")#"複数" for i in range(0,len(trs)):#tr tds=trs[i].find_elements_by_tag_name("td") #a link に挟まれたのが企業コード #num,code,market,name,price,eps,kessan #epsに「(連)3,358」「(連)」が含まれたら削除したい #PERの場合、場中は時刻がTDに入るので、市場終わりのときとTDの数が異なる。なのでtdsの数で、順番を変える。 #2021/02/26 #市場終、TIMEなし tdsの数で判別で順番指定が少し面倒 if len(tds)==9:#1から数えて5つ目(0だと4つ目)がTimeなのでブランクにしてトータルで9つは変えない。 obj=perTableColum(tds[0].text,tds[1].find_element_by_tag_name("a").text,tds[2].text,tds[3].text,'',tds[4].text,tds[5].text,\ tds[6].text,tds[7].text) print('TIME nasi') #場中、TIMEあり elif len(tds)==10:#掲示板のTDも数えて10個 obj=perTableColum(tds[0].text,tds[1].find_element_by_tag_name("a").text,tds[2].text,tds[3].text,tds[4].text,tds[5].text,\ tds[6].text,tds[7].text,tds[8].text) #print('Time ari') else: print('tdの数が異なります。WEBページの仕様が変わったか、コードの調整が必要です。強制終了します') sys.exit() obj.changeEpsKessanSyubetsuWord()#連、単の文字を調整する obj.changePerKessanSyubetsuWord()#連、単の文字を調整する obj.changeKessanDate()#決算の日付を調整 outputArray.append(obj) driver.close()#起動したウィンドウを閉じる output=outputArray return output #新しいデザインは未だ、2022/02/07 #注意点は、株価や出来高にカンマが付いているのでDB登録前に消しておく #年初来高値、BeautifulSoup#スクレイピング 使用 #年初来高値、追加、修正箇所 1115行くらいから作業 2022/02/10 # 次はここから # 新規OR不要 def commonDekidakaUntilTableTag(self,soupTableTag): # 新規 def getDekidakaNewType(self,koumokuStr,marketStr,pageNum): # 不要多分 def getPageCount2(self,koumokuStr,marketStr,pageNum): # 修正済 def mysqlConnectorInsertFuncDekidaka(self,inputStr,cursor): class yahooFinanceYearHigh(yahooFinanceDekidaka): def __init__(self): pass #override 年初来高値更新 新しいデザイン対応2022/02/11 def getDekidakaNewType(self,koumokuStr,marketStr,pageNum): #yahooFinanceDekidakaクラスを出来高と異なるのでここはyahooFinanceDekidakaクラスを継承している # のでオーバーライドする 2022/02/10 #出来高クラスを同じ内容を記載しつつ、個別に対応する。 output="" target_url=self.getrankingUrl(koumokuStr,marketStr,pageNum) r=requests.get(target_url) soup=BeautifulSoup(r.text,'lxml') common=self.commonTag(soup) soupDekidaka=self.commonDekidakaUntilTableTag(common) if soupDekidaka=="": print("getDekidakaNewType()の戻り値を取得できませんでした。") return output #commonDekidakaUntilTableTag()から受け取り、出来高用の内容に変える。 outputArray=[] tbody=soupDekidaka.find("tbody") trs=tbody.find_all("tr") #loop ##ここまでは出来高くらすと同じ 2022/02/11 for i in range(0,len(trs)): #注意点は、項目の数が以前のデザインと同じかどうか、mysqlのカラムの数と一致させる tds=trs[i].find_all("td") #tdは 個 next liに対応させる companyName=tds[0].find("a").text lis_parent=tds[0].find("ul") lis=lis_parent.find_all("li",recursive=False)#再起検索なし,recursive=False #liは3個あるがlis[2]は不要 code=lis[0].text market=lis[1].text #lis[2] 掲示板へのリンクなので不要 spans=tds[1].find_all("span")#,recursive=False kabuka=spans[0].find("span").text#階層の中のテキストは1つ hidukeTime=spans[1].find("span").text dt_now=datetime.datetime.now() StockDateY=dt_now.strftime('%Y') if hidukeTime.find(":")!=None:#時刻の場合は、日付にする #今日の年月日を取得する。 #dt_now=datetime.datetime.now() StockDate=dt_now.strftime('%Y/%m/%d')# hidukeTime=StockDate elif hidukeTime.find(StockDateY):#2022があるかどうか #2022/2/10なら何もしない #dt_now=datetime.datetime.now() #StockDate=dt_now.strftime('%Y') pass else:#02/10 月と日だけは年をつける tmpstr=hidukeTime hidukeTime=StockDateY+'/'+tmpstr#年と月日を結合する spans2=tds[2].find_all("span",recursive=False)#再帰検索はしない preKabuka=spans2[0].find("span").text#前回高値 preHidukeTime=spans2[1].find("span").text#前回日付 2022/02/10になっている #今日の高値 spans3=tds[3].find("span") takaneKabuka=spans3.find("span").text #クラスオブジェクトにして配列に渡す #code,market,name,torihikiDay,price,previousYearHighDay,previousYearHighPrice,highPrice): obj=yearHighTableColum(code,market,companyName,hidukeTime,kabuka,preHidukeTime,preKabuka,takaneKabuka)#8個入る outputArray.append(obj) output=outputArray return output #override def mysqlConnectorInsertFuncDekidaka(self,inputStr,cursor): #MysqlConnector版 今後はこちらで作業 #MySQLdb#古い更新されないライブラリらしいのでmysql-connector-pythonに今後切り替え #mysqlの接続はすでに行われた前提 tablename='Table_YearHigh' rowname="(Code,Market,Name,TorihikiDay,Price,PreviousYearHighDay,PreviousYearHighPrice,HighPrice,StockDate,CreateDate,Yobi)" tmp=[] tmp=inputStr.strip().split(",") dt_now=datetime.datetime.now() StockDate=dt_now.strftime('%Y/%m/%d')# createDate=dt_now.strftime('%Y/%m/%d %H:%M:%S')#今日の日付時刻を入れて、取り込み実行の日付で良い。後で削除操作するときの目安くらいだから厳密ではない if len(tmp)>=9: yobi=tmp[8]#CSVで個数をチェックして、こっちは9個あるので、tmp[8] is Yobi 2022/02/10 else: yobi="" #try: #%dにするとエラーになるのでINT型は%sにしておくとエラーにならない。なのでINTなのに%sとして記述してある。 cursor.execute("INSERT INTO " + tablename + " " + rowname + " VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",\ (tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5],tmp[6],tmp[7],StockDate,createDate,yobi)) #except MySQLdb.Error as e: #print('MySQLdb.Error: ', e) #override def outputCSVForTableColum(self,columArray,filename,path): 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:#テーブルのカラム順が良さそう、code,market,name,torihikiDay,price,previousYearHighDay, #previousYearHighPrice,highPrice ofile.write(i.code+','+i.market+','+i.name+','+i.torihikiDay+','+i.price.replace(',','')+','+\ i.previousYearHighDay+','+i.previousYearHighPrice.replace(',','')+','+i.highPrice.replace(',','')+'\n') ofile.close() #add override def getPageCount(self,koumokuCode,marketCode):#年初来高値のページの総数を取得する。これは親にも書いておく。 url='https://info.finance.yahoo.co.jp/ranking/?kd='#株式ランキング if koumokuCode=='' or marketCode=='': print('ランキング種別か市場の引数が設定されていません') sys.exit() pageNum=1 target_url = url+str(koumokuCode)+'&mk='+str(marketCode)+'&tm=d&vl=a'+'&p='+str(pageNum) r = requests.get(target_url) #requestsを使って、webから取得 soup = BeautifulSoup(r.text, 'lxml') #要素を抽出 #shijyounews=soup.find(id="shijyounews")#id contentsBodyBottom=soup.find(id="contents-body-bottom")#id rankData=contentsBodyBottom.find(class_="rankdata") #Classでスペース区切りのときは「create name」 → .create.nameのようにドットでつなぐらしい #paging=rankData.find(class_=".ymuiPagingBottom.clearFix") ULのClassで取得できなかった paging=rankData.find("ul") if paging==None: totalCount=1 else: if paging.find("a")==None: totalCount=1 else: atags=paging.find_all("a") if len(atags)>=1: totalCount=len(atags) else: totalCount=1#リンクがないので1Pしかない count=totalCount #print(count) return count #add def getYearHigh(self,koumokuCode,marketCode,pageNum): url='https://info.finance.yahoo.co.jp/ranking/?kd='#株式ランキング if koumokuCode=='' or marketCode=='': print('ランキング種別か市場の引数が設定されていません') sys.exit() if pageNum=='' or pageNum==0:#ページ番号の指定がないときは強制的に1にしておく pageNum=1 outputArray=[] target_url = url+str(koumokuCode)+'&mk='+str(marketCode)+'&tm=d&vl=a'+'&p='+str(pageNum) r = requests.get(target_url) #requestsを使って、webから取得 soup = BeautifulSoup(r.text, 'lxml') #要素を抽出 #shijyounews=soup.find(id="shijyounews")#id if soup.find(id="contents-body-bottom")==None: output=outputArray return output contentsBodyBottom=soup.find(id="contents-body-bottom")#id rankData=contentsBodyBottom.find(class_="rankdata") ttl=rankData.find(class_="ttl") #hiduke=ttl.find(class_=".dtl.yjSt")#更新日、時刻 「最終更新日時:」年、月、日 時 分 hiduke=ttl.find("div")#更新日、時刻 「最終更新日時:」年、月、日 時 分 # rankingTableWrapper=rankData.find(class_="rankingTableWrapper") table=rankingTableWrapper.find("table") #array kamokuObj=kamokuClass() if table==None:#'該当なし、1件もない場合、中身がない' print(kamokuObj.returnKamoku(koumokuCode)+':該当なし、1件もない場合、中身がない') else: tbody=table.find("tbody") trs=tbody.find_all("tr") print(kamokuObj.returnKamoku(koumokuCode)+":trの数"+str(len(trs))) for i in range(0,len(trs)): tds=trs[i].find_all("td") #Table obj を生成してtdのテキストを挿入する #tds[0].find("a").text,tds[1].text,tds[2].text, #tds[3].text,#mm/dd or time ここをyyyy/mm/dd timeにする #tds[4].text, #tds[5].text,#yyyy/mm/dd #tds[6].text,tds[7].text obj=yearHighTableColum(tds[0].find("a").text,tds[1].text,tds[2].text,tds[3].text,\ tds[4].text,tds[5].text,tds[6].text,tds[7].text) #日付や時刻を調整してAppendする obj.changeTimeAndDate(hiduke.text) outputArray.append(obj) time.sleep(1) output=outputArray return output ################################################################################################## ################################################################################################## ################################################################################################## ''' from selenium import webdriver import pandas as pd import requests#スクレイピング from bs4 import BeautifulSoup#スクレイピング いままでWebdriverライブラリを使用していたが、空白部分が自動で削除されてしまった。 削除してほしくなかったので、BeautifulSoupライブラリを使用した。こちらのほうはクロムは起動しないし 純粋にhtmlの文字を空白を含めて取得してくれた。 以下は今回作ったコードの一部。class cGetOption() なおdef returnDriver(self,lurl)はwebdriverを使用したもので実際には採用しなかった。 株探の先物オプションのURLを取得して そのページの文字列を取得。このとき、ある文字列Aと文字列Bの間の文字を取得(findFirstStrのところ) そしてタプルで、文字列とファイル名文字列を出力する ''' #先物オプションテキストコピペ 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() #以下はクラスにしていない。クラスにしなかった理由は特にない #メソッドを別ファイルにした。 #文字列の後ろ側の数字を後ろから探し、抜けていた場合0を埋める def latterHalf(text): textout=text pattern=re.compile('[0-9]+')#findで正規表現ができない i=0 checkArray=[]#タプルを入れる配列 while i>=0:#数字を探す m=pattern.search(textout, i) if m: #print(m.start()) #print(m.end()) #これをタプル配列に入れて、N,N-1で差が大きいときに0を入れる #print(m.span()) checkArray.append(m.span()) i = m.end()#m.start() + 1#m.end() else: break n=len(checkArray) if n>=2:#nは最低2こ必要 最後と最後の1こ前の #print("[n-1][0]",checkArray[n-1][0])#0番目の1個目 [0](0,1) #print("[n-2][1]",checkArray[n-2][1])#0番目の2個目 #個数はNだけど配列の指定はそれより1小さいのでN-1となる if (checkArray[n-1][0]-checkArray[n-2][1])>=6: print("put抜けている") #checkArray[n-1][1]の後ろに挿入する texttmp=textout[:checkArray[n-2][1]+2]+ '0' + textout[checkArray[n-2][1]+2:] textout=texttmp output=textout return output #end def #" ",,," " 27875 def outMojiMKIII(text): #空白を削除する前に、空白の個数を数える #株探の仕様が変わったらここを変える。2020/12/16 kuro=re.compile('[ ]+')#半角と全角の空白置換 半角全角が[半角全角]として入っている if text.count(' ',0,15)>=13: #空白13個はプット opt='put' textout=text.strip()#先頭、末尾の空白、改行を削除する else: #コールの場合、2種類あるのでそれをチェックする textout=text.strip()#先頭、末尾の空白を削除する check=kuro.sub(',',textout)#半角全角の空白を置換 count=check.split(",") #print ('len count:',len(count)) if len(count)>=5:#配列が5個以上なら、call,putが含まれる opt='callandput' else: opt='onlycall' pattern=re.compile('[0-9]+')#findで正規表現ができない if opt=='onlycall' or opt=='callandput': #次に、「call,putあり」OR「Callのみ」かをチェックする。 #optSub='callandput' 'onlycall' #callのみを先に処理して、put側を調べる matchobj=re.search('[0-9]+',textout)#最初の数字を探す if matchobj: #print('マッチした文字列:'+matchobj.group()) #print('マッチした文字列グループ:',matchobj.groups()) #print( '開始位置'+str(matchobj.start()) )#先頭位置は0番として数える 3番目(4文字目)に見つかった #print( '終了位置'+str(matchobj.end()) )#終了位置は次の番目を含んでいるので実際は1を引いた数が終了位置 #print(matchobj.span())#'マッチした文字列の (開始位置, 終了位置) のタプル'+ #2番めの数字を探す secondMatch=pattern.search(textout,matchobj.end())#compileを使用してpatternで再度位置を指定して探す if secondMatch: #print( '開始位置2:'+str(secondMatch.start()) )#先頭から数えて10番目に次の数字が見つかった #1番目と2番めの文字数が5以上空いていたら数字が抜けている事がわかる。 #ここに0を埋める処理を入れる if secondMatch.start()-matchobj.end() >=5: print('call 値が抜けてる') # hash = "355879ACB6" # hash = hash[:4] + '-' + hash[4:] #文字列の挿入。#1番目の文字の3文字目くらいに0を追加する。 texttmp=textout[:matchobj.end()+2]+ '0' + textout[matchobj.end()+2:] textout=texttmp #put側のチェック。再度optでチェックする if opt=='onlycall': #置換して終わる output=kuro.sub(',',textout)+',,,' elif opt=='callandput': #print('callandputの処理') tmp=latterHalf(textout)#後半PUTの処理をして文字を返す #置換して終わる output=kuro.sub(',',tmp) elif opt=='put': #putのみの場合 tmp=latterHalf(textout)#後半PUTの処理をして文字を返す #3つのカンマを付ける output=',,,'+kuro.sub(',',tmp)#>>,,,26250,285,-10,189 #end if return output ### end def def csvToMysql(difile,dtablename,doptionDate,dmonth): #ここからSTART #Mysqlに接続メソッドを入れる # データベースへの接続とカーソルの生成 connection = MySQLdb.connect( host='localhost', user='maseda', passwd='Test1030',#知られても問題ないパスワード db='Stock') cursor = connection.cursor() #csvファイル名 ifile=difile #table tablename=dtablename#"Test_Table_StockOption"#テスト用テーブルを使用する #カラム名 rowname="(Volume1,Change1,Price1,ExercisePrice,Price2,Change2,Volume2,Month,OptionDate,CreateDate,YOBI)" #VALUES (%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s)>>%dは%sにしないとエラーになった。 #カラム変数 month=dmonth#1#月限 optionDate=doptionDate#"2020/12/22"#データ取得の日付、たいていCSVに記載の日付になる。 dt_now=datetime.datetime.now() createDate=dt_now.strftime('%Y-%m-%d %H:%M:%S')#今日の日付時刻を入れて、取り込み実行の日付で良い。後で削除操作するときの目安くらいだから厳密ではない yobi=""#コメントがあればいれる。 count=0 #テーブルにデータを挿入する部分から書いていく #CSVを1行ごとに読み取り、列ごとにカラムに入れていく try:#ファイルが存在しないときのエラー処理try with open(ifile,'tr') as fin: for iline in fin: try: #Mysqlに直接インサートするバージョンを作業する # ここに実行したいコードを入力します #print(iline) if len(iline)==1 or len(iline)==0: continue tmp=iline.strip().split(",")#stripしてからsplit()だと理解している。 iline.split(",")のままだと改行が入ってくるのでstripで前後の空白と改行を削除する #CSVのデータが空の場合は、値0を入れる if tmp[0]=='': tmp[0]=0 if tmp[1]=='': tmp[1]=0 if tmp[2]=='': tmp[2]=0 if tmp[3]=='': tmp[3]=0 if tmp[4]=='': tmp[4]=0 if tmp[5]=='': tmp[5]=0 if tmp[6]=='': tmp[6]=0 #テストテーブルにインサートする前に、確認する前に、Splitを確認する。 #print(count,tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5],tmp[6]) #%dにするとエラーになるのでINT型は%sにしておくとエラーにならない。なのでINTなのに%sとして記述してある。 cursor.execute("INSERT INTO " + tablename + " " + rowname + " VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",\ (tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5],tmp[6],month,optionDate,createDate,yobi)) #こっちだと、%dに値がないよとエラーになる。正しい記述のはずがパイソンではエラーになるようだ。 #cursor.execute("INSERT INTO " + tablename + " " + rowname + " VALUES (%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s)",\ # (tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5],tmp[6],month,optionDate,createDate,yobi)) #print("insert into ,,,csvToMysql") except MySQLdb.Error as e: print('MySQLdb.Error: ', e) connection.rollback()#失敗したらもとに戻す。これだと途中で成功してもコミットされるので、1回でもエラーのときはBREAKのほうがいいかも。 print("強制終了MYSQL") connection.close() sys._exit() return #ofile.close() connection.commit() except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません。パス、ファイル名を確認してください", e) ofile.close() sys._exit()#ファイルがなければ終了 except Exception as e: # Exceptionは、それ以外の例外が発生した場合 print(e) # 接続を閉じる connection.close() print("Mysql書き込み終了") #defここまで #check file def checkFile(filename): #他にもimport os os.listdir(path) というのもある files=glob.glob('./option_python_execute/*.txt') for file in files: #print(file) if filename in file:#ファイル名を含んでいればTRUE print('すでに取り込み済みのファイルです。') sys.exit() #end def def fileMove(file,path): shutil.move(file, path)#('./new.txt', './sample') print('file move',file) #end def def selectMysql(tablename):#Month=1が指定なので、ここは引数にしたほうがよさそう # データベースへの接続とカーソルの生成 connection = MySQLdb.connect( host='localhost', user='maseda', passwd='Test1030',#知られても問題ないパスワード db='Stock') cursor = connection.cursor() try: # ここに実行したいコードを入力します cursor.execute("SELECT SUM(Volume1) as callVolume1, ExercisePrice, SUM(Volume2) as putVolume2 FROM "+ tablename +\ " WHERE Month=1 GROUP BY ExercisePrice ORDER BY ExercisePrice Desc") #カラム名を取得 #cursor.execute("show columns from Table_StockOption") # fetchall()で全件取り出し rows = cursor.fetchall() searchArray=[]#タプルで登録 for row in rows: print(row[0],row[1],row[2]) searchArray.append(row) # print(row[Volume1]) ERROR #print(searchArray[0])#IDの情報:('ID', 'int', 'NO', 'PRI', None, 'auto_increment') #for srow in searchArray: # print(srow)# except MySQLdb.Error as e: print('MySQLdb.Error: ', e) #表示 connection.commit() connection.close() #end def |