代码之家  ›  专栏  ›  技术社区  ›  jason

美化组合如何组合词

  •  1
  • jason  · 技术社区  · 8 年前

    运行此代码时,输出的某些单词被拆分。就像“公差”这个词被分为“通行费积怨”。我查看了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 回复  |  直到 8 年前
        1
  •  4
  •   Martijn Pieters    8 年前

    你在处理 用Microsoft Word格式化的HTML . 不要提取文本并尝试在没有上下文的情况下处理它。

    要处理的部分用 <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> 从包含特定链接目标的段落开始,一直到命名为 ITEM .

    接下来,通常的单词清理任务是删除 <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() method 最有助于你的案例,因为文本几乎被任意分割 <字体> 标签。

    现在,清晰地提取文本变得非常简单:

    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)
    
        2
  •  1
  •   Yossi    8 年前

    这个页面标记非常糟糕。您需要删除多余的标签才能解决问题。幸运的是,美女组可以做重担。下面的代码将删除所有 font 标签。

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