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 +4 -4
- data/README.md +34 -11
- data/lib/scrape_creators/client.rb +62 -39
- 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
|
|
@@ -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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
comments_page(url_or_code, options)[:comments]
|
|
75
|
+
all_items
|
|
77
76
|
end
|
|
78
77
|
|
|
79
|
-
def
|
|
78
|
+
def posts_page(handle, options = {})
|
|
80
79
|
source = (options[:source] || 'instagram').to_s
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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(
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
93
|
+
items: items,
|
|
98
94
|
cursor: next_cursor,
|
|
99
|
-
has_more:
|
|
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
|
|
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 =
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
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?
|