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 +4 -4
- data/README.md +34 -1
- data/lib/scrape_creators/client.rb +76 -11
- data/lib/scrape_creators/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 36ed3616d4e59159e3241d2a07b743ad39aa63ac4adf93e63c7f51fbd0e8f173
|
|
4
|
+
data.tar.gz: 946bcdd67f4f3e9d5b53819c5c15ace2a56a148871ed58844479349507d8ac09
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
79
|
-
|
|
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://')
|