🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
スクレイピング

スクレイピングとは、公開されているWebサイトからページ内の情報を抽出する技術です。

Beautiful Soup

Beautiful Soupは、Pythonのライブラリの一つ。スクレイピングに特化しています。HTMLデータの構文の解析を行うために、HTMLタグ/CSSのセレクタで抽出する部分を指定することが可能です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

2562閲覧

Beautifulsoupでのスクレイピングでtableから値がとれない

_whitecat_22

総合スコア1305

スクレイピング

スクレイピングとは、公開されているWebサイトからページ内の情報を抽出する技術です。

Beautiful Soup

Beautiful Soupは、Pythonのライブラリの一つ。スクレイピングに特化しています。HTMLデータの構文の解析を行うために、HTMLタグ/CSSのセレクタで抽出する部分を指定することが可能です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2020/12/14 10:19

前提・実現したいこと

後述の、とあるサイトの商品別の販売集計表をスクレイピングしたいのですが、値がとれずに困っています。

環境

  • Python 3.9.0

  • Windows 10 Pro

  • google chrome

  • Beautifulsoup

  • requests

  • selenium

  • chrome ドライバ

(google chrome以下のものは、いずれも最新版です)

発生している問題

下記のようなテーブルの「最高値(円)」「平均値(円)」「最安値(円)」「実勢価格(円)」の各値がとれません。

※ 掲載にあたり、一部内容を伏せるために項目名や項目値を加工しております。ご容赦ください。

テーブルの構造

・ 実際には、商品名=商品1とその直下の「a00001」(商品コード)は、同じ行で定義されています。(下記HTMLソースをご参照ください。)
・ また、商品10件ごとにヘッダー項目が挿入されており、これが1ページあたり5組もある大きなテーブルです。
・ ページ内に<table>タグで囲まれた箇所は、このテーブルのみです。
・ さらに、このテーブルの内容(明細)は、全体でおおよそ100ページ分ほどあります。

商品名最高値(円)平均値(円)最安値(円)実勢価格(円)販売開始日販売数(単位)累積販売金額(円)
商品190.091.593.092.01999/1/191,565,432173,452,222,567
a00001(12/7)(12/8)(12/13)(枚)(円)
商品280.081.583.082.01999/7/211,868,432423,452,288,590
a00002(12/7)(12/13)(12/8)(12/13)(枚)(円)
(...略...)
商品名最高値(円)平均値(円)最安値(円)実勢価格(円)販売開始日販売数(単位)累積販売金額(円)
商品1190.091.593.092.01999/1/191,565,432173,452,222,567
a00011(12/7)(12/13)(12/8)(12/13)(枚)(円)
(...略...)

html

1<html> 2<head> 3(...略...) 4<body> 5(...略...) 6 7<table cellspacing="0" class="cmn-table"> 8 <thead> 9 <tr> 10 <th class="cmn-table_a">商品名</th> 11 <th class="cmn-table_b">最高値(円)</th> 12 <th class="cmn-table_c">平均値(円)</th> 13 <th class="cmn-table_d">最安値(円)</th> 14 <th class="cmn-table_e">実勢価格(円)</th> 15 <th class="cmn-table_f">販売開始日</th> 16 <th class="cmn-table_g">販売数(単位)</th> 17 <th class="cmn-table_h">累積販売金額(円)</th> 18 </tr> 19 </thead> 20 <tbody> 21 <tr> 22 <th class="cmn-table_th"> 23 <a href="/item/?itemcode=a00001" title="商品名1の情報">商品1<span>a00001</span></th> 24 <td class="cmn-item"> 25 90.0<span>(12/7)</span> 26 </td> 27 <td class="cmn-item"> 28 91.5<span>(12/13)</span> 29 </td> 30 <td class="cmn-item"> 31 93.0<span>(12/8)</span> 32 </td> 33 <td class="cmn-item"> 34 92.0<span>(12/13)</span> 35 </td> 36 <td class="cmn-item"> 37 1999/1/19 38 </td> 39 <td class="cmn-item"> 40 1,565,432<span>(kL)</span> 41 </td> 42 <td class="cmn-item"> 43 173,452,222,567<span>(円)</span> 44 </td> 45 </tr> 46 <tr> 47 <th class="cmn-table_th"> 48 <a href="/item/?itemcode=a00002" title="商品名2の情報">商品2<span>a00002</span></th> 49 <td class="cmn-item"> 50 80.0<span>(12/7)</span> 51 </td> 52 <td class="cmn-item"> 53 81.5<span>(12/13)</span> 54 </td> 55 <td class="cmn-item"> 56 83.0<span>(12/8)</span> 57 </td> 58 <td class="cmn-item"> 59 82.0<span>(12/13)</span> 60 </td> 61 <td class="cmn-item"> 62 1999/7/21 63 </td> 64 <td class="cmn-item"> 65 1,868,432<span>(kL)</span> 66 </td> 67 <td class="cmn-item"> 68 423,452,288,590<span>(円)</span> 69   </td> 70 </tr> 71 72  (...略...) 73 74 </tbody> 75</table> 76 77(...略...) 78</body> 79</html>

該当のソースコード

試したこと

まずは、1ページ目の1行目だけでもとろうと思い、以下のようなコーディングをしました。
商品名と商品コードはとれましたが、最高値から累積販売金額までは、該当するタグが「<td class="cmn-item">」で同一です。
tdタグに対して、2カラム目(「平均値(円)」)以降はnext_siblingを指定してみましたが、「最高値(円)」「平均値(円)」「最安値(円)」「実勢価格(円)」のうち、最初の「最高値(円)」しかとれませんでした。
どのように指定すれば、「平均値(円)」「最安値(円)」「実勢価格(円)」の金額もとることが出来るでしょうか。
ご教示ください。
※上記4項目の日付については取得不要です。

python

1import sys 2import csv 3import datetime 4import requests 5 6from bs4 import BeautifulSoup 7from selenium import webdriver 8from selenium.webdriver.chrome.options import Options 9from time import sleep 10 11csv_date = datetime.datetime.today().strftime("%Y%m%d") 12csv_file_name = "stock_week_price_" + csv_date + ".csv" 13f = open(csv_file_name, "w", encoding="CP932", errors="ignore") 14 15writer = csv.writer(f, lineterminator="\n") 16csv_header = ["商品コード","商品名","最高値","平均値","最安値","実勢価格"] 17writer.writerow(csv_header) 18 19options = Options() 20driver = webdriver.Chrome('C:/Program Files/chromedriver_win32/chromedriver', options=options) 21 22url = "https://www.exzample.com/historical/?p=1" 23 24driver.get(url) 25res = requests.get(url) 26 27sleep(1) 28 29soup = BeautifulSoup(res.content, "lxml") 30 31soup.find('table') 32table = soup.find('table') 33 34csvlist = [] 35 36table.find('a').text 37a = table.find('a').text 38stock_code = a[-6:] 39stock_name = a[:-6] 40 41csvlist.append(item_code) 42csvlist.append(item_name) 43 44table.find('td').text 45td1 = table.find('td').text 46high_price = td1.split('(')[0] 47csvlist.append(high_price) 48 49table.td.next_sibling 50td2 = table.td.next_sibling 51average_price = td2.split('(')[0] 52csvlist.append(average_price) 53 54table.td.next_sibling 55td3 = table.td.next_sibling 56low_price = td3.split('(')[0] 57csvlist.append(low_price) 58 59table.td.next_sibling 60td4 = table.td.next_sibling 61actual_price = td4.split('(')[0] 62csvlist.append(actual_price) 63 64writer.writerow(csvlist) 65 66f.close() 67driver.close() 68 69sys.exit()

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

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

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

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

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

guest

回答1

0

ベストアンサー

next_sibling の使い方がおかしいです。
next_sibling はその名の通り、指定要素の次の要素を1つだけ取得します。

td1 = table.find('td').text
td2 = table.td.next_sibling
td3 = table.td.next_sibling
td4 = table.td.next_sibling

これだと、td1以外は全部同じ要素を取得しています。

next_siblings で同じ階層の要素のジェネレータを取得できますので、
こちらを利用する方が便利と思います。

追記
なお、最高値を表す要素 td1 を正しく取得して、
以降を次の様に指定すれば next_sibling でも各要素を取得できると思います。

td2 = td1.next_sibling
td3 = td2.next_sibling
td4 = td3.next_sibling

投稿2020/12/15 01:30

編集2020/12/15 01:57
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

_whitecat_22

2020/12/15 05:14

busyoda 様 ありがとうございます。 同階層の要素を取得できる、next_siblings のほうを使うべきということは理解できました。 ただ、まだ結果としては値がとれません。(私の組んだロジックが悪いのですが) ``` table.td.next_sibling td2 = table.td.next_sibling average_price = td2.split('(')[0] csvlist.append(average_price) table.td.next_sibling td3 = table.td.next_sibling low_price = td3.split('(')[0] csvlist.append(low_price) table.td.next_sibling td4 = table.td.next_sibling actual_price = td4.split('(')[0] csvlist.append(actual_price) ``` 上記箇所を、以下のようにしてみましたが、ダメでした。 ``` j = 0 td2 = [] for tds in table.td.next_siblings: print(tds) td2.append(tds.get_text()) price = td2.split('(')[0] csvlist.append(price) # stock_high_price j += 1 if j >= 3: break ``` 初学者であるため、基本的な質問で申し訳ございませんが、ご教示いただけましたら幸いです。 よろしくお願い申し上げます。
退会済みユーザー

退会済みユーザー

2020/12/15 05:21

修正頂いた内容は把握しました。 後、何がどうダメだったかを提示下さい。
_whitecat_22

2020/12/15 05:59

busyoda 様 ありがとうございます。 実行時のログを以下に掲載します。 以下のログを吐いて、プログラムが異常終了します。 Traceback (most recent call last): File "c:\Users\~\price.py", line 69, in <module> td2.append(tds.get_text()) File "C:\Users\~\AppData\Local\Programs\Python\Python39\lib\site-packages\bs4\element.py", line 921, in __getattr__ raise AttributeError( AttributeError: 'NavigableString' object has no attribute 'get_text' CSVにはヘッダーしか出力されない状況です。 本来、CSVファイルは異常終了時にpurgeすべきところですが。 何卒よろしくお願い申し上げます。
退会済みユーザー

退会済みユーザー

2020/12/15 06:47 編集

エラーの通りですが、 tds は NavigableString というオブジェクトですので、 get_text メソッドを保有していません。 BeautifulSoup4 では文字列を NavigableString オブジェクトとして扱っていますが、このオブジェクトはget_text以外も多くのメソッドをサポートしていません。 結論、str 関数で文字列に変換すれば問題ないと思います。 tds.get_text() の箇所を str(tds) にしてみて下さい。
_whitecat_22

2020/12/15 08:00

```python:price.py table.find('td').text td1 = table.find('td').text high_price = td1.split('(')[0] csvlist.append(high_price) td1.next_sibling td2 = str(td1.next_sibling) # Line 65 average_price = td2.split('(')[0] csvlist.append(average_price) ``` 上記のように修正したところ、エラーとなりました。 [Running] python -u "c:\Users\~\Documents\python\price.py" Traceback (most recent call last): File "c:\Users\~\Documents\python\price.py", line 65, in <module> td1.next_sibling AttributeError: 'str' object has no attribute 'next_sibling'
退会済みユーザー

退会済みユーザー

2020/12/15 08:47 編集

起こった事象を報告するだけでなく、 もう少しオブジェクト指向を理解する様努力して下さい。 自分でも考えましょう。 少なくとも、先程のエラーを経験した上であれば本件は難しくないです。 考えた結果、どこがどの様に分からないかを質問して下さい。 でないと、ただの丸投げです。 エラー自体は先程と似た様な内容です。 当然ですが、文字列型のデータは next_sibling なんてメソッドを保有するはずがないでしょう。 後、だいぶ前から気になっていましたが、 td1.next_sibling のステートメントはどういった意図で記載されてますか。 戻り値が何処にも格納されていないので、記載する意味がないと思いますが。 追記 少しアドバイスをするなら、 td1 = table.find('td').text は文字列を格納しています。 後、前回提示頂いたコードからずいぶんと様変わりしていますが、これでは意味がないと思います。 提示されたコードに対してアドバイスをしているのに、 それと全然違うコードで上手くいかないですって事後報告されても。。。
退会済みユーザー

退会済みユーザー

2020/12/15 08:55 編集

本件、当初は最高値しか取得できないことが問題であったと認識しています。 以下コードで最高値以外も表示されますよね。 for tds in soup.td.next_siblings: print(tds) 取得できた後の加工はまた別の問題です。 このままコメント欄で応答を続けても長くなりそうなので、 この質問は一旦解決済みにして、 改めて質問したい内容を明確に整理して新しく質問を立てて下さい。 回答依頼を頂ければそちらで応対します。
_whitecat_22

2020/12/15 08:56

大変申し訳ございません。 ご指摘の通り、私自身の姿勢にも問題がありました。 自身でもいろいろと試しながらすすめていたものですので、コードを大きく変えてしまったこともあります。 ご丁寧に回答をいただき、ありがとうございました。 頂戴した内容を基に、改めて自身で考えてすすめてみます。 誠にありがとうございました。
退会済みユーザー

退会済みユーザー

2020/12/15 09:07

玄人もそうですが、特に初学者のうちの試行錯誤は後々の宝になります。 頑張って下さい。
_whitecat_22

2020/12/15 09:11

最後の最後まで、ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問