【问题标题】:Getting soup contents into structured csv将汤内容转换为结构化 csv
【发布时间】:2020-07-08 17:08:27
【问题描述】:

我正在尝试抓取一个网站并将其放入结构化数据格式。 我想最终得到一个包含 6 列的 .csv:国家、日期、通用文本、财政文本、货币文本、外汇文本。

映射是这样的:

country <- h3
date <- h6
general_text <- p (h3) (the p tag that follows the h3 header)
fiscal_text  <- p (1st h5 ul li) (the p tag that follows the **first** h5. This tag is inside ul and li blocks)
monetary_text <- p (2nd h5 ul li) (the p tag that follows the **second** h5. This tag is inside ul and li blocks)
fx_text <- p (3rd h5 ul li) (the p tag that follows the **third** h5. This tag is inside ul and li blocks)

模式在下一个 h3(国家)标题处结束。

我发现很难将每个元素放在适当的位置/列中。

每个国家/地区的网站结构都重复这个(实际标签见下文):

h3
 p
h6
h5
 ul
  li
   p
h5
 ul
  li
   p
h5
 ul
  li
   p

我有以下代码用于简单的文本提取:

import requests
import io
import csv 
from bs4 import BeautifulSoup
from urllib.request import urlopen
URL = 'https://www.imf.org/en/Topics/imf-and-covid19/Policy-Responses-to-COVID-19'
page = requests.get(URL)
soup = BeautifulSoup(page.content, 'html.parser')

results = soup.find(class_='rr-intro')

with io.open('test.txt', 'w', encoding='utf8') as f:
    for header in results.find_all(['h3', 'h6', 'h5']):
        f.write(header.get_text() + u'\n') 
        for elem in header.next_siblings:
            if elem.name and elem.name.startswith('h'):
                # stop at next header
                break
            if elem.name and elem.find_all('p'):
                f.write(elem.get_text() + u'\n')

从 cmets 中,我认为创建列表并以某种方式压缩它们是有意义的。我试过这个:

h3 = results.find_all('h3')
h6 = results.find_all('h6')
h5 = results.find_all('h5')
h5f = results.find_all('h5', text='Fiscal')
h5m = results.find_all('h5', text='Monetary and macro-financial')
h5x = results.find_all('h5', text='Exchange rate and balance of payments')
country = [country.get_text() for country in h3]  #list of countries
date = [date.get_text() for date in h6]  #date string

我被困在这里了。不知道如何将 p-tags 的内容放到列表中的正确位置,以便将其压缩或直接放到 csv 中。

我是 python 菜鸟,所以我根据我在 stackoverflow 上找到的内容制作了这些。任何帮助将不胜感激。

编辑:澄清一下,我想要的结构看起来像这样。

<div class="rr-intro">

 <h3>
  Country 1
 </h3>
 <p>
  summary text
 </p>
 <h6>
  date
 </h6>
 <h5>
  Fiscal
 </h5>
 <ul>
  <li>
   <p>
    text for fiscal of country 1
   </p>
  </li>
 </ul>
 <h5>
  Monetary and macro-financial
 </h5>
 <ul>
  <li>
   <p>
    text for monetary of country 1
   </p>
  </li>
 </ul>
 <h5>
  Exchange rate and balance of payments
 </h5>
 <ul>
  <li>
   <p>
    text for FX of country 1
   </p>
  </li>
 </ul>
  <h3>
  Country 2
 </h3>
 <p>
  summary text
 </p>
 <h6>
  date
 </h6>
 <h5>
  Fiscal
 </h5>
 <ul>
  <li>
   <p>
    text for fiscal of country 2
   </p>
  </li>
 </ul>
 <h5>
  Monetary and macro-financial
 </h5>
 <ul>
  <li>
   <p>
    text for monetary of country 2
   </p>
  </li>
 </ul>
 <h5>
  Exchange rate and balance of payments
 </h5>
 <ul>
  <li>
   <p>
    text for FX of country 2
   </p>
  </li>
 </ul>
   <h3>
  Country 3
 </h3> 

等等……

【问题讨论】:

  • this 有帮助吗?
  • results 来自哪里?
  • @CannedScientist 我理解该代码,但我无法将其应用于我正在抓取的网站所需的逻辑。我需要元素 h3=country,h6=date,h5=text_type。 h3 后面是

    中的内容。 h6 没有内容(只是日期)。 h5 很复杂,因为

    • 之后。我不太明白如何将每个内容引导到 .csv 中的正确位置。我想我需要列清单什么的,但这超出了我的能力范围。
  • @xxMrPHDxx 抱歉。我错过了一行代码。它现在就在那里。

标签: python csv beautifulsoup


【解决方案1】:

我觉得最简单的方法是在您按顺序阅读元素时处理它们。为此,您可以跟踪当前部分,然后将文本附加到该部分。

一旦找到下一个h3 国家/地区,就可以使用 Python CSV DictWriter 写入一行信息。例如:

from collections import defaultdict
import requests
import csv 
from bs4 import BeautifulSoup

URL = 'https://www.imf.org/en/Topics/imf-and-covid19/Policy-Responses-to-COVID-19'
page = requests.get(URL)
soup = BeautifulSoup(page.content, 'html.parser')

results = soup.find('div', class_='rr-intro')

section_lookup = {
    'Fiscal' : 'fiscal_text',
    'Moneta' : 'monetary_text',
    'Macro-' : 'monetary_text',
    'Exchan' : 'fx_text',
}

with open('data.csv', 'w', encoding='utf8', newline='') as f_output:
    fieldnames = ['country', 'date', 'general_text', 'fiscal_text', 'monetary_text', 'fx_text']
    csv_output = csv.DictWriter(f_output, fieldnames=fieldnames)
    csv_output.writeheader()

    row = defaultdict(str)
    section = None

    for elem in results.find_all(['h3', 'h6', 'h5', 'p']):
        if elem.name == 'h3':
            if row:
                csv_output.writerow(row)
                row = defaultdict(str)

            row['country'] = elem.get_text(strip=True)
            section = "general_text"

        elif elem.name == 'h5':
            section = section_lookup[elem.get_text(strip=True)[:6]]
        elif elem.name == 'h6':
            row['date'] = elem.get_text(strip=True)[27:]
        elif elem.name == 'p' and section:
            row[section] = f"{row[section]} {elem.get_text(strip=True)}"

    if row:
        csv_output.writerow(row)

给你一个data.csv文件开始:

【讨论】:

  • 这对我来说是一种教育。我想出了另一个代码,它创建了 4 个不同的 .csv 文件并将它们连接起来,但由于多个 p-tags,它出现了一个错误。这要好得多。我不确定“行”逻辑在做什么,但我会从中学习。查找字典的使用也非常巧妙。我还学到了一些关于 'section_lookup' 和 [:6] 逻辑的知识。非常酷,非常感谢
  • row 是一个包含一行值的字典。我使用默认字典来更容易附加 p 标签。
  • 我在这里的最后一个挑战是处理一些编码。该页面是 UTF-8,我知道如何使用 Soup 发送文本和获取它。它读取和显示正常。但我认为,当脚本提取 p 标签时,它会丢失这个(例如,圣多美和普林西比)。文本在 csv 中出现乱码。我尝试将格式代码 (.encode('utf-8') ) 和 (.decode('utf-8', 'ignore') ) 添加到 'csv_output.writerow(row)' 和 'row = defaultdict(str) ',但情况会变得更糟。我会深入研究这个问题,但我认为如果有人使用它并遇到同样的问题,我应该指出它。干杯!
猜你喜欢
  • 1970-01-01
  • 2017-10-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-16
  • 1970-01-01
  • 1970-01-01
  • 2017-06-01
  • 2022-11-22
相关资源
最近更新 更多