nfl_data 0.0.11 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +5 -5
  2. data/.envrc +7 -0
  3. data/.gitignore +3 -3
  4. data/.rspec +1 -0
  5. data/.standard.yml +2 -0
  6. data/.travis.yml +6 -0
  7. data/CHANGELOG.md +11 -0
  8. data/Gemfile +1 -1
  9. data/README.md +94 -22
  10. data/Rakefile +5 -6
  11. data/bin/console +1 -1
  12. data/bin/setup +8 -0
  13. data/lib/nfl_data.rb +9 -18
  14. data/lib/nfl_data/api/player.rb +8 -26
  15. data/lib/nfl_data/api/schedule.rb +19 -0
  16. data/lib/nfl_data/api/statline.rb +8 -22
  17. data/lib/nfl_data/game.rb +5 -0
  18. data/lib/nfl_data/my_sports_feeds/client.rb +45 -0
  19. data/lib/nfl_data/my_sports_feeds/players_feed.rb +18 -0
  20. data/lib/nfl_data/my_sports_feeds/seasonal_games_feed.rb +22 -0
  21. data/lib/nfl_data/my_sports_feeds/weekly_player_gamelogs.rb +21 -0
  22. data/lib/nfl_data/parsers/player_parser.rb +21 -77
  23. data/lib/nfl_data/parsers/schedule_parser.rb +20 -0
  24. data/lib/nfl_data/parsers/statline_parser.rb +27 -71
  25. data/lib/nfl_data/player.rb +13 -0
  26. data/lib/nfl_data/schedule.rb +12 -0
  27. data/lib/nfl_data/statline.rb +20 -0
  28. data/lib/nfl_data/version.rb +1 -1
  29. data/nfl_data.gemspec +23 -22
  30. data/spec/api/player_spec.rb +24 -0
  31. data/spec/api/schedule_spec.rb +20 -0
  32. data/spec/api/statline_spec.rb +31 -0
  33. data/spec/models/game_spec.rb +15 -0
  34. data/spec/models/player_spec.rb +42 -0
  35. data/spec/models/schedule_spec.rb +17 -0
  36. data/spec/models/statline_spec.rb +63 -0
  37. data/spec/my_sports_feeds/client_spec.rb +17 -0
  38. data/spec/my_sports_feeds/players_feed_spec.rb +50 -0
  39. data/spec/my_sports_feeds/seasonal_games_feed_spec.rb +41 -0
  40. data/spec/my_sports_feeds/weekly_player_gamelogs_spec.rb +144 -0
  41. data/spec/parsers/player_parser_spec.rb +63 -0
  42. data/spec/parsers/schedule_parser_spec.rb +41 -0
  43. data/spec/parsers/statline_parser_spec.rb +68 -0
  44. data/spec/spec_helper.rb +55 -0
  45. metadata +98 -52
  46. data/.codeclimate.yml +0 -5
  47. data/.rubocop.yml +0 -2
  48. data/lib/nfl_data/api/team.rb +0 -23
  49. data/lib/nfl_data/models/player.rb +0 -20
  50. data/lib/nfl_data/models/statline.rb +0 -21
  51. data/lib/nfl_data/models/team.rb +0 -38
  52. data/lib/nfl_data/parsers/parser_helper.rb +0 -6
  53. data/lib/nfl_data/parsers/team_parser.rb +0 -94
  54. data/test/nfl_data/api/player_test.rb +0 -25
  55. data/test/nfl_data/api/statline_test.rb +0 -21
  56. data/test/nfl_data/api/team_test.rb +0 -13
  57. data/test/nfl_data/models/player_test.rb +0 -79
  58. data/test/nfl_data/models/statline_test.rb +0 -121
  59. data/test/nfl_data/models/team_test.rb +0 -43
  60. data/test/nfl_data/parsers/player_parser_test.rb +0 -86
  61. data/test/nfl_data/parsers/statline_parser_test.rb +0 -34
  62. data/test/nfl_data/parsers/team_parser_test.rb +0 -41
  63. data/test/test_helper.rb +0 -14
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NflData
4
+ module MySportsFeeds
5
+ class PlayersFeed
6
+ attr_reader :client
7
+
8
+ def initialize(client: MySportsFeeds::Client.new)
9
+ @client = client
10
+ end
11
+
12
+ def feed(position: nil)
13
+ params = {position: position}
14
+ client.get(endpoint: "players", params: params)["players"]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NflData
4
+ module MySportsFeeds
5
+ class SeasonalGamesFeed
6
+ attr_reader :client
7
+
8
+ def initialize(client: MySportsFeeds::Client.new)
9
+ @client = client
10
+ end
11
+
12
+ def feed(season_start_year:)
13
+ response = client.get(endpoint: "#{season_slug(season_start_year)}/games")
14
+ response["games"].map { |data| data["schedule"] }
15
+ end
16
+
17
+ def season_slug(year)
18
+ "#{year}-#{year + 1}-regular"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NflData
4
+ module MySportsFeeds
5
+ class WeeklyPlayerGamelogs
6
+ attr_reader :client
7
+
8
+ def initialize(client: MySportsFeeds::Client.new)
9
+ @client = client
10
+ end
11
+
12
+ def feed(season_start_year:, week:)
13
+ client.get(endpoint: "#{season_slug(season_start_year)}/week/#{week}/player_gamelogs")["gamelogs"]
14
+ end
15
+
16
+ def season_slug(year)
17
+ "#{year}-#{year + 1}-regular"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,84 +1,28 @@
1
- module NflData
2
- class PlayerParser
3
- include ParserHelper
4
-
5
- attr_reader :base_url
1
+ # frozen_string_literal: true
6
2
 
7
- def initialize
8
- @base_url = 'http://www.nfl.com/players/search?category=position&conferenceAbbr=null&playerType=current&conference=ALL&filter='
9
- end
10
-
11
- def get_by_position(position)
12
- if position == :all
13
- {
14
- quarterbacks: get('quarterback'),
15
- runningbacks: get('runningback'),
16
- wide_receivers: get('widereceiver'),
17
- tight_ends: get('tightend')
18
- }
19
- else
20
- # We have to remove the '_' and 's' because the NFL url has
21
- # singular position names and all are one word.
22
- { position => get(position.to_s.gsub(/s|_/, '')) }
3
+ module NflData
4
+ module Parsers
5
+ class PlayerParser
6
+ def parse(player_data:)
7
+ player_data.map { |data| data["player"] }.map do |data|
8
+ init_player(data)
9
+ end
23
10
  end
24
- end
25
-
26
- private
27
11
 
28
- def get(position)
29
- page_number = 1
30
- url = "#{@base_url}#{position}&d-447263-p=#{page_number}"
31
- return_val = []
32
-
33
- loop do
34
- players_found = update_or_create_players(url)
35
- return_val.push(players_found[1])
36
- break if players_found[0] == 0
37
- url = url.chomp(page_number.to_s)
38
- page_number += 1
39
- url += page_number.to_s
12
+ private
13
+
14
+ def init_player(data)
15
+ Player.new(
16
+ first_name: data["firstName"],
17
+ last_name: data["lastName"],
18
+ full_name: "#{data["firstName"]} #{data["lastName"]}".chomp,
19
+ position: data["primaryPosition"],
20
+ number: data["jerseyNumber"],
21
+ team: data.dig("currentTeam", "abbreviation").to_s,
22
+ msf_player_id: data["id"],
23
+ image_source: data["officialImageSrc"].to_s
24
+ )
40
25
  end
41
-
42
- return_val.flatten.map(&:to_hash)
43
- end
44
-
45
- def update_or_create_players(url)
46
- doc = open(url) { |f| Nokogiri(f) }
47
-
48
- # NFL.com stores players in 2 types of rows.
49
- # css class = odd or even.
50
- all_rows = doc.search('tr.odd') + doc.search('tr.even')
51
- players = all_rows.map { |row| parse_player_from_row(row.search('td')) }
52
- [players.count, players]
53
- end
54
-
55
- def parse_player_from_row(elements)
56
- # Get NFL.com Unique player id
57
- nfl_player_id = elements[2].to_s.split('/')[3]
58
-
59
- # player id is the only one with complicated parsing so we
60
- # can just extract the inner text out of the rest of the elements
61
- elements = elements.map(&:inner_text).map(&:strip)
62
- names = elements[2].split(',').map(&:strip).reverse
63
-
64
- Player.new(
65
- nfl_player_id: nfl_player_id,
66
- position: elements[0],
67
- number: elements[1],
68
- status: elements[3],
69
- team: make_jacksonville_abbreviation_consistent(elements[12]),
70
- first_name: names[0],
71
- last_name: names[1],
72
- full_name: names.join(' '),
73
- picture_link: get_picture_link(nfl_player_id, names[0], names[1])
74
- )
75
- end
76
-
77
- def get_picture_link(nfl_player_id, first_name, last_name)
78
- return nil if ENV['NFL_DATA_ENV'] == 'test'
79
- url = "http://www.nfl.com/player/#{first_name.gsub(/\s/, '')}#{last_name.gsub(/\s/, '')}/#{nfl_player_id}/profile"
80
- doc = open(url) { |f| Nokogiri(f) }
81
- doc.search('div.player-photo img').first.attributes['src'].value
82
26
  end
83
27
  end
84
28
  end
@@ -0,0 +1,20 @@
1
+ module NflData
2
+ module Parsers
3
+ class ScheduleParser
4
+ def parse(schedule_data:)
5
+ Schedule.new(games: schedule_data.map { |data| init_game(data) })
6
+ end
7
+
8
+ private
9
+
10
+ def init_game(data)
11
+ Game.new(
12
+ week: data["week"],
13
+ home_team: data.dig("homeTeam", "abbreviation"),
14
+ away_team: data.dig("awayTeam", "abbreviation"),
15
+ start_time: data["startTime"]
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,78 +1,34 @@
1
- module NflData
2
- class StatlineParser
3
- def initialize
4
- end
5
-
6
- def get(week, year, stat_type)
7
- case stat_type
8
- when :passing
9
- grab_week(week, year, 'Passing')
10
- when :rushing
11
- grab_week(week, year, 'Rushing')
12
- when :receiving
13
- grab_week(week, year, 'Receiving')
14
- end
15
- end
1
+ # frozen_string_literal: true
16
2
 
17
- private
18
-
19
- def get_player_id_from_profile_url(element)
20
- old_url = 'http://nfl.com' +
21
- element.search('a').first.attributes['href'].value
22
- new_url = ''
23
-
24
- begin
25
- open(old_url) do |resp|
26
- new_url = resp.base_uri.to_s
3
+ module NflData
4
+ module Parsers
5
+ class StatlineParser
6
+ def parse(statline_data:)
7
+ statline_data.map do |data|
8
+ init_statline(data)
27
9
  end
28
- rescue
29
- return nil
30
10
  end
31
11
 
32
- new_url.gsub('http://www.nfl.com', '').to_s.split('/')[3]
33
- end
34
-
35
- def grab_week(weeknum, year, stat_type)
36
- url = 'http://www.nfl.com/stats/weeklyleaders?type=REG' \
37
- "&week=#{weeknum}&season=#{year}&showCategory=#{stat_type}"
38
-
39
- doc = open(url) { |f| Nokogiri(f) }
40
-
41
- odds = doc.search('tr.odd')
42
- evens = doc.search('tr.even')
43
-
44
- all = odds + evens
45
-
46
- all.map do |player_row|
47
- elements = player_row.search('td')
48
-
49
- statline = Statline.new
50
-
51
- statline.nfl_player_id = get_player_id_from_profile_url(elements[0])
52
- statline.week = weeknum
53
- statline.year = year
54
-
55
- if stat_type == 'Rushing'
56
- statline.rush_atts = elements[4].inner_text.strip
57
- statline.rush_yards = elements[5].inner_text.strip
58
- statline.rush_tds = elements[7].inner_text.strip
59
- statline.fumbles = elements[8].inner_text.strip
60
- elsif stat_type == 'Passing'
61
- statline.pass_comp = elements[4].inner_text.strip
62
- statline.pass_att = elements[5].inner_text.strip
63
- statline.pass_yards = elements[6].inner_text.strip
64
- statline.pass_tds = elements[7].inner_text.strip
65
- statline.ints = elements[8].inner_text.strip
66
- statline.qb_rating = elements[11].inner_text.strip
67
- statline.fumbles = elements [10].inner_text.strip
68
- elsif stat_type == 'Receiving'
69
- statline.receptions = elements[4].inner_text.strip
70
- statline.rec_yards = elements[5].inner_text.strip
71
- statline.rec_tds = elements[7].inner_text.strip
72
- statline.fumbles = elements[8].inner_text.strip
73
- end
74
-
75
- statline.to_hash
12
+ private
13
+
14
+ def init_statline(data)
15
+ Statline.new(
16
+ rush_atts: data.dig("stats", "rushing", "rushAttempts") || 0,
17
+ rush_yards: data.dig("stats", "rushing", "rushYards") || 0,
18
+ rush_tds: data.dig("stats", "rushing", "rushTD") || 0,
19
+ fumbles: data.dig("stats", "fumbles", "fumbles") || 0,
20
+ pass_comp: data.dig("stats", "passing", "passCompletions") || 0,
21
+ pass_att: data.dig("stats", "passing", "passAttempts") || 0,
22
+ pass_yards: data.dig("stats", "passing", "passYards") || 0,
23
+ pass_tds: data.dig("stats", "passing", "passTD") || 0,
24
+ ints: data.dig("stats", "interceptions", "interceptions") || 0,
25
+ qb_rating: data.dig("stats", "passing", "qbRating") || 0,
26
+ receptions: data.dig("stats", "receiving", "receptions") || 0,
27
+ rec_yards: data.dig("stats", "receiving", "recYards") || 0,
28
+ rec_tds: data.dig("stats", "receiving", "recTD") || 0,
29
+ msf_game_id: data.dig("game", "id"),
30
+ msf_player_id: data.dig("player", "id")
31
+ )
76
32
  end
77
33
  end
78
34
  end
@@ -0,0 +1,13 @@
1
+ module NflData
2
+ Player = Struct.new(
3
+ :first_name,
4
+ :last_name,
5
+ :full_name,
6
+ :position,
7
+ :number,
8
+ :team,
9
+ :msf_player_id,
10
+ :image_source,
11
+ keyword_init: true
12
+ )
13
+ end
@@ -0,0 +1,12 @@
1
+ module NflData
2
+ Schedule = Struct.new(:games, keyword_init: true) {
3
+ def initialize(games: [])
4
+ super
5
+ end
6
+
7
+ def to_h
8
+ games.map!(&:to_h)
9
+ super
10
+ end
11
+ }
12
+ end
@@ -0,0 +1,20 @@
1
+ module NflData
2
+ Statline = Struct.new(
3
+ :rush_atts,
4
+ :rush_yards,
5
+ :rush_tds,
6
+ :fumbles,
7
+ :pass_comp,
8
+ :pass_att,
9
+ :pass_yards,
10
+ :pass_tds,
11
+ :ints,
12
+ :qb_rating,
13
+ :receptions,
14
+ :rec_yards,
15
+ :rec_tds,
16
+ :msf_game_id,
17
+ :msf_player_id,
18
+ keyword_init: true
19
+ )
20
+ end
@@ -1,3 +1,3 @@
1
1
  module NflData
2
- VERSION = '0.0.11'
2
+ VERSION = "0.1.1"
3
3
  end
@@ -1,30 +1,31 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("../lib", __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
 
5
- require 'nfl_data/version'
4
+ require "nfl_data/version"
6
5
 
7
6
  Gem::Specification.new do |spec|
8
- spec.name = 'nfl_data'
9
- spec.version = NflData::VERSION
10
- spec.authors = ['thetizzo']
11
- spec.email = ['j.m.taylor1@gmail.com']
12
- spec.homepage = 'https://github.com/thetizzo/nfl_data'
13
- spec.license = 'MIT'
14
- spec.summary = 'Parse NFL data like a boss'
15
- spec.description = 'Parse NFL data like a boss'
7
+ spec.name = "nfl_data"
8
+ spec.version = NflData::VERSION
9
+ spec.authors = ["thetizzo"]
10
+ spec.email = ["j.m.taylor1@gmail.com"]
11
+ spec.homepage = "https://github.com/thetizzo/nfl_data"
12
+ spec.license = "MIT"
13
+ spec.summary = "Parse NFL data like a boss"
14
+ spec.description = "Parse NFL data like a boss"
16
15
 
17
- spec.files = `git ls-files -z`.split("\x0")
18
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
- spec.require_paths = ['lib']
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
21
20
 
22
- spec.add_development_dependency 'bundler', '~> 1.6'
23
- spec.add_development_dependency 'rake'
24
- spec.add_development_dependency 'minitest', '~> 5.4.1'
25
- spec.add_development_dependency 'vcr', '~> 2.9.3'
26
- spec.add_development_dependency 'webmock', '~> 1.19.0'
27
- spec.add_development_dependency 'rubocop', '~> 0.35.1'
21
+ spec.add_development_dependency "rake", "~> 13.0"
22
+ spec.add_development_dependency "rspec", "~> 3.9"
23
+ spec.add_development_dependency "rspec-resembles_json_matchers", "~> 0.9"
24
+ spec.add_development_dependency "vcr", "~> 4.0.0"
25
+ spec.add_development_dependency "webmock", "~> 3.8"
26
+ spec.add_development_dependency "standard", "~> 0.5"
28
27
 
29
- spec.add_dependency 'nokogiri', '~> 1.6'
28
+ spec.add_dependency "nokogiri", "~> 1.10"
29
+ spec.add_dependency "typhoeus", "~> 1.4"
30
+ spec.add_dependency "zeitwerk", "~> 2.4"
30
31
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe NflData::Api::Player do
4
+ subject { described_class.new }
5
+
6
+ it "provides the players in json" do
7
+ VCR.use_cassette("msf_players") do
8
+ expect(JSON.parse(subject.players)).to resemble_json(
9
+ "players": [
10
+ {
11
+ "first_name": "B",
12
+ "last_name": "2Anger2",
13
+ "full_name": "B 2Anger2",
14
+ "position": "LB",
15
+ "number": 19,
16
+ "team": "JAX",
17
+ "msf_player_id": 19407,
18
+ "image_source": "google.com"
19
+ }
20
+ ]
21
+ )
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe NflData::Api::Schedule do
4
+ subject { described_class.new }
5
+
6
+ it "provides the season in json" do
7
+ VCR.use_cassette("msf_seasonal_games") do
8
+ expect(JSON.parse(subject.season(year: 2020))).to resemble_json(
9
+ "games": [
10
+ {
11
+ "week": 1,
12
+ "away_team": "KC",
13
+ "home_team": "DEN",
14
+ "start_time": Time.now
15
+ }
16
+ ]
17
+ )
18
+ end
19
+ end
20
+ end