【问题标题】:crawl dynamic webpage for data using scrapy使用scrapy抓取动态网页的数据
【发布时间】:2014-07-12 04:24:35
【问题描述】:

我正在尝试从 NBA 的官方统计数据中获取一些数据,以用于一些数据分析。我使用scrapy 作为我的主要抓取工具。但是,在检查网页元素后,我发现它是使用 javascript 动态生成的。我对javascript完全陌生,无法弄清楚它是如何工作的。(调用了哪个js文件,它是如何加载到哪个包含数据表的以及是否有更容易访问的方法来获取数据)我也有在 Network 中找到了一些 json 文件,我不知道它是如何使用的。

http://stats.nba.com/teamLineups.html?TeamID=1610612739&pageNo=1&rowsPerPage=100&Season=2008-09&sortField=MIN&sortOrder=DES&PerMode=Per48

谁能指导我使用上面的网址并告诉我网站如何实际运行以加载数据以及他们如何处理数据以使其以这种方式显示?

关键部分还是关于如何获取数据。我已经看到使用 POST 方法来取回数据的答案(抱歉,我什至不熟悉 GET/POST),但我仍然无法弄清楚这如何适用于这种情况。

感谢您的慷慨指导!

【问题讨论】:

    标签: javascript python scrapy web-crawler


    【解决方案1】:

    在此示例中,Javascript 仅允许在网页上发送、接收和显示内容,而无需为每个请求实际重新加载网页。 所以你不需要解析javascript,你只需要找到请求的信息,然后模仿那个请求,并解析响应。为此,您可以在 Firefox 中使用 Firebug,或在 Chrome 中使用开发工具(Windows 中为 ctrl+shift+J,Mac 中为 cmd+opt+J)。 在 Chrome 中,只需点击“网络”标签,您就会在点击网站时看到请求和响应。

    在这个特定的示例中,当您想要获取克利夫兰球队“2008-09”的统计数据时,javascript 会发送多个请求。 您可能感兴趣的阵容请求是: http://stats.nba.com/stats/teamdashlineups?PlusMinus=N&pageNo=1&GroupQuantity=5&TeamID=1610612739&GameID=&Location=&SeasonType=Regular+Season&Season=2008-09&PaceAdjust=N&DateFrom=&sortOrder=DES&VsConference=&OpponentTeamID=0&DateTo=&GameSegment=&LastNGames=0&VsDivision=&LeagueID=00&Outcome=&GameScope=&MeasureType=Base&PerMode=Per48&sortField=MIN&SeasonSegment=&Period=0&Rank=N&Month=0&rowsPerPage=100

    这是一个scrapy base spider的例子。只需要定义LineupItem,然后用scrapy crawl stats -o output.json执行即可。

    import json
    from scrapy.spider import Spider
    from scrapy.http import Request
    from nba.items import LineupItem
    from urllib import urlencode
    
    
    class StatsSpider(Spider):
        name = "stats"
        allowed_domains = ["stats.nba.com"]
        start_urls = (
            'http://stats.nba.com/',
            )
    
        def parse(self, response):
            return self.get_lineup('1610612739','2008-09')
    
        def get_lineup(self, team_id, season):
            params = {
                'Season':         season,
                'SeasonType':     'Regular Season',
                'LeagueID':       '00',
                'TeamID':         team_id,
                'MeasureType':    'Base',
                'PerMode':        'Per48',
                'PlusMinus':      'N',
                'PaceAdjust':     'N',
                'Rank':           'N',
                'Outcome':        '',
                'Location':       '',
                'Month':          '0',
                'SeasonSegment':  '',
                'DateFrom':       '',
                'DateTo':         '',
                'OpponentTeamID': '0',
                'VsConference':   '',
                'VsDivision':     '',
                'GameSegment':    '',
                'Period':         '0',
                'LastNGames':     '0',
                'GroupQuantity':  '5',
                'GameScope':      '',
                'GameID':         '',
                'pageNo':         '1',
                'rowsPerPage':    '100',
                'sortField':      'MIN',
                'sortOrder':      'DES'
            }
            return Request(
                url="http://stats.nba.com/stats/teamdashlineups?" + urlencode(params),
                dont_filter=True,
                callback=self.parse_lineup
            )
    
        def parse_lineup(self,response):
            data = json.loads(response.body)
            for lineup in data['resultSets'][1]['rowSet']:
                item = LineupItem()
                item['group_set'] = lineup[0]
                item['group_id'] = lineup[1]
                item['group_name'] = lineup[2]
                item['gp'] = lineup[3]
                item['w'] = lineup[4]
                item['l'] = lineup[5]
                item['w_pct'] = lineup[6]
                item['min'] = lineup[7]
                item['fgm'] = lineup[8]
                item['fga'] = lineup[9]
                item['fg_pct'] = lineup[10]
                item['fg3m'] = lineup[11]
                item['fg3a'] = lineup[12]
                item['fg3_pct'] = lineup[13]
                item['ftm'] = lineup[14]
                item['fta'] = lineup[15]
                item['ft_pct'] = lineup[16]
                item['oreb'] = lineup[17]
                item['dreb'] = lineup[18]
                item['reb'] = lineup[19]
                item['ast'] = lineup[20]
                item['tov'] = lineup[21]
                item['stl'] = lineup[22]
                item['blk'] = lineup[23]
                item['blka'] = lineup[24]
                item['pf'] = lineup[25]
                item['pfd'] = lineup[26]
                item['pts'] = lineup[27]
                item['plus_minus'] = lineup[28]
                yield item
    

    这将导致像这样的json记录:

    {"gp": 30, "fg_pct": 0.491, "group_name": "Ilgauskas,Zydrunas - James,LeBron - Wallace,Ben - West,Delonte - Williams,Mo", "group_set": "Lineups", "w_pct": 0.833, "pts": 103.0, "min": 484.9866666666667, "tov": 13.3, "fta": 21.6, "pf": 16.0, "blk": 7.7, "reb": 44.2, "blka": 3.0, "ftm": 16.6, "ft_pct": 0.771, "fg3a": 18.7, "pfd": 17.2, "ast": 23.3, "fg3m": 7.4, "fgm": 39.5, "fg3_pct": 0.397, "dreb": 32.0, "fga": 80.4, "plus_minus": 18.4, "stl": 8.3, "l": 5, "oreb": 12.3, "w": 25, "group_id": "980 - 2544 - 1112 - 2753 - 2590"}
    

    【讨论】:

    • 哇这么详细的解释!请注意,您是否可以解释更多有关回调函数的信息以及为什么没有像 scrapy 中那样的解析函数。教程?对那部分很困惑
    • 抱歉不清楚.. 为什么解析函数不使用传入的响应?
    • 因为我不需要来自 stats.nba.com 页面的任何内容,并且我不想将一个丑陋的大请求作为起始 URL。因此,我在第一个请求上空白,然后实现一个函数来获取给定团队和给定赛季的前 100 个阵容。要实际使用它,您需要实现一个爬取逻辑,该逻辑将向您想要的所有团队和季节的函数发出请求。
    【解决方案2】:

    Scrapy 无法运行 javascript,因此您必须分析 javascript 代码并在 Python 和 Scrapy 中执行类似的操作,或者识别 javascript 如何从服务器获取数据(它使用哪些 url 和参数)并将其用于您的脚本。可能需要做很多工作 - 首先使用 Firefox 中的 Firebug,然后使用 Python 和 Scrapy。

    如果您不知道如何执行此操作,最好使用模拟真实浏览器并可以运行 javascript 的 Selenium(或类似的东西)。您只需告诉Selenium 在页面上按下哪个按钮、在表单中输入什么文本等。


    import requests
    import json
    
    # set request as GET
    response = requests.get('http://stats.nba.com/stats/teamdashlineups?Season=2008-09&SeasonType=Regular+Season&LeagueID=00&TeamID=1610612739&MeasureType=Base&PerMode=Per48&PlusMinus=N&PaceAdjust=N&Rank=N&Outcome=&Location=&Month=0&SeasonSegment=&DateFrom=&DateTo=&OpponentTeamID=0&VsConference=&VsDivision=&GameSegment=&Period=0&LastNGames=0&GroupQuantity=5&GameScope=&GameID=&pageNo=1&rowsPerPage=100&sortField=MIN&sortOrder=DES')
    
    # change json into dictionary
    data =  json.loads(response.text)
    
    #print data
    
    import pprint
    
    pprint.pprint(data)
    
    for x in data['resultSets']:
        print x['rowSet']
    

    【讨论】:

    • 其实我之前做的是我尝试在python中使用json加载数据然后我想将它读入pandas中的数据框,但是格式不对
    • 现在您可以尝试更改 url 中的参数以获取其他数据 - 例如您可以更改年份。
    • 也许使用模块PrettyPrint(见上面的代码)来更好地查看数据。
    • 可能部分字典(子字典)可以在pandas中使用
    • 我找不到模块请求
    【解决方案3】:

    我可能无法详细回答您的问题,但我是这样理解的。

    当您转到一个页面时,浏览器GET 是该页面的源代码,与您在 chrome 中单击“查看页面源代码”时看到的源代码相同。浏览器解释代码,如果找到指向外部文件的“src”属性,它会将该文件导入源,再次使用GET 请求。

    <script src="/js/libs/modernizr.custom.16166.js"></script>
    

    导入 JavaScript 文件后,它们可以自行运行

    jsfile.js:

    function myFunction() {
    //
    //do stuff
    //
    }
    
    myFunction();
    

    对于您的 nba 站点,导入的文件会创建表并使用 ajax GET 请求填充它。

    您的 nba 网站似乎使用来自“jquery.statrequest.js”和“team-lineups.js”的 ajax GET 请求从这个 link 获取表格信息,它一团糟所以你可能仍然想要通常刮掉页面。

    如果您决定抓取该页面,您将无法使用 urllib,因为它只是获取页面源,它​​不会导入任何外部 .js 脚本并且不会运行 JavaScript 代码,在这种情况下,表格位于该页面不会被创建并填充 nba 统计数据。

    您将需要使用类似Mechanize 的东西,它可以模拟浏览器并导入和运行 JavaScript。

    我希望这能让您对您想了解的内容有所了解,我对浏览器的内部工作原理不是很熟悉。您可能想寻找一个提供免费 API 用于 nda 游戏统计数据的网站。

    这是来自 nba 网站的另一个 link,可能与您相关。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-15
      • 2023-04-03
      相关资源
      最近更新 更多