使用mechanize& amp; scaping table当某些行包含其他格式时很漂亮

时间:2014-08-27 21:09:55

标签: python web-scraping beautifulsoup screen-scraping mechanize

这是我想要刮掉的东西(缩短了一吨以便于阅读):

<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块的想法?

2 个答案:

答案 0 :(得分:1)

我建议做一些改变:

  1. 由于您对与<a>元素相关联的文本感兴趣,请更改您的行: col[0].stringcol[0].a.string。这将解决问题。

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

  3. 进行以上更改:

    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
    

答案 1 :(得分: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')])