bandcamp-discover 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.rdoc +4 -0
- data/lib/bandcamp-discover/scrapers/album.rb +3 -3
- data/lib/bandcamp-discover/scrapers/base.rb +13 -0
- data/lib/bandcamp-discover/scrapers/label.rb +1 -1
- data/lib/bandcamp-discover/scrapers/music.rb +29 -8
- data/lib/bandcamp-discover/version.rb +1 -1
- data/test/music_test.rb +40 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4591eadf76413cf74687ca5c4233890ff1c2e96d641dd8d966c8a1336935e00d
|
|
4
|
+
data.tar.gz: cbf6cb74fca34d127a78469106af4d89597d04f1a812cd1f748ef96e0ae186b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9eba8a9fbb1ebf9c31195bb2c4cfda3d00601d66dc4773c9590bc3e2368585e0522734b4c6f3540e27e28b13a9314a770535d893070edfcd5444ed99b8dbb49d
|
|
7
|
+
data.tar.gz: 6d5a34e8edd6c23cd1db88803e8c392911f96eb1a20eea31fe2fb31ed4258c423dc2e7ff14e243255a431e7f529eff3e20f2870bfe58deef6847a4f6c5f7c074
|
data/Gemfile.lock
CHANGED
data/README.rdoc
CHANGED
|
@@ -21,6 +21,10 @@ bio for the model and returned as +artists+ in the scrape result; each
|
|
|
21
21
|
entry in +albums+ carries its own credit as +artist+ (nil when the release
|
|
22
22
|
is credited to the page owner).
|
|
23
23
|
|
|
24
|
+
Pages are read at DOMContentLoaded and images, media and fonts are never
|
|
25
|
+
fetched: everything the scrapers use is in the server-rendered HTML, and the
|
|
26
|
+
/music grid is completed from the JSON Bandcamp ships for its own script.
|
|
27
|
+
|
|
24
28
|
Without a token, +label?+ falls back to matching /label|platform|records/i
|
|
25
29
|
and +accepts_demos?+ is always false.
|
|
26
30
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
require_relative "
|
|
2
|
-
require_relative "
|
|
1
|
+
require_relative "base"
|
|
2
|
+
require_relative "music"
|
|
3
3
|
|
|
4
4
|
module BandcampDiscover
|
|
5
5
|
module Scrapers
|
|
6
6
|
class Album < Base
|
|
7
7
|
def scrape(force: false)
|
|
8
8
|
super do |page|
|
|
9
|
-
|
|
9
|
+
visit(@url)
|
|
10
10
|
|
|
11
11
|
title = page.query_selector("meta[name=title]")&.[](:content)
|
|
12
12
|
|
|
@@ -7,10 +7,16 @@ module BandcampDiscover
|
|
|
7
7
|
class ScrapeError < StandardError; end
|
|
8
8
|
|
|
9
9
|
class Base
|
|
10
|
+
# Nothing read from a page is an image, a player or a font, and the
|
|
11
|
+
# album pages are twenty per label; not fetching them is most of the
|
|
12
|
+
# time and bandwidth a scrape used to cost.
|
|
13
|
+
BLOCKED_RESOURCES = %w[image media font].freeze
|
|
14
|
+
|
|
10
15
|
def initialize(url:, browser:, max_tasks: 2)
|
|
11
16
|
@url = url
|
|
12
17
|
@browser = browser
|
|
13
18
|
@page = browser.new_page
|
|
19
|
+
@page.route("**/*", ->(route, request) { BLOCKED_RESOURCES.include?(request.resource_type) ? route.abort : route.continue })
|
|
14
20
|
@max_tasks = max_tasks
|
|
15
21
|
end
|
|
16
22
|
|
|
@@ -20,6 +26,13 @@ module BandcampDiscover
|
|
|
20
26
|
|
|
21
27
|
private
|
|
22
28
|
|
|
29
|
+
# Everything read is in the server-rendered HTML, so the DOM is enough;
|
|
30
|
+
# waiting for "load" waited on every tracker and player asset, and one
|
|
31
|
+
# slow one failed the whole label.
|
|
32
|
+
def visit(url)
|
|
33
|
+
@page.goto(url, waitUntil: "domcontentloaded")
|
|
34
|
+
end
|
|
35
|
+
|
|
23
36
|
def guarded
|
|
24
37
|
yield
|
|
25
38
|
rescue Playwright::TimeoutError => e
|
|
@@ -2,6 +2,7 @@ require_relative "base"
|
|
|
2
2
|
require_relative "album"
|
|
3
3
|
require_relative "../roster"
|
|
4
4
|
require "async"
|
|
5
|
+
require "json"
|
|
5
6
|
require "async/semaphore"
|
|
6
7
|
|
|
7
8
|
module BandcampDiscover
|
|
@@ -23,18 +24,33 @@ module BandcampDiscover
|
|
|
23
24
|
# The grid alone tells a label from an artist (see Roster), and it is one
|
|
24
25
|
# page load against the twenty behind albums, so callers can look at it
|
|
25
26
|
# before paying for the rest.
|
|
27
|
+
#
|
|
28
|
+
# Bandcamp renders the first sixteen items as HTML and ships the rest as
|
|
29
|
+
# JSON in data-client-items for its own script to render after load. Read
|
|
30
|
+
# both and no script has to run: the grid is complete at DOMContentLoaded.
|
|
26
31
|
def grid
|
|
27
32
|
guarded do
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
url: absolute(item.query_selector("a")[:href]),
|
|
34
|
-
credit: item.query_selector(".artist-override")&.inner_text&.strip
|
|
35
|
-
}
|
|
33
|
+
visit(@url)
|
|
34
|
+
grid = @page.wait_for_selector("#music-grid")
|
|
35
|
+
|
|
36
|
+
rendered = grid.query_selector_all("li.music-grid-item").map do |item|
|
|
37
|
+
{url: item.query_selector("a")[:href], credit: item.query_selector(".artist-override")&.inner_text}
|
|
36
38
|
end
|
|
39
|
+
|
|
40
|
+
merge_grid(rendered, grid.get_attribute("data-client-items"))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# A credit is present in the JSON only when the release is not the
|
|
45
|
+
# owner's; the same convention as the rendered override.
|
|
46
|
+
def merge_grid(rendered, client_items_json)
|
|
47
|
+
client = JSON.parse(client_items_json.to_s.empty? ? "[]" : client_items_json).map do |item|
|
|
48
|
+
{url: item["page_url"], credit: item["artist"]}
|
|
37
49
|
end
|
|
50
|
+
|
|
51
|
+
(rendered + client)
|
|
52
|
+
.map { |item| {url: absolute(item[:url]), credit: credit_or_nil(item[:credit])} }
|
|
53
|
+
.uniq { |item| item[:url] }
|
|
38
54
|
end
|
|
39
55
|
|
|
40
56
|
def roster(grid, band_name:)
|
|
@@ -79,6 +95,11 @@ module BandcampDiscover
|
|
|
79
95
|
def absolute(href)
|
|
80
96
|
href.start_with?("https://") ? href : "#{@base_url}#{href}"
|
|
81
97
|
end
|
|
98
|
+
|
|
99
|
+
def credit_or_nil(credit)
|
|
100
|
+
text = credit.to_s.strip
|
|
101
|
+
text.empty? ? nil : text
|
|
102
|
+
end
|
|
82
103
|
end
|
|
83
104
|
end
|
|
84
105
|
end
|
data/test/music_test.rb
CHANGED
|
@@ -6,8 +6,12 @@ require "bandcamp-discover/scrapers/music"
|
|
|
6
6
|
class MusicTest < Minitest::Test
|
|
7
7
|
Music = BandcampDiscover::Scrapers::Music
|
|
8
8
|
|
|
9
|
+
FakePage = Struct.new(:routes) do
|
|
10
|
+
def route(pattern, handler) = (self.routes ||= []) << pattern
|
|
11
|
+
end
|
|
12
|
+
|
|
9
13
|
FakeBrowser = Struct.new(:pages) do
|
|
10
|
-
def new_page =
|
|
14
|
+
def new_page = FakePage.new
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
FakeAlbum = Struct.new(:url) do
|
|
@@ -43,4 +47,39 @@ class MusicTest < Minitest::Test
|
|
|
43
47
|
|
|
44
48
|
assert_equal [{url: nil, title: nil, tags: nil, player_url: nil, artist: "Helen"}], albums
|
|
45
49
|
end
|
|
50
|
+
|
|
51
|
+
# Bandcamp renders sixteen items and ships the rest as JSON for its own
|
|
52
|
+
# script; reading both makes the grid complete without running it.
|
|
53
|
+
def test_grid_merges_rendered_items_with_the_client_items_json
|
|
54
|
+
rendered = [
|
|
55
|
+
{url: "/album/overflowing", credit: " Early Moon "},
|
|
56
|
+
{url: "/album/own-release", credit: nil}
|
|
57
|
+
]
|
|
58
|
+
json = '[{"art_id":1,"artist":"David Cordero","band_id":3141249859,"id":2,"page_url":"/album/and-stillness-came","title":"And Stillness Came","type":"album"},' \
|
|
59
|
+
'{"art_id":3,"band_id":3141249859,"id":4,"page_url":"/track/pay","title":"Pay","type":"track"},' \
|
|
60
|
+
'{"art_id":5,"artist":"","band_id":3141249859,"id":6,"page_url":"/album/overflowing","title":"dup","type":"album"}]'
|
|
61
|
+
|
|
62
|
+
grid = @music.merge_grid(rendered, json)
|
|
63
|
+
|
|
64
|
+
assert_equal [
|
|
65
|
+
{url: "https://kranky.bandcamp.com/album/overflowing", credit: "Early Moon"},
|
|
66
|
+
{url: "https://kranky.bandcamp.com/album/own-release", credit: nil},
|
|
67
|
+
{url: "https://kranky.bandcamp.com/album/and-stillness-came", credit: "David Cordero"},
|
|
68
|
+
{url: "https://kranky.bandcamp.com/track/pay", credit: nil}
|
|
69
|
+
], grid
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_grid_without_client_items_is_the_rendered_items
|
|
73
|
+
rendered = [{url: "https://radiohead.bandcamp.com/album/kid-a-mnesia", credit: nil}]
|
|
74
|
+
|
|
75
|
+
assert_equal rendered, @music.merge_grid(rendered, nil)
|
|
76
|
+
assert_equal rendered, @music.merge_grid(rendered, "")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_pages_block_images_media_and_fonts
|
|
80
|
+
page = @music.instance_variable_get(:@page)
|
|
81
|
+
|
|
82
|
+
assert_equal ["**/*"], page.routes
|
|
83
|
+
assert_equal %w[image media font], BandcampDiscover::Scrapers::Base::BLOCKED_RESOURCES
|
|
84
|
+
end
|
|
46
85
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bandcamp-discover
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Julian RUbisch
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-09-
|
|
10
|
+
date: 2026-09-08 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rake
|