XPathは最も簡単なソリューションです。
items = raw_string.cssselect('div.items div.item')
texts = [item.xpath('br[1]/preceding-sibling::node()') for item in items]
XPathは;br[1]
の最初のbr
子を選択します。軸には、最初の;の前に発生するすべてdiv.item
のpreceding-sibling::
ノードが含まれます。その軸にあるすべての種類のノード(テキストまたは要素)を選択します。br
node()
より大きな目標がノードの子を要素ごとに分割することであるbr
場合は、いくつかの異なるアプローチを取ることができます。これが非常にトリッキーな理由は、マークアップのような要素br
とhr
不適切に設計されたマークアップであるためです。sgml、html、xmlなどのツリーのようなマークアップ言語を使用して、一緒にすべきものは、子のない区切り文字要素で分割するのではなく、共通の親要素でグループ化する必要があります。
テストケースを拡張して、より複雑な状況を示します。
html = """<div class="items">
<div class="item">
<br>
ItemLine1 ItemLine1 ItemLine1
<a href="">item</a>
Itemline1-b
<br>
<a class="z">item2</a>
ItemLine2 ItemLine2 ItemLine2
<br><br>
Itemline3
</div>
<br>
</div>"""
doc = lxml.html.fromstring(html)
itemlist = doc.cssselect('div.items div.item')
最初のアプローチは、段落内のすべてのノードを取得し、それらをによって異なるリストに分割することbr
です。このアプローチを使用する場合は、テキストが重複する可能性があるため、ElementTreeAPIのtext
and属性を使用しないでください。tail
def paras_by_br_nodes(parent):
"""Return a list of node children of parent (including text nodes) grouped by "paragraphs" which are delimited by <br/> elements."""
paralist = []
paras = []
for node in parent.xpath('node()'):
if getattr(node, 'tag', None) == 'br':
paralist.append(paras)
paras = []
else:
paras.append(node)
paralist.append(paras)
return paralist
print paras_by_br_nodes(itemlist[0])
これにより、次のようなリストが生成されます。
[['\n '],
['\n ItemLine1 ItemLine1 ItemLine1\n\t\t', <Element a at 0x10498a350>, '\n\t\tItemline1-b\n '],
[<Element a at 0x10498a230>, '\n ItemLine2 ItemLine2 ItemLine2\n '],
[],
['\n Itemline3\n ']]
2番目のアプローチは、ElementTree APIを利用し、text
andtail
属性にテキストノードを保持することです。このアプローチの欠点は、テキストを添付する要素がない場合、テキストノードを含めるだけでよいことです。この不均一なタイプのリストは、操作するのが少し面倒です。
def paras_by_br_text(parent):
paralist=[]
para=[parent.text]
for item in parent:
if item.tag=='br':
paralist.append(para)
para = [item.tail]
else:
para.append(item)
paralist.append(para)
return paralist
print paras_by_br_text(itemlist[0])
これにより、そのようなリストが作成されます。前のリストとは対照的に、リストの最初の位置にテキストノードノードしかないことに注意してください。これは、br.tail
テキストまたはparent.text
(最初の要素の前のテキスト)に対応します。
[['\n '],
['\n ItemLine1 ItemLine1 ItemLine1\n\t\t', <Element a at 0x1042f5170>],
[<Element a at 0x1042f5290>],
[],
['\n Itemline3\n ']]
最善のアプローチは、新しい要素を導入することだと思います。このhtmlは、他のコンテナ要素br
を使用する必要があるときに使用しています。p
代わりに、htmlを修正して、ノードのリストではなく要素のリストを返しましょう。
def paras_by_br(parent):
paralist = []
para = lxml.html.etree.Element('para')
if parent.text:
para.text = parent.text
for item in parent:
if item.tag=='br':
paralist.append(para)
para = lxml.html.etree.Element('para')
if item.tail:
para.text = item.tail
else:
para.append(item)
return paralist
paralist = paras_by_br(itemlist[0])
print "\n--------\n".join(lxml.html.etree.tostring(para) for para in paralist)
これにより、次のように出力されます。
<para>
</para>
--------
<para>
ItemLine1 ItemLine1 ItemLine1
<a href="">item</a>
Itemline1-b
</para>
--------
<para><a class="z">item2</a>
ItemLine2 ItemLine2 ItemLine2
</para>
--------
<para/>
para
元のドキュメントには存在しない新しい要素によってアイテムがどのようにグループ化されるかを確認します。