scrape_creators 0.4.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: 41069945ebf877cbd439c8bfbb318ba0114d4e9df6275aabacef16867417a7b4
4
- data.tar.gz: 24d8743a67a698cfc97f974015a8ff30ab33254b8dd155e96f1d4b0283e9d9c0
3
+ metadata.gz: 36ed3616d4e59159e3241d2a07b743ad39aa63ac4adf93e63c7f51fbd0e8f173
4
+ data.tar.gz: 946bcdd67f4f3e9d5b53819c5c15ace2a56a148871ed58844479349507d8ac09
5
5
  SHA512:
6
- metadata.gz: fdab6ab8877d97093743a3e52d3daa17bf33ac1c4b3a461b6ea85957e83bff213b9e27adbaa878b36e9af15858f014334aceda898beae6cb9d498d15e56e2d1f
7
- data.tar.gz: ddeaf8dc9f781648485b348a3ccaeb660a2dfe47a611e37a6c0cd9283fae66179a91521c11be731faf698b8cf22683cf5a8b8b3862b198d1c148f20641011bda
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
@@ -57,26 +57,58 @@ 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] || []) : []
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
+
75
+ all_items
65
76
  end
66
77
 
67
- def post(url_or_code, options = {})
78
+ def posts_page(handle, options = {})
68
79
  source = (options[:source] || 'instagram').to_s
80
+ params = { handle: handle }
81
+ cursor = options[:cursor] || options['cursor']
82
+ if cursor && !cursor.to_s.empty?
83
+ params[posts_cursor_param_for(source:)] = cursor
84
+ end
85
+ extra = options.reject { |k, _| %w[source cursor max_pages pages].include?(k.to_s) }
86
+ params.merge!(extra)
87
+
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)
92
+ {
93
+ items: items,
94
+ cursor: next_cursor,
95
+ has_more: posts_has_more?(res, source, next_cursor)
96
+ }
97
+ end
98
+
99
+ def post(url_or_code, options = {})
69
100
  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' })
101
+ source = options.dig(:source) || 'instagram'
102
+ post_url = post_url_for(source:)
103
+ params = { url: url }.merge(options)
72
104
  get(post_url, params)
73
105
  end
74
106
 
75
107
  def comments(url_or_code, options = {})
76
- source = (options[:source] || 'instagram').to_s
77
108
  url = normalize_url(url_or_code)
78
- params = { url: url }.merge(options.reject { |k, _| k.to_s == 'source' })
79
- comments_url = comments_url_for(source: source)
109
+ params = { url: url }.merge(options)
110
+ source = options.dig(:source) || 'instagram'
111
+ comments_url = comments_url_for(source:)
80
112
  res = get(comments_url, params)
81
113
  res.is_a?(Hash) ? (res["comments"] || []) : []
82
114
  end
@@ -121,6 +153,39 @@ module ScrapeCreators
121
153
 
122
154
  private
123
155
 
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)
166
+ return nil unless res.is_a?(Hash)
167
+
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']
173
+ end
174
+ return nil if cursor.nil? || cursor.to_s.empty? || cursor.to_s == '0'
175
+
176
+ cursor
177
+ end
178
+
179
+ def posts_has_more?(res, source, next_cursor)
180
+ return false unless res.is_a?(Hash)
181
+
182
+ if source.to_s == TIKTOK
183
+ res['has_more'].to_i == 1
184
+ else
185
+ !next_cursor.nil?
186
+ end
187
+ end
188
+
124
189
  def normalize_url(url_or_code)
125
190
  str = url_or_code.to_s.strip
126
191
  if str.start_with?('http://', 'https://')
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ScrapeCreators
4
- VERSION = "0.4.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.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - swlkr