package com.skripko.freelance.platform; import com.codeborne.selenide.Condition; import com.codeborne.selenide.ElementsCollection; import com.codeborne.selenide.SelenideElement; import com.skripko.freelance.Job; import org.openqa.selenium.Keys; import org.openqa.selenium.WebDriver; import org.openqa.selenium.interactions.Actions; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Set; import static com.codeborne.selenide.Selenide.*; import static com.codeborne.selenide.WebDriverRunner.getWebDriver; //missed "need to hire 3 freelancers" /** * Created by Skripko Sergey on 29.06.2015. */ public class Upwork implements FlProcessor { public static final FlProcessor instance = new Upwork(); public static final String URL_DOMAIN = "https://www.upwork.com/"; public static final String URL_DOMAIN_SUFFIX = URL_DOMAIN + "o/jobs/browse/"; private static Set<SearchQuery> searchQueries = new LinkedHashSet<>(); static { searchQueries.add(new SearchQuery("data science", URL_DOMAIN_SUFFIX + "jobs/?q=skills%3A(data-science)%20OR%20skills%3A(data-analysis)%20OR%20skills%3A(big-data)" + "%20OR%20skills%3A(machine-learning)&highlight=1&sortBy=s_ctime+desc", URL_DOMAIN_SUFFIX + "jobs/?q=skills%3A(r)&highlight=1&sortBy=s_ctime+desc")); searchQueries.add(new SearchQuery("mining", URL_DOMAIN_SUFFIX + "jobs/?q=skills%3A(data-mining)" + "%20OR%20skills%3A(parse)%20OR%20skills%3A(crawlers)%20OR%20skills%3A(web-crawler)&highlight=1&sortBy=s_ctime+desc", URL_DOMAIN_SUFFIX + "jobs/?q=skills%3A(data-scraping)%20OR%20skills%3A(web-scraping)&highlight=1&sortBy=s_ctime+desc")); searchQueries.add(new SearchQuery("hadoop", URL_DOMAIN_SUFFIX + "?q=skills%3A(hadoop)%20OR%20skills%3A(r-hadoop)%20OR%20skills%3A(apache-spark)&sortBy=s_ctime+desc")); searchQueries.add(new SearchQuery("shiny", URL_DOMAIN_SUFFIX + "?q=shiny&cn1%5B%5D=Data%20Science%20%26%20Analytics&sortBy=s_ctime+desc")); searchQueries.add(new SearchQuery("selenium", URL_DOMAIN_SUFFIX + "?q=skills%3A(selenium)" + "%20OR%20skills%3A(selenium-webdriver)%20OR%20skills%3A(scrapy-framework)&sortBy=s_ctime+desc")); searchQueries.add(new SearchQuery("documentum", URL_DOMAIN_SUFFIX + "?q=documentum&sort=create_time+desc", URL_DOMAIN_SUFFIX + "?q=dql&sort=create_time+desc")); } private Upwork() { } @Override public Set<SearchQuery> getSearchQueries() { return searchQueries; } @Override public boolean clickNextPage() { SelenideElement jobListPager = $(JobListSelectors.ROOT).$(JobListSelectors.PAGER); if (!jobListPager.exists()) { return false; } SelenideElement nextButton = jobListPager.$$("li").filter(Condition.hasText("Next")).get(0); if (!nextButton.exists() || nextButton.has(Condition.hasClass("disabled"))) { return false; } nextButton.$("a").click(); return true; } @Override public ElementsCollection getJobElements() { return $(JobListSelectors.ROOT).$$(JobListSelectors.JOB_CLASS); } @Override public Job parseElement(String queryName, SelenideElement el) { SelenideElement titleEl = el.$(JobSelectors.TITLE).shouldHave(Condition.attribute("itemprop", "url")); String idUrl = titleEl.attr("href"); String timePostedStr = el.$(JobSelectors.TIME_POSTED). shouldHave(Condition.attribute("itemprop", "datePosted")).getAttribute("datetime").substring(0, 10); long lifeDayDuration = extractJobDuration(timePostedStr); new Actions(getWebDriver()).keyDown(Keys.CONTROL).click(titleEl).keyUp(Keys.CONTROL).perform(); WebDriver secondDriver = switchTo().window(1); String applicantsStr = $$(".ng-binding").find(Condition.attribute("ng-bind", "data.applicantsCount")).getText(); secondDriver.close(); switchTo().window(0); int proposalCount = Integer.parseInt(applicantsStr); return new Job(getClass().getSimpleName(), queryName, idUrl, proposalCount, lifeDayDuration); } private long extractJobDuration(String strDate) { if (strDate == null || strDate.isEmpty()) { throw new IllegalArgumentException(); } try { Date date = new SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(strDate); return (System.currentTimeMillis() - date.getTime()) / (1000 * 60 * 60 * 24); } catch (ParseException e) { throw new IllegalArgumentException("Date parse exception. Wrong date pattern for:" + strDate); } } static class JobListSelectors { public static final String ROOT = ".js-search-results";//".jobs-list"; public static final String JOB_CLASS = ".job-tile"; public static final String PAGER = ".pagination"; } static class JobSelectors { public static final String TITLE = "header a"; public static final String TIME_POSTED = ".js-posted time"; } }