scrape_creators 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c214fa7c8b7b56a2483d3432420003745f73747e8391100312168466f9f03c7b
4
- data.tar.gz: 0ec20deecab206e3ba0f54707abbfee7e624485d5f7954311bdb34552b207f40
3
+ metadata.gz: 36ed3616d4e59159e3241d2a07b743ad39aa63ac4adf93e63c7f51fbd0e8f173
4
+ data.tar.gz: 946bcdd67f4f3e9d5b53819c5c15ace2a56a148871ed58844479349507d8ac09
5
5
  SHA512:
6
- metadata.gz: f15f5722980fca1ccb48a32e1d62927324ae944c81132cc3210f2a35d12209033ef244804c988a9f4b3924c28f78c9ddca63bf66e29dd0e24e9477195ad95aed
7
- data.tar.gz: 20aadfbd40d3362a8d6fbbe52223fb1bb4baafdaec99406e9a9bbe477e8ab1fd40b29bf7279fdfc813a88c3126237a5bd598bf7ccf2db5c0adb133e7c79d4c66
6
+ metadata.gz: 27340bf2224cccfc9c5b10041e844e2f202765f34faa5d632f9fd04e7e00b1ea22ccbdcba49ee367af7fad54200618fc108c9ae9b202ac14df93713892c34574
7
+ data.tar.gz: 8b4fa602579a360b37af6f80847fe923fa3afb6fb165af8862afd2a81d796ebec4bff0068c2ba2c2781b71d26314669b04ddc4d0d29065fc3068b0e424d9d09a
data/README.md CHANGED
@@ -7,7 +7,7 @@ A zero-dependency Ruby client for the [Scrape Creators API](https://docs.scrapec
7
7
  Add this line to your application's Gemfile:
8
8
 
9
9
  ```ruby
10
- gem 'scrape_creators', '0.2.0'
10
+ gem 'scrape_creators', '0.6.0'
11
11
  ```
12
12
 
13
13
  And then execute:
@@ -40,6 +40,39 @@ scraper = ScrapeCreators::Client.new
40
40
  posts = scraper.posts("zuck")
41
41
  ```
42
42
 
43
+ ### Fetch TikTok Profile Videos
44
+
45
+ ```ruby
46
+ posts = scraper.posts("zuck", source: 'tiktok')
47
+ ```
48
+
49
+ ### Fetch YouTube Channel Shorts
50
+
51
+ ```ruby
52
+ posts = scraper.posts("zuck", source: 'youtube')
53
+ ```
54
+
55
+ ### Cursor-based pagination
56
+
57
+ All three platforms support cursor-based pagination. By default `posts` returns the first page only. Pass `pages:` to fetch more (e.g. `5` for the first five pages of a creator's feed):
58
+
59
+ ```ruby
60
+ posts = scraper.posts("zuck", pages: 5) # Instagram
61
+ posts = scraper.posts("zuck", source: 'tiktok', pages: 5) # TikTok
62
+ posts = scraper.posts("zuck", source: 'youtube', pages: 5) # YouTube Shorts
63
+ ```
64
+
65
+ If you need finer control, `posts_page` returns `{ items:, cursor:, has_more: }` so you can page through the full timeline yourself:
66
+
67
+ ```ruby
68
+ page = scraper.posts_page("zuck", source: 'tiktok')
69
+ loop do
70
+ page[:items].each { |item| puts item['desc'] }
71
+ break unless page[:has_more] && page[:cursor]
72
+ page = scraper.posts_page("zuck", source: 'tiktok', cursor: page[:cursor])
73
+ end
74
+ ```
75
+
43
76
  ### Fetch Instagram Post / Reel Info
44
77
 
45
78
  ```ruby
@@ -50,16 +83,6 @@ info = scraper.post("https://www.instagram.com/p/DKSMEpKRd6h/", download_media:
50
83
 
51
84
  ```ruby
52
85
  comments = scraper.comments("https://www.instagram.com/p/DKSMEpKRd6h/")
53
-
54
- page = scraper.comments_page("https://www.instagram.com/p/DKSMEpKRd6h/")
55
- page[:comments]
56
- page[:cursor]
57
- page[:has_more]
58
-
59
- next_page = scraper.comments_page(
60
- "https://www.instagram.com/p/DKSMEpKRd6h/",
61
- cursor: page[:cursor]
62
- )
63
86
  ```
64
87
 
65
88
  ## License
@@ -57,49 +57,62 @@ module ScrapeCreators
57
57
 
58
58
  def posts(handle, options = {})
59
59
  source = (options[:source] || 'instagram').to_s
60
- params = { handle: handle }.merge(options.reject { |k, _| k.to_s == 'source' })
61
- posts_url = posts_url_for(source: source)
62
- res = get(posts_url, params)
63
- key = posts_key_for(source: source)
64
- res.is_a?(Hash) ? (res[key] || []) : []
65
- end
66
-
67
- def post(url_or_code, options = {})
68
- source = (options[:source] || 'instagram').to_s
69
- url = normalize_url(url_or_code)
70
- post_url = post_url_for(source: source)
71
- params = { url: url }.merge(options.reject { |k, _| k.to_s == 'source' })
72
- get(post_url, params)
73
- end
60
+ pages = options[:pages] || options[:max_pages] || 1
61
+ all_items = []
62
+ cursor = nil
63
+ has_more = true
64
+
65
+ pages.to_i.times do
66
+ break unless has_more
67
+
68
+ page = posts_page(handle, options.merge(cursor: cursor))
69
+ items = Array(page[:items])
70
+ all_items.concat(items)
71
+ cursor = page[:cursor]
72
+ has_more = page[:has_more] && !cursor.to_s.empty? && !items.empty?
73
+ end
74
74
 
75
- def comments(url_or_code, options = {})
76
- comments_page(url_or_code, options)[:comments]
75
+ all_items
77
76
  end
78
77
 
79
- def comments_page(url_or_code, options = {})
78
+ def posts_page(handle, options = {})
80
79
  source = (options[:source] || 'instagram').to_s
81
- url = normalize_url(url_or_code)
82
- extra = options.reject { |k, _| %w[source cursor continuationToken continuation_token].include?(k.to_s) }
83
- params = { url: url }.merge(extra)
84
- cursor = options[:cursor] || options['cursor'] || options[:continuationToken] || options['continuationToken'] || options[:continuation_token]
80
+ params = { handle: handle }
81
+ cursor = options[:cursor] || options['cursor']
85
82
  if cursor && !cursor.to_s.empty?
86
- if source == YOUTUBE
87
- params[:continuationToken] = cursor
88
- else
89
- params[:cursor] = cursor
90
- end
83
+ params[posts_cursor_param_for(source:)] = cursor
91
84
  end
85
+ extra = options.reject { |k, _| %w[source cursor max_pages pages].include?(k.to_s) }
86
+ params.merge!(extra)
92
87
 
93
- res = get(comments_url_for(source: source), params)
94
- comments = res.is_a?(Hash) ? (res['comments'] || []) : []
95
- next_cursor = next_comments_cursor(res, source)
88
+ res = get(posts_url_for(source:), params)
89
+ key = posts_key_for(source:)
90
+ items = res.is_a?(Hash) ? (res[key] || []) : []
91
+ next_cursor = next_posts_cursor(res, source)
96
92
  {
97
- comments: comments,
93
+ items: items,
98
94
  cursor: next_cursor,
99
- has_more: comments_has_more?(res, source, next_cursor)
95
+ has_more: posts_has_more?(res, source, next_cursor)
100
96
  }
101
97
  end
102
98
 
99
+ def post(url_or_code, options = {})
100
+ url = normalize_url(url_or_code)
101
+ source = options.dig(:source) || 'instagram'
102
+ post_url = post_url_for(source:)
103
+ params = { url: url }.merge(options)
104
+ get(post_url, params)
105
+ end
106
+
107
+ def comments(url_or_code, options = {})
108
+ url = normalize_url(url_or_code)
109
+ params = { url: url }.merge(options)
110
+ source = options.dig(:source) || 'instagram'
111
+ comments_url = comments_url_for(source:)
112
+ res = get(comments_url, params)
113
+ res.is_a?(Hash) ? (res["comments"] || []) : []
114
+ end
115
+
103
116
  def get(endpoint, params = {}, options = {})
104
117
  request(endpoint, method: :get, params: params, options: options)
105
118
  end
@@ -140,23 +153,33 @@ module ScrapeCreators
140
153
 
141
154
  private
142
155
 
143
- def next_comments_cursor(res, source)
156
+ def posts_cursor_param_for(source:)
157
+ case source.to_s
158
+ when TIKTOK then 'max_cursor'
159
+ when INSTAGRAM then 'next_max_id'
160
+ when YOUTUBE then 'continuationToken'
161
+ else 'cursor'
162
+ end
163
+ end
164
+
165
+ def next_posts_cursor(res, source)
144
166
  return nil unless res.is_a?(Hash)
145
167
 
146
- cursor = if source == YOUTUBE
147
- res['continuationToken']
148
- else
149
- res['cursor']
168
+ cursor = case source.to_s
169
+ when TIKTOK then res['max_cursor']
170
+ when INSTAGRAM then res['next_max_id']
171
+ when YOUTUBE then res['continuationToken'] || res['continuation_token']
172
+ else res['cursor']
150
173
  end
151
- return nil if cursor.nil? || cursor.to_s.empty?
174
+ return nil if cursor.nil? || cursor.to_s.empty? || cursor.to_s == '0'
152
175
 
153
176
  cursor
154
177
  end
155
178
 
156
- def comments_has_more?(res, source, next_cursor)
179
+ def posts_has_more?(res, source, next_cursor)
157
180
  return false unless res.is_a?(Hash)
158
181
 
159
- if source == TIKTOK
182
+ if source.to_s == TIKTOK
160
183
  res['has_more'].to_i == 1
161
184
  else
162
185
  !next_cursor.nil?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ScrapeCreators
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scrape_creators
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - swlkr