Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
270 lines (197 sloc) 12 KB
# Устанавливаем нужные пакеты
install.packages("rvest")
install.packages("purrr")
install.packages("readr")
install.packages("stringr")
install.packages("RCurl")
install.packages("XML")
install.packages("doMC")
# Для ОС Windows
install.packages("doMC", repos="http://R-Forge.R-project.org")
install.packages("dplyr")
install.packages("plotly")
install.packages("moments")
install.packages("Hmisc")
install.packages("pastecs")
install.packages("psych")
# 1 этап - Сбор данных с HH.ru
# Активируем первые пакеты для первого этапа сбора ссылок и обработки данных
library(rvest)
library(purrr)
library(readr)
library(stringr)
# Исходная ссылка на сайте, установите критерии отобора вакансий/резюме и вствьте её между кавычками, на конце ссылки ставим &page=%d
url_base <- "https://hh.ru/search/resume?exp_period=all_time&order_by=publication_time&citizenship=113&schedule=fullDay&text=&area=1&relocation=living&pos=full_text&work_ticket=113&logic=normal&profiles_order_by=rating&saved_search_id=2261699&employment=full&skill=170&skill=1518&skill=3018&skill=6379&specialization=1.225&specialization=1.359&specialization=5.4&specialization=5.224&specialization=5.219&specialization=17.625&specialization=17.196&specialization=17.324&specialization=17.333&specialization=17.303&specialization=17.623&salary_to=100000&page=%d"
# В скобках установите параметр сколько страниц нужно собирать, допустим по вашим критериям поисковая выдачала
# показала 12 страниц следовательно устанавливаете от 0 (первая страница) до 11 (двенадцатая страница)
map_df(0:11,function(i){
# cat(".") - выводит в консоль процесс сбора
cat(".")
page <- read_html(sprintf(url_base,i))
# Собираем данные данные в data frame с двумя клонками относительной (абсолютной ссылкой) и заголовком резюме/вакансии
data.frame(links = html_attr(html_nodes(page, ".HH-VisitedResume-Href"), "href"),
jobtitle = html_text(html_nodes(page,".HH-VisitedResume-Href")),
stringsAsFactors = FALSE
)
}) -> hh_resume
# На некоторых сайтах ссылки относительные, т.е. без доменного имени, например на hh.ru.
# В этом случае сначала добавляем в коллонку название доменного имени и создаём новый data frame
# Затем обьединяем две колонки host и links в новую колнку url, без пробелов
# Если на сайте ссылка абсолютная, то далее измените data frame на тот, который вы получили от первого парсера
hhresume <- data.frame(cbind(host= c("https://hh.ru"), links=hh_resume$links, jobtitle=hh_resume$jobtitle))
hh <- data.frame(url = paste(hhresume$host, hhresume$links, sep=''), title = hhresume$jobtitle)
write_csv(hh, "hh_resume.csv")
# Второй парсер собираем данные по ссылкам, которые получили от первого парсера
library(RCurl)
library(XML)
library(doMC)
registerDoMC(cores=2)
# Берём абсолютные ссылки из последнего data frame
urls <- hh$url
# Парсер параллельно проходит по ссылкам и собирает данные, подставляйте свои значение, те которые установлены на сайте
gender <- foreach(url=urls) %dopar% {
html=getURL(url, followlocation=TRUE)
doc=htmlParse(html,asText=TRUE)
return(xpathSApply(doc,'//span[@itemprop="gender"]',xmlValue))
}
age <- foreach(url=urls, .combine=rbind) %dopar% {
html=getURL(url, followlocation=TRUE)
doc=htmlParse(html,asText=TRUE)
return(xpathSApply(doc,'//span[@data-qa="resume-personal-age"]',xmlValue))
}
metro <- foreach(url=urls, .combine=rbind) %dopar% {
html=getURL(url, followlocation=TRUE)
doc=htmlParse(html,asText=TRUE)
return(xpathSApply(doc,'//span[@data-qa="resume-personal-metro"]',xmlValue))
}
salary <- foreach(url=urls) %dopar% {
html=getURL(url, followlocation=TRUE)
doc=htmlParse(html,asText=TRUE)
return(xpathSApply(doc,'//span[@class="resume-block__salary"]',xmlValue))
}
sumexp <- foreach(url=urls, .combine=cbind) %dopar% {
html=getURL(url, followlocation=TRUE)
doc=htmlParse(html,asText=TRUE)
return(xpathSApply(doc,'//span[@class="resume-block__title-text resume-block__title-text_sub"]',xmlValue))
}
intexp <- foreach(url=urls) %dopar% {
html=getURL(url, followlocation=TRUE)
doc=htmlParse(html,asText=TRUE)
return(xpathSApply(doc,'//div[@class="resume-block__experience-timeinterval"]',xmlValue))
}
# 2 этап - Обработка и очистка данных
# В столбце данных суммарного опыта очистим от слов, а цифры приведём в единый стандарт
sumexp <- gsub("[\\w ^Опыт работы есяц ев а д]", "", sumexp[1,])
sumexp <- gsub("[\\w ^г л]", ".", sumexp)
sumexp <- gsub("[\\w ^м]", "", sumexp)
sumexp <- gsub("[\\d ^.]", ".0", sumexp)
intexp <- do.call(rbind, lapply(intexp, data.frame, stringsAsFactors=FALSE))
# Выполним простые манипуляции над данными и подготовим для их визуализации
# Первоначальные данные у нас сохранены формате list, переведём всё в формат data.frame
gender <- do.call(rbind, lapply(gender, data.frame, stringsAsFactors=FALSE))
age <- do.call(rbind, lapply(age, data.frame, stringsAsFactors=FALSE))
metro <- do.call(rbind, lapply(metro, data.frame, stringsAsFactors=FALSE))
salary <- do.call(rbind, lapply(salary, data.frame, stringsAsFactors=FALSE))
sumexp <- do.call(rbind, lapply(sumexp, data.frame, stringsAsFactors=FALSE))
intexp <- do.call(cbind, lapply(intexp, data.frame, stringsAsFactors=FALSE))
# Обьединим в единый data.frame и переимнуем колонки
library(dplyr)
library(stringi)
resume <- data.matrix(c(gender, age, metro, salary, sumexp, intexp))
do.call(rbind, resume)
resume <- t(simplify2array(resume))
resume <- as.data.frame(stri_list2matrix(resume, byrow = FALSE, fill = ''))
resume = rename(resume, gender = V1, age = V2, metro = V3, salary = V4, sumexp = V5, intexp = V6)
resume$sumexp <- stri_pad_left(str=resume$sumexp, 2, pad=".")
# Очистим данные и приведём в единый стандарт в столбцах возраст, заработная плата и периоды работы
resume$age <- gsub("\\D", "", resume$age)
resume$salary <- gsub("\\D", "", resume$salary)
resume$intexp <- gsub("[\\w ^о есяц ев а д т]", "", resume$intexp)
resume$intexp <- gsub("[\\w г л]", ".", resume$intexp)
resume$intexp <- gsub("[\\w ^м]", "", resume$intexp)
resume$intexp <- gsub("[\\d ^.]", ".0", resume$intexp)
resume$intexp <- stri_pad_left(str=resume$intexp, 2, pad=".")
# Изменяет формат данных на числовой
resume$age <- as.numeric(as.character(resume$age))
resume$salary <- as.numeric(as.character(resume$salary))
resume$sumexp <- as.numeric(as.character(resume$sumexp))
resume$intexp <- as.numeric(as.character(resume$intexp))
# В колонках заменим NA на 0
resume$sumexp[is.na(resume$sumexp)] <- 0
resume$age[is.na(resume$age)] <- 0
resume$salary[is.na(resume$salary)] <- 0
resume$intexp[is.na(resume$intexp)] <- 0
str(resume)
write_csv(resume, "resume.csv")
# 3 этап - Статистическая обработка данных
# Источник: https://r-analytics.blogspot.ru/2012/02/r_28.html
# Арифметическая средняя:
mean(resume$age)
# Медиана
median(resume$salary[1:226])
# Дисперсия:
var(resume$sumexp)
# Стандартное отклонение:
sd(resume$intexp)
# Минимальное значение:
min(resume$age[1:224])
rownames(resume)[which.min(resume$age[1:224])]
# Максимальное значение:
max(resume$age)
rownames(resume)[which.max(resume$age)]
# Стандартная ошибка средней
sd(resume$age)/sqrt(length(resume$age))
# Квантили
quantile(resume$salary)
# Интерквартильный размах
IQR(resume$intexp)
# Децили
quantile(resume$intexp, p = seq(0, 1, 0.1))
# Одной функцией вычисляем минимальное (Min) и максимальное (Max) значение переменных,
# медианы (Median), арифметической средней (Mean), первого (1st Qu.) и третьего (3rd Qu.) квартилей.
summary(resume)
# Вычислим средний возраст в зависимости от пола
tapply(X = resume$age, INDEX = resume$gender, FUN = mean)
# Вычислим средний возраст в зависимости от пола и уровня заработной платы
tapply(X = resume$age, INDEX = list(resume$gender, resume$salary), FUN = mean)
# Расчитаем стандартные ошибки для средних значений для возраста и уровня заработной платы
SE <- function(x) {sd(x)/sqrt(length(x))}
tapply(X = resume$age, INDEX = resume$salary, FUN = SE)
library(moments)
# Коэффициент эксцесса
kurtosis(resume$sumexp)
# Коэффициент асимметрии
skewness(resume$intexp)
library(Hmisc)
describe(resume)
library(pastecs)
stat.desc(resume)
library(psych)
describe(resume)
describe.by(resume, resume$intexp)
# 4 этап - Визуализация данных
library(plotly)
p0 <- plot_ly(resume, x = ~gender, y = ~age, type = 'box', color = ~gender, colors = "Set1") %>%
layout(title = 'Количестов резюме в зависимости от возраста и пола',
yaxis = list(title = 'Возраст'),
xaxis = list(title = ' '))
p0
# Экспорт инфографики на хостинг plot.ly
Sys.setenv("plotly_username"="Ваш логин") # Вводим логин (имя) plot.ly
Sys.setenv("plotly_api_key"="Ваш API KEY") # Вводим ключ API plot.ly
plotly_POST(x = p0, filename = "gender-age", sharing = "public") # Отправляем в личный профиль на plot.ly
p1 <- plot_ly(resume, x = ~salary, y = ~age, z = ~sumexp, type = 'scatter3d', mode = "markers+lines",
color = ~factor(age), colors = "Set2") %>%
layout(title = 'Количестов резюме в зависимости от заработной платы, возраста и совокупного опыт работы',
scene = list(xaxis = list(title = 'Заработная плата'),
yaxis = list(title = 'Возраст'),
zaxis = list(title = 'Опыт работы')))
p1
plotly_POST(x = p1, filename = "salary-age-sumexp", sharing = "public") # Отправляем в личный профиль на plot.ly
p2 <- plot_ly(resume, x = ~salary, y = ~gender, type = 'scatter', name = "Пол", mode = 'markers', color = ~salary, colors = "Paired", size = ~salary) %>%
add_trace(y = ~age, mode = 'markers', name = "Возраст") %>%
layout(title = 'Распределение резюме по полу, возрасту и заработной платы, окрашены в зависимсти от уровня з/пл',
xaxis = list(title = 'Заработная плата'),
yaxis = list(title = 'Пол/Возраст'))
p2
plotly_POST(x = p2, filename = "salary-age-gender", sharing = "public") # Отправляем в личный профиль на plot.ly