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

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

新規登録して質問してみよう
ただいま回答率
85.46%
スクレイピング

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

CSV

CSV(Comma-Separated Values)はコンマで区切られた明白なテキスト値のリストです。もしくは、そのフォーマットでひとつ以上のリストを含むファイルを指します。

Python

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

pandas

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

Q&A

解決済

2回答

1160閲覧

webスクレイピングでの画像の取得について

kakugen

総合スコア3

スクレイピング

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

CSV

CSV(Comma-Separated Values)はコンマで区切られた明白なテキスト値のリストです。もしくは、そのフォーマットでひとつ以上のリストを含むファイルを指します。

Python

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

pandas

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

0グッド

0クリップ

投稿2020/10/13 00:52

編集2020/10/13 01:18

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

以前、このサイトで教えてもらった方法で、日本司法書士会連合会のホームページで、リストを作成しようとしていますが、一部画像が入っているところを上手く取得できません。 例えば、札幌司法書士の3番目の方は、画像の部分が空欄で出てきます。 これを一つ一つ目視で確認するのは大変なので、一つ列を作って、画像を張り付けるようにする方法を教えてください。 よろしくお願いいたします。

該当のソースコード

import pandas as pd import time url_tmpl = r"http://kensaku.shiho-shoshi.or.jp/search/member.php?search_code=01&search_name=&search_address=&x=140&y=16&pageID={}" max_pages = 30 dfs = [] for page_no in range(1, max_pages + 1): url = url_tmpl.format(page_no) print(f"fetching... {url}") # ページを取得する。 (df,) = pd.read_html(url) # 1行目の空白行無視 df = df.iloc[1:] # スリープ time.sleep(1) dfs.append(df) df = pd.concat(dfs) # "名前 フリガナ" となっているため、" " で分割して、列を分ける。 df[["氏名", "フリガナ"]] = df["氏名"].str.split(" ", expand=True) # "事務所所在地 TEL : 電話番号" となっているため、" TEL : " で分割して、列を分ける。 df[["事務所所在地", "電話番号"]] = df["事務所所在地"].str.split(" TEL : ", expand=True) df.to_csv("札幌司法書士会.csv", encoding='utf-8_sig')

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

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

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

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

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

guest

回答2

0

ベストアンサー

  • BeautifulSoupを使用する

対象の要素を取得していく中で、構成要素を検証し「img要素が含まれていた場合には~」「含まれていない場合には~」と処理を分岐させるもできるしまた<img>要素自体をそのままスルーして文字列だけの取得も可能

python

1from bs4 import BeautifulSoup 2import requests 3 4url = 対象URL 5for num in range(30): 6 html = requests.get(url + str(num+1)) 7 soup = BeautifulSoup(html.content, 'html.parser') 8 9
  • 取得した要素からのデータ抽出・整形

文中で<br>タグで改行されていたり\tが使用されていたりなど非常に作りの悪いサイトの様で
取得したテキストを分割したり正規表現などで各データを抽出・整形する必要がありそうです

python

1import re 2 3郵便番号パターン = r'〒\d{3}-\d{4}' 4電話番号パターン = r'TEL : \d{2,4}?-\d{2,4}?-\d{3,4}' 5 6 7table = soup.find('table', id='kojin') 8# table内から全てのtr要素を取得 9tr_list = soup.find_all('tr') 10for tr in tr_list: 11 # table要素内にtacクラスの要素がある場合に実行(要は1番目のtr要素の除外) 12 if tr.find_all(class_='tac'): 13 # 大まかにtr要素内の全ての文字列を取得・分割しておく 14 td_list = [td.text for td in tr.find_all('td')] 15 # 氏名を漢字表記とルビ表記で分割 16 # 氏名欄にimg要素が含まれていた場合には名前の先頭に目印として○をつける 17 if tr.find('img'): 18 names = td_list[1].split('\n') 19 names[0] = '○' + names[0] 20 else: 21 names = td_list[1].split('\n') 22 # 正規表現を使用して郵便番号の取得 23 zipcode = re.search(郵便番号パターン, td_list[4]).group() 24 # 正規表現を使用して電話番号の取得、電話番号を載せていないケースもある為try文を使用する 25 try: 26 telephone = re.search(電話番号パターン, td_list[4]).group() 27 locate = td_list[4].replace('\t','').replace('\n', '').replace(zipcode, '').replace(telephone, '') 28 # 電話番号を掲載していなかった場合の処理 29 except: 30 telephone = 'NO TEL' 31 locate = td_list[4].replace('\t','').replace('\n', '').replace(zipcode, '') 32 33

以上で、異体字時による画像の挿入によるデータ抽出失敗の回避と
所在や氏名などのデータの抽出が行う事ができるかと思います。
後はデータフレームとしてまとめてあげれば正常に目的の動作が得られるのではないでしょうか。

上記コードは実際には検証しておらず、またあくまでも方法の提示・解説であり
コピペすればそのままコードが動くというものではございませんのでご参考までにご活用ください。

追記

以下が正しいコードの実行方法となります。

python

1from bs4 import BeautifulSoup 2import requests 3import re 4 5郵便番号パターン = r'〒\d{3}-\d{4}' 6電話番号パターン = r'TEL : \d{2,4}?-\d{2,4}?-\d{3,4}' 7 8url = r'http://kensaku.shiho-shoshi.or.jp/search/member.php?search_code=01&search_name=&search_address=&x=140&y=16&pageID=' 9for num in range(30): 10 html = requests.get(url + str(num+1)) 11 soup = BeautifulSoup(html.content, 'html.parser') 12 13 table = soup.find('table', id='kojin') 14 tr_list = soup.find_all('tr') 15 for tr in tr_list: 16 if tr.find_all(class_='tac'): 17 td_list = [td.text for td in tr.find_all('td')] 18 if tr.find('img'): 19 names = td_list[1].split('\n') 20 names[0] = '○' + names[0] 21 else: 22 names = td_list[1].split('\n') 23 zipcode = re.search(郵便番号パターン, td_list[4]).group() 24 try: 25 telephone = re.search(電話番号パターン, td_list[4]).group() 26 locate = td_list[4].replace('\t','').replace('\n', '').replace(zipcode, '').replace(telephone, '') 27 except: 28 telephone = 'NO TEL' 29 locate = td_list[4].replace('\t','').replace('\n', '').replace(zipcode, '')

これで期待のデータの抽出できコード自体は正しく動くでしょうが、しかしこれではまだ多少の問題点が残ります。
例えば今回の例であればURLでは26ページ目までしか存在しませんが、それ以降のページも取得しようとコードが動いてしまったり、短時間での過度なリクエストを送ってしまうコードになってしまっており、相手方のサーバーへの負担をかけてしまう事になります。

その為、for文ではなくWhile文を使用し、次のページが存在する場合にのみスクレイピング+データの抽出を続行する形に改めて修正致しました。

python

1import re 2import time 3from urllib.parse import urljoin 4from bs4 import BeautifulSoup 5import requests 6 7 8# 予め正規表現でそれぞれのパターンを準備しておく 9郵便番号パターン = r'〒\d{3}-\d{4}' 10電話番号パターン = r'TEL : \d{2,4}?-\d{2,4}?-\d{3,4}' 11# 今回のケースの場合56行目で相対パスとして取得される為、予めここでもウェブサイトURL部分と分割しておく 12url = 'http://kensaku.shiho-shoshi.or.jp' 13href = '/search/member.php?search_code=01&search_name=&search_address=&x=140&y=16&pageID=1' 14 15while True: 16 html = requests.get(urljoin(url, href)) # urljoinでURLの結合 17 soup = BeautifulSoup(html.content, 'html.parser') 18 # table要素を指定 19 table = soup.find('table', id='kojin') 20 # table内から全てのtr要素を取得 21 tr_list = soup.find_all('tr') 22 for tr in tr_list: 23 # table要素内にtacクラスの要素がある場合に実行(要は1番目のtr要素の除外) 24 if tr.find_all(class_='tac'): 25 # 大まかにtr要素内の全ての文字列を取得・分割しておく 26 td_list = [td.text for td in tr.find_all('td')] 27 # 氏名を漢字表記とルビ表記で分割 28 # 氏名欄にimg要素が含まれていた場合には名前の先頭に目印として○をつける 29 if tr.find('img'): 30 names = td_list[1].split('\n') 31 names[0] = '○' + names[0] 32 else: 33 names = td_list[1].split('\n') 34 # 正規表現のパターンが一致した場合に郵便番号の取得 35 zipcode = re.search(郵便番号パターン, td_list[4]).group() 36 # 正規表現のパターンが一致した場合に電話番号の取得 37 # (電話番号を載せていないケースもある為try文を使用する) 38 try: 39 telephone = re.search(電話番号パターン, td_list[4]).group() 40 locate = td_list[4].replace('\t','').replace('\n', '').replace(zipcode, '').replace(telephone, '') 41 # 電話番号を掲載していなかった場合の処理 42 except: 43 telephone = 'NO TEL' 44 locate = td_list[4].replace('\t','').replace('\n', '').replace(zipcode, '') 45 46 print('電話番号:', telephone) 47 print('郵便番号:', zipcode) 48 print('住所:', locate) 49 print('氏名:', names) 50 print('='*20) 51 print('='*50) 52 # ページ内に[次のページ]を表す[>]が存在するかを判定 53 # 存在した場合Trueが返ってくる為、if文が実行される(hrefの値が更新され次のページにループする) 54 if soup.find(class_='pagebottom').find_all('a', attrs={'title':'next page'}): 55 # 取得したhrefは相対パスである為、16行目でウェブサイトURLと結合される 56 href = soup.find(class_='pagebottom').find('a', attrs={'title':'next page'}).get('href') 57 # 次のページが存在しなかった場合にはループが終了 58 else: 59 break 60 # 短時間に連続してリクエストを送る行為はマナー違反且つサーバーへの負荷となる為 61 # 標準ライブラリよりtimeモジュールを使用して1秒間隔でリクエストを送る様に調整 62 time.sleep(1) 63print('Done!')

データフレーム部に関しては引き続きご自身にてコーディングを行ってください。

投稿2020/10/13 03:49

編集2020/10/15 05:36
nto

総合スコア1438

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

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

kakugen

2020/10/13 14:53

ありがとうございます。ここまでのコードで動くことは確認しました。データフレームとして、まとめあげるのは、pandasを使用すればよいでしょうか?非エンジニアのため、色々と初歩的な質問ばかりで申し訳ございません。
kakugen

2020/10/13 15:41 編集

最終的に、下記のコードを書きましたが、同じページが繰り返して取得されてしまい、なおかつ画像のところが、うまく〇になりませんでした。 どのように修正すればよいでしょうか? 申し訳ありませんが、よろしくお願いいたします。 ### コード ```python from bs4 import BeautifulSoup import requests import html5lib import time url = "http://kensaku.shiho-shoshi.or.jp/search/member.php?search_code=01&search_name=&search_address=&x=119&y=17&pageID={}" for num in range(30): max_pages = 30 html = requests.get(url + str(num+1)) soup = BeautifulSoup(html.content, 'html.parser') import re 郵便番号パターン = r'〒\d{3}-\d{4}' 電話番号パターン = r'TEL : \d{2,4}?-\d{2,4}?-\d{3,4}' table = soup.find('table', id='kojin') # table内から全てのtr要素を取得 tr_list = soup.find_all('tr') for tr in tr_list: # table要素内にtacクラスの要素がある場合に実行(要は1番目のtr要素の除外) if tr.find_all(class_='tac'): # 大まかにtr要素内の全ての文字列を取得・分割しておく td_list = [td.text for td in tr.find_all('td')] # 氏名を漢字表記とルビ表記で分割 # 氏名欄にimg要素が含まれていた場合には名前の先頭に目印として○をつける if tr.find('img'): names = td_list[1].split('\n') names[0] = '○' + names[0] else: names = td_list[1].split('\n') # 正規表現を使用して郵便番号の取得 zipcode = re.search(郵便番号パターン, td_list[4]).group() # 正規表現を使用して電話番号の取得、電話番号を載せていないケースもある為try文を使用する try: telephone = re.search(電話番号パターン, td_list[4]).group() locate = td_list[4].replace('\t','').replace('\n', '').replace(zipcode, '').replace(telephone, '') # 電話番号を掲載していなかった場合の処理 except: telephone = 'NO TEL' locate = td_list[4].replace('\t','').replace('\n', '').replace(zipcode, '') import pandas as pd dfs = [] for page_no in range(1, max_pages + 1): url = url.format(page_no) print(f"fetching... {url}") # ページを取得する。 (df,) = pd.read_html(url) # 1行目の空白行無視 df = df.iloc[1:] # スリープ time.sleep(1) dfs.append(df) df = pd.concat(dfs) # "名前 フリガナ" となっているため、" " で分割して、列を分ける。 df[["氏名", "フリガナ"]] = df["氏名"].str.split(" ", expand=True) # "事務所所在地 TEL : 電話番号" となっているため、" TEL : " で分割して、列を分ける。 df[["事務所所在地", "電話番号"]] = df["事務所所在地"].str.split(" TEL : ", expand=True) df.to_csv("札幌司法書士会.csv", encoding='utf-8_sig') ```
nto

2020/10/15 03:05

非エンジニアの為~との事でしたが、私も非エンジニアです。 また先日回答したコードは、URLを代入し、ご自身が保持していたデータフレーム部分のコードを貼り付ければそのまま動作するというものではありません。 [同じページが繰り返して取得されてしまい~]については、ただコードを貼り付けた事により、ページを順番にfor文で取得しているにも関わらずfor文内で処理を行っておらずfor文を抜けた後にデータの取得を行っている為その様な結果が生じているのだと思います。 各ページで処理を行いたいならばfor文の中で実行し、各ページごとにデータを取得できる様にしましょう。 画像の回避についてもそれが原因と思われます。
kakugen

2020/10/15 04:28

ありがとうございます。後ほど、試させていただきます。
nto

2020/10/15 04:35

今一度お試しの上ご不明な点が御座いましたら改めてご質問ください。
nto

2020/10/15 05:37

回答内容に追記を致しましたのでご確認ください。
guest

0

残念ながら、pandasのread_htmlでは、読み込むHTMLのtable要素に含まれるimg要素などの情報は取り除かれてしまい、「画像が含まれていたかどうか」さえ調べる手段はありません。

ということは、read_htmlを使わずに、HTMLのtable要素を自前でスクレイピングし、そこに含まれるテキスト以外の情報を自分で取り扱う必要があります。

投稿2020/10/13 01:48

Daregada

総合スコア11990

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

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

kakugen

2020/10/13 14:31

ありがとうございました。初心者で分からなかったので、参考になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.46%

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

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

質問する

関連した質問