turtlechanのブログ

無知の私がLinuxをいじりながら書いていくブログ

よく忘れるのでxpathの書き方をまとめた

pythonスクレイピングするとき、私は lxml ライブラリを使用して xpath で要素を取得することが多い。Beautiful Soup ライブラリを使えばシンプルに目的の要素を取得できるっぽいが私は使ったことがない。
それはさておき、xpath の書き方を忘れて調べることが多いので自分の見やすいように主要な書き方をまとめます。

書き方

XPathの主要な書き方を表にまとめました。

取得したい要素 XPath
htmlの子であるbody直下のspan要素 /html/body/span
span要素(どこでも) //span
bodyの子のspan要素 //body/span
bodyの子孫のspan要素 //body//span
bodyの子であるすべての要素 //body/*
上からn番目のli要素 //ul/li[n]
class属性が"hoge"であるp要素 //p[@class="hoge"]
class属性に"hoge"が含まれるているp要素 //p[contains(@class, "hoge")]
src属性が"https://"で始まるimg要素 //img[starts-with(@src, "https://")]
src属性が".png"で終わるimg要素
(XPath2.0以上)
//img[ends-with(@src, ".png")]
直下の文字列が"ほげ"であるp要素 //p[text()="ほげ"]
直下の文字列に"ほげ"が含まれているp要素 //p[contains(text(), "ほげ")]

pythonで動作確認

ざっくり動作確認したいと思います。

※python2で書いてます。

準備

Web上のものを使いたいとろこですが、迷惑がかかると嫌なので適当に作った以下のHTMLファイルを使用します。ホームディレクトリ内に配置したと仮定する。

test.html
<!DOCTYPE html>
<html>
    <head>
    <style type="text/css">
    <!--
    * {
        border: solid 1px #000000;
        padding: 0.5em 1em;
        margin: 1em 0;
    }
    -->
    </style>
    <meta charset='utf-8'>
    <title>タートルちゃんのテストHTML</title>
    </head>
    <body>
        <h1>タートルちゃんのスクレイピング</h1>
        <p>これはパラグラフです。</p>
        <span>これは html/body 直下のspanです。</span>
        <p>タートルちゃんがスクレイピングテスト用に書いたHTMLです。</p>
        <h2>タートルちゃんの見出し</h2>
        <img src='https://cdn-ak.f.st-hatena.com/images/fotolife/t/turtlechan/20190903/20190903214546.png' alt='タートルちゃんの画像' width=300>
        <p>↑タートルちゃん</p>
        <a href='https://turtlechan.hatenablog.com/'>タートルちゃんのブログ</a>
        <a href='//localhost'>ローカルホスト</a>
        <p class='hoge'>ほげ</p>
        <p class='hogehoge'>ほげほげ</p>
        <ul>
            <li>リスト1</li>
            <li>リスト2</li>
            <li>リスト3</li>
        </ul>
        <div class='turtlechan'>
            <span>これは html/body/div 直下のspanです。</span>
            <p>div要素内のパラグラフ</p>
            <div class='kame'>
                <span>これは html/body/div/div 直下のspanです。</span>
                <p>カメ</p>
        </div>
    </body>
</html>

Webブラウザに読み込ませると以下のような感じになる。要素の親子関係が分かりやすいようにボーダーで囲った(汚い)。

test.htmlをブラウザで表示した画像

次にpythonで上記のHTMLを読み込む。以下のスクリプトをホームディレクトリに配置して動作を確認する。

scrape_test.py
#! /usr/bin/env python
# coding: utf-8

import lxml.html


''' HTMLファイルの読み込み '''
htmlText = None
with open('./test.html', 'r') as f:
    htmlText = f.read()

''' XPathでパースできるようにHtmlElementを生成 '''
html = lxml.html.fromstring(htmlText)
''' 以下に動作確認するコードを書く '''
elements = html.xpath('ここに動作確認するXPathを記述する')
''' 以上に動作確認するコードを書く '''

''' printで標準出力に出力 '''
for e in elements:
    print e.text

HtmlElement.xpath() の戻り値は一つだったとしてもリストで返ってくるので for ループで該当した要素のテキストを出力する形にしています。

動作確認

書き方の項目で書いた順番で確認していきます。

htmlの子であるbody直下のspan要素

elements = html.xpath('/html/body/span')
これは html/body 直下のspanです。

span要素(どこでも)

elements = html.xpath('//span')
これは html/body 直下のspanです。
これは html/body/div 直下のspanです。
これは html/body/div/div 直下のspanです。

先程含まれていなかったspan要素もちゃんと拾われていますね。HTMLファイル内すべてのspan要素が表示されるはずです。

bodyの子のspan要素

elements = html.xpath('//body/span')
これは html/body 直下のspanです。

bodyの子孫のspan要素

elements = html.xpath('//body//span')
これは html/body 直下のspanです。
これは html/body/div 直下のspanです。
これは html/body/div/div 直下のspanです。

bodyの子であるすべての要素

elements = html.xpath('//body/*')
タートルちゃんのスクレイピング
これはパラグラフです。
これは html/body 直下のspanです。
タートルちゃんがスクレイピングテスト用に書いたHTMLです。
タートルちゃんの見出し
None
↑タートルちゃん
タートルちゃんのブログ
ローカルホスト
ほげ
ほげほげ
            
            
            
            

ちょっと結果が分かりにくいですね。「print e.text」を「print e.tag」にして実行してみるます。

h1
p
span
p
h2
img
p
a
a
p
p
ul
div

body要素の子のみが表示されているのが確認できますね。

上からn番目のli要素

ここでは「上から2番目」のli要素にします。

elements = html.xpath('//ul/li[2]')
リスト2

ある要素の子に同じ要素名が複数ある場合に使います。

class属性が"hoge"であるp要素

elements = html.xpath('//p[@class="hoge"]')
ほげ

要素名[@属性名="値"] の形で使います。

class属性に"hoge"が含まれるているp要素

elements = html.xpath('//p[contains(@class, "hoge")]')
ほげ
ほげほげ

hoge が含まれていれば該当するので先程はなかった「ほげほげ」もヒットします。

src属性が"http://"で始まるimg要素

elements = html.xpath('//img[starts-with(@src, "https://")]')
None

img要素にテキストが存在しないので結果は None になってます。ちゃんとimg要素は拾われているので text を tag に変えて確認してみてください。

src属性が".png"で終わるimg要素(XPath2.0以上)

elements = html.xpath('//img[ends-with(@src, ".png")]')
Traceback (most recent call last):
  File "scrape_test.py", line 15, in 
    elements = html.xpath('//img[ends-with(@src, ".png")]')
  File "src/lxml/etree.pyx", line 1581, 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: Unregistered function

シェルが値を返しました 1

私の環境では対応していなかったので確認できませんでした。ごめんなさい。

直下の文字列が"ほげ"であるp要素

elements = html.xpath('//p[text()="ほげ"]')
Traceback (most recent call last):
  File "scrape_test.py", line 15, in 
    elements = html.xpath('//p[text()="ほげ"]')
  File "src/lxml/etree.pyx", line 1581, in lxml.etree._Element.xpath
  File "src/lxml/xpath.pxi", line 293, in lxml.etree.XPathElementEvaluator.__call__
  File "src/lxml/apihelpers.pxi", line 1527, in lxml.etree._utf8
ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters

シェルが値を返しました 1

怒られる。多分日本語を指定することはできない。

直下の文字列に"ほげ"が含まれているp要素

elements = html.xpath('//p[contains(text(), "ほげ")]')
Traceback (most recent call last):
  File "scrape_test.py", line 15, in 
    elements = html.xpath('//p[contains(text(), "ほげ")]')
  File "src/lxml/etree.pyx", line 1581, in lxml.etree._Element.xpath
  File "src/lxml/xpath.pxi", line 293, in lxml.etree.XPathElementEvaluator.__call__
  File "src/lxml/apihelpers.pxi", line 1527, in lxml.etree._utf8
ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters

シェルが値を返しました 1

これも上と同様ですね。

ちなみに日本語でなければ問題なく動作します。

elements = html.xpath('//p[contains(text(), "HTML")]')
タートルちゃんがスクレイピングテスト用に書いたHTMLです。

おわりに

かなりざっくりですがxpathの書き方をまとめました。他にも記述方法があるみたいですが、今の所私はこれで十分です。これだけでも大体のページでは必要な要素のみ抜き出せると思います。

皆様の参考になれば幸いです。

【Python】No.338 階段 (1) - yukicoder

python2で解く。
※私がPythonの学習のために解説する記事なので間違い等あるかもしれません。

問題URL: https://yukicoder.me/problems/no/388

ソースコード

no338.py
#! /usr/bin/env python
# coding: utf-8


def main():
    first = raw_input()
    s, f = map(int, first.split())

    print(s / f + 1)


if __name__ == '__main__':
    main()

解説

入力値

S F

0 ≦ S ≦ 456, 1 ≦ F ≦ 123

6 ~ 7行目

first = raw_input()
s, f = map(int, first.split())

変数 first に標準入力の値を文字列型で代入。

変数 first の split メソッドを使い、スペースで分割されたリストにする。リストの要素を int 関数で整数型に変換して s, f に代入。

8行目

print(s / f + 1)

今の階層 = 上がった階層 + はじめの階層

変数 s にステアくんが登った段数、変数 f に1階層分の段数。
上がった階層は、s 割る f をすれば求まる。変数 s, f ともに整数型なので小数点以下は切り捨てられる。
はじめの階層は、サンプルから最初ステアくんは1階層にいることが分かる。
なので s / f + 1 を標準出力(print)すればいい。

 

Firefox(Quantum) タブをサイドバーに表示する設定メモ

最近のディスプレイは横長なので、縦方向は貴重です。横方向に表示できるなら横に表示して、縦方向を最大限表示できるようにしたいです。
最近の Firefox はタブバーを消したりする方法が変わった(Quantumから?)ので、その設定方法を備忘録として。
タブをサイドバーに表示することを想定して書いています。私の設定メモです。

userChrome.css を編集

Firefox のメニューバー → ヘルプ → トラブルシューティング情報」を開いて「プロファイルディレクトリー」に書かれているのがプロファイルディレクトリの場所。
[プロファイルディレクトリー]/chrome/userChrome.css がレイアウトの設定を担っている。
はじめは chrome ディレクトリがないと思うので自分で作る。userChrome.css も同様。

 

私はタブを Vertical Tabs Reloaded というアドオンを入れてサイドバーにしている。
その際、邪魔な部分を消すために設定した userChrome.css を載せておく。

userChrome.css
/* タブバーを消す */
toolbar#TabsToolbar.customization-target {
    visibility: collapse;
}
/* サイドバーのヘッダーを消す */
#sidebar-header {
    display: none;
}
/* サイドバー幅の制限をなくす */
#sidebar-box {
    overflow-x: hidden !important;
}
#sidebar {
    min-width: 0px !important;
    max-width: none !important;
    overflow-x: hidden !important;
}

設定していない Firefox のウィンドウは以下。

Firefoxのウィンドウ

上記を設定した Firefox のウィンドウは以下。

Firefoxのウィンドウ

スッキリしました。

おわりに

標準でもタブをサイドバーに表示するという設定があってもいいんじゃないかと思うのは私だけでしょうか?

私の設定メモだが、何かの参考になれば幸いです。