Website nur innerhalb bestimmten Blocks durchsuchen, Dom Manipulation?

berndoa

Top Contributor
Hallo,
ich schriebe vielleicht am besten erst msal was ich vorhabe:

Eine bestimmte Website, auf der ich mit javascript suchen und Buttons klicken will, hat diese Struktur:

[CODE lang="html" title="<"]<div class="product">
<a class="toLookFor" href="blabla"> blabla A blabla 7 </a>

<div class="btnParent34" data-auction="30166787" onclick="show(this)">
</div>
</div>

<div class="product">
<a class="toLookFor" href="blabla"> blabla A blabla 7 </a>

<div class="btnParent34" data-auction="30166787" onclick="show(this)">
</div>
</div>


<div class="product">
<a class="toLookFor" href="blabla"> blabla A blabla 7 </a>

<div class="btnParent34" data-auction="30166787" onclick="show(this)">
</div>
</div>

<div class="product">
<a class="toLookFor" href="blabla"> blabla A blabla 7 </a>

<div class="btnParent34" data-auction="30166787" onclick="show(this)">
</div>
</div>[/CODE]


Kurz erklärt:
Die Seite besteht aus mehreren Blöcken die sich innerhalb div class="product" befinden.
zwischen dem a class und dem div class="btn.."... tag
befindet sich natürlich allerlei code, die a class hat natürlich noch einige sie umgebende tags und so.

nur der vereinfachte code oben sind die tiele die für meine sache relevant sind.

Letztlich will ich durch die seite gehen, den ersten "block" suchen
in dem vom a class="tolookfor" der innere text den buchstaben A sowie die zahl 8 enthält.
und falls der gerade untersucht block in der a class innerhtml das enthält, dann will ich dass er auf das btnparentteil des selben blocks klickt.

also ganz grob gesprochen:
suche nach dem nächsten a class="tolookfor"
falls es nicht A und 8 enthält, wiederhole den letzten schritt.
falls es A und 8 enthält, dann durchsuche den nachfolgenden html text nach dem wort "btnparent34" und klicke auf jenes div element.




frage ist nur wie ich sozusagen meine sucherei auf innerhalb desselben div block beshränke bzw. javascriptmässig einfach den code weiter durchlaufe bis zum auftauchen des gewünschten schlüsselworts.

Wie mache ich sowas am geschicktesten?
 

mihe7

Top Contributor
Sollte in etwa so funktionieren:
Javascript:
[... document.querySelectorAll("div.product")].
    filter(prod => [... prod.querySelectorAll("a.toLookFor")].
            filter(a => a.innerText.match(/A/)).
            filter(a => a.innerText.match(/8/)).length > 0).
    map(prod => prod.querySelector("div.btnParent34")).
    forEach(btn => btn.click());
 

berndoa

Top Contributor
Sollte in etwa so funktionieren:
Javascript:
[... document.querySelectorAll("div.product")].
    filter(prod => [... prod.querySelectorAll("a.toLookFor")].
            filter(a => a.innerText.match(/A/)).
            filter(a => a.innerText.match(/8/)).length > 0).
    map(prod => prod.querySelector("div.btnParent34")).
    forEach(btn => btn.click());
hm, ich vermute mal Das sind wieder Lambda Ausdrücke? Müsste ich mich doch einmal einlesen um zu kapieren was da genau passiert :)
 

berndoa

Top Contributor
Wenn ichs statt nem Buchstaben nach nem String suchen wollen würde, sagen wir "Gurke" würde ich dann auch einfach da
Java:
.filter(a => a.innerText.match(/Gurke/))
schreiben im Code oben?
 

mihe7

Top Contributor
Also einfach [... ] drum herum packen und aus ner nodelist ist ein array geworden?
Ja. Der Spread-Operator funktioniert mit iterierbaren Objekten. Es sollte auch mit Array.from(nl) funktionieren, wobei nl die NodeList wäre.

Wenn ichs statt nem Buchstaben nach nem String suchen wollen würde, sagen wir "Gurke" würde ich dann auch einfach da
Java:
.filter(a => a.innerText.match(/Gurke/))
schreiben im Code oben?
Ja, gematched wird ein regulärer Ausdruck. Damit lässt sich auch nach komplexen Dingen filtern, wie z. B. gültige E-Mail-Adressen etc.
 

berndoa

Top Contributor
Okay, der Part klappt. Nun versage ich total am nächsten part:
Wenn wie oben gewünscht ein Knopf gedrückt wird (habe das foreach durch ein [0].click ersetzt)
dann taucht ein Popup auf mit einem Text drin sowie einem "OK" Button.

just for fun wollte ich mal den ok button drücken lassen, was nicht wirklich funktionierte.
dann wollte ich um zu testen ob iche s überhaupt richtig mache und das richitige dom objekt gefunden wurde, einfahc mal den Text auf der Konsole ausgeben lassen.
Was auch nicht funktionierte.

Ich würde ejtzt sagen ich weiß nicht was ich falsch mache. Aber ich habe bei dem kram mit den Lambda Ausdrücken und diesem verschachtelten/verketteten eh keinen plan was ich tue.
Ich habe einfahc nur versucht, deinen Code zu imitieren und anzupassen.

Aber ich zeig mal was ich habe:
Also das ist der html teil, der sich auf das popup bezieht:

HTML:
<div class="modal-content">
        <div class="modal-header">
          <span class="close" id="x_close">×</span>
          <h4 class="modal-title"><!--?php?--></h4>
        </div>
        <div class="modal-body">Text duh</div>
          <div class="modal-footer">
            <button type="button" onclick="close()" class="modal-cancel" data-dismiss="modal">Ok</button>
          </div>
        </div>

Mein Code (komplett):

Javascript:
$(document).ready(function () {
doit();
popup1();
});

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function doit() {

    await sleep(90);

    [... document.querySelectorAll("div.product_")].
            filter(prod => [... prod.querySelectorAll("a.toLookFor")].
            filter(a => a.innerText.match(/DeFi/)).
            filter(a => a.innerText.match(/10$/)).length > 0).
            map(prod => prod.querySelector("div.btnParent34"))[0].click();

}

async function popup1(){
    await sleep(5000);

    ([... document.querySelectorAll("div.modal-content")].
    filter(div => div.matches(".modal-footer")).
    map(prod => prod.querySelector("button.modal-cancel")))[0].click();



}


Irgendwie bekomme ich da 2 Fehler die mir in der Konsole angezeigt werden:

Code:
1. Quellübergreifende (Cross-Origin) Anfrage blockiert:
Die Gleiche-Quelle-Regel verbietet das Lesen der externen
Ressource auf https://stats.g.doubleclick.net/j/collect?t=dc&aip=1&_r=3
&v=1&_v=j90&tid=UA-147990636-1&cid=1718201843.1622718231&jid=1528056246&
gjid=665055489&_gid=1067567482.1622718231&_u=QACAAUAAAAAAAC~&z=598170086.
 (Grund: CORS-Anfrage schlug fehl).

2. Uncaught (in promise) TypeError: [].filter(...).map(...)[0] is undefined


Ansonsten sind auch noch ein paar Warnungen drin a la:
Source-Map-Fehler: Error: request failed with status 404
Ressourcen-Adresse: blabla
Source-Map-Adresse: jquery.min.map

Ich weiß einfach nichtt was ich falsch mache. oder kanns sein dass die Seite aufgrund Sichehreitseinstellungen oder so nciht das Nötige rausrückt? Sprich ich versuche was automatishc zu machen mittels Dom Manipulation was die Seite nicht haben will und daher die nötigen Sachen nicht rausrückt?
 

mihe7

Top Contributor
Ich weiß einfach nichtt was ich falsch mache. oder kanns sein dass die Seite aufgrund Sichehreitseinstellungen oder so nciht das Nötige rausrückt?
Der Fehler 1 bezieht sich darauf, dass von einem Skript aus eine Ressource angefragt wurde, wobei die Herkunft von Skript und Ressource voneinander abweichen. Damit würde eine Ressource herkunftsübergreifend verwendet. Das lässt der Browser nicht zu (Same-Origin-Policy), außer der angefragte Server erlaubt explizit das Teilen der Ressourcen (Cross-Origin Resource Sharing - CORS).

Ich weiß allerdings nicht, wo die Requests bei Dir herkommen.

Die Query
([... document.querySelectorAll("div.modal-content")]. filter(div => div.matches(".modal-footer")). map(prod => prod.querySelector("button.modal-cancel")))[0].click();
ist aber falsch, denn hier würdest Du ja alle div.modal-content danach filtern, ob sie auch .modal-footer matchen. Tatsächlich lässt sich die Abfrage in dem Fall viel einfacher schreiben:
Javascript:
const cancelButton = ([... document.querySelectorAll("div.modal-content div.modal-footer button.modal-cancel")])[0];
// oder, wenn der Button nur einmal auf der Seite auftaucht:
const cancelButton = document.querySelector("div.modal-content div.modal-footer button.modal-cancel");
 

berndoa

Top Contributor
Der Fehler 1 bezieht sich darauf, dass von einem Skript aus eine Ressource angefragt wurde, wobei die Herkunft von Skript und Ressource voneinander abweichen. Damit würde eine Ressource herkunftsübergreifend verwendet. Das lässt der Browser nicht zu (Same-Origin-Policy), außer der angefragte Server erlaubt explizit das Teilen der Ressourcen (Cross-Origin Resource Sharing - CORS).

Ich weiß allerdings nicht, wo die Requests bei Dir herkommen.

Die Query

ist aber falsch, denn hier würdest Du ja alle div.modal-content danach filtern, ob sie auch .modal-footer matchen. Tatsächlich lässt sich die Abfrage in dem Fall viel einfacher schreiben:
Javascript:
const cancelButton = ([... document.querySelectorAll("div.modal-content div.modal-footer button.modal-cancel")])[0];
// oder, wenn der Button nur einmal auf der Seite auftaucht:
const cancelButton = document.querySelector("div.modal-content div.modal-footer button.modal-cancel");
hm, mein gedanke war eigentlich der:
Erst mal alle divs mit class="modal-content" suchen, dann auf die herausfiltern die ein div mit class="modal-footer" haben .
und dann eben auf jenes div mit modal-footer class abbilden.

Wie hätte ich das richtig gemahct, wenn ich da mit filtern und map und so gearbeitet hätte?

ich verstehe gerade nicht ganz warum deine variante so funktioniert.
da steht ja jetzt einfach die 3 speziellen tags hintereinander ohne dass irgendwie irgendein zusammenhang für die vorgegeben ist.

Woher weiß da das programm das nach dem content divs gesucht werden soll, die footer div enthalten und in denen dann zu den buttons navigiert werden soll?
Weil das liest sich, nur weil die drei klassenausdrücke da stehen, sich jetzt nicht so heraus. woher kommt da die implizite verkettung sozusagen?
 

mihe7

Top Contributor
ich verstehe gerade nicht ganz warum deine variante so funktioniert.
So funktionieren CSS-Selektoren nun einmal (s. https://developer.mozilla.org/de/docs/Web/CSS/CSS_Selectors). Kombiniert man zwei Selektoren, indem man sie durch ein Leerzeichen trennt, werden vom zweiten Selektor alle Elemente ausgewählt, die als Vorfahren ein Element besitzen, das vom ersten Selektor wurde. Sprich: div.modal-content div.modal-footer button.modal-cancel wählt alle button-Elemente mit Klasse modal-cancel aus, die in einem div mit Klasse modal-footer enthalten sind, wobei sich dieses wiederum in einem div mit Klasse modal-content befinden muss.

Wie hätte ich das richtig gemahct, wenn ich da mit filtern und map und so gearbeitet hätte?
Puh, jetzt muss ich ausholen und das ganzen Gedöns erklären.

map bildet ein Array elementweise auf ein neues Array ab. Die Elemente des neuen Arrays entsprechen den Rückgabewerten einer anzugebenden Funktion, die für jedes Element des Ausgangsarrays aufgerufen wird. Hört sich kompliziert an, ist aber ganz einfach:
Javascript:
function verdoppeln(zahl) {
    return 2*zahl;
}

const x = [1,2,3];
const y = x.map(verdoppeln);
Es wird nun also ein Element e nach dem anderen aus x genommen, durch die Funktion verdoppeln gejagt und das Ergebis in einem neuen Array gespeichert, das der Konstante y zugewiesen wird. Die Funktion verdoppeln kann auch anonym angegeben werden:
Javascript:
const x = [1,2,3];
const y = x.map(function(zahl) { return 2*zahl; });
Oder auch kurz per Lambda-Ausdruck:
Javascript:
const x = [1,2,3];
const y = x.map(zahl => 2*zahl);

filter wählt dagegen bestimmte Elemente aus dem Array aus, d. h. die Elemente, für die die angegebene Funktion einen Wert liefert, der als true interpretiert wird. So liefert x.filter(zahl => zahl % 2 == 1) nur die ungeraden Zahlen aus x.

So, jetzt können wir mal durchgehen, was ich eingangs gemacht habe, ich schreibe das mal ein klein wenig um:
Javascript:
[... document.querySelectorAll("div.product")].
    filter(prod => [... prod.querySelectorAll("a.toLookFor")].
            filter(a => a.innerText.match(/A/)).
            filter(a => a.innerText.match(/8/)).length > 0).
    map(prod => prod.querySelector("div.btnParent34")).
    forEach(btn => btn.click());
Zeile 1: erzeuge ein Array, das alle div-Element mit Klasse product enthält.
Zeilen 2-4: filtere die product-Divs
Zeile 5: erzeuge aus den gefilterten product-Divs ein neues Array, das indem jedes product-Div durch den Rückgabewert der angegebenen Funktion ersetzt wird. Die Funktion liefert zu einem gegebenen product-Div das in ihm enthaltene btnParent34-div. Das neue Array enthält somit alle btn-Divs.
Zeile 6: für jedes Element btn aus dem eben erzeugten btn-Div-Array führe btn.click() aus.

Der Filter in den Zeilen 2 bis 4 sorgt dafür, dass nur die product-Divs übrig bleiben, die wenigstens ein a-Element mit Klasse toLookFor enthalten.

So, jetzt kann ich erst Deine Frage beantworten. Die Zeile 5 wäre schon fast das, was Du bräuchtest, denn Du könntest sagen:
Javascript:
[... document.querySelectorAll("div.modal-content")].
    map(prod => prod.querySelector("div.modal-footer")).
    map(footer => footer.querySelector("button.modal-cancel")).
    forEach(btn => btn.click());
Das könnte in Deinem Fall bereits reichen, allerdings könnte es theoretisch sein, dass es mehrere modal-footer in einem modal-content gibt und mehrere modal-cancel in einem modal-footer. Dann funktioniert das so nicht mehr, dafür müsste querySelectorAll statt querySelector verwendet werden. Nun liefert querySelectorAll aber eine NodeList bzw. mittels [... ] ein Array von divs/buttons:

Javascript:
[... document.querySelectorAll("div.modal-content")].
    map(prod => [... prod.querySelectorAll("div.modal-footer")]).
    map(footers => ...)
Die footers in Zeile 3 sind also keine divs mehr sondern ein Array von divs. Jetzt könnte man also schreiben
Javascript:
[... document.querySelectorAll("div.modal-content")].
    map(prod => [... prod.querySelectorAll("div.modal-footer")]).
    map(footers => footers.map(footer => [... footer.querySelectorAll("button.modal-cancel")])
dann würde man ein Array erhalten, dessen Elemente Arrays sind, die ihrerseits button.modal-cancel-Elemente enthalten. Du merkst, es wird hässlich.

Das Problem ist, dass man durch das map ein Element auf ein Array abbildet. Tatsächlich würde man lieber ein Element auf viele Elemente abbilden, nämlich auf diejenigen, die in dem Array enthalten sind. Für solche Fälle gibt es statt map eine andere Möglichkeit: flatMap.
Javascript:
[... document.querySelectorAll("div.modal-content")].
    flatMap(prod => [... prod.querySelectorAll("div.modal-footer")]).
    flatMap(footer => [... footer.querySelectorAll("button.modal-cancel")]).
    forEach(btn => btn.click());
Das Ergebnis von Zeile 2 ist nun kein Array von Arrays (mit modal-footer-divs) mehr, sodern ein flaches Array, das alle modal-footer-divs enthält. Analog gilt das für Zeile 3.
 

berndoa

Top Contributor
So funktionieren CSS-Selektoren nun einmal (s. https://developer.mozilla.org/de/docs/Web/CSS/CSS_Selectors). Kombiniert man zwei Selektoren, indem man sie durch ein Leerzeichen trennt, werden vom zweiten Selektor alle Elemente ausgewählt, die als Vorfahren ein Element besitzen, das vom ersten Selektor wurde. Sprich: div.modal-content div.modal-footer button.modal-cancel wählt alle button-Elemente mit Klasse modal-cancel aus, die in einem div mit Klasse modal-footer enthalten sind, wobei sich dieses wiederum in einem div mit Klasse modal-content befinden muss.


Puh, jetzt muss ich ausholen und das ganzen Gedöns erklären.

map bildet ein Array elementweise auf ein neues Array ab. Die Elemente des neuen Arrays entsprechen den Rückgabewerten einer anzugebenden Funktion, die für jedes Element des Ausgangsarrays aufgerufen wird. Hört sich kompliziert an, ist aber ganz einfach:
Javascript:
function verdoppeln(zahl) {
    return 2*zahl;
}

const x = [1,2,3];
const y = x.map(verdoppeln);
Es wird nun also ein Element e nach dem anderen aus x genommen, durch die Funktion verdoppeln gejagt und das Ergebis in einem neuen Array gespeichert, das der Konstante y zugewiesen wird. Die Funktion verdoppeln kann auch anonym angegeben werden:
Javascript:
const x = [1,2,3];
const y = x.map(function(zahl) { return 2*zahl; });
Oder auch kurz per Lambda-Ausdruck:
Javascript:
const x = [1,2,3];
const y = x.map(zahl => 2*zahl);

filter wählt dagegen bestimmte Elemente aus dem Array aus, d. h. die Elemente, für die die angegebene Funktion einen Wert liefert, der als true interpretiert wird. So liefert x.filter(zahl => zahl % 2 == 1) nur die ungeraden Zahlen aus x.

So, jetzt können wir mal durchgehen, was ich eingangs gemacht habe, ich schreibe das mal ein klein wenig um:
Javascript:
[... document.querySelectorAll("div.product")].
    filter(prod => [... prod.querySelectorAll("a.toLookFor")].
            filter(a => a.innerText.match(/A/)).
            filter(a => a.innerText.match(/8/)).length > 0).
    map(prod => prod.querySelector("div.btnParent34")).
    forEach(btn => btn.click());
Zeile 1: erzeuge ein Array, das alle div-Element mit Klasse product enthält.
Zeilen 2-4: filtere die product-Divs
Zeile 5: erzeuge aus den gefilterten product-Divs ein neues Array, das indem jedes product-Div durch den Rückgabewert der angegebenen Funktion ersetzt wird. Die Funktion liefert zu einem gegebenen product-Div das in ihm enthaltene btnParent34-div. Das neue Array enthält somit alle btn-Divs.
Zeile 6: für jedes Element btn aus dem eben erzeugten btn-Div-Array führe btn.click() aus.

Der Filter in den Zeilen 2 bis 4 sorgt dafür, dass nur die product-Divs übrig bleiben, die wenigstens ein a-Element mit Klasse toLookFor enthalten.

So, jetzt kann ich erst Deine Frage beantworten. Die Zeile 5 wäre schon fast das, was Du bräuchtest, denn Du könntest sagen:
Javascript:
[... document.querySelectorAll("div.modal-content")].
    map(prod => prod.querySelector("div.modal-footer")).
    map(footer => footer.querySelector("button.modal-cancel")).
    forEach(btn => btn.click());
Das könnte in Deinem Fall bereits reichen, allerdings könnte es theoretisch sein, dass es mehrere modal-footer in einem modal-content gibt und mehrere modal-cancel in einem modal-footer. Dann funktioniert das so nicht mehr, dafür müsste querySelectorAll statt querySelector verwendet werden. Nun liefert querySelectorAll aber eine NodeList bzw. mittels [... ] ein Array von divs/buttons:

Javascript:
[... document.querySelectorAll("div.modal-content")].
    map(prod => [... prod.querySelectorAll("div.modal-footer")]).
    map(footers => ...)
Die footers in Zeile 3 sind also keine divs mehr sondern ein Array von divs. Jetzt könnte man also schreiben
Javascript:
[... document.querySelectorAll("div.modal-content")].
    map(prod => [... prod.querySelectorAll("div.modal-footer")]).
    map(footers => footers.map(footer => [... footer.querySelectorAll("button.modal-cancel")])
dann würde man ein Array erhalten, dessen Elemente Arrays sind, die ihrerseits button.modal-cancel-Elemente enthalten. Du merkst, es wird hässlich.

Das Problem ist, dass man durch das map ein Element auf ein Array abbildet. Tatsächlich würde man lieber ein Element auf viele Elemente abbilden, nämlich auf diejenigen, die in dem Array enthalten sind. Für solche Fälle gibt es statt map eine andere Möglichkeit: flatMap.
Javascript:
[... document.querySelectorAll("div.modal-content")].
    flatMap(prod => [... prod.querySelectorAll("div.modal-footer")]).
    flatMap(footer => [... footer.querySelectorAll("button.modal-cancel")]).
    forEach(btn => btn.click());
Das Ergebnis von Zeile 2 ist nun kein Array von Arrays (mit modal-footer-divs) mehr, sodern ein flaches Array, das alle modal-footer-divs enthält. Analog gilt das für Zeile 3.
Vielen Dank für die ausführliche Erklärung! :O

Den "Nachfahrensselektor" mit dem Leerzeichen sowie die ganzen Eltern-Kind-Sachen kannte ich nicht, das macht ja Alles viel einfacher!

Damit kriege ich es vermutlich Alles hin, was ich will :)
 

Oben