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 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: []