
Интро
Моя история такая же, как и у других сотен айтишников в Украине. Учился я в математическом классе, мою школу на протяжении многих лет окружают недостройки, которые в свое время были притонами. Дальше был физ-мат КПИ, на котором меня научили усердно загребать знания. Ну и классический поиск себя, начиная с середины университетской жизни...
Пробовал я работать в банках, в магазинах электроники и бытовой техники, обувных магазинах, страховании и еще многих местах. К счастью, мой стаж во всех этих отраслях ограничивался одним днем. Были в этой нестабильной череде опыта, и профессии приближенные к IT — php’шные конторки, в которых я окончательно понял, какой работы я не хочу.
Неизменным было то, что я постоянно учил что-то техническое. Сперва HTML, потом CSS, далее РНР, JS, а потом взялся за голову и со злости выучил Java SE. Последняя технология привела меня в ряды автоматизаторов web-приложений и понеслось...
Я показывал отличный результат, осваивал новые вершины, перенимал у своих опытных сотрудников профессиональные навыки, поднимался вверх по карьерной лестнице. И внезапно понял, что мои финансовые возможности не совпадают с желаниями, ведь я все так же как и тот маленький мальчик из математического класса, продолжал жить в районе, где трубы заводов заменяют достопримечательности, а все окружающее напоминает гетто после бомбардировки.
И тут подвернулось дело. Мне понадобились автомобили, много автомобилей! Автомобили всех популярных марок, а также все их модели... И все это нужно было оформить в структурированном виде.
Техническая часть вопроса
Сперва, прошелся по поиску и не нашел данных в удобном для меня виде. Сразу после этого начал просматривать англоязычные автомобильные сайты. Остановился на cars.com, там есть отличный блок, в котором можно искать новые машины. Проклацал я этот функционал и убедился, что список машин меня удовлетворяет. Ну а дальше дело техники...
Открываю эклипс, создаю новый проект с такими зависимостями в pom.xml:
<dependencies><dependency><groupid>org.seleniumhq.selenium</groupid><artifactid>selenium-server</artifactid><version>2.25.0</version></dependency><dependency><groupid>javax.xml</groupid><artifactid>jaxp-api</artifactid><version>1.4.2</version></dependency><dependency><groupid>org.testng</groupid><artifactid>testng</artifactid><version>6.1.1</version><scope>test</scope></dependency></dependencies>
Selenium-server нужен для возможности взаимодействовать с web-интерфейсом через браузер, jaxp-api необходим для формирования
Далее по диагонали освежил jax-документацию в своей памяти и написал незамысловатый класс, который предназначен для занесения в конечный документ марок машин и их моделей.
public class Hijacker { private DocumentBuilderFactory docFactory = null; private DocumentBuilder docBuilder = null; private Document document = null; private Element rootEl = null; private TransformerFactory transformerFactory = null; private Transformer transformer = null; private DOMSource source = null; public Hijacker() { docFactory = DocumentBuilderFactory.newInstance(); try { docBuilder = docFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { e.printStackTrace(); } document = docBuilder.newDocument(); rootEl = document.createElement("cars"); document.appendChild(rootEl); transformerFactory = TransformerFactory.newInstance(); try { transformer = transformerFactory.newTransformer(); } catch (TransformerConfigurationException e) { e.printStackTrace(); } } public void addEntity(String makeVal, String modelVal) { if (modelVal.equals("All Models") || makeVal.equals("All Makes")) { return; } else { Element brandEl = document.createElement("brand"); Attr make = document.createAttribute("make"); make.setValue(makeVal); brandEl.setAttributeNode(make); rootEl.appendChild(brandEl); Element model = document.createElement("model"); model.setTextContent(modelVal); brandEl.appendChild(model); } } public void createReport() throws TransformerException { source = new DOMSource(document); StreamResult result = new StreamResult(new File("E:\\CarEntities.xml")); transformer.transform(source, result); } }
Конструктор и два метода полностью решают вопрос работы с
public class Parser { Hijacker extractor = new Hijacker(); @Test public void parseCars() throws InterruptedException, TransformerException { WebDriver driver = new FirefoxDriver(); driver.get("www.cars.com"); WebElement makeEl = driver.findElement(By.id("newMake")); Select makeDropDown = new Select(makeEl); List<webelement> makeList = makeDropDown.getOptions(); for (WebElement make : makeList) { Thread.sleep(2000); WebElement modelEl = driver.findElement(By.id("newModel")); Select modelDropDown = new Select(modelEl); makeDropDown.selectByVisibleText(make.getText()); List<webelement> modelList = modelDropDown.getOptions(); for (WebElement carModel : modelList) { extractor.addEntity(make.getText(), carModel.getText()); } } extractor.createReport(); driver.close(); } }
В этом классе, пожалуй, следует обратить внимание только на строчку с приостановкой потока. В связи с тем, что в зависимости от того, какую марку автомобиля мы выбираем, подгружается список ее всех возможных моделей, что иногда может происходить медленнее, чем отрабатывает WebDriver.
В результате получаем документ с четкой структурой вида:
<brand make="Acura"><model>TSX</model></brand><brand make="Acura"><model>ZDX</model></brand><brand make="Aston Martin"><model>DB9</model></brand><brand make="Aston Martin«><model>DBS</model></brand>
В заключение хочу сказать, что этот рассказ получился менее захватывающим, чем фильм «Угнать за 60 секунд», но для попытки скрестить технический материал и мой аутсорсинговый юмор — самое оно.