seo-position-tracker-ruby 0.1.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 +7 -0
- data/bin/seo +8 -0
- data/lib/seo-position-tracker-ruby.rb +291 -0
- metadata +61 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 75bbf62f93880877046ab481be2bcb9f0d44565bea3940c4c45ad53ea37d11b7
|
4
|
+
data.tar.gz: 471bac889d5b72a1ae60531ef28372f2010720a1beb90d63a2f7f2b319a49c13
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b67340d10300101682c78c347910ee0a94c6a1b13bb336427d7d094c949b22724ae273149d4388ad2983af0dd5459c00995006b361e49a2e0e48162ad78164a4
|
7
|
+
data.tar.gz: 1bd9fd1a2ecf004df29f89ef1da4e2c70c056b61360ddb99191175b3a0776a47ba8952846411a3b391904f4878406f9968f9dbfbcae2d2eb71b95968c8ccf5b4
|
data/bin/seo
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
require 'google_search_results'
|
2
|
+
require 'optparse'
|
3
|
+
require 'json'
|
4
|
+
require 'csv'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
module SeoPositionTracker
|
8
|
+
class CLI
|
9
|
+
def initialize(argv)
|
10
|
+
@argv = argv
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
options = {
|
15
|
+
query: 'coffee',
|
16
|
+
target_keywords: ['coffee'],
|
17
|
+
target_websites: ['starbucks.com'],
|
18
|
+
search_engine: ['google', 'bing', 'duckduckgo', 'yahoo', 'yandex', 'naver'],
|
19
|
+
api_key: '5868ece26d41221f5e19ae8b3e355d22db23df1712da675d144760fc30d57988',
|
20
|
+
language: nil,
|
21
|
+
country: nil,
|
22
|
+
location: nil,
|
23
|
+
domain: nil,
|
24
|
+
save_to: 'CSV'
|
25
|
+
}
|
26
|
+
|
27
|
+
OptionParser.new do |opts|
|
28
|
+
opts.banner = "Usage: seo [options]"
|
29
|
+
opts.on('-q', '--query QUERY', String, 'Search query. Default "coffee".') { |q| options[:query] = q }
|
30
|
+
opts.on('-k', '--target-keywords KEYWORDS', Array, 'Target keywords to track. Default "coffee".') { |k| options[:target_keywords] = k }
|
31
|
+
opts.on('-w', '--target-websites WEBSITES', Array, 'Target websites to track. Default "starbucks.com".') { |w| options[:target_websites] = w }
|
32
|
+
opts.on('-e', '--search-engine ENGINES', Array, 'Choosing a search engine to track: "google", "bing", "duckduckgo", "yahoo", "yandex", "naver". You can select multiple search engines by separating them with a comma: google,bing. All search engines are selected by default.') { |e| options[:search_engine] = e }
|
33
|
+
opts.on('-a', '--api-key API_KEY', String, 'Your SerpApi API key: https://serpapi.com/manage-api-key. Default is a test API key to test CLI.') { |a| options[:api_key] = a }
|
34
|
+
opts.on('-l', '--language LANGUAGE', String, 'Language of the search. Supported only for "google", "yahoo" and "yandex" engines. Default is nil.') { |l| options[:language] = l }
|
35
|
+
opts.on('-c', '--country COUNTRY', String, 'Country of the search. Supported only for "google", "bing" and "yahoo" engines. Default is nil.') { |c| options[:country] = c }
|
36
|
+
opts.on('-p', '--location LOCATION', String, 'Location of the search. Supported only for "google", "bing", "duckduckgo" and "yandex" engines. Default is nil.') { |p| options[:location] = p }
|
37
|
+
opts.on('-d', '--domain DOMAIN', String, 'Search engine domain to use. Supported only for "google", "yahoo" and "yandex" engines. Default is nil.') { |d| options[:domain] = d }
|
38
|
+
opts.on('-s', '--save-to SAVE', String, 'Saves the results in the current directory in the selected format (CSV, JSON, TXT). Default CSV.') { |s| options[:save_to] = s }
|
39
|
+
end.parse!(@argv)
|
40
|
+
|
41
|
+
tracker = SeoPositionTracker::Scraper.new(
|
42
|
+
query=options[:query],
|
43
|
+
api_key=options[:api_key],
|
44
|
+
keywords=options[:target_keywords],
|
45
|
+
websites=options[:target_websites],
|
46
|
+
language=options[:language],
|
47
|
+
country=options[:country],
|
48
|
+
location=options[:location],
|
49
|
+
domain=options[:domain]
|
50
|
+
)
|
51
|
+
|
52
|
+
position_data = []
|
53
|
+
|
54
|
+
options[:search_engine]&.each do |engine|
|
55
|
+
case engine
|
56
|
+
when 'google'
|
57
|
+
data = tracker.scrape_google
|
58
|
+
position_data.concat(data)
|
59
|
+
when 'bing'
|
60
|
+
data = tracker.scrape_bing
|
61
|
+
position_data.concat(data)
|
62
|
+
when 'duckduckgo'
|
63
|
+
data = tracker.scrape_duckduckgo
|
64
|
+
position_data.concat(data)
|
65
|
+
when 'yahoo'
|
66
|
+
data = tracker.scrape_yahoo
|
67
|
+
position_data.concat(data)
|
68
|
+
when 'yandex'
|
69
|
+
data = tracker.scrape_yandex
|
70
|
+
position_data.concat(data)
|
71
|
+
when 'naver'
|
72
|
+
data = tracker.scrape_naver
|
73
|
+
position_data.concat(data)
|
74
|
+
else
|
75
|
+
puts "\"#{engine}\" is an unknown search engine."
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
if position_data.any?
|
80
|
+
tracker.print(position_data)
|
81
|
+
puts "Saving data in #{options[:save_to].upcase} format..."
|
82
|
+
|
83
|
+
case options[:save_to].upcase
|
84
|
+
when 'CSV'
|
85
|
+
tracker.save_to_csv(position_data)
|
86
|
+
when 'JSON'
|
87
|
+
tracker.save_to_json(position_data)
|
88
|
+
when 'TXT'
|
89
|
+
tracker.save_to_txt(position_data)
|
90
|
+
end
|
91
|
+
|
92
|
+
puts "Data successfully saved to #{options[:query].gsub(" ", "_")}.#{options[:save_to].downcase} file."
|
93
|
+
else
|
94
|
+
puts "Unfortunately, no matches were found."
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Scraper
|
100
|
+
attr_accessor :query, :api_key, :keywords, :websites, :language, :country, :location, :domain
|
101
|
+
|
102
|
+
def initialize(query, api_key, keywords, websites, language, country, location, domain)
|
103
|
+
@query = query
|
104
|
+
@api_key = api_key
|
105
|
+
@keywords = keywords
|
106
|
+
@websites = websites
|
107
|
+
@language = language
|
108
|
+
@country = country
|
109
|
+
@location = location
|
110
|
+
@domain = domain
|
111
|
+
end
|
112
|
+
|
113
|
+
def scrape_google(language = 'en', country = 'us', location = 'United States', domain = 'google.com')
|
114
|
+
language, country, location, domain = check_params(language=language, country=country, location=location, domain=domain)
|
115
|
+
|
116
|
+
params = {
|
117
|
+
api_key: @api_key, # https://serpapi.com/manage-api-key
|
118
|
+
q: @query, # search query
|
119
|
+
engine: 'google', # search engine
|
120
|
+
google_domain: domain, # Google domain to use
|
121
|
+
hl: language, # language of the search
|
122
|
+
gl: country, # country of the search
|
123
|
+
location: location, # location of the search
|
124
|
+
num: 100 # 100 results from Google search
|
125
|
+
}
|
126
|
+
|
127
|
+
search = GoogleSearch.new(params) # data extraction on the SerpApi backend
|
128
|
+
results = search.get_hash # JSON -> Ruby hash
|
129
|
+
|
130
|
+
find_positions(results, 'google')
|
131
|
+
end
|
132
|
+
|
133
|
+
def scrape_bing(country = 'us', location = 'United States')
|
134
|
+
country, location = check_params(country=country, location=location)
|
135
|
+
|
136
|
+
params = {
|
137
|
+
api_key: @api_key, # https://serpapi.com/manage-api-key
|
138
|
+
q: @query, # search query
|
139
|
+
engine: 'bing', # search engine
|
140
|
+
cc: country, # country of the search
|
141
|
+
location: location, # location of the search
|
142
|
+
count: 50 # 50 results from Bing search
|
143
|
+
}
|
144
|
+
|
145
|
+
search = BingSearch.new(params) # data extraction on the SerpApi backend
|
146
|
+
results = search.get_hash # JSON -> Ruby hash
|
147
|
+
|
148
|
+
find_positions(results, 'bing')
|
149
|
+
end
|
150
|
+
|
151
|
+
def scrape_duckduckgo(location = 'us-en')
|
152
|
+
location = check_params(location=location)
|
153
|
+
|
154
|
+
params = {
|
155
|
+
api_key: @api_key, # https://serpapi.com/manage-api-key
|
156
|
+
q: @query, # search query
|
157
|
+
engine: 'duckduckgo', # search engine
|
158
|
+
kl: location # location of the search
|
159
|
+
}
|
160
|
+
|
161
|
+
search = DuckduckgoSearch.new(params) # data extraction on the SerpApi backend
|
162
|
+
results = search.get_hash # JSON -> Ruby hash
|
163
|
+
|
164
|
+
find_positions(results, 'duckduckgo')
|
165
|
+
end
|
166
|
+
|
167
|
+
def scrape_yahoo(language = 'language_en', country = 'us', domain = 'uk')
|
168
|
+
language, country, domain = check_params(language=language, country=country, domain=domain)
|
169
|
+
|
170
|
+
params = {
|
171
|
+
api_key: @api_key, # https://serpapi.com/manage-api-key
|
172
|
+
p: @query, # search query
|
173
|
+
engine: 'yahoo', # search engine
|
174
|
+
yahoo_domain: domain, # Yahoo! domain to use
|
175
|
+
vl: language, # language of the search
|
176
|
+
vc: country # country of the search
|
177
|
+
}
|
178
|
+
|
179
|
+
search = YahooSearch.new(params) # data extraction on the SerpApi backend
|
180
|
+
results = search.get_hash # JSON -> Ruby hash
|
181
|
+
|
182
|
+
find_positions(results, 'yahoo')
|
183
|
+
end
|
184
|
+
|
185
|
+
def scrape_yandex(language = 'en', domain = 'yandex.com')
|
186
|
+
language, domain = check_params(language=language, domain=domain)
|
187
|
+
|
188
|
+
params = {
|
189
|
+
api_key: @api_key, # https://serpapi.com/manage-api-key
|
190
|
+
text: @query, # search query
|
191
|
+
engine: 'yandex', # search engine
|
192
|
+
yandex_domain: domain, # Yandex domain to use
|
193
|
+
language: language # language of the search
|
194
|
+
}
|
195
|
+
|
196
|
+
search = YandexSearch.new(params) # data extraction on the SerpApi backend
|
197
|
+
results = search.get_hash # JSON -> Ruby hash
|
198
|
+
|
199
|
+
find_positions(results, 'yandex')
|
200
|
+
end
|
201
|
+
|
202
|
+
def scrape_naver
|
203
|
+
params = {
|
204
|
+
api_key: @api_key, # https://serpapi.com/manage-api-key
|
205
|
+
query: @query, # search query
|
206
|
+
engine: 'naver', # search engine
|
207
|
+
where: 'web' # web organic results
|
208
|
+
}
|
209
|
+
|
210
|
+
search = NaverSearch.new(params) # data extraction on the SerpApi backend
|
211
|
+
results = search.get_hash # JSON -> Ruby hash
|
212
|
+
|
213
|
+
find_positions(results, 'naver')
|
214
|
+
end
|
215
|
+
|
216
|
+
def save_to_csv(data)
|
217
|
+
keys = data[0].keys
|
218
|
+
|
219
|
+
File.open("#{@query.gsub(' ', '_')}.csv", 'w', encoding: 'utf-8') do |csv_file|
|
220
|
+
writer = CSV.new(csv_file)
|
221
|
+
writer << keys
|
222
|
+
data.each { |row| writer << row.values }
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def save_to_json(data)
|
227
|
+
File.open("#{@query.gsub(' ', '_')}.json", 'w', encoding: 'utf-8') do |json_file|
|
228
|
+
json_file.write(JSON.pretty_generate(data))
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def save_to_txt(data)
|
233
|
+
File.open("#{@query.gsub(' ', '_')}.txt", 'w', encoding: 'utf-8') do |txt_file|
|
234
|
+
data.each do |element|
|
235
|
+
txt_file.puts("#{element[:engine]}, #{element[:position]}, #{element[:title]}, #{element[:link]}")
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def print(data)
|
241
|
+
puts JSON.pretty_generate(data)
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
|
246
|
+
def find_positions(results, engine)
|
247
|
+
data = Set.new # to get rid of repetitive results
|
248
|
+
|
249
|
+
@keywords&.each do |keyword|
|
250
|
+
@websites&.each do |website|
|
251
|
+
results[:organic_results]&.each do |result|
|
252
|
+
check = result[:title].downcase.include?(keyword.downcase) && result[:link].include?(website)
|
253
|
+
|
254
|
+
next unless check
|
255
|
+
|
256
|
+
data.add({
|
257
|
+
engine: engine,
|
258
|
+
position: result[:position],
|
259
|
+
title: result[:title],
|
260
|
+
link: result[:link]
|
261
|
+
})
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
data.to_a
|
267
|
+
end
|
268
|
+
|
269
|
+
def check_params(lang = nil, country = nil, location = nil, domain = nil)
|
270
|
+
checked_params = []
|
271
|
+
|
272
|
+
if lang
|
273
|
+
checked_params << @lang ? @lang : lang
|
274
|
+
end
|
275
|
+
|
276
|
+
if country
|
277
|
+
checked_params << @country ? @country : country
|
278
|
+
end
|
279
|
+
|
280
|
+
if location
|
281
|
+
checked_params << @location ? @location : location
|
282
|
+
end
|
283
|
+
|
284
|
+
if domain
|
285
|
+
checked_params << @domain ? @domain : domain
|
286
|
+
end
|
287
|
+
|
288
|
+
checked_params.length == 1 ? checked_params[0] : checked_params
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: seo-position-tracker-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Artur Chukhrai
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-07-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: google_search_results
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.2'
|
27
|
+
description: A simple Ruby CLI and in-code SEO position tracking tool for Google and
|
28
|
+
5 other search engines.
|
29
|
+
email:
|
30
|
+
- chukhraiartur@gmail.com
|
31
|
+
executables:
|
32
|
+
- seo
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- bin/seo
|
37
|
+
- lib/seo-position-tracker-ruby.rb
|
38
|
+
homepage: https://github.com/chukhraiartur/seo-position-tracker-ruby
|
39
|
+
licenses:
|
40
|
+
- BSD 3-Clause
|
41
|
+
metadata: {}
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 3.0.0
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubygems_version: 3.3.26
|
58
|
+
signing_key:
|
59
|
+
specification_version: 4
|
60
|
+
summary: Ruby SEO Position Tracker
|
61
|
+
test_files: []
|