seo-position-tracker-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ # Enable local usage from cloned repo
6
+ require "seo-position-tracker-ruby"
7
+
8
+ SeoPositionTracker::CLI.new(ARGV).run
@@ -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: []