QA Fest — конференция №1 по тестированию и автоматизации. Финальная программа уже на сайте >>
×Закрыть

Как я угонял автомобили

Интро

Моя история такая же, как и у других сотен айтишников в Украине. Учился я в математическом классе, мою школу на протяжении многих лет окружают недостройки, которые в свое время были притонами. Дальше был физ-мат КПИ, на котором меня научили усердно загребать знания. Ну и классический поиск себя, начиная с середины университетской жизни...

Пробовал я работать в банках, в магазинах электроники и бытовой техники, обувных магазинах, страховании и еще многих местах. К счастью, мой стаж во всех этих отраслях ограничивался одним днем. Были в этой нестабильной череде опыта, и профессии приближенные к 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 необходим для формирования xml-документа, testng отвечает за запуск класса, который будет «угонять» машины.

Далее по диагонали освежил 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);
	}

}

Конструктор и два метода полностью решают вопрос работы с xml-документом. Последний этап требует захода на сайт с машинами и проход по спискам марок и моделей автомобилей. Для этого я использовал ранее упомянутый selenium и testNG.

public class Parser {
	
	Hijacker extractor = new Hijacker();
	
	@Test
	public void parseCars() 
			throws InterruptedException, TransformerException {
		
		WebDriver driver = new FirefoxDriver();
		
		driver.get("http://www.cars.com");
		
		WebElement makeEl = driver.findElement(By.<span></span>id("newMake"));
		Select makeDropDown = new Select(makeEl);
		List<webelement> makeList = makeDropDown.getOptions();
				
		for (WebElement make : makeList) {
			
			Thread.sleep(2000);
			
			WebElement modelEl = driver.findElement(By.<span></span>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 секунд», но для попытки скрестить технический материал и мой аутсорсинговый юмор — самое оно.

LinkedIn

31 комментарий

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Еще один вариант, попроще: Если один раз загрузить www.cars.com с Fiddler, можно обнаружить уже на 99% готовый json рекордсет для контрола по адресу www.cars.com/js/mmyCrp.js , причем в виде готовом для дальнейшего процессинга

Не спортивно же! А так то да. И без всякого Fiddler можно, обычным F12

Ну если родные ключи в замке зажигания, то зачем КРАБ доставать ? :)

еще проще — в Chrome Developer Tools ставится breakpoint на select#newModel (subtree modification), после чего сразу видно, откуда приходят данные. печатаем:

curl -s www.cars.com/…ownsSelect.json | cut -b 9- | python -m json.tool | less

время на все про все — как раз 60 сек. :)

хотя, справедливости ради, надо сказать, что по моему опыту вариант с селениумом тоже имеет право на жизнь — в тех редких случаях, когда сайт нервно реагирует на большое количество явно негуманоидных запросов, а симулировать правдоподобные комбинации cookie, referer и прочих параметров в коде слишком напряжно.

ЗЫ а вот кому хочется настоящего секса — попробуйте вытащить что-нибудь с google+ local.

Обалденный хак :))

Но к слову, у них не везде совпадают производители/модели, и база не самая полная, например в used cars есть Bugatti, а в новых нет :)

А проверка на уникальность?

На уникальность чего? =)

Марки и модели. Перед тем как добавить в xml. Может такие уже есть.

И почему не так


<brand make="Aston Martin">
<model>DB9</model>
<model>DBS</model>
</brand>
?

Этот вариант мне сейчас пришел в голову тоже, когда я все это писал, то не прикинул этот вариант =(

Касательно проверки на уникальность, то я понадеялся, что на сайте нет повторов марок =)

Как минимум по годам точно есть. Точнее, я не заходил но должны быть. Ниже я привёл пример что там должно быть как минимум два астон мартина которые в вашем иксэмэле будут задублированы.

Ну а в целом — я задачи не знаю, может и так прокатит )

Ну и вообще сайт может не захотеть отдать сразу все десять тысяч запросов — я бы на его месте так и сделал. Что тогда? Стирать и заново. Отслеживание уникальности позволит настроить паука на повторный набег или вообще на другой сайт.

Как вы будете различать Aston Martin DB9 и Aston Martin DB9 Volante

Скрапер на Selenium это прикольно.
Это что то новенькое. Интересно как у него со скоростью.

Судя по оДеску все скраперы заказываются на питоне, реже на перле.

Я когда то писал video uploader на вагон сайтов на Watin:)

PHPисты бы взялись за CURL+regexp ;)

Фу, извращение, зачем regexp? Если есть DOM query?

Кляц правой кнопкой мыши(получить XPath) и хвала Zend"у все у наших руках =)

Если верстка кривая — ДОМ на на ней сломается и придется обойтись дубовым дуракоустойчивым регекспом :)

Хотя все парсерные библиотеки и готовые средства языка я вполне приветствую и пользуюсь ими.

В свое время понравился такой вариант.
Nodejs + jquery. Пишем не через DOM, а селекторами.
nodejs.ru/404

под php такие (или почти такие) чудеса тоже есть, но некоторые — кривоваты и с утечками памяти.

Маленькое занудство, надеюсь, вас не обидит... А почему не XSLT?

ЗЫ еще один такой пост и DOU надо будет задуматься о подключении какого-нибудь syntax highlighter’а (вроде того, что юзает stackoverflow)

Наверное потому, что я об XSLT только мельком читал где-то, а XML — ежедневный клиент =)

Парсер, выполненный в виде теста, ето ок.

А возможно ли попросить автора поделиться готовым XML ?

Спасибо!

Ну кто-то же должен был стать первым :)

Хабравирус на доу )

О, Боже, код на доу. Код на доу.

Подписаться на комментарии