7

私は新聞で政治指導者の出来事を抽出する小さなプロジェクトを行っています。時々政治家が言及され、リンクのある親も子もいません。(意味的に悪いマークアップのためだと思います)。

そこで、最も近いリンクを見つけて抽出できる関数を作成したいと思います。以下の場合、検索文字列はRasmussenであり、必要なリンクは次のとおり/307046です。

#-*- coding: utf-8 -*-

from bs4 import BeautifulSoup
import re

tekst = '''
<li>
  <div class="views-field-field-webrubrik-value">
    <h3>
      <a href="/307046">Claus Hjort spiller med mrkede kort</a>
    </h3>
  </div>
  <div class="views-field-field-skribent-uid">
    <div class="byline">Af: <span class="authors">Dennis Kristensen</span></div>
  </div>
  <div class="views-field-field-webteaser-value">
    <div class="webteaser">Claus Hjort Frederiksens argumenter for at afvise
      trepartsforhandlinger har ikke hold i virkeligheden. Hans rinde er nok
      snarere at forberede det ideologiske grundlag for en Løkke Rasmussens
      genkomst som statsministe
    </div>
  </div>
  <span class="views-field-view-node">
    <span class="actions">
      <a href="/307046">Ls mere</a>
      |
      <a href="/307046/#comments">Kommentarer (4)</a>
    </span>
  </span>
</li>
'''

to_find = "Rasmussen"
soup = BeautifulSoup(tekst)
contexts = soup.find_all(text=re.compile(to_find)) 

def find_nearest(element, url, direction="both"):
    """Find the nearest link, relative to a text string.
    When complete it will search up and down (parent, child),
    and only X levels up down. These features are not implemented yet.
    Will then return the link the fewest steps away from the
    original element. Assumes we have already found an element"""

    # Is the nearest link readily available?
    # If so - this works and extracts the link.
    if element.find_parents('a'):
        for artikel_link in element.find_parents('a'):
            link = artikel_link.get('href')
            # sometimes the link is a relative link - sometimes it is not
            if ("http" or "www") not in link:
                link = url+link
                return link
    # But if the link is not readily available, we will go up
    # This is (I think) where it goes wrong
    # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
    if not element.find_parents('a'):
        element =  element.parent
        # Print for debugging
        print element #on the 2nd run (i.e <li> this finds <a href=/307056> 
        # So shouldn't it be caught as readily available above?
        print u"Found: %s" % element.name
        # the recursive call
        find_nearest(element,url)

# run it
if contexts:
    for a in contexts:
        find_nearest( element=a, url="http://information.dk")

以下の直接呼び出しは機能します。

print contexts[0].parent.parent.parent.a['href'].encode('utf-8')

参考までに、申し訳ありませんがコード全体がbitbucketにあります:https ://bitbucket.org/achristoffersen/politikere-i-medierne

(BeautifulSoup 4を使用したps)


編集:SimonSapinは私に最も近いものを定義するように頼みます:最も近いとは、どちらの方向でも、検索語から最も少ないネストレベルであるリンクを意味します。上記のテキストではa href、drupalベースの新聞サイトによって作成されたものは、検索文字列が見つかったタグの直接の親でも子でもありません。とてもBeautifulSoupが見つかりません。

私は、「最も少ない文字」が離れていることもしばしば機能すると思います。その場合、soulutionはfindとrfindと一緒にハッキングされる可能性がありますが、私は本当にBSを介してこれを実行したいと思います。これはうまくいくので:contexts[0].parent.parent.parent.a['href'].encode('utf-8')それをスクリプトに一般化することが可能でなければなりません。

編集:多分私はBeautifulSoupソリューションを探していることを強調する必要があります。@ erik85によって提案されているように、BSをカスタム/単純な幅優先探索と組み合わせると、すぐに面倒になると思います。

4

2 に答える 2

12

誰かがおそらくコピー&ペーストで動作する解決策を考え出し、あなたはこれがあなたの問題を解決すると思うでしょう. ただし、問題はコードではありません。それはあなたの戦略です。コードの再設計に適用する必要がある「分割統治」と呼ばれるソフトウェア設計原則があります。HTML 文字列をツリー/グラフとして解釈するコードを、最も近いノードの検索 (おそらく幅優先検索) から分離します。より良いソフトウェアを設計する方法を学ぶだけでなく、問題はおそらく存在しなくなります

あなたはこれを自分で解決するのに十分賢いと思いますが、スケルトンも提供したいと思います。

def parse_html(txt):
    """ reads a string of html and returns a dict/list/tuple presentation"""
    pass

def breadth_first_search(graph, start, end):
    """ finds the shortest way from start to end
    You can probably customize start and end to work well with the input you want
    to provide. For implementation details see the link in the text above.
    """
    pass

def find_nearest_link(html,name):
    """putting it all together"""
    return breadth_first_search(parse_html(html),name,"link")

PS: これを行うことは別の原則にも適用されますが、数学から: 解決策がわからない問題があり (選択した部分文字列に近いリンクを見つける)、解決策がわかっている問題のグループがあると仮定します (グラフトラバーサル)、次に、解決できる問題のグループに一致するように問題を変換しようとするため、基本的なソリューション パターン (選択した言語/フレームワークで既に実装されている可能性があります) を使用するだけで完了です。

于 2012-08-04T12:02:49.643 に答える
2

lxml を使用したソリューションを次に示します。主なアイデアは、前後の要素をすべて見つけてから、それらの要素をラウンドロビン反復処理することです。

def find_nearest(elt):
    preceding = elt.xpath('preceding::*/@href')[::-1]
    following = elt.xpath('following::*/@href')
    parent = elt.xpath('parent::*/@href')
    for href in roundrobin(parent, preceding, following):
        return href

BeautifulSoups (または bs4) のnext_elements と previous_elementsを使用した同様のソリューションも可能です。


import lxml.html as LH
import itertools

def find_nearest(elt):
    preceding = elt.xpath('preceding::*/@href')[::-1]
    following = elt.xpath('following::*/@href')
    parent = elt.xpath('parent::*/@href')
    for href in roundrobin(parent, preceding, following):
        return href

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # http://docs.python.org/library/itertools.html#recipes
    # Author: George Sakkis
    pending = len(iterables)
    nexts = itertools.cycle(iter(it).next for it in iterables)
    while pending:
        try:
            for n in nexts:
                yield n()
        except StopIteration:
            pending -= 1
            nexts = itertools.cycle(itertools.islice(nexts, pending))

tekst = '''
<li>
  <div class="views-field-field-webrubrik-value">
    <h3>
      <a href="/307046">Claus Hjort spiller med mrkede kort</a>
    </h3>
  </div>
  <div class="views-field-field-skribent-uid">
    <div class="byline">Af: <span class="authors">Dennis Kristensen</span></div>
  </div>
  <div class="views-field-field-webteaser-value">
    <div class="webteaser">Claus Hjort Frederiksens argumenter for at afvise
      trepartsforhandlinger har ikke hold i virkeligheden. Hans rinde er nok
      snarere at forberede det ideologiske grundlag for en Løkke Rasmussens
      genkomst som statsministe
    </div>
  </div>
  <span class="views-field-view-node">
    <span class="actions">
      <a href="/307046">Ls mere</a>
      |
      <a href="/307046/#comments">Kommentarer (4)</a>
    </span>
  </span>
</li>
'''

to_find = "Rasmussen"
doc = LH.fromstring(tekst)

for x in doc.xpath('//*[contains(text(),{s!r})]'.format(s = to_find)):
    print(find_nearest(x))

収量

/307046
于 2012-08-07T20:20:28.850 に答える