Commit 108f7e4e by Mai Hoang Thai Ha

Merge branch 'Task/8_create_top_page_ID1' into…

Merge branch 'Task/8_create_top_page_ID1' into 'Task/9_create_city_list_page_ID2/industry_list_page_ID3'

Task/8 create top page id1

See merge request !8
parents c7c696ea 5e581a61
......@@ -57,3 +57,6 @@ end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
gem 'slim-rails', '~> 3.2'
gem 'nokogiri', '~> 1.11', '>= 1.11.7'
gem 'httparty', '~> 0.18.1'
gem 'rubocop-rails', '~> 2.11', '>= 2.11.3'
\ No newline at end of file
......@@ -62,6 +62,7 @@ GEM
zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
ast (2.4.2)
bindex (0.8.1)
bootsnap (1.7.5)
msgpack (~> 1.0)
......@@ -83,6 +84,9 @@ GEM
ffi (1.15.3)
globalid (0.4.2)
activesupport (>= 4.2.0)
httparty (0.18.1)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
jbuilder (2.11.2)
......@@ -97,6 +101,9 @@ GEM
mini_mime (>= 0.1.1)
marcel (1.0.1)
method_source (0.9.2)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2021.0704)
mini_mime (1.1.0)
minitest (5.14.4)
msgpack (1.4.2)
......@@ -104,6 +111,9 @@ GEM
nio4r (2.5.7)
nokogiri (1.11.7-x86_64-linux)
racc (~> 1.4)
parallel (1.20.1)
parser (3.0.2.0)
ast (~> 2.4.1)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
......@@ -148,11 +158,29 @@ GEM
method_source
rake (>= 0.13)
thor (~> 1.0)
rainbow (3.0.0)
rake (13.0.3)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
regexp_parser (2.1.1)
rexml (3.2.5)
rubocop (1.18.3)
parallel (~> 1.10)
parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.7.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.7.0)
parser (>= 3.0.1.1)
rubocop-rails (2.11.3)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
ruby-progressbar (1.11.0)
rubyzip (2.3.2)
sass-rails (6.0.0)
sassc-rails (~> 2.1, >= 2.1.1)
......@@ -191,6 +219,7 @@ GEM
turbolinks-source (5.2.0)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
unicode-display_width (2.0.0)
web-console (4.1.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
......@@ -219,6 +248,7 @@ DEPENDENCIES
bootsnap (>= 1.4.4)
byebug
capybara (>= 3.26)
httparty (~> 0.18.1)
jbuilder (~> 2.7)
listen (~> 3.3)
mysql2 (~> 0.5)
......@@ -227,6 +257,7 @@ DEPENDENCIES
puma (~> 5.0)
rack-mini-profiler (~> 2.0)
rails (~> 6.1.3, >= 6.1.3.2)
rubocop-rails (~> 2.11, >= 2.11.3)
sass-rails (~> 6.0)
selenium-webdriver
slim-rails (~> 3.2)
......
=> #<Job:0x00007f490830e500
=> #<Job:0x00007f490830e500
id: 1,
title: "[Hà Nội] - CHUYÊN GIA TƯ VẤN BẢO HIỂM KHÁCH HÀNG ƯU TIÊN - Kênh Liên Kết Ngân Hàng Techcombank (Bancassurance)",
type: nil,
salary: "18 Tr - 28 Tr VND",
experience: "1 - 2 Năm",
position: "Nhân viên",
expiration_date: Sun, 15 Aug 2021 00:00:00.000000000 UTC +00:00,
description:
"MÔ TẢ CÔNG VIỆC: Bancassurance là kênh hợp tác giữa tập đoàn bảo hiểm nhân thọ Manulife và ngân hàng Techcombank.Chuyên Gia Bảo Hiểm (TIS) làm việc tại ngân hàng Techcombank, phụ trách các công việc sau: Khai thác nguồn khách hàng tiềm năng và chất lượng từ ngân hàng, Techcombank tăng trung bình 150.000 khách hàng mới/tháng nhờ hệ thống Digital Banking & Zero free. TIS nhận thông tin giới thiệu khách hàng tiềm năng từ các lực lượng Quan hệ khách hàng ưu tiên (PRM) để tư vấn các sản phẩm bảo hiểm phù hợp với nhu cầu tài chính của từng khách hàng. Được refer nguồn khách hàng tiềm năng từ toàn bộ các lực lượng bán: GDV, CSO, RBO, SRBO, PRM để tư vấn sản phảm bảo hiểm đầu tư Hỗ trợ khách hàng hoàn thiện bộ hồ sơ bảo hiểm theo đúng quy định của Manulife và Techcombank Là đầu mối chính chăm sóc, hỗ trợ khi khách hàng cần được giải đáp thắc mắc về các vấn đề liên quan đến bảo hiểm, xây dựng mối quan hệ tốt đẹp với Khách hàng ĐỊA ĐIỂM & THỜI GIAN LÀM VIỆC Địa điểm: Siêu chi nhánh/chi nhánh lớn của Ngân hàng trên toàn quốc Thời gian làm việc: 8h30 – 17h30, thứ 2 – thứ 6, & sáng thứ 7 QUYỀN LỢI: Lương cơ bản: 18.000.000 – 28.000.000/tháng, được xét tăng lương hàng năm theo kết quả xếp loại. Thu nhập TIS luôn cạnh tranh so với thị trường. Được ký Hợp đồng lao động, là nhân viên chính thức của Techcombank (Hưởng lương tháng 13, thưởng hiệu quả công việc và các quyền lợi khác như: Chương trình vay ưu đãi, Bảo hiểm sức khỏe Aeon, chế độ 15 ngày phép/năm, khám sức khỏe định kì ...) Thưởng kinh doanh hấp dẫn theo quý căn cứ trên hiệu quả công việc, Mức thưởng từ 24-96 triệu đồng/tháng. Được hướng dẫn, đào tạo thường xuyên bởi Giám đốc bảo hiểm vùng, Giám đốc kinh doanh vùng (Manulife), Giám đốc chi nhánh…. Được hỗ trợ tối đa bởi các TIA (Admin) trước/trong/sau quá trình bán hàng, giúp Chuyên Gia Bảo Hiểm tập trung vào tư vấn sản phẩm cho khách hàng. Cơ hội thăng tiến lên vị trí Quản Lí Quan Hệ Khách Hàng Ưu Tiên/ Giám Đốc Dịch Vụ/ Giám Đốc Chi Nhánh hoặc các vị trí đào tạo bán/ phát triển sản phẩm bảo hiểm tại Hội sở. Được làm việc tại Techcombank, là nơi làm việc chuyên nghiệp với: Mức tăng trưởng ấn tượng: Ngân hàng TMCP số 1 tại Việt Nam về lợi nhuận trong 5 năm liên tiếp Môi trường làm việc gắn kết với các hoạt động teambuilding hàng năm, sinh nhật, các câu lạc bộ ngoài giờ, Year-end Party…",
benefit:
"[\"Chế độ bảo hiểm\", \"Du Lịch\", \"Phụ cấp\", \"Du lịch nước ngoài\", \"Đồng phục\", \"Chế độ thưởng\", \"Chăm sóc sức khỏe\", \"Đào tạo\", \"Tăng lương\", \"Công tác phí\", \"Phụ cấp thâm niên\", \"Nghỉ phép năm\", \"CLB thể thao\"]",
requirement:
"KINH NGHIỆM/KỸ NĂNG CHI TIẾT Trình độ học vấn: Tốt nghiệp Cao đẳng/Đại học (Ưu tiên chuyên ngành Bảo hiểm, ngân hàng, Kinh tế, tài chính, quản trị kinh doanh.......) Có tối thiểu 1 năm kinh nghiệm tư vấn trong lĩnh vực tư vấn Bảo hiểm nhân thọ và 1 năm kinh nghiệm sales Tài chính/Ngân hàng. Đã có kinh nghiệm trong mảng Ngân hàng/Dịch vụ Khách hàng ưu tiên là lợi thế. Am hiểu về các sản phẩm bảo hiểm và về khách hàng cá nhân hoặc khách hàng doanh nghiệp Kỹ năng: Kĩ năng tư vấn, thuyết phục và khai thác nhu cầu của khách hàng Kỹ năng phát triển mối quan hệ với khách hàng Có tinh thần học hỏi, quan tâm gắn bó và phát triển lâu dài với Techcombank",
other_info: "[\"Bằng cấp: Cao đẳng\", \"Độ tuổi: Không giới hạn tuổi\"]",
company_id: 1,
created_at: Thu, 15 Jul 2021 05:51:56.429636000 UTC +00:00,
updated_at: Thu, 15 Jul 2021 05:51:56.429636000 UTC +00:00>
......@@ -74,4 +74,128 @@ footer {
margin-left: 15px;
}
}
}
// Top page
// search
// latest-job
.latest-job {
h2 {
margin-bottom: 30px;
}
h2::after {
content: "";
display: block;
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
}
.job-item {
font-size: 14px;
.job-title {
text-decoration: none;
color: black;
font-size: 20px;
font-weight: 500;
}
.job-caption {
line-height: 18px;
.job-company {
text-decoration: none;
font-size: 14px;
color: $gray-700;
}
.job-salary {
color: #008563;
margin: 0;
}
.job-locations {
ul {
list-style: none;
margin: 0 0 6px 0;
padding: 0;
color: $gray-700;
}
}
.job-desc {
overflow: hidden;
text-overflow: ellipsis;
line-height: 18px;
-webkit-line-clamp: 2;
height: 36px;
display: -webkit-box;
-webkit-box-orient: vertical;
}
}
}
.job-item::after {
content: "";
display: block;
margin-top: 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
}
.job-item:last-child::after{
border-bottom: none;
}
}
// top_cities
.top_cities {
h2 {
margin-bottom: 30px;
}
h2::after {
content: "";
display: block;
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
}
.city-item {
margin-bottom: 12px;
a {
font-size: 18px;
text-decoration: none;
color: #287ab9;
margin: 10px 0;
}
}
.all-cities-btn {
color: #287ab9;
font-size: 20px;
text-decoration: none;
}
.all-cities-btn:hover {
text-decoration: underline;
}
}
// top_industries
.top_industries {
h2 {
margin-bottom: 30px;
}
h2::after {
content: "";
display: block;
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
}
.industry-item {
margin-bottom: 12px;
a {
font-size: 18px;
text-decoration: none;
color: #287ab9;
margin: 10px 0;
}
}
.all-industries-btn {
color: #287ab9;
font-size: 20px;
text-decoration: none;
}
.all-industries-btn:hover {
text-decoration: underline;
}
}
\ No newline at end of file
class TopController < ApplicationController
def index
@latest_jobs = Job.order(updated_at: :desc).limit(5)
@top_cities = City.all.map { |city| [city, city.jobs.count] }.sort_by(&:second).reverse.take(9)
@top_industries = Industry.all.map { |industry| [industry, industry.jobs.count] }.sort_by(&:second).reverse.take(9)
end
end
module ApplicationHelper
def full_title(page_title = '')
base_title = 'VenJob'
if page_title.empty?
base_title
else
page_title + " | " + base_title
end
end
end
......@@ -2,8 +2,7 @@ doctype html
html
head
title
| hi
- # <%= full_title(yield(:title))
=full_title(yield(:title))
meta[name="viewport" content="width=device-width,initial-scale=1" charset="utf-8"]
= csrf_meta_tags
= csp_meta_tag
......@@ -12,6 +11,6 @@ html
= render 'layouts/shim'
body
= render 'layouts/header'
.container
.container-fluid
= yield
= render 'layouts/footer'
\ No newline at end of file
.latest-job.mb-5
.container
h2
| Latest jobs
- @latest_jobs.each do |job|
.job-item.mb-4
= link_to job.title, "#", class: "job-title"
.job-caption
= link_to job.company.name, "#", class: "job-company"
p.job-salary
| Salary:
= job.salary
.job-locations
ul
- job.cities.each do |city|
li
= city.name
.job-desc
p
= job.description
\ No newline at end of file
.search.text-center.mb-5
.overlay
.container
.row
.col-xl-9.mx-auto
h1.mb-5.mt-5
| Total:
= pluralize(Job.count, "job")
.row.justify-content-start
.col-md-10.mb-md-0.no-padding
span.fa.fa-search.form-control-feedback
= search_field_tag :search, params[:search], placeholder: "Find a job", class: "form-control rounded-left no-border-radius bg-light h-100"
.col-md-2.mb-md-0.no-padding
= button_tag "", class: "h-100 w-100 btn btn-block btn-lg btn-info fa fa-search rounded-right no-border-radius ", name: nil
| Search
\ No newline at end of file
.top_cities.mb-5
.container
h2 Top cities
.row.align-items-start.mb-3
- @top_cities.each do |city, city_jobs|
.col-4.city-item
= link_to city.name, '#', class: 'city-name'
span.job-count
| (
= pluralize(city_jobs, "job")
|)
= link_to 'See all cities', '#', class:'all-cities-btn'
\ No newline at end of file
.top_industries.mb-5
.container
h2 Top industries
.row.align-items-start
- @top_industries.each do |industry, industry_jobs|
.col-4.industry-item
= link_to industry.name, '#', class: 'industry-name'
span.job-count
| (
= pluralize(industry_jobs, "job")
|)
= link_to 'See all industries', '#', class:'all-industries-btn'
\ No newline at end of file
- provide(:title, 'Home page')
= render 'search'
= render 'latest_jobs'
= render 'top_cities'
= render 'top_industries'
......@@ -2,7 +2,7 @@ class CreateJobs < ActiveRecord::Migration[6.1]
def change
create_table :jobs do |t|
t.string :title
t.string :type
t.string :job_type
t.string :salary
t.string :experience
t.string :position
......
class CreateIndustriesJobs < ActiveRecord::Migration[6.1]
def change
create_table :industries_jobs do |t|
t.references :job, null: false, foreign_key: true
t.references :industry, null: false, foreign_key: true
t.timestamps
end
end
end
class CreateCitiesJobs < ActiveRecord::Migration[6.1]
def change
create_table :cities_jobs do |t|
t.references :job, null: false, foreign_key: true
t.references :city, null: false, foreign_key: true
t.timestamps
end
end
end
......@@ -106,7 +106,7 @@ ActiveRecord::Schema.define(version: 2021_07_20_055614) do
create_table "jobs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "title"
t.string "type"
t.string "job_type"
t.string "salary"
t.string "experience"
t.string "position"
......
This diff is collapsed. Click to expand it.
require 'open-uri'
require 'csv'
require 'zip'
namespace :crawler do
desc 'crawler from CareerBuilder'
task jobs: :environment do
ARGV.each { |a| task a.to_sym { ; } }
total_pages = 0
if ARGV.length == 1 && ARGV[0] == 'TEST'
total_pages = 1
elsif ARGV.length == 1 && ARGV[0] == 'ALL'
first_page = Nokogiri::HTML(HTTParty.get('https://careerbuilder.vn/viec-lam/tat-ca-viec-lam-vi.html').body)
jobs_per_page = first_page.css('div.job-item').count
total_jobs = first_page.css('.search-result-list .job-found p').text.split(' ').first.gsub(',', '').to_i
total_pages = (total_jobs.to_f / jobs_per_page).round
else
exit
end
(1..total_pages).each do |page|
parsed_page = Nokogiri::HTML(HTTParty.get("https://careerbuilder.vn/viec-lam/tat-ca-viec-lam-trang-#{page}-vi.html").body)
jobs_item = parsed_page.css('div.job-item .job_link')
jobs_item.each do |item|
job_page = Nokogiri::HTML(HTTParty.get('https://careerbuilder.vn/vi/tim-viec-lam/' +
CGI.escape(item.attribute('href').text.remove('https://careerbuilder.vn/vi/tim-viec-lam/'))).body)
job_detail = job_page.css('section.job-detail-content')
# title - company
title = job_page.css('div.job-desc h1.title').text
company = job_page.css('div.job-desc a.job-company-name').text
# info box
info_box_item = job_detail.css('.detail-box ul li')
# city, update_at, industry, type, salary, experience, level, expiration_date
job_industries = []
update_at, job_type, salary, experience, level, expiration_date = ''
job_cities = []
job_detail.css('.detail-box .map p a').each do |part|
city = part.text
job_cities << city
end
info_box_item.each do |info_item|
info = info_item.text
if info.include?(key = 'Ngày cập nhật')
update_at = info.squish.remove(key).strip
elsif info.include?(key = 'Ngành nghề')
job_industries = info.squish.remove(key).strip.split(' , ')
elsif info.include?(key = 'Hình thức')
job_type = info.squish.remove(key).strip
elsif info.include?(key = 'Lương')
salary = info.squish.remove(key).strip
elsif info.include?(key = 'Kinh nghiệm')
experience = info.squish.remove(key).strip
elsif info.include?(key = 'Cấp bậc')
level = info.squish.remove(key).strip
elsif info.include?(key = 'Hết hạn nộp')
expiration_date = info.squish.remove(key).strip
end
end
# benefit
benefit_list = []
other_info_list = []
benefits = job_detail.css('ul.welfare-list li')
benefits.each do |part|
benefit = part.text.strip
benefit_list << benefit
end
# description, requirement
description, requirement = ''
job_detail_row = job_detail.css('div.detail-row')
job_detail_row.each do |part|
job_detail_text = part.text
if job_detail_text.include?('Mô tả Công việc')
description = job_detail_text.partition('Mô tả Công việc').last.squish.strip
elsif job_detail_text.include?('Yêu Cầu Công Việc')
requirement = job_detail_text.partition('Yêu Cầu Công Việc').last.squish.strip
end
end
# other info
other_info = job_detail.css('div.content_fck ul li')
other_info.each do |part|
info = part.text.squish.strip
other_info_list << info
end
company = Company.find_or_create_by(name: company)
job = Job.find_or_create_by(
title: title,
job_type: job_type,
salary: salary,
position: level,
experience: experience,
expiration_date: expiration_date,
benefit: benefit_list.each { |benefit| },
description: description,
requirement: requirement,
other_info: other_info_list.each { |info| }
)
company.jobs << job
job_industries.each do |industry|
industry_id = Industry.find_or_create_by(name: industry)
job.industries << industry_id
end
job_cities.each do |city|
city_id = City.find_or_create_by(name: city)
job.cities << city_id
end
end
end
end
desc 'crawler industry form CareerBuilder'
task industries: :environment do
parsed_page = Nokogiri::HTML(HTTParty.get('https://careerbuilder.vn/tim-viec-lam.html').body)
list_job = parsed_page.css('div.list-of-working-positions ul.list-jobs li a')
industry_list = []
list_job.each do |part|
industry = part.text.squish.strip
industry_list << industry
end
industry_list.each do |industry|
Industry.create(name: industry)
end
end
desc 'crawler city form CareerBuilder'
task cities: :environment do
parsed_page ||= Nokogiri::HTML(HTTParty.get('https://careerbuilder.vn/tim-viec-lam.html').body)
list_location = parsed_page.css('div.main-jobs-by-location ul li')
city_list = []
list_location.each do |part|
city_item = part.text
region = 1
if city_item.include?(key = 'Việc làm tại')
city_item = city_item.remove(key).strip
region = 0
end
city = {
name: city_item,
region: region
}
city_list << city
end
city_list.each do |city|
City.create(
name: city[:name],
region: city[:region]
)
end
end
end
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment