当某些行包含其他格式时,使用 mechanize & beautiful 修饰表格

Posted

技术标签:

【中文标题】当某些行包含其他格式时,使用 mechanize & beautiful 修饰表格【英文标题】:Scaping a table using mechanize & beautiful when some rows contain additional formatting 【发布时间】:2014-10-21 14:49:45 【问题描述】:

以下是我要抓取的内容(缩短一吨以使其易于阅读):

<table class="sortable  row_summable stats_table" id="per_game">
<colgroup><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col><col></colgroup>
<thead>
<tr class="">
  <th data-stat="season" align="center"  class="tooltip sort_default_asc"  tip="If listed as single number, the year the season ended.<br>&#x2605; - Indicates All-Star for league.<br>Only on regular season tables.">Season</th>
  <th data-stat="age" align="center"  class="tooltip sort_default_asc"  tip="Age of Player at the start of February 1st of that season.">Age</th>
</tr>
</thead>
<tbody>
<tr  class="full_table" id="per_game.2009">
   <td align="left" ><a href="/players/r/rondora01/gamelog/2009/">2008-09</a></td>
   <td align="right" >22</td>
</tr>
<tr  class="full_table" id="per_game.2010">
   <td align="left" ><a href="/players/r/rondora01/gamelog/2010/">2009-10</a><span class="bold_text" style="color:#c0c0c0">&nbsp;&#x2605;</span></td>
   <td align="right" >23</td>
</tr>
</tfoot>
</table>

这是我正在使用的代码:

from bs4 import BeautifulSoup
import requests
import mechanize
from mechanize import Browser
import csv

mech = Browser()
url = "http://www.basketball-reference.com/players/r/rondora01.html"
# url = "http://www.basketball-reference.com/players/r/rosede01.html"
RR = mech.open(url)

html = RR.read()
soup = BeautifulSoup(html)
table = soup.find(id="per_game")

for row in table.findAll('tr')[1:]: 
    col = row.findAll('td')
    season = col[0].string
    age = col[1].string
    team = col[2].string
    pos = col[3].string
    games_played = col[4].string
    record = (season, age, team, pos, games_played)
    print "|".join(record)

但是,如果您在 HTML 中注意到,在第二行中,与第一行相比,本赛季还有一个额外的span。它创造了一个小星星。我的代码一直运行直到任何具有该附加参数的行,然后崩溃。关于使代码足够灵活以忽略额外的span 块的想法?

【问题讨论】:

【参考方案1】:

您可以通过以下方式改进代码:首先,将所有标题读入列表,然后逐行读取所有参数,使用zip() 将每个标题与值匹配并制作字典:

headers = [item.text for item in table('th')]
for row in table('tr')[1:]:
    params = [item.text.strip() for item in row('td')]
    print dict(zip(headers, params))

打印:

u'Lg': u'NBA', u'FT': u'1.5', u'3P': u'0.1', u'TOV': u'1.8', u'2PA': u'5.4', u'Tm': u'BOS', u'FG': u'2.4', u'3PA': u'0.4', u'DRB': u'2.8', u'2P': u'2.3', u'AST': u'3.8', u'Season': u'2006-07', u'FT%': u'.647', u'PF': u'2.3', u'PTS': u'6.4', u'FGA': u'5.8', u'GS': u'25', u'G': u'78', u'STL': u'1.6', u'Age': u'20', u'TRB': u'3.7', u'FTA': u'2.4', u'BLK': u'0.1', u'FG%': u'.418', u'Pos': u'PG', u'2P%': u'.432', u'MP': u'23.5', u'ORB': u'0.9', u'3P%': u'.207'
u'Lg': u'NBA', u'FT': u'1.4', u'3P': u'0.1', u'TOV': u'1.9', u'2PA': u'9.0', u'Tm': u'BOS', u'FG': u'4.6', u'3PA': u'0.2', u'DRB': u'3.2', u'2P': u'4.5', u'AST': u'5.1', u'Season': u'2007-08', u'FT%': u'.611', u'PF': u'2.4', u'PTS': u'10.6', u'FGA': u'9.3', u'GS': u'77', u'G': u'77', u'STL': u'1.7', u'Age': u'21', u'TRB': u'4.2', u'FTA': u'2.3', u'BLK': u'0.2', u'FG%': u'.492', u'Pos': u'PG', u'2P%': u'.499', u'MP': u'29.9', u'ORB': u'1.0', u'3P%': u'.263'
u'Lg': u'NBA', u'FT': u'2.2', u'3P': u'0.2', u'TOV': u'2.6', u'2PA': u'8.9', u'Tm': u'BOS', u'FG': u'4.8', u'3PA': u'0.6', u'DRB': u'4.0', u'2P': u'4.6', u'AST': u'8.2', u'Season': u'2008-09', u'FT%': u'.642', u'PF': u'2.4', u'PTS': u'11.9', u'FGA': u'9.5', u'GS': u'80', u'G': u'80', u'STL': u'1.9', u'Age': u'22', u'TRB': u'5.2', u'FTA': u'3.4', u'BLK': u'0.1', u'FG%': u'.505', u'Pos': u'PG', u'2P%': u'.518', u'MP': u'33.0', u'ORB': u'1.3', u'3P%': u'.313'
u'Lg': u'NBA', u'FT': u'2.2', u'3P': u'0.2', u'TOV': u'3.0', u'2PA': u'10.2', u'Tm': u'BOS', u'FG': u'5.7', u'3PA': u'1.0', u'DRB': u'3.2', u'2P': u'5.5', u'AST': u'9.8', u'Season': u'2009-10\xa0\u2605', u'FT%': u'.621', u'PF': u'2.4', u'PTS': u'13.7', u'FGA': u'11.2', u'GS': u'81', u'G': u'81', u'STL': u'2.3', u'Age': u'23', u'TRB': u'4.4', u'FTA': u'3.5', u'BLK': u'0.1', u'FG%': u'.508', u'Pos': u'PG', u'2P%': u'.536', u'MP': u'36.6', u'ORB': u'1.2', u'3P%': u'.213'
u'Lg': u'NBA', u'FT': u'1.1', u'3P': u'0.1', u'TOV': u'3.4', u'2PA': u'9.2', u'Tm': u'BOS', u'FG': u'4.7', u'3PA': u'0.6', u'DRB': u'3.1', u'2P': u'4.5', u'AST': u'11.2', u'Season': u'2010-11\xa0\u2605', u'FT%': u'.568', u'PF': u'1.8', u'PTS': u'10.6', u'FGA': u'9.9', u'GS': u'68', u'G': u'68', u'STL': u'2.3', u'Age': u'24', u'TRB': u'4.4', u'FTA': u'1.9', u'BLK': u'0.2', u'FG%': u'.475', u'Pos': u'PG', u'2P%': u'.491', u'MP': u'37.2', u'ORB': u'1.3', u'3P%': u'.233'
...

如果要从参数值中去除不可打印的字符,可以依赖string.printable

import string

params = [filter(lambda x: x in string.printable, item.text) 
          for item in row.find_all('td')] 

另见:Stripping non printable characters from a string in python


输出到 csv 的完整代码(带有玩家姓名):

import csv
import string
from bs4 import BeautifulSoup
from mechanize import Browser

mech = Browser()
url = "http://www.basketball-reference.com/players/r/rondora01.html"
RR = mech.open(url)

html = RR.read()
soup = BeautifulSoup(html)
table = soup.find(id="per_game")
player_name = soup.select('div#info_box h1')[0].text.strip()

with open('result.csv', 'w') as f:
    writer = csv.writer(f)

    writer.writerow(['Name'] + [item.text for item in table('th')])

    for row in table('tr')[1:]:
        writer.writerow([player_name] + [filter(lambda x: x in string.printable, item.text)
                                         for item in row('td')])

【讨论】:

谢谢@alecxe。完美的答案。一个额外的附加组件——然后是否可以将它们保存到 CSV 文件中,而不是打印?或者,更好的是,将它们添加到具有相同标题标题的预先存在的 CSV 中? @Craig 当然,请参阅更新的答案。希望对您有所帮助。 太好了,这非常有用。是否有一种简单的方法可以在打印之前手动将列添加到 csv?假设我想为从该表中抓取的所有行添加“玩家姓名”作为标题,并带有“Rajon Rondo”。最终目标是为许多玩家执行此操作,并存储在单个 csv 中。再次感谢亚历克 @Craig 当然,然后将玩家名称也从页面中删除 - 我已经改进了答案末尾的完整代码,检查它是否有帮助。谢谢。【参考方案2】:

我建议进行一些更改:

    由于您对与 &lt;a&gt; 元素关联的文本感兴趣,因此请更改您的行: col[0].stringcol[0].a.string。这样就可以解决问题了。

    修复第一个问题后,您将在该表的最后一行遇到错误(因为它的结构不同)。要解决此问题,请将这一行 for row in table.findAll('tr')[1:]: 更改为 for row in table.findAll('tr')[1:-1]:。这将负责跳过最后一行。

进行上述更改:

for row in table.findAll('tr')[1:-1]: 
    col = row.findAll('td')
    season = col[0].a.string
    age = col[1].string
    team = col[2].string
    pos = col[3].string
    games_played = col[4].string
    record = (season, age, team, pos, games_played)
    print "|".join(record)

打印:

2006-07|20|BOS|NBA|PG
2007-08|21|BOS|NBA|PG
2008-09|22|BOS|NBA|PG
2009-10|23|BOS|NBA|PG
2010-11|24|BOS|NBA|PG
2011-12|25|BOS|NBA|PG
2012-13|26|BOS|NBA|PG
2013-14|27|BOS|NBA|PG

【讨论】:

以上是关于当某些行包含其他格式时,使用 mechanize & beautiful 修饰表格的主要内容,如果未能解决你的问题,请参考以下文章

当某些行已经存在时如何防止SQL插入? [复制]

当某些行包含逗号作为千位分隔符和“标志并且没有小数的行没有标志时如何在R中读取数据

当列表包含某些内容时,为啥使用 zip() 只写入 CSV 文件?

当multiselect选项设置为true时,为复选框列添加自定义格式化程序

当某些项目相互依赖时,如何运行异步进程列表?

React JS - 为每个表行调用相同的 API 端点,直到响应包含某些条件