プロジェクト

全般

プロフィール

15章学習記録 » 履歴 » バージョン 1

Tatsuya ISHIGAKI, 2025/08/17 04:38

1 1 Tatsuya ISHIGAKI
# 第15章 HTML / XML を扱う
2
3
## 15.1 XML をパースする (xml.etree.)ElementTree
4
XML をパース・生成する機能を提供
5
6
### XML のパース
7
ファイル内の XML をパースする
8
- 関数 `parse(source, parser=None)`
9
  - `source` ファイルパスか、ファイルオブジェクトで指定
10
  - `parser` 使用するパーサーを指定 (未指定なら標準の `XMLParser` が使用される)
11
  - **戻り値** `ElementTree` インスタンス (xml.etree.ElementTree.ElementTree)
12
13
  ```xml
14
  <?xml version="1.0" encoding="UTF-8"?>
15
  <weather>
16
    <local_weather name="Tokyo" area="kanto">
17
      <condition>Sunny</condition>
18
      <temperature>25</temperature>
19
      <humidity>47</humidity>
20
      <rainy_percent time_class="AM">0%</rainy_percent>
21
      <rainy_percent time_class="PM">10%</rainy_percent>
22
    </local_weather>
23
    <local_weather name="Kanagawa" area="kanto">
24
      <condition>Cloudy</condition>
25
      <temperature>26</temperature>
26
      <humidity>38</humidity>
27
      <rainy_percent time_class="PM">50%</rainy_percent>
28
      <rainy_percent time_class="AM">20%</rainy_percent>
29
    </local_weather>
30
  </weather>
31
  ```
32
33
  ```python
34
  >>> import xml.etree.ElementTree as ET
35
  >>> tree = ET.parse('sample.xml')
36
  >>> root = tree.getroot()  # 最上位の階層の要素を取り出す
37
  >>> root
38
  <Element 'weather' at 0x7f58f40d9a40>
39
  ```
40
41
文字列の XML をパースする
42
- 関数 `fromstring(text, parser=None)`
43
  - `text` 文字列
44
  - `parser` 使用するパーサーを指定 (未指定なら標準の `XMLParser` が使用される)
45
  - **戻り値** `Element` インスタンス (xml.etree.ElementTree.Element)
46
47
  ```python
48
  >>> xml_data = '''<?xml version="1.0" encoding="UTF-8"?>
49
  ... <weather>
50
  ...     <local_weather name="Tokyo" area="kanto">
51
  ...          <condition>Sunny</condition>
52
  ...     </local_weather>
53
  ... </weather>
54
  ... '''
55
  >>> root = ET.fromstring(xml_data)
56
  >>> root
57
  <Element 'weather' at 0x7f58f40d9860>
58
  ```
59
60
- parse() で返される ElementTree、fromstring() で返される Element の差に注意
61
  - ElementTree からルート要素を取得すれば Element
62
63
### 要素の取得や検索
64
65
- `Element` オブジェクトのメソッド
66
67
  |属性、メソッド|解説|戻り値|
68
  |---|---|---|
69
  |`tag`|要素のタグ名|str|
70
  |`attrib`|要素の全属性|dict|
71
  |`text`|要素のテキスト (開始タグから、次の子要素または終了タグまで)|str|
72
  |`items()`|要素の全属性の、属性名と属性値のペアのリスト|list|
73
  |`keys()`|要素の全属性名|list|
74
  |`get(key, default=None)`|要素の指定属性名の属性値<br>属性がない場合は `default` を返す|属性値か default 設定値|
75
76
  ```python
77
  >>> import xml.etree.ElementTree as ET
78
  >>> root = ET.parse('sample.xml')
79
  >>> root[0]  # 要素がElementオブジェクトである
80
  >>> <Element 'local_weather' at 0x7f43ad8be4f0>
81
  >>> root[0].tag  # tagを表示
82
  'local_weather''
83
  >>> root[0].attrib  # attribを表示
84
  {'name': 'Tokyo', 'area': 'kanto'}
85
  >>> root[0].items()  # itemsを取得
86
  [('name', 'Tokyo'), ('area', 'kanto')]
87
  >>> root[0].keys()  # keysを取得
88
  ['name', 'area']
89
  >>> root[0].get('name')
90
  'Tokyo'
91
  >>> root[0][1]  # 各要素は配列になっているため、インデックスで指定してアクセスすることもできる
92
  <Element 'temperature' at 0x7f58f40d9d10>
93
  >>> root[0][1].text  # 要素のテキストを取得
94
  '25'
95
  ```
96
97
- `ElementTree` の再帰イテレート (`Element` も同様に可能)
98
  - この場合は深さ優先探索
99
100
  ```python
101
  >>> import xml.etree.ElementTree as ET
102
  >>> root = ET.parse('sample.xml')
103
  >>> for local_weather in root.iter('local_weather'):
104
  ...     print(local_weather.attrib)
105
  ...
106
  {'name': 'Tokyo', 'area': 'kanto'}
107
  {'name': 'Kanagawa', 'area': 'kanto'}
108
  ```
109
110
- 要素検索メソッド
111
  - 引数の `match` はタグ名かパス (XPath)
112
113
  |メソッド名|解説|戻り値|
114
  |---|---|---|
115
  |`find(match, namespace=None)`|`match` に該当する最初の子要素を返す|Element か None|
116
  |`findall(match, namespace=None)`|`match` に該当する子要素すべてを返す|list|
117
118
119
  <br>get(), find(), findll()
120
  ```python
121
  >>> for local_weather in root.findall('local_weather'):
122
  ...     name = local_weather.get('name')
123
  ...     rainy_percent = local_weather.find('rainy_percent')
124
  ...     condition = local_weather.find('condition').text
125
  ...     print(name , condition, rainy_percent.text, rainy_percent.attrib['time_class'])
126
  Tokyo Sunny 0% AM
127
  Kanagawa Cloudy 50% PM
128
  ```
129
130
  <br>XPath での検索
131
  ```python
132
   # 名前空間を使用しない場合
133
   >>> kanagawa_temperature = root.find('./local_weather[@name="Kanagawa"]/temperature')
134
   >>> kanagawa_temperature.text
135
   '26'
136
   # 名前空間をワイルドカード検索で指定した場合
137
   >>> kanagawa_temperature = root.find('./{*}weather/{*}local_weather[@name="Kanagawa"]/{*}temperature')
138
   >>> kanagawa_temperature.text
139
   '26'
140
  ```
141
142
## 15.2 XML / HTML を高速かつ柔軟にパースする lxml
143
`pip install lxml`
144
145
### HTML をパースする lxml.etree.HTMLParser
146
HTML のパースに適した `HTMLParser` クラス
147
148
- 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)`
149
  - `encoding` エンコードを指定
150
  - `remove_blank_text` True の場合、タブ間の空白や改行を除外
151
  - `remove_comments` True の場合、「<!-- コメント -->」部分を除外する
152
  - `target` パーサーへ渡すコールバックオブジェクトを指定
153
  - `recover` True の場合、整形式ではない (non well-formed) HTML に対してもパースを試みる
154
  - `commpact` True の場合、メモリ使用量を抑える
155
  - **戻り値** `HTMLParser` オブジェクト
156
157
  ```html
158
  <li class="toctree-l1">
159
    <a class="reference internal" href="intro.html">はじめに</a>
160
  </li>
161
  <li class="toctree-l1">
162
    <a class="reference internal" href="functions.html">組み込み関数</a>
163
  </li>
164
  ...
165
  ```
166
167
  ```python
168
  >>> from lxml import etree
169
  >>> from urllib.request import urlopen
170
  >>> source = urllib.request.urlopen('https://docs.python.org/ja/3/library/').read()  # URLを開きHTMLを読み込む
171
  >>> tree = etree.fromstring(source, etree.HTMLParser())  # HTMLParserに渡してツリーを取得
172
  >>> elements = tree.findall('.//li[@class="toctree-l1"]/a')  # XPath式を使い、class属性を指定してliタグを検索
173
  >>> for element in elements:  # 検索結果を表示
174
  ...     print(element.text, element.attrib['href'])
175
  ...
176
  はじめに intro.html
177
  組み込み関数 functions.html
178
  組み込み定数 constants.html
179
  組み込み型 stdtypes.html
180
  (省略)
181
  ```
182
183
### HTML を書き換える lxml.html
184
HTML のパースが lxml.etee と同様にできる
185
186
- `lxml.html.parse()` の使用例
187
  ```python
188
   >>> from lxml import html
189
   >>> import urllib.request
190
   # リクエストオブジェクトを作成し、URLを開く
191
   >>> res = urllib.request.urlopen('https://docs.python.org/ja/3/library/')
192
   >>> root = html.parse(res).getroot()
193
   >>> div_toctree = root.find('.//div[@class="toctree-wrapper compound"]/')  # 特定の要素を検索
194
   >>> print(html.tostring(div_toctree, pretty_print=True, encoding='unicode'))
195
   <ul>
196
   <li class="toctree-l1">
197
   <a class="reference internal" href="intro.html">はじめに</a><ul>
198
   <li class="toctree-l2"><a class="reference internal" href="intro.html#notes-on-availability">利用可能性について</a></li>
199
   </ul>
200
   (省略)
201
  ```
202
203
- リンクの書き換え
204
  - 関数 `lxml.html.make_links_absolute(html, base_href)`
205
    - `html` 対象の tree を指定
206
    - `base_href` ベースとなる URL を指定
207
    - **戻り値** `lxml.html.HtmlElement` オブジェクト
208
209
### 整形式ではない (non well-formed) XML をパースする
210
211
- class `lxml.etree.XMLParser(略)`
212
  - `recover` True の場合、整形式ではない XML に対してもパースを試みる
213
  - `remove_blank_text` True の場合、タブ間の空白や空行を除外する
214
  - `remove_comments` True の場合、「<!-- コメント -->」部分を除外する
215
  - **戻り値** `XMLParser` オブジェクト
216
217
  ```xml
218
  <?xml version="1.0" encoding="UTF-8"?>
219
  <weather>
220
    <local_weather name="Tokyo">
221
      <condition>Sunny</condition>
222
      <temperature>25</temperature>
223
      <humidity>47</humidity>
224
  </weather>
225
  ```
226
227
  ```python
228
  >>> from lxml import etree
229
  >>> tree = etree.parse('broken.xml')  # XMLの終了タグがないため、パースでエラーになる
230
  Traceback (most recent call last):
231
    (省略)
232
  lxml.etree.XMLSyntaxError: Opening and ending tag mismatch: local_weather line 3 and weather, line 7, column 11
233
  >>> parser = etree.XMLParser(recover=True)  # XMLParserでrecover引数にTrueを指定する
234
  >>> tree = etree.parse('broken.xml', parser)  # パース時にパーサーを指定することで、エラーが出ずに解析できる
235
  >>> tree.find('./local_weather').attrib
236
  {'name': 'Tokyo'}
237
  ```
238
239
### XPath
240
lxml は `XPath 1.0` に準拠している為、要素の指定で利用できる
241
242
## 15.3 使いやすい HTML パーサーを利用する Beautiful Soup 4
243
`pip install beautifulsoup4`
244
`pip install html5lib`
245
246
### BeautifulSoup オブジェクトの作成
247
モジュールインポートは `import bs4`
248
249
- class `bs4.BeautifulSoup(markup="", features=None, builder=None, parse_only=None, from_encoding=None, exclude_encoding=None, element_classes=None, **kwargs)`
250
  - `markup` パースるマークアップを文字列またはファイルオブジェクトで指定
251
  - `features` 使用するパーサーを指定
252
  - **戻り値** `bs4.BeautifulSoup` インスタンス
253
254
---
255
**(いったんここまで)**
256
**(動作確認は途中だが、時間足りなく、試験範囲外なので後回し)**