网页抓取带有 <pre> 预格式化文本且无标签的 .html 页面

Posted

技术标签:

【中文标题】网页抓取带有 <pre> 预格式化文本且无标签的 .html 页面【英文标题】:Web scraping a .htm page with <pre> preformatted text and no tags 【发布时间】:2018-07-13 16:28:00 【问题描述】:

我是一个从事网络抓取项目的新手。我需要将这些选举结果放入数据框(或 Excel)中以便对其进行分析。

最棘手的是,它是一个 .htm 文件,所有数据都作为“预格式化文本”(PRE)标签之间的一个大文本块,并且数据本身没有单独的标签。我只对像表格一样设置的数据部分感兴趣:

https://www.stlouisco.com/portals/8/docs/document%20library/elections/eresults/el140805/EXEC.htm

我一直在 Python 中尝试使用 BeautifulSoup。但是,如果您查看 URL 上的源代码,您就会明白为什么 BeautifulSoup 并没有让我走得太远——因为数据不是使用标签来结构化的。结构基本上是这样的:

<html>
<pre>
COUNTY EXECUTIVE                                  PRIMARY ELECTION                                   OFFICIAL FINAL RESULTS
                                              ST. LOUIS COUNTY, MISSOURI
RUN DATE:08/18/14 01:20 PM                        TUESDAY, AUGUST 5, 2014
                                              STATISTICS
                                                                 WITH 681 OF 681 PRECINCTS REPORTING
                                               TOTAL  PERCENT                                                       TOTAL  PERCENT
   01 = REGISTERED VOTERS - TOTAL                661,393             05 = BALLOTS CAST - LIBERTARIAN                 1,121     .58
   02 = BALLOTS CAST - TOTAL                     192,495             06 = BALLOTS CAST - CONSTITUTION                  314     .16 
   03 = BALLOTS CAST - DEMOCRATIC                129,918   67.49     07 = BALLOTS CAST - NONPARTISAN                 6,225    3.23
   04 = BALLOTS CAST - REPUBLICAN                 54,917   28.53     08 = VOTER TURNOUT - TOTAL                              29.10
                                     - - - - - - - - - - - - - - - - - - - - - - - -
                                       01    02    03    04    05    06    07    08
                                     - - - - - - - - - - - - - - - - - - - - - - - -
0101 AP1,2,7,43                      1317 . 298 . 214 .  69 . . 3 . . 1 .  11 22.63
0103 AP3,27 NRW2,8,15,29             1453 . 186 . 179 . . 5 . . 1 . . 0 . . 1 12.80
0104 AP4                              231 .  51 .  34 . . 4 . . 0 . . 0 .  13 22.08
0105 AP5,18,21,39                    1289 . 268 . 198 .  47 . . 4 . . 1 .  18 20.79
0106 AP6                                2 . . 1 . . 0 . . 0 . . 0 . . 0 . . 1 50.00
0108 AP8,20                           586 . 142 .  86 .  44 . . 4 . . 0 . . 8 24.23
0109 AP9,25                           533 . 119 .  85 .  29 . . 2 . . 3 . . 0 22.33
0110 AP10                            1044 . 158 . 114 .  34 . . 2 . . 0 . . 8 15.13

...

2832 WH32,38,44                       296 .  51 .  23 .  28 . . 0 . . 0 . . 0 17.23
2834 WH34,43                         2043 . 609 . 267 . 321 . . 1 . . 0 .  20 29.81
2835 WH35                             543 . 173 .  60 . 110 . . 0 . . 0 . . 3 31.86
    ====================================================================================================================================
              (DEMOCRATIC)                                           WITH   681 OF 681  REPORTING
                                               VOTES  PERCENT                                                    VOTES  PERCENT
COUNTY EXECUTIVE
  (Vote for )  1
   01 = CHARLIE A. DOOLEY                         39,038   30.52
   02 = STEVE STENGER                             84,993   66.46     03 = RONALD E. LEVY                             3,862    3.02
                                   ------------------
                                       01    02    03
                                   ------------------
0101 AP1,2,7,43                        59   134    19
0103 AP3,27 NRW2,8,15,29              154    18     5
0104 AP4                                7    25     2
0105 AP5,18,21,39                      55   133     9
0106 AP6                                0     0     0
0108 AP8,20                            28    50     7
0109 AP9,25                            21    57     6
0110 AP10                              56    54     1
0111 AP11,24                           53    54     1
0112 AP12                              19    41     1
0113 AP13                              23    46     2
0114 AP14,15,16 NOR31                  25    56     4

...

2819 WH19,20,22                        25   162     7
2825 WH25                              17   109     9
2831 WH31                              18   112     7
2832 WH32,38,44                         0    22     1
2834 WH34,43                           31   218    10
2835 WH35                              16    41     3
====================================================================================================================================
                  (REPUBLICAN)                                           WITH 681 OF 681  REPORTING
                                               VOTES  PERCENT
COUNTY EXECUTIVE
  (Vote for )  1
   01 = TONY POUSOSA                              16,439   32.10
   02 = RICK STREAM                               34,772   67.90
                                   ------------
                                       01    02
                                   ------------
0101 AP1,2,7,43                        24    37
0103 AP3,27 NRW2,8,15,29                1     4
0104 AP4                                1     3
0105 AP5,18,21,39                      13    28
0106 AP6                                0     0
0108 AP8,20                            16    28
0109 AP9,25                             9    19
0110 AP10                              13    19
0111 AP11,24                            7    32

...

</pre>
<p>Some closing text that is irrelevant to this project.</p>
</html>

我希望使用 Python 来自动化这个过程,这样我就可以在其他类似的选举结果网页上运行它。

这是我所能得到的。我能够创建一个对象列表,每个列表项都是一行数据。我希望它成为一个数据框,去掉所有额外的空格和句点。不过,我不确定如何从这里做到这一点。我想我什至可能从错误的角度考虑这个问题。

# STEP 1: Importing the Libraries

import requests
from bs4 import BeautifulSoup


# STEP 2: Collecting and Parsing the webpage

# Collect the election results page
page = requests.get('https://www.stlouisco.com/portals/8/docs/document%20library/elections/eresults/el140805/EXEC.htm')

# Parse the page and create a Beautiful Soup object
soup = BeautifulSoup(page.text, 'html.parser')


# STEP 3: Create an object with just the text
soup2 = soup.text

# Split the text at each line break \n; this creates a list object
[x.strip() for x in soup2.split('\n')]


Output: 

[...
'0212 BON12                           1678 . 685 . 376 . 295 . . 1 . . 0 .  13 40.82',
'0213 BON13,23,26,29                  2174 . 796 . 500 . 261 . . 3 . . 2 .  30 36.61',
'0214 BON14                             17 . . 4 . . 0 . . 0 . . 0 . . 0 . . 4 23.53',
'0215 BON15                           1340 . 369 . 224 . 129 . . 2 . . 1 .  13 27.54',
'0216 BON16                            204 . 104 .  68 .  36 . . 0 . . 0 . . 0 50.98',
'0217 BON17                            589 .  93 .  71 .  16 . . 1 . . 0 . . 5 15.79',
'0218 BON18                            195 .  48 .  28 .  17 . . 0 . . 1 . . 2 24.62',
'0219 BON19 CLA15                     1340 . 443 . 255 . 172 . . 5 . . 0 .  11 33.06',
...]

我感到被困住了,如果有任何建议,我将不胜感激! (如果 Python 不是自动将其放入数据框的最佳方式……我也欢迎您提供反馈。)谢谢。

【问题讨论】:

尝试在 BeautiflulSoup 中将 'html.parser' 更改为 html5lib。你肯定会注意到一个巨大的变化。首先这样做soup = BeautifulSoup(page.text, 'html5lib'),然后print(soup) 感谢 SIM 的建议。不幸的是,我像你说的那样运行它,它几乎完全一样 - 数据仍然只是一个位于
 
之间的大文本块。 (它不是使用标签构成的。)感谢您的帮助!
【参考方案1】:

看看你引用的例子,你需要编写一个解析器,因为你的数据很复杂,而且每一行(很可能是每一页)都不同。

以这一行为例,希望我能解释一下原因:

0101 AP1,2,7,43                      1317 . 298 . 214 .  69 . . 3 . . 1 .  11 22.63
    这部分:0101 在每一行中都相对一致,因为这似乎是某种用零填充的整数索引。后跟 1 个空格。 但是,下一部分 (AP1,2,7,43) 遵循一定的规则,但其内容会有所不同。例如,我们知道逗号分隔值的数量在每一行中有所不同,并且这些值有时可以包含空格(例如AP3,27 NRW2,8,15,29)。然后是大量空白,直到下一部分 - 即似乎是投票数字。 对于这些数字/整数列,每列由一个空格分隔,后跟一个点和空格的组合分隔符。如果整数小于 10,则填充该数字,以便重复 ". " 分隔符并放置在百位位置。 最后一列,22.63 是一个带 2 位小数的常规浮点数。

这还没有触及其他各行各业的规则。

鉴于数据集的复杂性,您最好使用pyparsing 或PLY 等工具编写一个简单的语法来创建可以自动从每一行提取信息的迷你解析器,然后可以将其放入数据结构并保存到数据框中。此处适用的使用 pyparsing 的一个很好的示例显示 how to parse street addresses。 More examples can be found here.

值得注意的是,所有这些都可以通过编写自定义文本操作函数和代码来处理,但考虑到您打算将事情自动化,解析器是您最好的选择,因为它可重用且适应性更强。强>

【讨论】:

这个解释超级清楚。你给了我一个创造我需要的东西的入口!非常感谢您澄清我的情况并与我分享一些工具 - 我很快就会深入研究。 @mindexplore:不是问题,很乐意提供帮助 - 而且,也谢谢你!我希望您的项目取得进展,因为它看起来很有趣 - 圣路易斯肯定会从中受益匪浅!【参考方案2】:

查看文件格式,我认为这应该是可行的。您需要的行似乎具有固定的字符大小。如果您阅读每一行,则可以按字符长度对其进行拆分。

前 5 个字符:数字(例如 0101)

接下来的 32 个字符:描述(例如 AP3,27 NRW2,8,15,29 )

接下来的 6 个字符:第 1 列

等等。 但请记住,如果此文件的格式发生更改,您的代码将被破坏。

【讨论】:

以上是关于网页抓取带有 <pre> 预格式化文本且无标签的 .html 页面的主要内容,如果未能解决你的问题,请参考以下文章

HTML&CSS基础学习笔记1.8-预格式文本

&lt;pre&gt;标签

iPhone 上未正确显示预格式化文本

使pre的内容自动换行

Android WebView 错误地处理预格式化文本中的换行符

Day-2