0

I've come across a case where BeautifulSoup is concatenating text together--typically, I do not see this problem with other sites' navigation...

In [96]: nav_menu = """
<ul class="joomla-nav" id="topmenu">
<li id="current" class="selected item101"><a href="/" >Etusivu</a></li><li class="parent item107"><a href="/yritysesittely" >Yritys</a><ul><li class="item753"><a href="/yritys/yritysesittely" >Yritysesittely</a></li><li class="item755"><span class="separator">Kuvagalleria</span>
</li><li class="item754"><a href="/yritys/projektiesittely" >Projektiesittely</a></li></ul></li><li class="parent item108"><a class="tuotteetlinkki" href="/tuotteet" >Tuotteet</a><ul><li class="parent item317"><a href="/" >Rakentaminen</a><ul><li class="item110"><a href="/tuotteet/2012-02-17-10-36-23/mestaripihatto" >Mestaripihatto</a></li><li class="item111"><a href="/tuotteet/2012-02-17-10-36-23/rakennukset-ja-suunnittelu" >Rakentaminen ja suunnittelu</a></li><li class="item132"><a href="/tuotteet/2012-02-17-10-36-23/betonielementit" >Betonielementit</a></li><li class="item831"><a href="/tuotteet/2012-02-17-10-36-23/kattorakenteet-2" >Kattorakenteet</a></li><li class="item806"><a href="/tuotteet/2012-02-17-10-36-23/mestarikasvattamo" >Mestarikasvattamo</a></li><li class="item135"><a href="/tuotteet/2012-02-17-10-36-23/betoniritilat" >Betoniritilät</a></li></ul></li><li class="parent item318"><a href="#" >Pihattokalusteet ja -laitteet</a><ul><li class="item140"><a href="/tuotteet/2012-02-17-10-38-33/lypsy-ja-maidonkasittely" >GEA lypsy ja maidonkäsittely</a></li><li class="item270"><a href="/tuotteet/2012-02-17-10-38-33/pihattokalusteet" >GEA  pihattokalusteet</a></li><li class="item145"><a href="/tuotteet/2012-02-17-10-38-33/ruokintalaitteet" >GEA  ruokinta</a></li><li class="item146"><a href="/tuotteet/2012-02-17-10-38-33/lannanpoisto" >GEA lannanpoistolaitteet</a></li><li class="item147"><a href="/tuotteet/2012-02-17-10-38-33/varaosat-ja-tarvikkeet" >Varaosat ja tarvikkeet</a></li><li class="item141"><a href="/tuotteet/2012-02-17-10-38-33/merivirta-kalusteet" >Merivirta - kalusteet</a></li><li class="item785"><a href="/tuotteet/2012-02-17-10-38-33/2012-08-27-11-06-48" >Nautojen hyvinvointi</a></li></ul></li><li class="parent item814"><a href="#" >Karjatalous</a><ul><li class="item148"><a href="/tuotteet/2012-02-17-10-38-34/ilmanvaihto" >Ilmanvaihto</a></li><li class="item149"><a href="/tuotteet/2012-02-17-10-38-34/irtorehusiilot" >Irtorehusiilot</a></li><li class="item820"><a href="/tuotteet/2012-02-17-10-38-34/valaistus" >Valaistus</a></li><li class="item821"><a href="/tuotteet/2012-02-17-10-38-34/lietesaeilioen-verkkoaita" >Lietesäiliön verkkoaita</a></li></ul></li><li class="parent item320"><a href="/" >Lannankäsittely </a><ul><li class="item114"><a href="/tuotteet/2012-02-17-10-42-54/flygt-lietepumput" >Flygt-lietepumput</a></li></ul></li></ul></li><li class="item109"><a href="/jaelkimarkkinointilomake" >Jälkimarkkinointi</a></li><li class="item115"><a href="/yhteystiedot" >Yhteystiedot</a></li></ul>
"""

In [97]: nav_content = BeautifulSoup(nav_menu, "html.parser")

In [98]: nav_content.getText()
Out[98]: '\n\nEtusivuYritysYritysesittelyKuvagalleria\nProjektiesittelyTuotteetRakentaminenMestaripihattoRakentaminen ja suunnitteluBetonielementitKattorakenteetMestarikasvattamoBetoniritilätPihattokalusteet ja -laitteetGEA lypsy ja maidonkäsittelyGEA  pihattokalusteetGEA  ruokintaGEA lannanpoistolaitteetVaraosat ja tarvikkeetMerivirta - kalusteetNautojen hyvinvointiKarjatalousIlmanvaihtoIrtorehusiilotValaistusLietesäiliön verkkoaitaLannankäsittely Flygt-lietepumputJälkimarkkinointiYhteystiedot\n'

EDIT: Before commenting, please take note that the function getText() returns a concatenated string: 'EtusivuYritysYritysesittely' and so on. However, these should be separate words: 'Etusivu Yritys Yritysesittely'; the code below correctly splits each word.

Per the post Python Beautifulsoup get_text() not getting all text, I also switched the parser to lxml, but got similar results.

I've found the method outlined at BeautifulSoup Grab Visible Webpage Text to be a decent work around:

In [100]: def visible(element):
     ...:     if element.parent.name in ['style', 'script', '[document]', 'head', 'title', 'meta']:
     ...:         return False
     ...:     elif isinstance(element,bs4.element.Comment):
     ...:         return False
     ...:     return True
     ...:

In [101]: visible_texts = list(filter(visible, nav_content.findAll(text=True)))

In [102]: ' '.join(visible_texts)
Out[102]: '\n Etusivu Yritys Yritysesittely Kuvagalleria \n Projektiesittely Tuotteet Rakentaminen Mestaripihatto Rakentaminen ja suunnittelu Betonielementit Kattorakenteet Mestarikasvattamo Betoniritilät Pihattokalusteet ja -laitteet GEA lypsy ja maidonkäsittely GEA  pihattokalusteet GEA  ruokinta GEA lannanpoistolaitteet Varaosat ja tarvikkeet Merivirta - kalusteet Nautojen hyvinvointi Karjatalous Ilmanvaihto Irtorehusiilot Valaistus Lietesäiliön verkkoaita Lannankäsittely  Flygt-lietepumput Jälkimarkkinointi Yhteystiedot'

but am really struggling to figure out the difference. Of course I've read the documentation, but still haven't figured out how soup.findAll(text=True) returns different results. For a moment I wondered if this behavior is due to the fact that the nav items are in Finnish (strings are weird!), but after translating the text to English it was clear that's not the issue. So signs point to the markup being malformed, but I'd really like to understand why before I commit to swapping out the code...any clarity is much appreciated.

Community
  • 1
  • 1
measureallthethings
  • 1,102
  • 10
  • 26
  • I don't understand what is a problem ? maybe create working example so we could run it. And show expected result. – furas Feb 01 '17 at 01:21
  • Do you have an example on `soup.findAll(text=True)` returning inconsistent results? – Shane Feb 01 '17 at 01:33
  • I tried `soup.getText()` , `''.join(visible_texts)`, `''.join(soup.findAll(text=True))` and always get the same string. I don't know where is the problem. I see only one difference - you use space `' '` to join elements and it adds extra spaces in your second result. – furas Feb 01 '17 at 01:51
  • I made a quick edit--the code above is now a working example. – measureallthethings Feb 01 '17 at 03:11

2 Answers2

2

I looked at the BS4 source code:

def get_text(self, separator="", strip=False,
             types=(NavigableString, CData)):
    """
    Get all child strings, concatenated using the given separator.
    """
    return separator.join([s for s in self._all_strings(
                strip, types=types)])
getText = get_text

Turns out the separator argument is the reason behind the behavior I encountered...changing my code like so returns the expected results:

In [113]: nav_content.getText(separator=" ")
Out[113]: '\n \n Etusivu Yritys Yritysesittely Kuvagalleria \n Projektiesittely Tuotteet Rakentaminen Mestaripihatto Rakentaminen ja suunnittelu Betonielementit Kattorakenteet Mestarikasvattamo Betoniritilät Pihattokalusteet ja -laitteet GEA lypsy ja maidonkäsittely GEA  pihattokalusteet GEA  ruokinta GEA lannanpoistolaitteet Varaosat ja tarvikkeet Merivirta - kalusteet Nautojen hyvinvointi Karjatalous Ilmanvaihto Irtorehusiilot Valaistus Lietesäiliön verkkoaita Lannankäsittely  Flygt-lietepumput Jälkimarkkinointi Yhteystiedot \n'
measureallthethings
  • 1,102
  • 10
  • 26
0
get_text()

If you only want the text part of a document or tag, you can use the get_text() method. It returns all the text in a document or beneath a tag, as a single Unicode string

find_all(text=True)

return a list of text which are decendent of corrent context node.

宏杰李
  • 11,820
  • 2
  • 28
  • 35