Selenium: Testautomatisierung-Tool oder Bot zur Whisky-Datenanalyse
Selenium ist ein bekanntes Framework zum automatisierten Testen von Web-Anwendungen durch den Browser. Selenium automatisiert aber auch gut Prozesse, die mit dem Browser bedient werden. In diesem Artikel stelle ich Selenium anhand eines Browser Klick-Bots vor, der die Begehrtheit von Whiskies berechnet. 🥃
Selenium und Whisky
Weitere Infos zu Selenium findet ihr auf Wikipedia und auf der Selenium Webseite:
Aufsetzen eines Selenium Projektes
Maven Konfiguration
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
</dependency>
Browser Treiber
Selenium verwenden
Browser öffnen und schließen
@Service
class WhiskyCrawler {
private val log = LogManager.getLogger()
private val browser: ChromeDriver
init {
System.setProperty("webdriver.chrome.driver",
"./src/main/resources/drivers/windows/chromedriver.exe")
browser = ChromeDriver()
}
@PreDestroy
fun closeBrowser() {
browser.close()
}
fun collectDataFor(whiskyName: String) {
...
- Die Annotation @Service erzeugt eine Spring Bean. Die main-Methode unserer Spring-Anwendung lädt die WhiskyCrawler Bean und ruft die Methode collectDataFor auf. Die Implementierung von collectDataFor zeige ich weiter unten.
- Der Kotlin init-Block wird direkt nach der Initialisierung durch den Default-Konstruktor ausgeführt. Im init-Block stelle ich die Verbindung zwischen meiner Kotlin-Anwendung und dem Browser-Treiber her, indem ich die System Property "webdriver.chrome.driver" mit dem Pfad zur heruntergeladenen Datei setze. Danach instanziiere ich das Attribut browser mit der Klasse ChromeDriver aus der Selenium-Bibliothek. Dann öffnet sich der Selenium gesteuerte Browser automatisch, so dass wir ihn in der collectDataFor-Methode benutzen können.
- Innerhalb des init-Blocks könnten wir jetzt noch diverse Einstellungen am Selenium gesteuerten Browser vornehmen, z. B. das Setzen eines Proxies. In meiner einfachen Demo ist das nicht nötig. Hier setze ich nur die System-Property, damit Selenium den Chrome Treiber im Dateisystem findet.
- Wenn die Spring Bean WhiskyCrawler beim Beenden des Programms zerstört wird, wird zum Abschluss durch die Spring-Annotation @PreDestroy die Methode closeBrowser ausgeführt. Diese schließt den Selenium gesteuerten Browser durch die Methode close.
Webseite aufrufen und Daten aus html mit XPath lesen
private fun searchWhiskies(whiskyName: String): Map<String, List<String>> {
browser.get("https://whiskybase.com/search?q=%s".format(whiskyName))
val map = mutableMapOf<String, MutableList<String>>()
for(whisky in browser.findElements(
By.xpath("//div[@class='row']//a[@class='clickable']")))
map.putIfAbsent(whisky.text, mutableListOf(whisky.getAttribute(
"href")))?.add(whisky.getAttribute("href"))
return map
}
- browser.get öffnet mittels HTTP GET im Selenium gesteuerten Browser die als Parameter übergebene URL.
- browser.findElements sucht hier mittels XPath (By.xpath) nach HTML Elementen und gibt dann alle zum XPath passenden Elemente als Liste von WebElement Objekten zurück. In der zuvor geöffneten Whiskybase Webseite sind das Links zur Detailseite der einzelnen Whiskies, siehe gelbe Markierungen im nächsten Screenshot.
Suchergebnis für Alrik, einen deutschen, rauchigen Single Malt aus dem Harz 😍 |
- Der hier verwendete XPath-Ausdruck besteht aus 2 Teilen. Im ersten Teil //div[@class='row'] werden alle div-Tags auf der kompletten Seite ausgewählt, die im Attribut class den Wert row haben. Sie sehen so aus: <div class="row" ...>.
Im zweiten Teil werden analog a-Tags innerhalb der im ersten Teil selektierten div-Tags gesucht. Weitere Infos zur Syntax von XPath findet ihr hier: https://www.w3schools.com/xml/xpath_syntax.asp - Den Namen des gefunden Whiskies packe ich als Key in eine Map und die URL zur Detailseite des Whiskies als Value. Da es gleichnamige Whisky Abfüllungen geben kann (z.B. bei Standard Abfüllungen aus verschiedenen Jahren: "Ardbeg Uigeadail"), ist der Value eine Liste von URLs (bzw. List<String>). Zum Auslesen der URL ("href") bietet das Interface WebElement die Methode getAttribute an.
Knöpfe klicken und Exception-Handling
try {
browser.findElement(By.xpath("//button[@name='allow']"))?.click()
} catch (e: Exception) {
// Clicking allow button was not required.
}
- Auf Instanzen des Interfaces WebElement kann die Methode click aufgerufen werden, die einen Mausklick auf dem jeweiligen Element ausführt. Mit browser.findElements habe ich einen Knopf (Button) in der Webseite gesucht. Wenn er gefunden wurde, klicke ich ihn mit der Methode click an.
- Bei Selenium kommt es häufig zu Exceptions, z.B. weil der Knopf im Browser doch nicht angezeigt wird, es beim Laden der Seite einen Timeout gibt usw. Damit mein Selenium basierter Bot weiter läuft und die anderen Whiskies auswertet, fange ich hier jede beliebige Exception und ignoriere sie. Je nach dem was euer Programm tun soll, benötigt ihr natürlich eine andere Fehlerbehandlung.
Formulare ausfüllen
private fun searchWhiskies(whiskyName: String): Map<String, List<String>> {
browser.get("https://whiskybase.com")
val searchField = browser.findElement(By.id("search-input"))
searchField.sendKeys(whiskyName)
searchField.sendKeys(Keys.ENTER)
- Den zu suchende Whisky schreibe ich mit der Methode sendKeys in das Text-Eingabefeld für die Whisky-Suche. Das Text-Eingabefeld holte ich zuvor mit der bekannten findElement Methode.
- Neben einfachen Strings können auch Spezial-Tasten wie Enter mit der sendKeys Methode in den Browser geschickt werden.
Fazit
Whiskybase Detailseite mit vielen Infos die per XPath lesbar sind |
Kommentare