0

私はscraperwiki.comでPythonスクレーパーを開発しており、以下を含むhtmlページを解析する必要があります。

<div class="items">
  <div class="item">
       ItemLine1 ItemLine1 ItemLine1
       <br> 
       ItemLine2 ItemLine2 ItemLine2
 </div>
 <br>
</div>

私が今していることは:

import scraperwiki
import lxml.html

#.......................
raw_string = lxml.html.fromstring(scraperwiki.scrape(url_to_scrape))
my_line = ((raw_string.cssselect("div.items div.item")[0]).text)
print (my_line)

そしてそれは印刷ItemLine1 ItemLine1 ItemLine1するだけです。[0]を[1]に変更すると、例外がスローされます。

どうすればそれをこすりますか?xpathを使用する必要がありますか?

4

1 に答える 1

2

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.itempreceding-sibling::ノードが含まれます。その軸にあるすべての種類のノード(テキストまたは要素)を選択します。brnode()

より大きな目標がノードの子を要素ごとに分割することであるbr場合は、いくつかの異なるアプローチを取ることができます。これが非常にトリッキーな理由は、マークアップのような要素brhr不適切に設計されたマークアップであるためです。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のtextand属性を使用しないでください。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を利用し、textandtail属性にテキストノードを保持することです。このアプローチの欠点は、テキストを添付する要素がない場合、テキストノードを含めるだけでよいことです。この不均一なタイプのリストは、操作するのが少し面倒です。

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元のドキュメントには存在しない新しい要素によってアイテムがどのようにグループ化されるかを確認します。

于 2012-10-29T13:50:10.483 に答える