プロジェクト

全般

プロフィール

操作

第15章 HTML / XML を扱う

15.1 XML をパースする (xml.etree.)ElementTree

XML をパース・生成する機能を提供

XML のパース

ファイル内の XML をパースする

  • 関数 parse(source, parser=None)

    • source ファイルパスか、ファイルオブジェクトで指定
    • parser 使用するパーサーを指定 (未指定なら標準の XMLParser が使用される)
    • 戻り値 ElementTree インスタンス (xml.etree.ElementTree.ElementTree)
    <?xml version="1.0" encoding="UTF-8"?>
    <weather>
      <local_weather name="Tokyo" area="kanto">
        <condition>Sunny</condition>
        <temperature>25</temperature>
        <humidity>47</humidity>
        <rainy_percent time_class="AM">0%</rainy_percent>
        <rainy_percent time_class="PM">10%</rainy_percent>
      </local_weather>
      <local_weather name="Kanagawa" area="kanto">
        <condition>Cloudy</condition>
        <temperature>26</temperature>
        <humidity>38</humidity>
        <rainy_percent time_class="PM">50%</rainy_percent>
        <rainy_percent time_class="AM">20%</rainy_percent>
      </local_weather>
    </weather>
    
    >>> import xml.etree.ElementTree as ET
    >>> tree = ET.parse('sample.xml')
    >>> root = tree.getroot()  # 最上位の階層の要素を取り出す
    >>> root
    <Element 'weather' at 0x7f58f40d9a40>
    

文字列の XML をパースする

  • 関数 fromstring(text, parser=None)

    • text 文字列
    • parser 使用するパーサーを指定 (未指定なら標準の XMLParser が使用される)
    • 戻り値 Element インスタンス (xml.etree.ElementTree.Element)
    >>> xml_data = '''<?xml version="1.0" encoding="UTF-8"?>
    ... <weather>
    ...     <local_weather name="Tokyo" area="kanto">
    ...          <condition>Sunny</condition>
    ...     </local_weather>
    ... </weather>
    ... '''
    >>> root = ET.fromstring(xml_data)
    >>> root
    <Element 'weather' at 0x7f58f40d9860>
    
  • parse() で返される ElementTree、fromstring() で返される Element の差に注意

    • ElementTree からルート要素を取得すれば Element

要素の取得や検索

  • Element オブジェクトのメソッド

    属性、メソッド 解説 戻り値
    tag 要素のタグ名 str
    attrib 要素の全属性 dict
    text 要素のテキスト (開始タグから、次の子要素または終了タグまで) str
    items() 要素の全属性の、属性名と属性値のペアのリスト list
    keys() 要素の全属性名 list
    get(key, default=None) 要素の指定属性名の属性値
    属性がない場合は default を返す
    属性値か default 設定値
    >>> import xml.etree.ElementTree as ET
    >>> root = ET.parse('sample.xml')
    >>> root[0]  # 要素がElementオブジェクトである
    >>> <Element 'local_weather' at 0x7f43ad8be4f0>
    >>> root[0].tag  # tagを表示
    'local_weather''
    >>> root[0].attrib  # attribを表示
    {'name': 'Tokyo', 'area': 'kanto'}
    >>> root[0].items()  # itemsを取得
    [('name', 'Tokyo'), ('area', 'kanto')]
    >>> root[0].keys()  # keysを取得
    ['name', 'area']
    >>> root[0].get('name')
    'Tokyo'
    >>> root[0][1]  # 各要素は配列になっているため、インデックスで指定してアクセスすることもできる
    <Element 'temperature' at 0x7f58f40d9d10>
    >>> root[0][1].text  # 要素のテキストを取得
    '25'
    
  • ElementTree の再帰イテレート (Element も同様に可能)

    • この場合は深さ優先探索
    >>> import xml.etree.ElementTree as ET
    >>> root = ET.parse('sample.xml')
    >>> for local_weather in root.iter('local_weather'):
    ...     print(local_weather.attrib)
    ...
    {'name': 'Tokyo', 'area': 'kanto'}
    {'name': 'Kanagawa', 'area': 'kanto'}
    
  • 要素検索メソッド

    • 引数の match はタグ名かパス (XPath)
    メソッド名 解説 戻り値
    find(match, namespace=None) match に該当する最初の子要素を返す Element か None
    findall(match, namespace=None) match に該当する子要素すべてを返す list


    get(), find(), findll()

    >>> for local_weather in root.findall('local_weather'):
    ...     name = local_weather.get('name')
    ...     rainy_percent = local_weather.find('rainy_percent')
    ...     condition = local_weather.find('condition').text
    ...     print(name , condition, rainy_percent.text, rainy_percent.attrib['time_class'])
    Tokyo Sunny 0% AM
    Kanagawa Cloudy 50% PM
    


    XPath での検索

     # 名前空間を使用しない場合
     >>> kanagawa_temperature = root.find('./local_weather[@name="Kanagawa"]/temperature')
     >>> kanagawa_temperature.text
     '26'
     # 名前空間をワイルドカード検索で指定した場合
     >>> kanagawa_temperature = root.find('./{*}weather/{*}local_weather[@name="Kanagawa"]/{*}temperature')
     >>> kanagawa_temperature.text
     '26'
    

15.2 XML / HTML を高速かつ柔軟にパースする lxml

pip install lxml

HTML をパースする lxml.etree.HTMLParser

HTML のパースに適した HTMLParser クラス

  • class lxml.etree.HTMLParser(encoding=None, remove_blank_text=False, remove_comments=False, remove_pis=False, strip_cdata=True, no_network=True, target=None, schema: XMLSchema=None, recover=True, compact=True, collect_ids=True, huge_tree=False)

    • encoding エンコードを指定
    • remove_blank_text True の場合、タブ間の空白や改行を除外
    • remove_comments True の場合、「」部分を除外する
    • target パーサーへ渡すコールバックオブジェクトを指定
    • recover True の場合、整形式ではない (non well-formed) HTML に対してもパースを試みる
    • commpact True の場合、メモリ使用量を抑える
    • 戻り値 HTMLParser オブジェクト
    <li class="toctree-l1">
      <a class="reference internal" href="intro.html">はじめに</a>
    </li>
    <li class="toctree-l1">
      <a class="reference internal" href="functions.html">組み込み関数</a>
    </li>
    ...
    
    >>> from lxml import etree
    >>> from urllib.request import urlopen
    >>> source = urllib.request.urlopen('https://docs.python.org/ja/3/library/').read()  # URLを開きHTMLを読み込む
    >>> tree = etree.fromstring(source, etree.HTMLParser())  # HTMLParserに渡してツリーを取得
    >>> elements = tree.findall('.//li[@class="toctree-l1"]/a')  # XPath式を使い、class属性を指定してliタグを検索
    >>> for element in elements:  # 検索結果を表示
    ...     print(element.text, element.attrib['href'])
    ...
    はじめに intro.html
    組み込み関数 functions.html
    組み込み定数 constants.html
    組み込み型 stdtypes.html
    省略
    

HTML を書き換える lxml.html

HTML のパースが lxml.etee と同様にできる

  • lxml.html.parse() の使用例

     >>> from lxml import html
     >>> import urllib.request
     # リクエストオブジェクトを作成し、URLを開く
     >>> res = urllib.request.urlopen('https://docs.python.org/ja/3/library/')
     >>> root = html.parse(res).getroot()
     >>> div_toctree = root.find('.//div[@class="toctree-wrapper compound"]/')  # 特定の要素を検索
     >>> print(html.tostring(div_toctree, pretty_print=True, encoding='unicode'))
     <ul>
     <li class="toctree-l1">
     <a class="reference internal" href="intro.html">はじめに</a><ul>
     <li class="toctree-l2"><a class="reference internal" href="intro.html#notes-on-availability">利用可能性について</a></li>
     </ul>
     省略
    
  • リンクの書き換え

    • 関数 lxml.html.make_links_absolute(html, base_href)
      • html 対象の tree を指定
      • base_href ベースとなる URL を指定
      • 戻り値 lxml.html.HtmlElement オブジェクト

整形式ではない (non well-formed) XML をパースする

  • class lxml.etree.XMLParser(略)

    • recover True の場合、整形式ではない XML に対してもパースを試みる
    • remove_blank_text True の場合、タブ間の空白や空行を除外する
    • remove_comments True の場合、「」部分を除外する
    • 戻り値 XMLParser オブジェクト
    <?xml version="1.0" encoding="UTF-8"?>
    <weather>
      <local_weather name="Tokyo">
        <condition>Sunny</condition>
        <temperature>25</temperature>
        <humidity>47</humidity>
    </weather>
    
    >>> from lxml import etree
    >>> tree = etree.parse('broken.xml')  # XMLの終了タグがないため、パースでエラーになる
    Traceback (most recent call last):
      省略
    lxml.etree.XMLSyntaxError: Opening and ending tag mismatch: local_weather line 3 and weather, line 7, column 11
    >>> parser = etree.XMLParser(recover=True)  # XMLParserでrecover引数にTrueを指定する
    >>> tree = etree.parse('broken.xml', parser)  # パース時にパーサーを指定することで、エラーが出ずに解析できる
    >>> tree.find('./local_weather').attrib
    {'name': 'Tokyo'}
    

XPath

lxml は XPath 1.0 に準拠している為、要素の指定で利用できる

15.3 使いやすい HTML パーサーを利用する Beautiful Soup 4

pip install beautifulsoup4
pip install html5lib

BeautifulSoup オブジェクトの作成

モジュールインポートは import bs4

  • class bs4.BeautifulSoup(markup="", features=None, builder=None, parse_only=None, from_encoding=None, exclude_encoding=None, element_classes=None, **kwargs)
    • markup パースるマークアップを文字列またはファイルオブジェクトで指定
    • features 使用するパーサーを指定
    • 戻り値 bs4.BeautifulSoup インスタンス

(いったんここまで)
(動作確認は途中だが、時間足りなく、試験範囲外なので後回し)

Tatsuya ISHIGAKI さんが4ヶ月前に更新 · 1件の履歴