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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

selenium

Selenium(セレニウム)は、ブラウザをプログラムで作動させるフレームワークです。この原理を使うことにより、ブラウザのユーザーテストなどを自動化にすることができます。

Q&A

解決済

2回答

2684閲覧

BeautifulSoupで親要素の指定を無視して子要素が取得されてしまう

jackojacko_

総合スコア17

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

selenium

Selenium(セレニウム)は、ブラウザをプログラムで作動させるフレームワークです。この原理を使うことにより、ブラウザのユーザーテストなどを自動化にすることができます。

0グッド

0クリップ

投稿2017/07/27 04:26

編集2017/07/28 08:27

###前提・実現したいこと
カーセンサーnetの中古車検索API(https://webservice.recruit.co.jp/carsensor/reference.html)を用いてスクレイピングをしようとしています。
BeautifulSoupを使っているのですが、<body>内の<name>を出力したいのに、以下のコードではbodyより手前にある<brand>内の<name>が出力されてしまいます。
どうすればbody内のnameを取得できるでしょうか?

###発生している問題・エラーメッセージ
カーセンサーnetのAPIを使うと、たとえば車種「プリウス」で検索すると以下のようなXMLが出力されます。
以下の「ミニバン」を取得したいのに、「トヨタ」が取得されてしまいます。

XML

1<results xmlns="http://webservice.recruit.co.jp/carsensor/"> 2<api_version>1.01</api_version> 3<results_available>11262</results_available> 4<results_returned>10</results_returned> 5<results_start>1</results_start> 6<usedcar> 7<id>CU4326907244</id> 8<brand> 9<code>TO</code> 10<name>トヨタ</name> 11</brand> 12<model>プリウスα</model> 13<grade>1.8 S ツーリングセレクション</grade> 14<price>3107000</price> 15<inspection>新車未登録</inspection> 16<maintenance>法定整備付</maintenance> 17<warranty>保証付</warranty> 18<recycle>リ未</recycle> 19<engine>ハイブリッド</engine> 20<desc> 21新車車検3年!各色選べます(オプション色は32,400円高)!グレード変更OK!9型ナビフルセグ地デジ&CD録音機能&Bluetooth接続&DVD再生&バックカメラ&ETC&マット付! 22</desc> 23<body> 24<code>M</code> 25<name>ミニバン</name> 26</body> 27(以下略) 28</usedcar> 29</results>

###該当のソースコード

Python

1import lxml.html 2import selenium 3from selenium import webdriver 4from bs4 import BeautifulSoup 5 6bodytype=[] 7 8url='http://webservice.recruit.co.jp/carsensor/usedcar/v1/?key=(APIキー)&model=' 9 10driver = webdriver.Chrome('C:\selenium\chromedriver') 11 12driver.get(url + "プリウス") 13data = driver.page_source.encode('utf-8') 14soup = BeautifulSoup(data, "lxml") 15 16if soup.find("body").find("name"): 17 body = soup.find("body") 18 bodytype.append(body.find("name").string) 19else: 20 bodytype.append("NA") 21 22driver.quit()

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

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

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

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

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

guest

回答2

0

ベストアンサー

以下のような最低限のxmlデータで再現しました。
soup.find("body")すると<body>xmlデータ全体</body>が返ります。

回答修正

パーサlxmlと指定すると、HTMLパーサとして動作し、誤解釈してしまっているようです。
lxml-xmlXMLパーサとして指定することで、正常に動作することが確認できました。
各パーサについてはInstalling a parserに記載されています。

Python

1xml = """ 2<?xml version="1.0" encoding="UTF-8"?> 3<results> 4 <usedcar> 5 <brand> 6 <code>TO</code><name>トヨタ</name> 7 </brand> 8 <body> 9 <code>M</code><name>ミニバン</name> 10 </body> 11 </usedcar> 12</results> 13""" 14from bs4 import BeautifulSoup 15soup = BeautifulSoup(xml,'lxml-xml') 16print(soup.find("body")) # 正常 <body><code>M</code><name>ミニバン</name></body> 17print(soup.find("body").find("name")) # 正常 <name>ミニバン</name>

以前の回答

ちょっとxmlパーサの動作が不可解です。

とりあえずxmlデータ(=文字列)中の<body><body2>replaceすると正しく取得できました。
バッドノウハウですが。

Python

1xml = """ 2<?xml version="1.0" encoding="UTF-8"?> 3<results> 4 <usedcar> 5 <brand> 6 <code>TO</code><name>トヨタ</name> 7 </brand> 8 <body> 9 <code>M</code><name>ミニバン</name> 10 </body> 11 </usedcar> 12</results> 13""" 14 15soup = BeautifulSoup(xml,'lxml') 16print(soup.find("body")) # <body><results>~</results></body> !? 17print(soup.find("body").find("name")) # <name>トヨタ</name> !? 18 19results = soup.find("results") 20usedcar = results.find('usedcar') 21print(usedcar.find('body')) # None !? 22print(usedcar.find("brand")) # <brand><code>TO</code><name>トヨタ</name></brand> 23 24# バッドノウハウ : <body> -> <body2> に置換 25xml = xml.replace( '<body>', '<body2>') 26xml = xml.replace( '</body>', '</body2>') 27 28soup = BeautifulSoup(xml,'lxml') 29print(soup.find("body2").find("name")) # <name>ミニバン</name>

投稿2017/07/27 07:03

編集2017/07/28 08:42
can110

総合スコア38262

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

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

jackojacko_

2017/07/28 08:28 編集

ありがとうございます!XMLパーサの問題なのですね…… 根本的な解決は難しそうですね。APIを公開してくださっている各社には<body>タグを使わないようにお勧めしたいですね……
can110

2017/07/28 08:43

回答修正しました。正しくXMLパーサを指定することで正常に動作するようになります。
jackojacko_

2017/07/28 08:49

ありがとうございます!確かに<body>タグってHTMLにほぼあるタグですもんね。また理解が深まりました。
can110

2017/07/28 08:55

HTMLパーサとしてのfind動作は、直下に<body>タグがなければ「全体を<body>で囲って返す」という、ちょっとお節介な動作をしているようです。一部のHTMLコード片からfindしたいときには、まあ便利な動きかもしれません。
guest

0

##追記
ご回答を受けて、<body>の子タグの<body>の子タグの<name>と指定したところ、そちらでもうまくいきました!
(パーサーを他に替えてもどうしてもHTMLと認識してしまうこともあるので……)

Python

1import lxml.html 2import selenium 3from selenium import webdriver 4from bs4 import BeautifulSoup 5 6bodytype=[] 7 8url='http://webservice.recruit.co.jp/carsensor/usedcar/v1/?key=(APIキー)&model=' 9 10driver = webdriver.Chrome('C:\selenium\chromedriver') 11 12driver.get(url + "プリウス") 13data = driver.page_source.encode('utf-8') 14soup = BeautifulSoup(data, "lxml") 15 16if soup.find("name"): 17 bodytype.append(soup.find("body").find("body").find("name").string) 18else: 19 bodytype.append(None) 20 21driver.quit()

##自己解決
根本的な原因はわからないままなのですが、「<body>の子要素の<name>」という風に親要素との関連で定義するのを諦めて、「2つ目の<name>タグ」という条件で指定するようにしたところ、一応本来の目的は達成できました。

Python

1import lxml.html 2import selenium 3from selenium import webdriver 4from bs4 import BeautifulSoup 5 6bodytype=[] 7 8url='http://webservice.recruit.co.jp/carsensor/usedcar/v1/?key=(APIキー)&model=' 9 10driver = webdriver.Chrome('C:\selenium\chromedriver') 11 12driver.get(url + "プリウス") 13data = driver.page_source.encode('utf-8') 14soup = BeautifulSoup(data, "lxml") 15 16if soup.find("name"): 17 bodytype.append(soup.find("name")[1].string) 18else: 19 bodytype.append("NA") 20 21driver.quit()

投稿2017/07/28 08:33

編集2017/09/12 03:42
jackojacko_

総合スコア17

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問