Beautifulsoup, как рекомбинировать слова - PullRequest
0 голосов
/ 26 августа 2018

Некоторые из выведенных слов разделяются при запуске этого кода.Как и слово «допуски» разбито на «допуски».Я посмотрел на источник 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()

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

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

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

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> независимо от того, являются ли они на самом деле братьями и сестрами.

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

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)
0 голосов
/ 28 августа 2018

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

soup = BeautifulSoup(html.text, 'html.parser')
for font in soup.find_all('font'):
    font.unwrap()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...