Beautifulsoup, как рекомбинировать слова

1

Некоторые из выводимых слов разделяются при запуске этого кода. Словно слово "допуски" разделено на "толерантность". Я посмотрел на источник html, и мне кажется, что страница была создана.

Есть также много других мест, где слово разделено. Как мне перекомпилировать их перед написанием текста?

import requests, codecs
from bs4 import BeautifulSoup
from bs4.element import Comment

path='C:\\Users\\jason\\Google Drive\\python\\'

def tag_visible(element):
    if element.parent.name in ['sup']:
        return False
    if isinstance(element, Comment):
        return False
    return True

ticker = 'TSLA'
quarter = '18Q2'    
mark1= 'ITEM 1A'
mark2= 'UNREGISTERED SALES'
url_new='https://www.sec.gov/Archives/edgar/data/1318605/000156459018019254/tsla-10q_20180630.htm'

def get_text(url,mark1,mark2):
    html = requests.get(url) 
    soup = BeautifulSoup(html.text, 'html.parser')

    for hr in soup.select('hr'):
        hr.find_previous('p').extract()

    texts = soup.findAll(text=True)

    visible_texts = filter(tag_visible, texts) 
    text=u" ".join(t.strip() for t in visible_texts)

    return text[text.find(mark1): text.find(mark2)]

text = get_text(url_new,mark1,mark2)

file=codecs.open(path + "test.txt", 'w', encoding='utf8')
file.write (text)
file.close()
  • 1
    Похоже, что вы соединяете текст вместе с пробелами здесь: text=u" ".join(t.strip() for t in visible_texts)
  • 0
    Не используйте codecs.open() . В Python 3 используйте open() , в Python 2 io.open() . Кодовая база кодеков имеет много проблем с производительностью и реализацией, когда дело доходит до файлового ввода-вывода.
Теги:
beautifulsoup

2 ответа

4
Лучший ответ

Вы имеете дело с HTML, отформатированным в Microsoft Word. Не извлекайте текст и не пытайтесь обработать его без этого контекста.

Раздел, который вы хотите обработать, четко очерчен тегами <a name="...">, позволяет начать с выбора всех элементов маркером <a name="ITEM_1A_RISK_FACTORS">, вплоть до, но не включая <a name="ITEM2_UNREGISTERED_SALES"> marker:

def sec_section(soup, item_name):
    """iterate over SEC document paragraphs for the section named item_name

    Item name must be a link target, starting with ITEM
    """

    # ask BS4 to find the section
    elem = soup.select_one('a[name={}]'.format(item_name))
    # scan up to the parent text element
    # html.parser does not support <text> but lxml does
    while elem.parent is not soup and elem.parent.name != 'text':
        elem = elem.parent

    yield elem
    # now we can yield all next siblings until we find one that
    # is also contains a[name^=ITEM] element:
    for elem in elem.next_siblings:
        if not isinstance(elem, str) and elem.select_one('a[name^=ITEM]'):
            return
        yield elem

Эта функция дает нам все дочерние узлы из узла <text> в документе HTML, которые начинаются с абзаца, содержащего конкретную целевую ссылку, вплоть до следующей целевой ссылки, которая называет ITEM.

Затем обычной задачей очистки Word является удаление тегов <font>, атрибутов style:

def clean_word(elem):
    if isinstance(elem, str):
        return elem
    # remove last-rendered break markers, non-rendering but messy
    for lastbrk in elem.select('a[name^=_AEIOULastRenderedPageBreakAEIOU]'):
        lastbrk.decompose()
    # remove font tags and styling from the document, leaving only the contents
    if 'style' in elem.attrs:
        del elem.attrs['style']
    for e in elem:  # recursively do the same for all child nodes
        clean_word(e)
    if elem.name == 'font':
        elem = elem.unwrap()
    return elem

Метод Tag.unwrap() - это то, что больше всего поможет вашему делу, так как текст делится почти произвольно тегами <font>.

Теперь просто тривиально извлечь текст чисто:

for elem in sec_section(soup, 'ITEM_1A_RISK_FACTORS'):
    clean_word(elem)
    if not isinstance(elem, str):
        elem = elem.get_text(strip=True)
    print(elem)

Это выводит среди остальной части текста:

•that the equipment and processes which we have selected for Model 3 production will be able to accurately manufacture high volumes of Model 3 vehicles within specified design tolerances and with high quality;

Текст теперь правильно соединен, и повторное объединение не требуется.

Весь раздел все еще находится в таблице, но clean_word() очистил это сейчас до гораздо более разумного:

<div align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td valign="top">
<p> </p></td>
<td valign="top">
<p>•</p></td>
<td valign="top">
<p>that the equipment and processes which we have selected for Model 3 production will be able to accurately manufacture high volumes of Model 3 vehicles within specified design tolerances and with high quality;</p></td></tr></table></div>

поэтому вы можете использовать более интеллектуальные методы извлечения текста для дальнейшего обеспечения чистого преобразования текста здесь; вы можете преобразовать такие таблицы маркеров в префикс *, например:

def convert_word_bullets(soup, text_bullet="*"):
    for table in soup.select('div[align=left] > table'):
        div = table.parent
        bullet = div.find(string='\u2022')
        if bullet is None:
            # not a bullet table, skip
            continue
        text_cell = bullet.find_next('td')
        div.clear()
        div.append(text_bullet + ' ')
        for i, elem in enumerate(text_cell.contents[:]):
            if i == 0 and elem == '\n':
                continue  # no need to include the first linebreak
            div.append(elem.extract())

Кроме того, вы, вероятно, захотите также пропустить разрывы страниц (сочетание элементов <p>[page number]</p> и <hr/>), если вы запустите

for pagebrk in soup.select('p ~ hr[style^=page-break-after]'): 
    pagebrk.find_previous_sibling('p').decompose()
    pagebrk.decompose()

Это более явная, чем ваша собственная версия, где вы удаляете все элементы <hr/> и предшествующий элемент <p> независимо от того, являются ли они фактически братьями и сестрами.

Выполните оба перед очисткой HTML-слова. В сочетании с вашей функцией, которая вместе становится:

def get_text(url, item_name):
    response = requests.get(url) 
    soup = BeautifulSoup(response.content, 'html.parser')

    for pagebrk in soup.select('p ~ hr[style^=page-break-after]'): 
        pagebrk.find_previous_sibling('p').decompose()
        pagebrk.decompose()

    convert_word_bullets(soup)
    cleaned_section = map(clean_word, sec_section(soup, item_name))

    return ''.join([
        elem.get_text(strip=True) if elem.name else elem
        for elem in cleaned_section])


text = get_text(url, 'ITEM_1A_RISK_FACTORS')
with open(os.path.join(path, 'test.txt'), 'w', encoding='utf8') as f:
    f.write(text)
1

Эта разметка страницы очень плохая. Вам нужно будет удалить лишние теги, чтобы исправить проблему. К счастью для вас, beautifulsoup может сделать тяжелый подъем. В приведенном ниже коде будут удалены все теги font.

soup = BeautifulSoup(html.text, 'html.parser')
for font in soup.find_all('font'):
    font.unwrap()

Ещё вопросы

Сообщество Overcoder
Наверх
Меню