R в качестве инструмента мониторинга цен

от автора

В данной статье хотел бы коснуться такой темы как мониторинг конкурентов. Понимаю, что у данной темы есть как много сторонников, ведь так или иначе мониторинг необходим для успешного развития почти любой компании, так и противники, которые защищают интересы своего бизнеса от мониторщиков.

Те, кто как то связан с продажами на конкурентном рынке, наверняка знают, что мониторинг конкурентов является важной задачей. Результаты используются для совершенно различных целей — от изменения локальных политик ценообразования и ведения ассортимента до составления стратегических планов развития компании. Автор решил попрактиковаться в решении данной задачи и промониторить одного из крупных ритейлеров электроники в России, чьим регулярным клиентом автор является. Что из этого вышло —

Вместо введения

Сразу стоит сказать, что в статье не будет описаний методов социальной инженерии или общения с фирмами, предоставляющими услуги мониторинга. Также добавлю, что не будет и описания анализа мониторинга, только алгоритм сбора и некоторые трудности, с которыми пришлось столкнуться во время работы. Последнее время автор все чаще применяет R, и сбор данных решено было сделать с его использованием. К тому же все большую популярность набирают открытые данные (например вот, вот или вот) и навык работы с ними прямо из используемой среды будет полезен. Все действия носят демонстративных характер, и полученные дынные не были переданы никому.

Анализ сайта

Первое, что нужно сделать, изучить структуру сайта исследуемого конкурента. Начнем с контента. Есть несколько уровней товарного классификатора, и количество уровней для любого товара всегда одинаково. До списка товаров можно добраться через уровень 2 или уровень 3. Для дальнейшей работы было решено использовать уровень 2.
Следующим шагом было исследование исходного кода страницы со списком требуемых товаров и выяснение ее структуры. Каждый товар находится в отдельном HTML-контейнере. Тут нас ждет первая сложность — в коде страницы содержится информация только о первых 30 товарах в списке — понятно, что нас это не устраивает. Автор не понял как программно показать следующие 30 товаров, поэтому решено было изучить мобильную версию сайта. В мобильной версии внизу страницы есть ссылки на следующую и на последнюю страницы. Плюс — мобильная версия сайта гораздо менее «замусоренная» лишними ссылками и тэгами.

Пишем код

Изначально решено было разделить код на несколько составляющих:
1. Функция, которая собирает информацию с конкретной страницы. Принимает на вход URL конкретной страницы, на которой находится до 15 товаров (отличие мобильной версии). Функция возвращает data.frame содержащий информацию о наличии, наименовании, артикуле, количестве отзывов и ценах товаров.

Первая функция

getOnePageBooklet <- function(strURLsub="", curl=getCurlHandle()){      # loading the required page      html <- getURL(strURLsub,                      .encoding='UTF-8',                      curl=curl)      # parsing html      html.raw <- htmlTreeParse(           html,           useInternalNodes=T      )      # searching for SKU nodes      html.parse.SKU <- xpathApply(html.raw,                                    path="//section[@class='b-product']",                                    fun=xmlValue)            # some regex :)      noT <- gsub(' ([0-9]+)\\s([0-9]+) ',' \\1\\2 ',unlist(html.parse.SKU))      noT <- gsub(';',',',noT)      noT <- gsub('\r\n',';',noT)      noT <- trim(noT)      noT <- gsub("(\\s;)+", " ", noT)      noT <- gsub("^;\\s ", "", noT)      noT <- gsub(";\\s+([0-9]+)\\s+;", "\\1", noT)      noT <- gsub(" ; ", "", noT)      noT <- gsub("Артикул ", "", noT)      noT <- gsub("\\s+Купить;\\s*", "", noT)      noT <- gsub("\\s+руб.;\\s*", "", noT)      noT <- gsub(";\\s+", ";", noT)       # text to list      not.df <- strsplit(noT,';')       # list to nice df      tryCatch(      not.df <- as.data.frame(matrix(unlist(not.df),                                       nrow = length(not.df),                                       byrow = T)), error=function(e) {print(strURLsub)}  )  } 

2. Функция, которая, используя функцию 1, собирает информацию со всех страниц заданного уровня товарного классификатора. Основной смысл данной функции — найти номер последней страницы, пробежаться от первой до последней с помощью функции 1 и объединить полученные результаты в один data.frame. Результат функции — data.frame со всеми товарами данного уровня товарного классификатора.

Вторая функция

getOneBooklet <- function(strURLmain="", curl=getCurlHandle()){      # data frane for the result      df <- data.frame(inStock=character(), SKU=character(), Article=numeric(), Comment=numeric(), Price=numeric())             # loading main subpage      html <- getURL(strURLmain,                      .encoding='UTF-8',                      curl=curl)      # parsing main subpage      html.raw <- htmlTreeParse(           html,           useInternalNodes=T      )      # finding last subpage      html.parse.pages <- xpathApply(html.raw,                                      path="//a[@class='page g-nouline']",                                      fun=xmlValue)      if(length(html.parse.pages)==0){           urlMax <- 1      }else{           urlMax <- as.numeric(unlist(html.parse.pages)[length(unlist(html.parse.pages))])                }       # loop for all sybpages      tryCatch(      for(iPage in 1:urlMax){           strToB <- paste0(strURLmain, '?pageNum=',iPage)           df.inter <- getOnePageBooklet(strToB, curl)           df <- rbind(df, df.inter)      }, error=function(e) {print(iPage)})      # write.table(df, paste0('D:\\', as.numeric(Sys.time()) ,'.csv'), sep=";")           df } 

3. Функция, которая, используя функцию 2, собирает информацию со всех имеющихся уровней товарного классификатора. Кроме того к полученным данным добавляются названия уровней классификатора. На всякий случай, когда целиком собрана информация по одной категории — результат сохраняется на диске.

Третья функция

getOneCity <- function(urlMain = "http://m.tramlu.ru", curl = getCurlHandle()){      df.prices <- data.frame(inStock = character(),                               SKU = character(),                               Article = numeric(),                               Comment = numeric(),                               Price = numeric(),                               level1 = character(),                              level2 = character())      level1 <- getAllLinks(urlMain, curl)      numLevel1 <- length(level1[,2])            for (iLevel1 in 1:numLevel1){           strURLsubmain <- paste0(urlMain, level1[iLevel1, 2])           level2 <- getAllLinks(strURLsubmain, curl)           numLevel2 <- length(level2[,2])                      for (iLevel2 in 1:numLevel2){                strURLsku <- paste0(urlMain, level2[iLevel2,2])                df.temp <- getOneBooklet(strURLsku, curl)                df.temp$level1 <- level1[iLevel1,1]                df.temp$level2 <- level2[iLevel2,1]                                df.prices <- rbind(df.prices, df.temp)           }           write.table(df.prices, paste0('D:\\', iLevel1 ,'.csv'), sep=";", quote = FALSE)                }      df.prices } 

В дальнейшем, испугавшись молота бана, пришлось добавить еще функцию — использующую прокси. В последствии выяснилось, что банить никто даже не собирался, весь классификатор, с учетом тестирования скриптов, был собран без проблем, поэтому все ограничилось созданием data.frame с информацией по ста прокси, которые так и не использовались.

Получение списка proxy

getProxyAddress <- function(){      htmlProxies <- getURL('http://www.google-proxy.net/',                             .encoding='UTF-8')      #htmlProxies <- gsub('</td></tr>','  \n ', htmlProxies)      htmlProxies <- gsub('\n','', htmlProxies)      htmlProxies <- gsub('(</td><td>)|(</td></tr>)',' ; ', htmlProxies)      # parsing main subpage      htmlProxies.raw <- htmlTreeParse(           htmlProxies,           useInternalNodes=T      )       # finding last subpage      html.parse.proxies <- xpathApply(htmlProxies.raw,                                      path="//tbody",                                      fun=xmlValue)      html.parse.proxies<- gsub('( )+','', html.parse.proxies)      final <- unlist(strsplit(as.character(html.parse.proxies),';'))      final <- as.data.frame(matrix(final[1:800],                                      nrow = length(final)/8,                                      ncol = 8,                                     byrow=T))      #final <- gsub('( )+','', final)      names(final) <- c('IP','Port','Code','Country','Proxy type','Google','Https','Last checked')      sapply(final, as.character) } 

Использовать прокси можно вот так

Как использовать прокси

opts <- list(      proxy         = "1.1.1.1",       proxyport     = "8080" ) getURL("http://habrahabr.ru", .opts = opts) 

Структуры всех составляющих кода похожи. Используются функции getURL из пакета RCurl, htmlTreeParse и xpathApply из пакета XML и несколько регулярных выражений.
Последней сложностью стало указание города, цены в котором мы хотим узнать. По умолчанию при загрузке данных выдавалась информация по ценам доставки по почте с неопределенным наличием товара. При заходе на сайт исследуемой компании возникает окно, которое предлагает выбрать город-месторасположение. Данная информация затем сохраняется в куки браузера и используется для показа цен и товаров в выбранном городе. Для того, чтобы R мог сохранять куки, необходимо задать свойства используемого соединения. Далее необходимо просто загрузить в R страницу, соответствующую интересующему городу.

Свойства соединения

agent    ="Mozilla/5.0" curl = getCurlHandle() curlSetOpt(cookiejar="cookies.txt",              useragent = agent,             followlocation = TRUE,             curl=curl) 

Вместо заключения

Все, задав требуемые параметры, запускаем функцию 3, ждем около получаса и получаем список со всеми товарами и категориями исследуемого сайта. Получилось более 60 000 цен для города Москва. Результат представляется в виде data.frame и нескольких сохраненных файлов на диске.
Скрипт целиком находится на GitHub.

Спасибо за уделенное внимание.

ссылка на оригинал статьи http://habrahabr.ru/post/255173/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *