使用Scrapy抓取动态网页数据
我想从NBA的官方网站获取一些数据,用于数据分析。我主要使用scrapy这个工具来抓取数据。不过,在查看网页元素时,我发现这些数据是通过JavaScript动态生成的。我对JavaScript完全不熟悉,搞不清楚它是怎么工作的(哪个js文件被调用,数据是如何加载的,包含数据表的部分,以及有没有更简单的方法获取这些数据)。我还在网络部分发现了一些json文件,但我不知道这些是怎么用的。
有没有人能帮我解答一下,使用上面的链接,告诉我这个网站是如何运作的,以便加载数据,以及他们是如何处理这些数据使其以这种方式展示出来的?
关键还是在于如何获取这些数据。我看到有些回答提到使用POST方法来获取数据(抱歉,我对GET/POST都不太熟悉),但我还是搞不清楚这在这个情况下是怎么应用的。
谢谢你们的热心指导!
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”来完成的,内容比较复杂,所以你可能还是想直接抓取页面。
如果你决定抓取页面,你将无法使用urllib,因为它只是获取页面的源代码,并不会导入任何外部的.js脚本,也不会运行JavaScript代码,这样页面上的表格就不会被创建和填充NBA的统计数据。
你需要使用像Mechanize这样的工具,它可以模拟浏览器并导入和运行JavaScript。
希望这些信息能给你一些启发,我对浏览器的内部工作原理不是很熟悉。你可以寻找一些提供NBA比赛统计数据的免费API的网站。
这里还有一个来自NBA网站的链接,可能对你有帮助。
Scrapy无法运行JavaScript,所以你需要分析JavaScript代码,然后在Python和Scrapy中做类似的事情,或者弄清楚JavaScript是如何从服务器获取数据的(它使用了哪些网址和参数),然后在你的脚本中使用这些信息。这可能需要很多工作——首先要用Firefox中的Firebug工具,然后再用Python和Scrapy。
如果你对怎么做完全没有头绪,那不如使用Selenium
(或者类似的工具),它可以模拟真实的浏览器,并且能够运行JavaScript。你只需要告诉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']
在这个例子中,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 基础爬虫的例子。你只需要定义 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"}