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

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

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

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

Beautiful Soup

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

Python

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

pandas

Pandasは、PythonでRにおけるデータフレームに似た型を持たせることができるライブラリです。 行列計算の負担が大幅に軽減されるため、Rで行っていた集計作業をPythonでも比較的簡単に行えます。 データ構造を変更したりデータ分析したりするときにも便利です。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

解決済

1回答

1280閲覧

テーブルのスクレイピングで値のないカラムにも値をセットしてcsv出力したい

azzuro

総合スコア53

スクレイピング

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

Beautiful Soup

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

Python

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

pandas

Pandasは、PythonでRにおけるデータフレームに似た型を持たせることができるライブラリです。 行列計算の負担が大幅に軽減されるため、Rで行っていた集計作業をPythonでも比較的簡単に行えます。 データ構造を変更したりデータ分析したりするときにも便利です。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

0グッド

0クリップ

投稿2020/12/01 06:39

編集2020/12/02 07:13

初めてスクレイピングをしようとしています。
色々なサイトを参考に何とか動くようにはなりましたが、想定通りの結果にならず躓いています。
よろしくお願いします。

前提・実現したいこと

あるサイトからテーブルの内容をスクレイピングし、csv出力したいです。
リンク内容
テーブルの一部カラムが欠損しているのがネックなようで思ったような取得結果になりません。

サイトには下記のようなテーブルが複数あります。
同じ機械の場合、機械欄はブランクとなっています。

機械メーカー数量
機械Aabc1
機械Bbbb1
ccc3
ddd2
eee5
fff1

最終的に取得したい結果は下記のようなcsvです

csv

1機械,メーカー,数量 2機械A,abc,1 3機械B,bbb,1 4機械B,ccc,1 5機械B,ddd,2 6機械B,eee,5 7機械B,fff,1

発生している問題・エラーメッセージ

試してみたところ、こんな形式で出力されてしまいました。

csv

1機械種別,メーカー,数量,機械A,abc,1,機械B,bbb,1,ccc,1,ddd,2,eee,5,fff,1

該当のソースコード

python

1from bs4.element import Tag 2import requests 3from bs4 import BeautifulSoup 4import csv 5 6url = 'https://xxxxxxx' 7# リクエスト 8res = requests.get(url) 9# エンコーディング 10res.encoding = res.apparent_encoding 11# htmlをパース 12soup = BeautifulSoup(res.text, "html.parser") 13# データ取得 14tables = soup.find_all(class_="tbl-data-01") 15file = 'C:/xxxxxxx/result.csv' 16with open(file, "a", encoding='utf-8') as file: 17 writer = csv.writer(file) 18 for table in tables: 19 rows = table.findAll("tbody") 20 for row in rows: 21 csvRow = [] 22 for cell in row.findAll(['td', 'th']): 23 print(cell.text) 24 csvRow.append(cell.get_text()) 25 if len(csvRow) > 0: 26 writer.writerow(csvRow)

試したこと

上記のコードの前に下記でやってみたところ、各機械の1行目しか取れませんでした。
HTMLのソースを眺めていてtbodyfind_allした方が良さそうだと検討をつけて
上記のコードになりました。

python

1from bs4.element import Tag 2import requests 3from bs4 import BeautifulSoup 4import csv 5 6url = 'https://xxxxxxx' 7# リクエスト 8res = requests.get(url) 9# エンコーディング 10res.encoding = res.apparent_encoding 11# htmlをパース 12soup = BeautifulSoup(res.text, "html.parser") 13tables = soup.find_all(class_="tbl-data-01") 14file = 'C:/xxxxxxx/result.csv' 15with open(file, "a", encoding='utf-8') as file: 16 writer = csv.writer(file) 17 for table in tables: 18 #print(table.tbody) 19 rows = table.findAll("tr") 20 for row in rows: 21 csvRow = [] 22 for cell in row.findAll(['td', 'th']): 23 print(cell.text) 24 csvRow.append(cell.get_text()) 25 if len(csvRow) > 0: 26 writer.writerow(csvRow)

このコードで取得できたのが下記の結果です

csv

1機械,メーカー,数量 2機械A,abc,1 3機械B,bbb,1

補足情報(FW/ツールのバージョンなど)

python 3.8.1
vscode 1.48.2

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

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

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

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

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

guest

回答1

0

ベストアンサー

どの様なソースかわかりませんが、以下の様にすれば
実現が可能になるのではないでしょうか。
メーカー名だけを保持する変数を用意し
もしもメーカー名が空だった場合には
そのまま前度のメーカー名が補填するという流れで再現が可能かと思います。

(本質問ページ上のtable要素を対象にテストしたものであり、
質問者様のスクレイピングを行おうとしているページにそのまま流用できるかは分かりかねます)

python

1from bs4 import BeautifulSoup 2import requests 3import pandas as pd 4 5header = [th.text for th in tables.find_all('th')] 6result = [] 7for tr in tables.find('tbody').find_all('tr'): 8 tmp = [td.text for td in tr.find_all('td')] 9 if tmp[0] != '': 10 maker = tmp[0] 11 else: 12 tmp[0] = maker 13 result.append(tmp) 14 15df = pd.DataFrame(result) 16df.to_csv('result.csv', encoding='cp932', header=header, index=False)

追記

とりあえずは以下で目的の形での取得が可能かと思います。
ちょっと複雑で説明が難しいです。
for文ではenumerarteで何番目かを判定しています。
0番目(初回)のループである場合にはヘッダー行は無視し
それ以外の場合には要素をtd要素を抽出します。

今回のソースの場合[機械種別]が記載されたtrにrowspanが設定されていた為
rowspanを抽出し、rowspanが1を超える場合には
.find_next_siblings()で兄弟要素を取得し
先頭列に[機械種別]の記載がないデータの数(列(rowspan-1)×2行)分のデータをスライスして抽出しています。
抽出したデータの先頭に保持していたgenre変数(機械種別名)をおいてデータを整形し
都度resultリストに追加していき、最後にdfに変換するという流れです。

python

1from bs4 import BeautifulSoup 2import requests 3import pandas as pd 4 5url = 'https://ja.nc-net.or.jp/search/equipment/?cl[]=1' 6 7res = requests.get(url) 8soup = BeautifulSoup(res.content, 'html.parser') 9tables = soup.find('table', id='equip1_list') 10 11#print(tables.prettify()) 12header = [th.text for th in tables.find_all('th')] 13result = [] 14for e, tr in enumerate(tables.find('tbody').find_all('tr')): 15 if e != 0: 16 genre, maker, num = [td.text for td in tr.find_all('td')] 17 span = int(tr.find_all('td')[0].get('rowspan')) 18 result.append([genre, maker, num.replace('台', '')]) 19 20 if span > 1: 21 after = [td.text for td in tr.find_next_siblings()[0: (span-1)*2]] 22 for i in range(0, len(after), 2): 23 maker, num = after[i: i+2] 24 result.append([genre, maker, num.replace('台', '')]) 25 26 27df = pd.DataFrame(result) 28df.to_csv('result.csv', encoding='cp932', header=header, index=False)

投稿2020/12/02 03:42

編集2020/12/02 10:25
nto

総合スコア1438

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

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

azzuro

2020/12/02 07:24

ご回答ありがとうございます。 具体的なサイトのリンク先を提示しました。 機械種別をうまく保持することができないでいます。 タグを見ると「td rowspan="メーカー数"」のようになっており、tr.find_all('td rowspan*')としてみましたがダメでした。 ntoさんのアドバイスは理解できるのですが、やり方がわからないでいます。
nto

2020/12/02 08:39 編集

これちょっとソースの作りが特殊ですね。 print(tables)として対象箇所のソースを確認するとわかりやすいのですが 一般的にhtmlの構造的には<td>要素は<tr>要素の中にあるべきものですが 同一種別のメーカーが複数ある場合にはなぜか先頭を除くメーカーがすべて<td>要素が<tr>要素の外でコーディングされてしまっている様です。 Chromeのデベロッパーツールなどは、そういった要素があった場合、自動的に補完や整形された状態で表示されてしまう為、実際のソースはデベロッパーツール上で表示されているものとは少し形が異なってしまっている様です。
azzuro

2020/12/02 08:03

特殊なソースなんですね。。。 htmlは全くの門外漢で今回初めてやることになったので、そこらへんのソースの特殊性等に気がつかないままでした。 わからないながらも悪戦苦闘して種別に値のあるものだけしか取れないか、同一種別が複数あるものが取れてもサンプルであげたようにだらだらと1行で取れてしまうかです。 もし、何か良いお知恵があれば引き続きおつきあい頂けるとありがたいです。
nto

2020/12/02 10:27

先ほど回答の追記を致しました。 完全にお手伝いとなってしまい、また解説もやや難しくなってしまいましたが どうかじっくり解説やコードの流れを理解していただけると幸いです。
azzuro

2020/12/03 01:27

想定通りの結果が出力できました。 enumerarteやfind_next_siblings()の使い方を知ることができました。 調べても、ここら辺を探し出すことができなかったのでなるほどと思いました。 実際に動かしてみて値がどのように取れていくのか確認したことで、解説ではよくわからなかった 「データの数(列(rowspan-1)×2行)分のデータをスライス」の部分も理解できました。 全コードご提示いただくことになってしまいましたが、大変勉強になりました。 どうもありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問