hawatel_search_jobs 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/.codeclimate.yml +19 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +1169 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +70 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +146 -0
- data/Rakefile +6 -0
- data/hawatel_search_jobs.gemspec +30 -0
- data/lib/hawatel_search_jobs.rb +64 -0
- data/lib/hawatel_search_jobs/api.rb +9 -0
- data/lib/hawatel_search_jobs/api/careerbuilder.rb +170 -0
- data/lib/hawatel_search_jobs/api/careerjet.rb +126 -0
- data/lib/hawatel_search_jobs/api/indeed.rb +158 -0
- data/lib/hawatel_search_jobs/api/reed.rb +186 -0
- data/lib/hawatel_search_jobs/api/xing.rb +121 -0
- data/lib/hawatel_search_jobs/client.rb +165 -0
- data/lib/hawatel_search_jobs/helpers.rb +1 -0
- data/lib/hawatel_search_jobs/helpers/base.rb +39 -0
- data/lib/hawatel_search_jobs/version.rb +3 -0
- data/spec/careerbuilder_spec.rb +87 -0
- data/spec/careerjet_spec.rb +53 -0
- data/spec/client_spec.rb +62 -0
- data/spec/indeed_spec.rb +62 -0
- data/spec/reed_spec.rb +81 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/xing_spec.rb +48 -0
- metadata +158 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
module HawatelSearchJobs
|
2
|
+
module Api
|
3
|
+
module CareerJet
|
4
|
+
class << self
|
5
|
+
include HawatelSearchJobs::Helpers::Base
|
6
|
+
|
7
|
+
DEFAULT = {
|
8
|
+
:keywords => '',
|
9
|
+
:location => '',
|
10
|
+
:company => ''
|
11
|
+
}
|
12
|
+
|
13
|
+
# Search jobs based on specified keywords or location
|
14
|
+
#
|
15
|
+
# @param args [Hash]
|
16
|
+
# @option args :query [Hash] search criteria
|
17
|
+
# - *:keywords* (String) - keywords for search
|
18
|
+
# @option args [Integer] :page page number (default value 0)
|
19
|
+
# @option args [String] :query_key option provided by page() method
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# search(:settings => HawatelSearchJobs.careerjet,:query => {:keywords => 'ruby'})
|
23
|
+
# search(:query_key => 'http://api.../search?locale_code=US_en&sort=date&keywords=ruby', :page => 0)
|
24
|
+
#
|
25
|
+
# @return [Hash<OpenStruct>]
|
26
|
+
def search(args)
|
27
|
+
args[:query] = DEFAULT.merge(args[:query]) if args[:query]
|
28
|
+
args[:page] = 0 if args[:page].nil?
|
29
|
+
|
30
|
+
if args[:query_key].to_s.empty?
|
31
|
+
url_request = build_url(args)
|
32
|
+
else
|
33
|
+
url_request = args[:query_key]
|
34
|
+
end
|
35
|
+
|
36
|
+
if url_request
|
37
|
+
attributes = Hash.new
|
38
|
+
response = send_request(url_request)
|
39
|
+
result = JSON(response.body)
|
40
|
+
if response.code == '200' && result['type'] == 'ERROR'
|
41
|
+
attributes[:code] = 501
|
42
|
+
attributes[:msg] = result['error']
|
43
|
+
return OpenStruct.new(attributes)
|
44
|
+
else
|
45
|
+
attributes[:code] = response.code
|
46
|
+
attributes[:msg] = response.message
|
47
|
+
return OpenStruct.new(attributes) if response.code != '200'
|
48
|
+
end
|
49
|
+
attributes[:totalResults] = result['hits']
|
50
|
+
attributes[:page] = args[:page]
|
51
|
+
attributes[:last] = result['pages'] - 1
|
52
|
+
attributes[:key] = url_request
|
53
|
+
attributes[:jobs] = parse_raw_data(result)
|
54
|
+
OpenStruct.new(attributes)
|
55
|
+
else
|
56
|
+
OpenStruct.new({:code => 501, :msg => 'lack of keywords or api setting'})
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Show next page of results
|
61
|
+
#
|
62
|
+
# @param args [Hash]
|
63
|
+
# @option args [Integer] :page specified page number (default 0)
|
64
|
+
# @option args [String] :query_key full url from last query
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
# page({:query_key => result.key, :page => 2}
|
68
|
+
#
|
69
|
+
# @return [Hash<OpenStrunct>]
|
70
|
+
def page(args)
|
71
|
+
args[:page] = 0 if args[:page].nil?
|
72
|
+
args[:query_key] = args[:query_key].gsub(/&page=.*/, '') << "&page=#{args[:page]+1}"
|
73
|
+
return search(args)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Build query URL
|
79
|
+
#
|
80
|
+
# @param args [Hash]
|
81
|
+
# @option args query [Hash] - search criteria
|
82
|
+
# - *:keywords* (String) - keywords for search
|
83
|
+
# - *:location* (String) - specified location for search criteria (default: europe)
|
84
|
+
# @option settings [Hash]
|
85
|
+
# - *:api* (String) - api ip or domainname
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# build_url(:query => {:keywords => 'ruby'}, :settings => {:api => 'http://api...'} }
|
89
|
+
#
|
90
|
+
# @return [String] ready to call URL
|
91
|
+
def build_url(args)
|
92
|
+
keywords = args[:query][:keywords] if !args[:query][:keywords].to_s.empty?
|
93
|
+
api_url = args[:settings][:api] if !args[:settings][:api].to_s.empty?
|
94
|
+
if keywords && api_url
|
95
|
+
!args[:query][:location].to_s.empty? ? location = args[:query][:location] : location = 'europe'
|
96
|
+
"http://#{api_url}/search?locale_code=US_en&pagesize=25&sort=date&keywords=#{keywords}&location=#{location}&page=1"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
# Build jobs array with specified attributes
|
102
|
+
#
|
103
|
+
# @param result [Hash]
|
104
|
+
# @option result [Hash] :jobs jobs hash array return from API
|
105
|
+
#
|
106
|
+
# @return [Array<OpenStruct>]
|
107
|
+
def parse_raw_data(result)
|
108
|
+
jobs = Array.new
|
109
|
+
return jobs if result['jobs'].to_s.empty?
|
110
|
+
result['jobs'].each do |offer|
|
111
|
+
job = Hash.new
|
112
|
+
job[:jobtitle] = offer['title']
|
113
|
+
job[:location] = offer['locations']
|
114
|
+
job[:company] = offer['company']
|
115
|
+
job[:date] = convert_date_to_format(offer['date'],'%d/%m/%y')
|
116
|
+
job[:url] = offer['url']
|
117
|
+
job = convert_empty_to_nil(job)
|
118
|
+
jobs << OpenStruct.new(job)
|
119
|
+
end
|
120
|
+
return jobs
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module HawatelSearchJobs
|
6
|
+
module Api
|
7
|
+
module Indeed
|
8
|
+
class << self
|
9
|
+
include HawatelSearchJobs::Helpers::Base
|
10
|
+
|
11
|
+
DEFAULT = {
|
12
|
+
:keywords => '',
|
13
|
+
:location => '',
|
14
|
+
:company => ''
|
15
|
+
}
|
16
|
+
|
17
|
+
# Search jobs based on specified keywords or location
|
18
|
+
#
|
19
|
+
# @param args [Hash]
|
20
|
+
# @option args [String] :query_key full url from last query, option deliverd by page() method
|
21
|
+
# @option args :query [Hash] search criteria
|
22
|
+
# - *:keywords* (String) keywords for search
|
23
|
+
# - *:location* (String) specified location for search criteria (default all countries)
|
24
|
+
# @option args :settings [Hash]
|
25
|
+
# - *:api* (String) hostname or ip address api server
|
26
|
+
# - *:publisher* (String) authentication string
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# search(:settings => HawatelSearchJobs.indeed,:query => {:keywords => 'ruby'})
|
30
|
+
# search(:query_key => 'http://api.../ads/apisearch?publisher=12323&q=ruby')
|
31
|
+
#
|
32
|
+
# @return [Hash<OpenStruct>]
|
33
|
+
def search(args)
|
34
|
+
args[:query] = DEFAULT.merge(args[:query]) if args[:query]
|
35
|
+
if args[:query_key].to_s.empty?
|
36
|
+
url_request = build_url(args)
|
37
|
+
else
|
38
|
+
url_request = args[:query_key]
|
39
|
+
end
|
40
|
+
|
41
|
+
if url_request
|
42
|
+
attributes = Hash.new
|
43
|
+
response = send_request(url_request)
|
44
|
+
result = JSON(response.body)
|
45
|
+
if response.code == '200' && result['error']
|
46
|
+
attributes[:code] = 501
|
47
|
+
attributes[:msg] = result['error']
|
48
|
+
return OpenStruct.new(attributes)
|
49
|
+
else
|
50
|
+
attributes[:code] = response.code
|
51
|
+
attributes[:msg] = response.message
|
52
|
+
return OpenStruct.new(attributes) if response.code != '200'
|
53
|
+
end
|
54
|
+
attributes[:totalResults] = result['totalResults']
|
55
|
+
attributes[:page] = result['pageNumber']
|
56
|
+
#attributes[:last] = (result['totalResults'] / 25) - 1
|
57
|
+
attributes[:last] = paging_info(25, result['totalResults'])
|
58
|
+
attributes[:key] = url_request
|
59
|
+
attributes[:jobs] = parse_raw_data(result)
|
60
|
+
OpenStruct.new(attributes)
|
61
|
+
else
|
62
|
+
OpenStruct.new({:code => 501, :msg => 'lack of api or publisher setting'})
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Show next page of results
|
67
|
+
#
|
68
|
+
# @param args [Hash]
|
69
|
+
# @option args [Integer] :page page number (default 0)
|
70
|
+
# @option args [String] :query_key url from last query
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# page({:query_key => result.key, :page => 2}
|
74
|
+
#
|
75
|
+
# @return [Hash<OpenStruct>]
|
76
|
+
def page(args)
|
77
|
+
args[:page] = 0 if args[:page].nil?
|
78
|
+
if args[:query_key]
|
79
|
+
url = args[:query_key].gsub(/&start=.*/, '') << "&start=#{args[:page]*25}"
|
80
|
+
search({:query_key => url})
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
# Build query URL
|
86
|
+
#
|
87
|
+
# @param args [Hash]
|
88
|
+
# option args :query [Hash]
|
89
|
+
# - *:keywords* (String) keywords for search
|
90
|
+
# - *:location* (String) search jobs from specified location
|
91
|
+
# - *:salary* (String) show only position above specified salary
|
92
|
+
# - *:company* (String) find position from specified company
|
93
|
+
# @option args :settings [Hash]
|
94
|
+
# - *:api* (String) hostname or ip address api server
|
95
|
+
# - *:publisher* (String) authentication string
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
# build_url(:query => {:keywords => 'ruby'}, :settings => {:api => 'http://api...',:publisher => '23234'}}
|
99
|
+
#
|
100
|
+
# @return [String]
|
101
|
+
def build_url(args)
|
102
|
+
api_url = args[:settings][:api]
|
103
|
+
publisher = args[:settings][:publisher]
|
104
|
+
location = args[:query][:location]
|
105
|
+
salary = args[:query][:salary]
|
106
|
+
company = args[:query][:company]
|
107
|
+
keywords = args[:query][:keywords]
|
108
|
+
|
109
|
+
if !keywords.to_s.empty? && !company.to_s.empty?
|
110
|
+
keywords = "company:#{company}+#{keywords}"
|
111
|
+
elsif keywords.to_s.empty? && !company.to_s.empty?
|
112
|
+
keywords = "company:#{company}"
|
113
|
+
end
|
114
|
+
if api_url && publisher
|
115
|
+
"http://#{api_url}/ads/apisearch?publisher=#{publisher}&q=#{keywords}&salary=#{salary}&l=#{location}"\
|
116
|
+
"&v=2&format=json&limit=25&start=0"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Build jobs array with specified attributes
|
121
|
+
#
|
122
|
+
# @param result [Hash]
|
123
|
+
# @option result [Hash] :results job attributes
|
124
|
+
#
|
125
|
+
# @return [Array<OpenStruct>]
|
126
|
+
def parse_raw_data(result)
|
127
|
+
jobs = Array.new
|
128
|
+
return jobs if result['results'].to_s.empty?
|
129
|
+
result['results'].each do |offer|
|
130
|
+
job = Hash.new
|
131
|
+
job[:jobtitle] = offer['jobtitle'] if offer['jobtitle']
|
132
|
+
job[:location] = "#{offer['country']}, #{offer['city']}"
|
133
|
+
job[:company] = offer['company']
|
134
|
+
job[:date] = convert_date_to_format(offer['date'],'%d/%m/%y')
|
135
|
+
job[:url] = offer['url']
|
136
|
+
job = convert_empty_to_nil(job)
|
137
|
+
jobs << OpenStruct.new(job)
|
138
|
+
end
|
139
|
+
return jobs
|
140
|
+
end
|
141
|
+
|
142
|
+
def paging_info(limit, total_result)
|
143
|
+
return nil if total_result == 0
|
144
|
+
|
145
|
+
mod = total_result.to_i % limit.to_i
|
146
|
+
if mod == 0
|
147
|
+
last = (total_result.to_i / limit.to_i) - 1
|
148
|
+
else
|
149
|
+
last = (total_result.to_i / limit.to_i).to_i
|
150
|
+
end
|
151
|
+
|
152
|
+
last
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module HawatelSearchJobs
|
4
|
+
module Api
|
5
|
+
##
|
6
|
+
# = Reed.co.uk API
|
7
|
+
#
|
8
|
+
# @see https://www.reed.co.uk/developers/Jobseeker
|
9
|
+
module Reed
|
10
|
+
class << self
|
11
|
+
include HawatelSearchJobs::Helpers::Base
|
12
|
+
|
13
|
+
DEFAULT = {
|
14
|
+
:keywords => '',
|
15
|
+
:location => '',
|
16
|
+
}
|
17
|
+
|
18
|
+
RESULT_LIMIT = 25
|
19
|
+
|
20
|
+
# Search jobs by specific criteria
|
21
|
+
# @param [Hash] args
|
22
|
+
# @option args :settings [Hash] search criteria
|
23
|
+
# - *:api* (String) - URL API (default: reed.co.uk/api)
|
24
|
+
# - *:version* (String) - a version of API (default: 1.0)
|
25
|
+
# - *:clientid* (String) - API Key (see https://www.reed.co.uk/developers/Jobseeker)
|
26
|
+
# @option args :query [Hash] settings of API
|
27
|
+
# - *:keywors* (String)
|
28
|
+
# - *:location* (String)
|
29
|
+
# @example
|
30
|
+
# jobs = Reed.search({:settings => {
|
31
|
+
# :api => 'reed.co.uk/api',
|
32
|
+
# :version => '1.0',
|
33
|
+
# :clientid => 'secret-code'},
|
34
|
+
# :query => {
|
35
|
+
# :keywords => 'ruby',
|
36
|
+
# :location => 'London'})
|
37
|
+
# @return [Hash] First page of the result (see constant RESULT_LIMIT)
|
38
|
+
def search(args)
|
39
|
+
args[:query] = DEFAULT.merge(args[:query]) if args[:query]
|
40
|
+
|
41
|
+
if args[:query_key].nil?
|
42
|
+
url_request = prepare_conn_string(args) + prepare_query(args)
|
43
|
+
else
|
44
|
+
url_request = args[:query_key]
|
45
|
+
end
|
46
|
+
|
47
|
+
response = api_request(url_request, args[:settings][:clientid])
|
48
|
+
attributes = build_jobs_table(response, url_request)
|
49
|
+
OpenStruct.new(attributes)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get a specific page result
|
53
|
+
# At the beging you have to run {search} method and get :key from result and pass it to the argument :query_key
|
54
|
+
# @param [Hash] args
|
55
|
+
# @option args :settings [Hash] see {search}
|
56
|
+
# @option args [Integer] :page page number counted from 0
|
57
|
+
# @option args [String] :query_key
|
58
|
+
# @example
|
59
|
+
# jobs = Careerbuilder.page({:settings => {
|
60
|
+
# :api => 'api.careerbuilder.com',
|
61
|
+
# :version => 'v2',
|
62
|
+
# :clientid => 'secret-code'},
|
63
|
+
# :page => 5,
|
64
|
+
# :query_key => 'value from :key returned by search method'})
|
65
|
+
# @return [Hash] Job offers from specific page
|
66
|
+
def page(args)
|
67
|
+
page = args[:page].to_i || 0
|
68
|
+
if args[:query_key]
|
69
|
+
limit = result_limit(args[:query_key])
|
70
|
+
url_request = args[:query_key].gsub(/&resultsToSkip=\d+/, '') << "&resultsToSkip=#{page * limit}"
|
71
|
+
args[:query_key] = url_request
|
72
|
+
search(args)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def build_jobs_table(response, url_request)
|
79
|
+
attributes = Hash.new
|
80
|
+
attributes[:code] = response.code.to_i
|
81
|
+
attributes[:msg] = response.message
|
82
|
+
|
83
|
+
attributes[:totalResults] = 0
|
84
|
+
attributes[:page] = nil
|
85
|
+
attributes[:last] = nil
|
86
|
+
attributes[:key] = url_request
|
87
|
+
attributes[:jobs] = nil
|
88
|
+
|
89
|
+
|
90
|
+
if response.code.to_i == 200
|
91
|
+
json_response = JSON.parse(response.body)
|
92
|
+
begin
|
93
|
+
if !json_response['results'].to_s.empty?
|
94
|
+
attributes[:totalResults] = json_response['totalResults'] || 0
|
95
|
+
|
96
|
+
page_info = paging_info(url_request, attributes[:totalResults])
|
97
|
+
attributes[:page] = page_info.page
|
98
|
+
attributes[:last] = page_info.last
|
99
|
+
|
100
|
+
attributes[:key] = url_request
|
101
|
+
attributes[:jobs] = parse_raw_data(json_response)
|
102
|
+
end
|
103
|
+
rescue
|
104
|
+
raise "Something wrong with returned data: #{json_response}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
attributes
|
108
|
+
end
|
109
|
+
|
110
|
+
def api_request(url, clientid = nil)
|
111
|
+
opt = Hash.new
|
112
|
+
|
113
|
+
if clientid
|
114
|
+
opt = { :basic_auth => {
|
115
|
+
:username => clientid,
|
116
|
+
:password => ''
|
117
|
+
}}
|
118
|
+
end
|
119
|
+
|
120
|
+
send_request(url, opt)
|
121
|
+
end
|
122
|
+
|
123
|
+
def prepare_query(args)
|
124
|
+
"keywords=#{args[:query][:keywords]}&locationName=#{args[:query][:location]}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def prepare_conn_string(args)
|
128
|
+
conn_string = "https://#{args[:settings][:api]}/#{args[:settings][:version]}/search?resultsToTake=#{RESULT_LIMIT}&"
|
129
|
+
conn_string
|
130
|
+
end
|
131
|
+
|
132
|
+
def parse_raw_data(data)
|
133
|
+
jobs = Array.new
|
134
|
+
data['results'].each do |offer|
|
135
|
+
job = Hash.new
|
136
|
+
job[:jobtitle] = offer['jobTitle'] if offer['jobTitle']
|
137
|
+
job[:location] = "United Kingdom, #{offer['locationName']}"
|
138
|
+
job[:company] = offer['employerName']
|
139
|
+
job[:date] = convert_date_to_format(offer['date'],'%d/%m/%y')
|
140
|
+
job[:url] = offer['jobUrl']
|
141
|
+
|
142
|
+
job = convert_empty_to_nil(job)
|
143
|
+
|
144
|
+
jobs << OpenStruct.new(job)
|
145
|
+
end
|
146
|
+
jobs
|
147
|
+
rescue
|
148
|
+
raise "Cannot parse raw data: #{data}"
|
149
|
+
end
|
150
|
+
|
151
|
+
def result_limit(url)
|
152
|
+
result = url.match(/\resultsToTake=(\d+)/)
|
153
|
+
result ? result[1].to_i : RESULT_LIMIT
|
154
|
+
end
|
155
|
+
|
156
|
+
def result_skip(url)
|
157
|
+
result = url.match(/\&resultsToSkip=(\d+)/)
|
158
|
+
result ? result[1].to_i : 0
|
159
|
+
end
|
160
|
+
|
161
|
+
def paging_info(url, total_result)
|
162
|
+
return OpenStruct.new({:page => nil, :last => nil}) if total_result == 0
|
163
|
+
|
164
|
+
result_limit = result_limit(url)
|
165
|
+
result_skip = result_skip(url)
|
166
|
+
|
167
|
+
if result_skip == 0 && total_result > 0
|
168
|
+
page = 0
|
169
|
+
else
|
170
|
+
page = (result_skip / result_limit).to_i
|
171
|
+
end
|
172
|
+
|
173
|
+
mod = total_result.to_i % result_limit.to_i
|
174
|
+
if mod == 0
|
175
|
+
last = (total_result.to_i / result_limit.to_i) - 1
|
176
|
+
else
|
177
|
+
last = (total_result.to_i / result_limit.to_i).to_i
|
178
|
+
end
|
179
|
+
|
180
|
+
OpenStruct.new({:page => page, :last => last})
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|