Ver código fonte

First actual commit

Axel Nordh 3 anos atrás
pai
commit
a6e2a09832

+ 38 - 0
PW-HtmlUnit/.classpath

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>

+ 1 - 0
PW-HtmlUnit/.gitignore

@@ -0,0 +1 @@
+/target/

+ 23 - 0
PW-HtmlUnit/.project

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>PW-HtmlUnit</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+	</natures>
+</projectDescription>

+ 2 - 0
PW-HtmlUnit/.settings/org.eclipse.jdt.core.prefs

@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning

+ 4 - 0
PW-HtmlUnit/.settings/org.eclipse.m2e.core.prefs

@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1

+ 0 - 0
PW-HtmlUnit/logs/elgiganten.log


+ 52 - 0
PW-HtmlUnit/pom.xml

@@ -0,0 +1,52 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>PriceWatch</groupId>
+  <artifactId>PW-HtmlUnit</artifactId>
+  <version>0.0.1-SNAPSHOT</version>
+  
+  <dependencies>
+  <!-- https://mvnrepository.com/artifact/net.sourceforge.htmlunit/htmlunit -->
+	<dependency>
+	    <groupId>net.sourceforge.htmlunit</groupId>
+	    <artifactId>htmlunit</artifactId>
+	    <version>2.66.0</version>
+	</dependency>
+	<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
+	<dependency>
+	    <groupId>org.seleniumhq.selenium</groupId>
+	    <artifactId>selenium-java</artifactId>
+	    <version>4.6.0</version>
+	</dependency>
+	<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
+	<dependency>
+	    <groupId>com.google.guava</groupId>
+	    <artifactId>guava</artifactId>
+	    <version>31.1-jre</version>
+	</dependency>
+	<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
+	<dependency>
+	    <groupId>org.junit.jupiter</groupId>
+	    <artifactId>junit-jupiter-api</artifactId>
+	    <version>5.9.1</version>
+	    <scope>test</scope>
+	</dependency>
+	    <dependency>
+        <groupId>com.google.code.gson</groupId>
+        <artifactId>gson</artifactId>
+        <version>2.2.2</version>
+        <scope>compile</scope>
+    </dependency>
+    <dependency>
+        <groupId>mysql</groupId>
+        <artifactId>mysql-connector-java</artifactId>
+        <version>8.0.30</version>
+    </dependency>
+<!-- https://mvnrepository.com/artifact/online.sanen/sendEmail -->
+<dependency>
+    <groupId>online.sanen</groupId>
+    <artifactId>sendEmail</artifactId>
+    <version>1.0.1</version>
+</dependency>
+
+  </dependencies>
+</project>

+ 45 - 0
PW-HtmlUnit/src/main/java/Main/ParseUrls.java

@@ -0,0 +1,45 @@
+package Main;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import Objects.Product;
+import Parsers.ElGiganten;
+import Parsers.Komplett;
+import Parsers.MediaMarkt;
+import Parsers.NetOnNet;
+import Parsers.Webhallen;
+
+public class ParseUrls {
+
+	List<Product> products = new ArrayList<Product>();
+
+	public ParseUrls(List<Product> urls) {
+		products = urls;
+	}
+
+	public void run() {
+
+		List<Product> elGigantenProducts = products.stream().filter(p -> p.getStore().equals(StoreParser.EL_GIGANTEN)).toList();
+		List<Product> mediaMarktProducts = products.stream().filter(p -> p.getStore().equals(StoreParser.MEDIA_MARKT)).toList();
+		List<Product> komplettProducts = products.stream().filter(p -> p.getStore().equals(StoreParser.KOMPLETT)).toList();
+		List<Product> webhallenProducts = products.stream().filter(p -> p.getStore().equals(StoreParser.WEBHALLEN)).toList();
+		List<Product> netOnNetProducts = products.stream().filter(p -> p.getStore().equals(StoreParser.NET_ON_NET)).toList();
+
+		MediaMarkt mediaMarkt = new MediaMarkt();
+		mediaMarktProducts.forEach(mediaMarkt::checkProduct);
+
+		Komplett komplett = new Komplett();
+		komplettProducts.forEach(komplett::checkProduct);
+
+		Webhallen webhallen = new Webhallen();
+		webhallenProducts.forEach(webhallen::checkProduct);
+
+		NetOnNet netOnNet = new NetOnNet();
+		netOnNetProducts.forEach(netOnNet::checkProduct);
+
+		ElGiganten elGiganten = new ElGiganten();
+		elGigantenProducts.forEach(elGiganten::checkProduct);
+	}
+
+}

+ 51 - 0
PW-HtmlUnit/src/main/java/Main/StoreParser.java

@@ -0,0 +1,51 @@
+package Main;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import Objects.Product;
+
+public class StoreParser {
+
+	private static List<Product> urls = new ArrayList<Product>();
+
+	public static final String NET_ON_NET = "NetOnNet";
+	public static final String WEBHALLEN = "Webhallen";
+	public static final String KOMPLETT = "Komplett";
+	public static final String EL_GIGANTEN = "ElGiganten";
+	public static final String MEDIA_MARKT = "MediaMarkt";
+
+	public static final String NINTENDO_SWITCH_OLED_NEON_RED = "Nintendo Switch Oled Neon Red";
+	public static final String NINTENDO_SWITCH_OLED_WHITE = "Nintendo Switch Oled White";
+
+	public static void main(String[] args) {
+
+		urls.add(new Product(NINTENDO_SWITCH_OLED_NEON_RED, MEDIA_MARKT,
+				"https://www.mediamarkt.se/sv/product/_nintendo-switch-oled-r%C3%B6d-bl%C3%A5-1335207.html"));
+		urls.add(new Product(NINTENDO_SWITCH_OLED_WHITE, MEDIA_MARKT,
+				"https://www.mediamarkt.se/sv/product/_nintendo-switch-oled-vit-1335206.html"));
+
+		urls.add(new Product(NINTENDO_SWITCH_OLED_NEON_RED, EL_GIGANTEN,
+				"https://www.elgiganten.se/product/gaming/spelkonsoler-tillbehor/nintendo-switch/nintendo-konsol/nintendo-switch-oled-gamingkonsol-med-neon-joy-con-kontroller/336728"));
+		urls.add(new Product(NINTENDO_SWITCH_OLED_WHITE, EL_GIGANTEN,
+				"https://www.elgiganten.se/product/gaming/spelkonsoler-tillbehor/nintendo-switch/nintendo-konsol/nintendo-switch-oled-gamingkonsol-med-vita-joy-con-kontroller/336729"));
+
+		urls.add(new Product(NINTENDO_SWITCH_OLED_NEON_RED, KOMPLETT,
+				"https://www.komplett.se/product/1190985/gaming/nintendo/nintendo-switch-oled-2021-64gb-neon"));
+		urls.add(new Product(NINTENDO_SWITCH_OLED_WHITE, KOMPLETT,
+				"https://www.komplett.se/product/1190967/gaming/nintendo/nintendo-switch-oled-2021-64gb-bit"));
+
+		urls.add(new Product(NINTENDO_SWITCH_OLED_NEON_RED, WEBHALLEN,
+				"https://www.webhallen.com/se/product/337847-Nintendo-Switch-OLED-Model-Neon-Red-Neon-Blue"));
+		urls.add(new Product(NINTENDO_SWITCH_OLED_WHITE, WEBHALLEN,
+				"https://www.webhallen.com/se/product/336870-Nintendo-Switch-OLED-Model"));
+
+		urls.add(new Product(NINTENDO_SWITCH_OLED_NEON_RED, NET_ON_NET,
+				"https://www.netonnet.se/art/gaming/spel-och-konsol/nintendo/nintendo-konsol/nintendo-switch-oled-model-neon-redneon-blue/1019709.14414/"));
+		urls.add(new Product(NINTENDO_SWITCH_OLED_WHITE, NET_ON_NET,
+				"https://www.netonnet.se/art/gaming/spel-och-konsol/nintendo/nintendo-konsol/nintendo-switch-oled-model-white/1019708.14414/"));
+
+		ParseUrls parseUrls = new ParseUrls(urls);
+		parseUrls.run();
+	}
+}

+ 68 - 0
PW-HtmlUnit/src/main/java/Objects/Product.java

@@ -0,0 +1,68 @@
+package Objects;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class Product {
+	String name;
+	String store;
+	String url;
+	String updatedDate;
+	float price;
+
+	public Product(String name, String store, String url) {
+		super();
+		this.name = name;
+		this.store = store;
+		this.url = url;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public String getStore() {
+		return store;
+	}
+
+	public String getUrl() {
+		return url;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public void setStore(String store) {
+		this.store = store;
+	}
+
+	public void setUrl(String url) {
+		this.url = url;
+	}
+
+	public String getUpdatedDate() {
+		return updatedDate;
+	}
+
+	public void setUpdatedDate(String updatedDate) {
+		this.updatedDate = updatedDate;
+	}
+
+	public void setUpdatedDate(LocalDateTime updatedDate) {
+		this.updatedDate = updatedDate.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+	}
+
+	public float getPrice() {
+		return price;
+	}
+
+	public void setPrice(float price) {
+		this.price = price;
+	}
+
+	@Override public String toString() {
+		return String.format("Store %s Product name %s price %s updates %s", store, name, price, updatedDate);
+	}
+
+}

+ 25 - 0
PW-HtmlUnit/src/main/java/Objects/test.js

@@ -0,0 +1,25 @@
+/**
+ * 
+ */
+ var prebootInitFn = (function() {
+      
+
+function start(prebootData,win){const _document=(win||window).document||{},currentScript=_document.currentScript||[].slice.call(_document.getElementsByTagName("script"),-1)[0];if(!currentScript)return void console.error("Preboot initialization failed, no currentScript has been detected.");let serverNode=currentScript.parentNode;if(!serverNode)return void console.error("Preboot initialization failed, the script is detached");serverNode.removeChild(currentScript);let eventSelectors=(prebootData.opts||{}).eventSelectors||[];const appData={root:prebootData.opts?getAppRoot(_document,prebootData.opts,serverNode):null,events:[]};prebootData.apps&&prebootData.apps.push(appData),eventSelectors=eventSelectors.map(eventSelector=>(eventSelector.hasOwnProperty("replay")||(eventSelector.replay=!0),eventSelector)),eventSelectors.forEach(eventSelector=>handleEvents(_document,prebootData,appData,eventSelector))}
+
+function createOverlay(_document){let overlay=_document.createElement("div");return overlay.setAttribute("id","prebootOverlay"),overlay.setAttribute("style","display:none;position:absolute;left:0;top:0;width:100%;height:100%;z-index:999999;background:black;opacity:.3"),_document.documentElement.appendChild(overlay),overlay}
+
+function getAppRoot(_document,opts,serverNode){const root={serverNode};return root.clientNode=opts.buffer?createBuffer(root):root.serverNode,opts.disableOverlay||(root.overlay=createOverlay(_document)),root}
+
+function handleEvents(_document,prebootData,appData,eventSelector){const serverRoot=appData.root.serverNode;!serverRoot||eventSelector.events.forEach(eventName=>{const handler=createListenHandler(_document,prebootData,eventSelector,appData);serverRoot.addEventListener(eventName,handler,!0),prebootData.listeners&&prebootData.listeners.push({node:serverRoot,eventName,handler})})}
+
+function createListenHandler(_document,prebootData,eventSelector,appData){const CARET_EVENTS=["keyup","keydown","focusin","mouseup","mousedown"],CARET_NODES=["INPUT","TEXTAREA"],matches=_document.documentElement.matches||_document.documentElement.msMatchesSelector,opts=prebootData.opts;return function(event){const node=event.target;if(!matches.call(node,eventSelector.selector))return;const root=appData.root,eventName=event.type;if(!node||!eventName)return;const keyCodes=eventSelector.keyCodes;if(keyCodes&&keyCodes.length&&!keyCodes.filter(keyCode=>event.which===keyCode).length)return;eventSelector.preventDefault&&event.preventDefault(),eventSelector.action&&eventSelector.action(node,event);const nodeKey=getNodeKeyForPreboot({root,node});if(CARET_EVENTS.indexOf(eventName)>=0){const isCaretNode=CARET_NODES.indexOf(node.tagName?node.tagName:"")>=0;prebootData.activeNode={root,node,nodeKey,selection:isCaretNode?getSelection(node):void 0}}else"change"!==eventName&&"focusout"!==eventName&&(prebootData.activeNode=void 0);if(opts&&!opts.disableOverlay&&eventSelector.freeze){const overlay=root.overlay;overlay.style.display="block",overlay.classList.add("is-loading"),setTimeout(()=>{overlay.style.display="none"},1e4)}eventSelector.replay&&appData.events.push({node,nodeKey,event,name:eventName})}}
+
+function getSelection(node){const nodeValue=(node=node||{}).value||"",selection={start:nodeValue.length,end:nodeValue.length,direction:"forward"};try{(node.selectionStart||0===node.selectionStart)&&(selection.start=node.selectionStart,selection.end=node.selectionEnd?node.selectionEnd:0,selection.direction=node.selectionDirection?node.selectionDirection:"none")}catch{}return selection}
+
+function createBuffer(root){const serverNode=root.serverNode;if(!serverNode||!serverNode.parentNode||serverNode===document.documentElement||serverNode===document.body)return serverNode;const rootClientNode=serverNode.cloneNode(!1);return rootClientNode.style.display="none",serverNode.parentNode.insertBefore(rootClientNode,serverNode),serverNode.setAttribute("ng-non-bindable",""),rootClientNode}
+
+function getNodeKeyForPreboot(nodeContext){const ancestors=[],root=nodeContext.root,node=nodeContext.node;let temp=node;for(;temp&&temp!==root.serverNode&&temp!==root.clientNode;)ancestors.push(temp),temp=temp.parentNode;temp&&ancestors.push(temp);let key=node.nodeName||"unknown";for(let i2=ancestors.length-1;i2>=0;i2--)if(temp=ancestors[i2],temp.childNodes&&i2>0)for(let j=0;j<temp.childNodes.length;j++)if(temp.childNodes[j]===ancestors[i2-1]){key+="_s"+(j+1);break}return key}
+
+
+      return (function initAll(opts,win){const theWindow=win||window,data=theWindow.prebootData={opts,apps:[],listeners:[]};return()=>start(data,theWindow)})({"buffer":true,"replay":true,"disableOverlay":false,"eventSelectors":[{"selector":"input:not([data-preboot-ssr-value]),textarea:not([data-preboot-ssr-value])","events":["keypress","keyup","keydown","input","change"]},{"selector":"select,option","events":["change"]},{"selector":"input:not([data-preboot-ssr-value])","events":["keyup"],"preventDefault":true,"keyCodes":[13],"freeze":true},{"selector":"form","events":["submit"],"preventDefault":true,"freeze":true},{"selector":"input,textarea","events":["focusin","focusout","mousedown","mouseup"],"replay":false},{"selector":"button","events":["click"],"preventDefault":true,"freeze":true},{"selector":"a","events":["mousedown","mouseup"],"freeze":true},{"selector":"input[data-preboot-ssr-value],textarea[data-preboot-ssr-value]","events":["keypress","keyup","keydown","input","change"],"replay":false},{"selector":"input[data-preboot-focus=\"true\"],textarea[data-preboot-focus=\"true\"]","events":["focusin","focusout"],"replay":true}],"appRoot":"elk-app"});
+    })();

+ 39 - 0
PW-HtmlUnit/src/main/java/Parsers/ElGiganten.java

@@ -0,0 +1,39 @@
+package Parsers;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import Objects.Product;
+
+public class ElGiganten extends ParserBase {
+
+	public void checkProduct(Product product) {
+		ChromeDriver driver = getSeleniumDriver();
+
+		driver.get(product.getUrl());
+		WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
+		wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//span[contains(@data-ta,'productprice-text')]")));
+		//
+		WebElement findElement = driver.findElement(By.xpath("//span[contains(@data-ta,'productprice-text')]"));
+
+		String priceValueString = findElement.getText();
+		Float priceValue = formatPrice(priceValueString);
+		float storedProductPrice = getStoredProductPrice(product);
+
+		if ((storedProductPrice != -1 && priceValue < storedProductPrice) || storedProductPrice == -1) {
+			product.setUpdatedDate(LocalDateTime.now());
+			product.setPrice(priceValue);
+			insertProduct(product);
+		}
+
+		driver.close();
+		driver.quit();
+	}
+
+}

+ 51 - 0
PW-HtmlUnit/src/main/java/Parsers/Komplett.java

@@ -0,0 +1,51 @@
+package Parsers;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import com.google.common.collect.Lists;
+
+import Objects.Product;
+
+public class Komplett extends ParserBase {
+
+	public void checkProduct(Product product) {
+		ChromeDriver driver = getSeleniumDriver();
+
+		driver.get(product.getUrl());
+		WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
+		wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(By.xpath("//div[@class='product-price']"), 0));
+		//
+		WebElement foundElement = driver.findElement(By.xpath("//div[@class='product-price']"));
+
+		String dataObject = foundElement.getAttribute("data-initobject");
+
+		ArrayList<String> objectParts = Lists.newArrayList(dataObject.split(","));
+		List<String> list = objectParts.stream().filter(p -> String.valueOf(p.split(":")[0]).equals("\"priceNowRaw\"")).toList();
+
+		float priceValue = -1;
+		if (!list.isEmpty()) {
+			String priceString = list.get(0).split(":")[1];
+			priceValue = formatPrice(priceString);
+		}
+		float storedProductPrice = getStoredProductPrice(product);
+
+		if ((storedProductPrice != -1 && priceValue < storedProductPrice) || storedProductPrice == -1) {
+			product.setUpdatedDate(LocalDateTime.now());
+			product.setPrice(priceValue);
+			insertProduct(product);
+		}
+
+		driver.close();
+		driver.quit();
+	}
+
+}

+ 39 - 0
PW-HtmlUnit/src/main/java/Parsers/MediaMarkt.java

@@ -0,0 +1,39 @@
+package Parsers;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import Objects.Product;
+
+public class MediaMarkt extends ParserBase {
+
+	public void checkProduct(Product product) {
+		ChromeDriver driver = getSeleniumDriver();
+
+		driver.get(product.getUrl());
+		WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
+		wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(By.xpath("//*[@itemprop='price']"), 0));
+		//
+		WebElement foundElement = driver.findElement(By.xpath("//*[@itemprop='price']"));
+
+		String priceValueString = foundElement.getAttribute("content");
+		Float priceValue = formatPrice(priceValueString);
+		float storedProductPrice = getStoredProductPrice(product);
+
+		if ((storedProductPrice != -1 && priceValue < storedProductPrice) || storedProductPrice == -1) {
+			product.setUpdatedDate(LocalDateTime.now());
+			product.setPrice(priceValue);
+			insertProduct(product);
+		}
+
+		driver.close();
+		driver.quit();
+	}
+
+}

+ 39 - 0
PW-HtmlUnit/src/main/java/Parsers/NetOnNet.java

@@ -0,0 +1,39 @@
+package Parsers;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import Objects.Product;
+
+public class NetOnNet extends ParserBase {
+
+	public void checkProduct(Product product) {
+		ChromeDriver driver = getSeleniumDriver();
+
+		driver.get(product.getUrl());
+		WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
+		wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(By.xpath("//div[@class='price-big']"), 0));
+		//
+		WebElement foundElement = driver.findElement(By.xpath("//div[@class='price-big']"));
+
+		String priceValueString = foundElement.getText();
+		Float priceValue = formatPrice(priceValueString);
+		float storedProductPrice = getStoredProductPrice(product);
+
+		if ((storedProductPrice != -1 && priceValue < storedProductPrice) || storedProductPrice == -1) {
+			product.setUpdatedDate(LocalDateTime.now());
+			product.setPrice(priceValue);
+			insertProduct(product);
+		}
+
+		driver.close();
+		driver.quit();
+	}
+
+}

+ 172 - 0
PW-HtmlUnit/src/main/java/Parsers/ParserBase.java

@@ -0,0 +1,172 @@
+package Parsers;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.Properties;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.chrome.ChromeOptions;
+
+import Objects.Product;
+
+public class ParserBase {
+	private static final String USERNAME = "storeParser";
+	private static final String PASSWORD = "Rd0.mw4YVY6LLosq";
+	private static final String DATABASE = "StoreParser";
+	private static final String URL = "jdbc:mysql://nordh.xyz:3306/";
+
+	Connection conn;
+
+	private void openDbConnection() {
+		if (conn == null) {
+			try {
+				conn = DriverManager.getConnection(URL + DATABASE, USERNAME, PASSWORD);
+			} catch (final SQLException e) {
+				throw new RuntimeException(e.getMessage(), e);
+			}
+		}
+	}
+
+	private void closeDbConnection() {
+		try {
+			conn.close();
+		} catch (SQLException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	public void insertProduct(Product product) {
+		openDbConnection();
+		String sql = "INSERT INTO Product (storeName, productName, price, updated) VALUE (?,?,?,?) ON DUPLICATE KEY UPDATE price = ?, updated = ?";
+		try (PreparedStatement stat = conn.prepareStatement(sql)) {
+			stat.setString(1, product.getStore());
+			stat.setString(2, product.getName());
+			stat.setFloat(3, product.getPrice());
+			stat.setString(4, product.getUpdatedDate());
+			stat.setFloat(5, product.getPrice());
+			stat.setString(6, product.getUpdatedDate());
+
+			stat.execute();
+
+			sendUpdatedPriceMail(product);
+
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public void sendUpdatedPriceMail(Product product) {
+		String to = "axelnordh@gmail.com";
+		String from = "axelnordh@gmail.com";
+		String host = "smtp.gmail.com";
+		Properties properties = System.getProperties();
+
+		properties.put("mail.smtp.host", host);
+		String port = "465";
+		properties.put("mail.smtp.port", port);
+		properties.put("mail.smtp.ssl.enable", "true");
+		properties.put("mail.smtp.auth", "true");
+
+		properties.put("mail.smtp.starttls.enable", "true");
+		properties.put("mail.smtp.socketFactory.port", port);
+		properties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+		properties.put("mail.smtp.ssl.checkserveridentity", "true");
+		properties.put("mail.smtp.socketFactory.fallback", "false");
+
+		Session session = Session.getInstance(properties, new javax.mail.Authenticator() {
+			@Override protected PasswordAuthentication getPasswordAuthentication() {
+				return new PasswordAuthentication(from, "fprgcnbbhbwiwdmi");
+			}
+		});
+
+		session.setDebug(true);
+		try {
+			MimeMessage message = new MimeMessage(session);
+
+			message.setFrom(from);
+			message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
+			message.setSubject("Nytt bättre pris från " + product.getStore());
+			message.setText(String.format("%s fick ett bättre pris från %s, nya priset är %s (%s)", product.getName(),
+					product.getStore(), product.getPrice(), product.getUpdatedDate()));
+
+			Transport transport = session.getTransport("smtps");
+			transport.connect(host, Integer.valueOf(port), from, "fprgcnbbhbwiwdmi");
+			transport.sendMessage(message, message.getAllRecipients());
+			transport.close();
+		} catch (MessagingException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public float getStoredProductPrice(Product product) {
+		openDbConnection();
+		String sql = "SELECT price FROM Product WHERE productName = ? AND storeName = ?";
+
+		float result = -1;
+
+		try (PreparedStatement stat = conn.prepareStatement(sql)) {
+			stat.setString(1, product.getName());
+			stat.setString(2, product.getStore());
+
+			ResultSet rs = stat.executeQuery();
+
+			while (rs.next()) {
+				result = rs.getFloat("price");
+			}
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+		return result;
+	}
+
+	protected ChromeDriver getSeleniumDriver() {
+		ChromeOptions options = new ChromeOptions();
+
+		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/src/main/resources/chromedriver.exe");
+		System.setProperty("webdriver.chrome.silentOutput", "true");
+		// Fixing 255 Error crashes
+		options.addArguments("--no-sandbox");
+		options.addArguments("--disable-dev-shm-usage");
+
+		// Options to trick bot detection
+		// Removing webdriver property
+		options.addArguments("--disable-blink-features=AutomationControlled");
+		options.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation"));
+		options.setExperimentalOption("useAutomationExtension", null);
+
+		// Changing the user agent / browser fingerprint
+		options.addArguments("window-size=1920,1080");
+		options.addArguments(
+				"user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36");
+
+		// Other
+		options.addArguments("disable-infobars");
+
+		ChromeDriver driver = new ChromeDriver(options);
+		driver.executeScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
+		return driver;
+	}
+
+	protected void closeDriver(WebDriver driver) {
+		driver.close();
+	}
+
+	public float formatPrice(String priceValueString) {
+		return Float.valueOf(priceValueString.replaceAll("[^\\d.]", ""));
+	}
+
+}

+ 41 - 0
PW-HtmlUnit/src/main/java/Parsers/Webhallen.java

@@ -0,0 +1,41 @@
+package Parsers;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import Objects.Product;
+
+public class Webhallen extends ParserBase {
+
+	public void checkProduct(Product product) {
+		ChromeDriver driver = getSeleniumDriver();
+
+		driver.get(product.getUrl());
+		WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
+		wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(
+				By.xpath("//div[@id='add-product-to-cart']/div[contains(@class,'price-value')]"), 0));
+		//
+		WebElement foundElement = driver
+				.findElement(By.xpath("//div[@id='add-product-to-cart']/div[contains(@class,'price-value')]/span"));
+
+		Float priceValue = formatPrice(foundElement.getText());
+
+		float storedProductPrice = getStoredProductPrice(product);
+
+		if ((storedProductPrice != -1 && priceValue < storedProductPrice) || storedProductPrice == -1) {
+			product.setUpdatedDate(LocalDateTime.now());
+			product.setPrice(priceValue);
+			insertProduct(product);
+		}
+
+		driver.close();
+		driver.quit();
+	}
+
+}

BIN
PW-HtmlUnit/src/main/resources/chromedriver.exe


BIN
PW-HtmlUnit/src/main/resources/geckodriver.exe


BIN
PW-HtmlUnit/src/main/resources/modify_header_value-0.1.8.xpi


+ 81 - 0
PW-HtmlUnit/src/test/java/ElGigantenParserTest.java

@@ -0,0 +1,81 @@
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.time.Duration;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.Alert;
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.NoAlertPresentException;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+
+public class ElGigantenParserTest {
+	private WebDriver driver;
+	private String baseUrl;
+	private boolean acceptNextAlert = true;
+	private StringBuffer verificationErrors = new StringBuffer();
+	JavascriptExecutor js;
+
+	@BeforeAll public void setUp() throws Exception {
+		System.setProperty("webdriver.chrome.driver", "");
+		driver = new ChromeDriver();
+		baseUrl = "https://www.google.com/";
+		driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(60));
+		js = (JavascriptExecutor) driver;
+	}
+
+	@Test public void testUntitledTestCase() throws Exception {
+		driver.get(
+				"https://www.elgiganten.se/product/gaming/spelkonsoler-tillbehor/nintendo-switch/nintendo-konsol/nintendo-switch-oled-gamingkonsol-med-neon-joy-con-kontroller/336728");
+		driver.findElement(By.xpath("//aside[@id='buy-box']/div/elk-buy-box-telecom-offering/div/elk-price/span/span")).click();
+		driver.findElement(By.xpath("//aside[@id='buy-box']/div/elk-buy-box-telecom-offering/div/elk-price/span/span")).click();
+		// ERROR: Caught exception [ERROR: Unsupported command [doubleClick |
+		// xpath=//aside[@id='buy-box']/div/elk-buy-box-telecom-offering/div/elk-price/span/span | ]]
+		// ERROR: Caught exception [unknown command []]
+	}
+
+	@AfterAll public void tearDown() throws Exception {
+		driver.quit();
+		String verificationErrorString = verificationErrors.toString();
+		if (!"".equals(verificationErrorString)) {
+			fail(verificationErrorString);
+		}
+	}
+
+	private boolean isElementPresent(By by) {
+		try {
+			driver.findElement(by);
+			return true;
+		} catch (NoSuchElementException e) {
+			return false;
+		}
+	}
+
+	private boolean isAlertPresent() {
+		try {
+			driver.switchTo().alert();
+			return true;
+		} catch (NoAlertPresentException e) {
+			return false;
+		}
+	}
+
+	private String closeAlertAndGetItsText() {
+		try {
+			Alert alert = driver.switchTo().alert();
+			String alertText = alert.getText();
+			if (acceptNextAlert) {
+				alert.accept();
+			} else {
+				alert.dismiss();
+			}
+			return alertText;
+		} finally {
+			acceptNextAlert = true;
+		}
+	}
+}