Hi Swiffer 👋 ,
hier nun mein Vorschlag, für deine Website Automatisierung/Steuerung richner.teamonline.ch, bzw. für die Anforderung Daten der Website in eine CSV Datei zu überführen.
#AutoIt3Wrapper_AU3Check_Stop_OnWarning=y
#AutoIt3Wrapper_Run_Au3Stripper=y
#AutoIt3Wrapper_UseUpx=n
#Au3Stripper_Parameters=/sf /sv /mo /rm /rsln
Opt('MustDeclareVars', 1)
#include-once
#include <File.au3>
#include "..\lib\au3WebDriver\wd_helper.au3"
#include "..\lib\au3WebDriver\wd_capabilities.au3"
; initialization ---------------------------------------------------------------
Global Const $sDriver = 'Firefox' ; Chrome|Firefox
Global Const $bIsHeadlessMode = False ; False|True
Global Const $iDelay = 300 ; Verzögerung als Unterstützung für ein robustes Warteverhalten (Seitenaufbau, Klicks, Texte)
Global $sSession = Null
; processing -------------------------------------------------------------------
_GetNewestDriver() ; Dient zum aktualisieren des WebDrivers, kann später auskommentiert werden
_SetLogLevel() ; Log level auf error ($_WD_DEBUG_Error) reicht meiner Meinung nach, kann natürlich variiert werden
_SetupDriver() ; Session für chrome oder firefox (WebDriver) erstellen
_Actions() ; Steuerung der Artikel-mit-Preise Webseite
_TeardownDriver() ; WebDriver wieder herunterfahren
; setup and teardown functions -------------------------------------------------
Func _GetNewestDriver()
_WD_UpdateDriver(StringLower($sDriver), _PathFull('..\util\webDriver'))
EndFunc
Func _SetLogLevel()
;~ $_WD_DEBUG = $_WD_DEBUG_None ; no logging
$_WD_DEBUG = $_WD_DEBUG_Error ; logging in case of Error
;~ $_WD_DEBUG = $_WD_DEBUG_Info ; logging with additional information
;~ $_WD_DEBUG = $_WD_DEBUG_Full ; logging with full details for developers
EndFunc
Func _SetupDriver()
_SetDriverOptions()
_WD_Startup()
_WD_CapabilitiesStartup()
Local $sCapabilities = _
(StringLower($sDriver) == 'chrome') _
? _CreateChromeDriverCapabilities() _
: _CreateFirefoxDriverCapabilities()
$sSession = _WD_CreateSession($sCapabilities)
EndFunc
Func _SetDriverOptions()
Local Const $sDriverExe = _
(StringLower($sDriver) == 'chrome') _
? 'chromedriver.exe' _
: 'geckodriver.exe'
Local Const $sDriverPath = _PathFull('..\util\webDriver')
Local Const $sDriverExeFilePath = $sDriverPath & '\' & $sDriverExe
Local Const $iPort = _
(StringLower($sDriver) == 'chrome') _
? 9515 _
: 4444
Local Const $sDriverParams = _
(StringLower($sDriver) == 'chrome') _
? '--verbose --log-path="' & $sDriverPath & '\chromedriver.log"' _
: '--log trace'
_WD_Option('Driver', $sDriverExeFilePath)
_WD_Option('Port', $iPort)
_WD_Option('DriverParams', $sDriverParams)
EndFunc
Func _CreateChromeDriverCapabilities()
_WD_CapabilitiesAdd('alwaysMatch', 'chrome')
_WD_CapabilitiesAdd('w3c', True)
_WD_CapabilitiesAdd('excludeSwitches', 'enable-automation')
_WD_CapabilitiesAdd('args', '--window-size=1366,768')
If $bIsHeadlessMode Then
_WD_CapabilitiesAdd('args', '--headless')
EndIf
Return _WD_CapabilitiesGet()
EndFunc
Func _CreateFirefoxDriverCapabilities()
_WD_CapabilitiesAdd('alwaysMatch', 'firefox')
_WD_CapabilitiesAdd('browserName', 'firefox')
_WD_CapabilitiesAdd('acceptInsecureCerts', True)
_WD_CapabilitiesAdd('binary', 'C:\Program Files\Mozilla Firefox\firefox.exe') ; Ggf. einmalig diesen Pfad anpassen
_WD_CapabilitiesAdd('args', '--window-size=1366,768')
If $bIsHeadlessMode Then
_WD_CapabilitiesAdd('args', '--headless')
EndIf
Return _WD_CapabilitiesGet()
EndFunc
Func _TeardownDriver()
_WD_DeleteSession($sSession)
_WD_Shutdown()
EndFunc
; website functions ------------------------------------------------------------
Func _Actions()
Local Const $sCsvFilePath = _CreateArticlesAndPricesCsvFile() ; CSV Datei anlegen
_OpenArticlesWebsite() ; Webseite öffnen
Local Const $iMaxSiteCount = _GetPaginationMaxSiteCount() ; Anzahl der Seiten ermitteln, durch die navigiert werden muss,
; um alle Artikel/Preise zu berücksichtigen (nicht nur die der ersten Seite)
For $i = 1 To $iMaxSiteCount - 1 Step 1 ; Hier "- 1" da wir uns bereits auf der ersten Seite befinden
Local $aListOfArticles = _GetListOfArticles() ; Artikel der jeweiligen Seite holen
Local $aListOfPrices = _GetListOfPrices() ; Preise der jeweiligen Seite holen
; Dieser Schritt dient nur der Sicherheit, dass zu jedem Artikel auch ein dazugehöriger Preis gefunden wurde
Local $aListOfArticlesAndPrices = _VerifyArticleCountAndPriceCountMatches($aListOfArticles, $aListOfPrices)
; CSV Datei mit gefundenen Werte füllen
_WriteArticlesAndPricesToCsvFile($sCsvFilePath, $aListOfArticles, $aListOfPrices)
_ChooseNextArticlePage() ; Zur nächsten Seite navigieren
; und von vorn (Daten sammeln und in CSV schreiben)
Next
EndFunc
Func _CreateArticlesAndPricesCsvFile()
Local Const $sFilePath = @ScriptDir & '\' & @YEAR & @MON & @MDAY & '-' & @HOUR & @MIN & @SEC & '-artikelnummer-verkaufspreis.csv'
Local Const $sCsvColumnNames = 'Artikelnummer;Verkaufspreis' & @CRLF
_WriteFile($sFilePath, $sCsvColumnNames)
Return $sFilePath
EndFunc
Func _OpenArticlesWebsite()
_NavigateTo('https://richner.teamonline.ch/waschtische-bidets-badezimmermoebel/aufsatzwaschtische-keramik')
EndFunc
Func _GetPaginationMaxSiteCount()
Local Const $sPaginationTextSelector = '(//ul[@class="pagination"])[1]/li[contains(@class, "label")]'
Local Const $sPaginationText = _GetElementText($sPaginationTextSelector)
Local Const $sRegExPatternOfMaxSiteCount = ' von (\d+)'
Local Const $iReturnMatchesFlag = 1
Return StringRegExp($sPaginationText, $sRegExPatternOfMaxSiteCount, $iReturnMatchesFlag)[0]
EndFunc
Func _GetListOfArticles()
Local Const $sArticleDescriptionSelector = '//h6[@class="product-itemnumber-desc"]'
Local $aListOfElementsTexts = _GetElementsTexts($sArticleDescriptionSelector)
Return _RemoveUnnecessaryTextsFromList($aListOfElementsTexts)
EndFunc
Func _RemoveUnnecessaryTextsFromList($aList)
Local Const $iCount = _GetCount($aList)
For $i = 0 To $iCount Step 1
$aList[$i] = StringReplace($aList[$i], 'Artikel-Nr.: ', '')
$aList[$i] = StringReplace($aList[$i], 'Farbe: ', '')
$aList[$i] = StringReplace($aList[$i], 'Ausführung: ', '')
$aList[$i] = StringReplace($aList[$i], @LF, '')
Next
Return $aList
EndFunc
Func _GetListOfPrices()
Local Const $sArticlePriceSelector = '//div[@class="product-price "]/span'
Return _GetElementsTexts($sArticlePriceSelector)
EndFunc
Func _VerifyArticleCountAndPriceCountMatches($aArticles, $aPrices)
If _GetCount($aArticles) == _GetCount($aPrices) Then
Return
EndIf
Local Const $iErrorIconFlag = 16
Local Const $sTimeoutInSeconds = 10
Local Const $sErrorMessage = 'Anzahl gefundener Artikel und die Anzahl der gefundenen Preise stimmt nicht überein.'
MsgBox($iErrorIconFlag, 'Error', $sErrorMessage, $sTimeoutInSeconds)
_TeardownDriver() ; WebDriver vorzeitig herunterfahren (bei Bedarf auskommentieren)
EndFunc
Func _WriteArticlesAndPricesToCsvFile($sFile, $aArticles, $aPrices)
For $i = 0 To _GetCount($aArticles) Step 1
_AppendToFile($sFile, $aArticles[$i] & ';' & $aPrices[$i] & @CRLF)
Next
EndFunc
Func _ChooseNextArticlePage()
Local Const $sPaginationArrowNextSelector = '(//ul[@class="pagination"])[1]//li[@class="arrow next "]'
_ClickElement($sPaginationArrowNextSelector)
EndFunc
; webdriver functions ----------------------------------------------------------
Func _NavigateTo($sUrl)
_WD_Navigate($sSession, $sUrl)
_WD_LoadWait($sSession, $iDelay)
EndFunc
Func _FindElement($sSelector)
Local $sElement = _WD_FindElement($sSession, $_WD_LOCATOR_ByXPath, $sSelector)
If @error <> $_WD_ERROR_Success Then
ConsoleWrite('Error for XPath selector ''' & $sSelector & '''.' & @CRLF)
_TeardownDriver() ; WebDriver vorzeitig herunterfahren (bei Bedarf auskommentieren)
EndIf
Return $sElement
EndFunc
Func _FindElements($sSelector)
Return _WD_FindElement($sSession, $_WD_LOCATOR_ByXPath, $sSelector, Default, True)
EndFunc
Func _WaitFor($sSelector)
Local Const $iTimeoutInMilliseconds = 5000
Local Const $iElementVisibleFlag = 1
_WD_WaitElement($sSession, $_WD_LOCATOR_ByXPath, $sSelector, $iDelay, $iTimeoutInMilliseconds, $iElementVisibleFlag)
EndFunc
Func _GetElementText($sSelector)
_WaitFor($sSelector)
Return _WD_ElementAction($sSession, _FindElement($sSelector), 'text')
EndFunc
Func _GetElementsTexts($sSelector)
Local Const $aListOfElements = _FindElements($sSelector)
Local Const $iCount = _GetCount($aListOfElements)
Local $aListOfElementsTexts[$iCount + 1]
For $i = 0 To $iCount Step 1
$aListOfElementsTexts[$i] = _WD_ElementAction($sSession, $aListOfElements[$i], 'text')
Next
Return $aListOfElementsTexts
EndFunc
Func _ClickElement($sSelector)
_WaitFor($sSelector)
_WD_ElementAction($sSession, _FindElement($sSelector), 'click')
EndFunc
; helper functions -------------------------------------------------------------
Func _WriteFile($sFile, $sText)
Local Const $iUtf8WithoutBomAndOverwriteCreationMode = 256 + 2 + 8
Local $hFile = FileOpen($sFile, $iUtf8WithoutBomAndOverwriteCreationMode)
FileWrite($hFile, $sText)
FileClose($hFile)
EndFunc
Func _GetCount($aList)
Return UBound($aList) - 1
EndFunc
Func _AppendToFile($sFile, $sText)
Local Const $iUtf8WithoutBomAndAppendMode = 256 + 1
Local $hFile = FileOpen($sFile, $iUtf8WithoutBomAndAppendMode)
FileWrite($hFile, $sText)
FileClose($hFile)
EndFunc
Alles anzeigen
- Die wichtigsten Stellen habe ich kommentiert.
- Ich hoffe das reicht dir, ansonsten sind die Funktionen und Variablen treffend benannt (wie im post vorher bereits angekündigt 😅).
- Frage bitte gerne nach, wenn dir irgendwas unklar sein sollte 🤝 .
- Fehlerbehandlung ist nur minimal vorhanden, da sobald die Seite geladen ist (im WebDriver, chrome oder firefox), sie recht stabil ist und daher schätze ich den Bedarf dazu als gering ein.
- Der Ablauf diesbzgl. kann noch ausgebaut/robuster gemacht werden (wirst du sehen ob du da mehr brauchst oder nicht).
- Gerne auch hierzu nachfragen bei Bedarf.
- Derzeit besteht eine leichte Verzögerung von 300 ms $iDelay, Zeile 18, für das Interagieren mit den Website-Elementen.
- Beim lesen von Texten (Artikel und Preise etc.) sowie bei Klicks ist dies so. Kannst du gern noch anpassen, doch zu weniger als 150 ms würde ich nicht raten.
- Dies dient der Robustheit der Ausführung. Es wird damit etwas langsamer, doch darauf kommt es dir sicherlich nicht an oder?
- 💡 Eine Unschönheit ist, dass ich den automatischen Lauf im headless Modus ($bIsHeadlessMode), Zeile 17, nicht erfolgreich umsetzen konnte.
- Also dafür müsste ich noch etwas Zeit aufwenden um das "Warum" zu klären und dies dann zu fixen. Jedoch auf Grund deiner Ausführungen sehe ich auch hier den Bedarf erstmal nicht.
- Wie das ganze im headless Modus funktioniert, ist aber vorbereitet und du kannst sehen wie es sein müsste.
- Falls du doch möchtest, dass der Browser unsichtbar seine Arbeit verrichtet und dein Skript die CSV anlegt, gib Bescheid. Dann setze ich mich vielleicht nochmal ran 🤝 .
- Randnotiz: Im kommenden WebDriver Tutorial wird die Strukturierung nochmal anders sein.
Das Skript allein, ist so nicht lauffähig. Alle Abhängigkeiten1, welche benötigt werden, inklusive des Main.au3 Skriptes können hier heruntergeladen werden.
Das Ergebnis, der Ausführung ist, dass eine CSV Datei mit den gefundenen Artikelnummern und Verkaufspreisen angelegt wird. Also Beispiel für den heutigen Lauf:
20230126-171516-artikelnummer-verkaufspreis.csv
Artikelnummer;Verkaufspreis
234057100242;893.00
234057100244;998.00
234064100242;942.00
234064100244;1'047.00
234051100242;439.00
234051100244;547.00
234072100242;535.00
234072100244;640.00
212144242;770.00
212144244;875.00
212149242;842.00
212149244;947.00
212156241;806.00
212156243;911.00
212157241;842.00
212157243;947.00
213120100241;330.00
213120100243;435.00
234551100;683.00
234552100;974.00
234155100183;710.00
234155100184;835.00
234156100183;767.00
234156100184;892.00
234157100183;668.00
234157100184;793.00
234158100183;725.00
234158100184;850.00
234161100183;835.00
234161100184;959.00
234162100183;891.00
234162100184;1'015.00
234163100183;851.00
234163100184;977.00
234164100183;909.00
234164100184;1'035.00
234188100183;484.00
234188184;609.00
234189100183;540.00
234189184;665.00
234190100183;519.00
234190184;644.00
234191100183;575.00
234191184;700.00
234192100183;608.00
234192184;733.00
234193100183;665.00
234193184;790.00
234194100183;625.00
234194184;750.00
234195100183;681.00
234195184;806.00
234196100183;608.00
234196184;733.00
234197100183;665.00
234197184;790.00
234198100183;625.00
234198184;750.00
234199100183;681.00
234199184;806.00
234171242;909.00
234171244;1'034.00
234172242;781.00
234172244;909.00
234173242;790.00
234173244;918.00
234174242;926.00
234174244;1'051.00
234176100244;989.00
234177100244;1'153.00
212650100242;326.00
212650100244;437.00
212651100241;326.00
212651100243;437.00
212652100241;326.00
212652100243;437.00
212653100242;352.00
212653100244;461.00
212654100241;371.00
212654100243;478.00
212655100241;371.00
212655100243;478.00
212656100242;362.00
212656100244;473.00
212657100241;352.00
212657100243;461.00
212658100241;352.00
212658100243;461.00
212659100241;380.00
212659100243;484.00
212660100241;380.00
212660100243;484.00
212661100242;376.00
212661100244;480.00
212662100241;390.00
212662100243;497.00
212663100241;390.00
212663100243;497.00
212699100241;329.00
212699100243;426.00
292145;926.00
292146;926.00
292147;926.00
292148;926.00
292149;926.00
292150;926.00
292151;926.00
292152;813.00
292153;669.00
212892100242;351.00
212625241;560.00
212625242;560.00
212625100293;694.00
212625100294;694.00
212627241;1'104.00
212627242;1'104.00
212627100293;1'238.00
212627100294;1'238.00
212900241;488.00
212900242;488.00
212900100293;626.00
212900100294;626.00
Alles anzeigen
Dateiname ist @ScriptDir & '\' & @YEAR & @MON & @MDAY & '-' & @HOUR & @MIN & @SEC & '-artikelnummer-verkaufspreis.csv'. Anpassbar in Zeile 142.
Viel Erfolg Swiffer damit 😀 .
Viele Grüße
Sven
1 Der benötigte WebDriver (entweder Chrome oder Firefox), wird via "_WD_UpdateDriver()" heruntergeladen. Daher ist dieser nicht im verlinktem Verzeichnis vorhanden.