[Beautifulsoup4+lxml]lxml.etree.XPathEvalError: Invalid expression への対処

記事内に広告が含まれています。

pythonを用いたスクレイピング手法であるBeautifulsoup4が有名です。このライブラリで得られたhtmlデータから、XPathを用いて欲しい情報を抽出する際に起きた瑣末なエラーについてメモします。

結論: Xpathの末尾に余計な文字をつけてはいけない

XPathを指定する際、末尾に余計なスラッシュ / が残っていると表題のエラーが発生します。

#エラーが出るコード
root = html.fromstring(str(soup))
titles = root.xpath("//p/")

#正常に動作するコード
root = html.fromstring(str(soup))
titles = root.xpath("//p")

エラーの再現

テストとして、手元にある htmlファイル test.html からpタグ の情報を取り出してターミナルに表示するコードを書いてみます。

.
├── bs_test.py         #正常なコード
├── bs_test_error.py   #エラーが起きるコード
└── test.html          #解析対象

test.htmlの中身は以下の通りです。

<!DOCTYPE html>
<html lang="ja">
 <head>
 <meta charset="utf-8">
 
 </head>
 <body>

 <h1>タイトル</h1>

 <h2 id="headline">見出し1</h2>
 <p id="content" name="content_name">コンテンツの内容1</p>

 <h2 id="headline">見出し1</h2>
 <p id="content" name="content_name">コンテンツの内容2</p>
 </body>
</html>

コンテンツの内容1, コンテンツの内容2という文字列を取り出したいと思います。

エラーが発生する場合

bs_test_error.py(エラーが発生するコード)

from lxml import html
from bs4 import BeautifulSoup

#htmlファイルを読み込む
with open("test.html") as file:
    soup = BeautifulSoup(file, 'html.parser')

#lxmlを用いて、xpathで指定した要素を抽出する。
root = html.fromstring(str(soup))
titles = root.xpath("//p/")            #<- pの末尾にスラッシュが付随
for i in range(len(titles)):
    print(titles[i].text)

ターミナルで実行すると、

$python bs_test.py      
Traceback (most recent call last):
  File "/Users/yosid/Documents/blg/SQL/mongoDB/bs_test.py", line 10, in <module>
    titles = root.xpath("//p/") #リスト形式で返される
  File "src/lxml/etree.pyx", line 1599, in lxml.etree._Element.xpath
  File "src/lxml/xpath.pxi", line 305, in lxml.etree.XPathElementEvaluator.__call__
  File "src/lxml/xpath.pxi", line 225, in lxml.etree._XPathEvaluatorBase._handle_result
lxml.etree.XPathEvalError: Invalid expression

正常に動作する場合の例

from lxml import html
from bs4 import BeautifulSoup

#htmlファイルを読み込む
with open("test.html") as file:
    soup = BeautifulSoup(file, 'html.parser')

#lxmlを用いて、xpathで指定した要素を抽出する。
root = html.fromstring(str(soup))
titles = root.xpath("//p")            #<-末尾のスラッシュを消去
for i in range(len(titles)):
    print(titles[i].text)

ターミナルで実行すると、

$python bs_test.py
コンテンツの内容1
コンテンツの内容2

両コードの差分の確認

念の為、二つのコードの差分も見てみると、xpathを記述している部分しか差がないのが確認できます。

$diff bs_test_error.py bs_test.py      
10c10
< titles = root.xpath("//p/") 
---
> titles = root.xpath("//p") 

まとめ

Beautifulsoup4とlxmlを利用した際に起きたエラーについてメモを共有しました。

僕はXPathの末尾の違いに気づくのに結構時間がかかったのですが(アホ)、使い慣れていない人などは気をつけてください。

以下に使用したライブラリのドキュメントを添付しています。

Beautifulsoup4の公式ドキュメント

Beautiful Soup Documentation — Beautiful Soup 4.12.0 documentation

lxmlの公式ドキュメント

lxml - Processing XML and HTML with Python
lxml - the most feature-rich and easy-to-use library for processing XML and HTML in the Python language

XPathの文法の解説記事

XPath基礎編(2) ー XPathの書き方 - Qiita
前回の記事では、XPathの基本概念を簡単に紹介しました。今回はXPathによるWebページ(HTML)からデータを指定・取得する方法、つまりXPathの書き方を紹介します。#1.タグ(要素)で指…
Python
スポンサーリンク

コメント