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

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

ただいまの
回答率

90.34%

  • Python 3.x

    11213questions

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

  • selenium

    806questions

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

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

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,163

jackojacko_

score 15

前提・実現したいこと

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

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

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

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

該当のソースコード

import lxml.html
import selenium
from selenium import webdriver
from bs4 import BeautifulSoup

bodytype=[]

url='http://webservice.recruit.co.jp/carsensor/usedcar/v1/?key=(APIキー)&model='

driver = webdriver.Chrome('C:\selenium\chromedriver')

driver.get(url + "プリウス")
data = driver.page_source.encode('utf-8')
soup = BeautifulSoup(data, "lxml")

if soup.find("body").find("name"):
   body = soup.find("body")
   bodytype.append(body.find("name").string)
else:
   bodytype.append("NA")

driver.quit()
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

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

 回答修正

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

xml = """
<?xml version="1.0" encoding="UTF-8"?>
<results>
  <usedcar>
    <brand>
      <code>TO</code><name>トヨタ</name>
    </brand>
    <body>
      <code>M</code><name>ミニバン</name>
    </body>
  </usedcar>
</results>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(xml,'lxml-xml')
print(soup.find("body"))              # 正常 <body><code>M</code><name>ミニバン</name></body>
print(soup.find("body").find("name")) # 正常 <name>ミニバン</name>

 以前の回答

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

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

xml = """
<?xml version="1.0" encoding="UTF-8"?>
<results>
  <usedcar>
    <brand>
      <code>TO</code><name>トヨタ</name>
    </brand>
    <body>
      <code>M</code><name>ミニバン</name>
    </body>
  </usedcar>
</results>
"""

soup = BeautifulSoup(xml,'lxml')
print(soup.find("body"))              # <body><results>~</results></body> !?
print(soup.find("body").find("name")) # <name>トヨタ</name> !?

results = soup.find("results")
usedcar = results.find('usedcar')
print(usedcar.find('body'))  # None !?
print(usedcar.find("brand")) # <brand><code>TO</code><name>トヨタ</name></brand>

# バッドノウハウ : <body> -> <body2> に置換
xml = xml.replace( '<body>', '<body2>')
xml = xml.replace( '</body>', '</body2>')

soup = BeautifulSoup(xml,'lxml')
print(soup.find("body2").find("name")) # <name>ミニバン</name>

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/28 17:28 編集

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

    キャンセル

  • 2017/07/28 17:43

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

    キャンセル

  • 2017/07/28 17:49

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

    キャンセル

  • 2017/07/28 17:55

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

    キャンセル

0

追記

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

import lxml.html
import selenium
from selenium import webdriver
from bs4 import BeautifulSoup

bodytype=[]

url='http://webservice.recruit.co.jp/carsensor/usedcar/v1/?key=(APIキー)&model='

driver = webdriver.Chrome('C:\selenium\chromedriver')

driver.get(url + "プリウス")
data = driver.page_source.encode('utf-8')
soup = BeautifulSoup(data, "lxml")

if soup.find("name"):
   bodytype.append(soup.find("body").find("body").find("name").string)
else:
   bodytype.append(None)

driver.quit()

自己解決

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

import lxml.html
import selenium
from selenium import webdriver
from bs4 import BeautifulSoup

bodytype=[]

url='http://webservice.recruit.co.jp/carsensor/usedcar/v1/?key=(APIキー)&model='

driver = webdriver.Chrome('C:\selenium\chromedriver')

driver.get(url + "プリウス")
data = driver.page_source.encode('utf-8')
soup = BeautifulSoup(data, "lxml")

if soup.find("name"):
   bodytype.append(soup.find("name")[1].string)
else:
   bodytype.append("NA")

driver.quit()

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 90.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Python 3.x

    11213questions

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

  • selenium

    806questions

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