Mehrere curl-Abrufe "gleichzeitig" ?

Begonnen von TomLee, 07 Dezember 2025, 22:01:16

Vorheriges Thema - Nächstes Thema

TomLee

Hi,

aus Unerfahrenheit beschäftigt mich die Frage, ob ich in einem Perl-Skript Pausen einlegen muss/sollte, wenn ich ich über curl mehrere API-Endpunkte (sagen wir etwa 20) der gleichen API abrufe.

Ich hab das noch nicht ausprobiert, die Funktionen in einer Schleife hintereinander auszuführen, weil ich mir unsicher bin.
Die Requests würd ich gerne in dem unten angeführten Skript mit einbauen.

Die Frage ist also wie man das richtig macht?

Aktuell bin ich bei 13 Abrufen, die Requests laufen direkt hintereinander in einer Schleife durch und es funzt wie ich es mir vorgestell habe.

So sieht das aktuell aus:


sub mergegooglelocations {
    my $sub = sub {
        my $filename = shift // return;
        my ($error, @content) = FileRead($filename);
        Debug "Fehler beim Lesen von $filename: $error\n" if $error;
        my $json = join "", @content;
        return decode_json($json);
    };

    my $path = '/opt/fhem/www/googlelocations';

    my %seen;       # id => Place-HashRef
    my $total  = 0; # alle Einträge gezählt
    my $dupes  = 0; # doppelte IDs gezählt
    my @dupe_ids;   # Liste der doppelten IDs

    for (sort glob("$path/location*.json")) {
        my $json = $sub->($_);
        next if !$json || !exists $json->{places};

        for (@{ $json->{places} }) {

            # Nur Einträge mit definierter ID
            next unless defined $_->{id} && length $_->{id};
            $total++;

            # Prüfen auf Duplikat
            if ( exists $seen{ $_->{id} } ) {
                $dupes++;
                push @dupe_ids, $_->{id};
                Debug "Doppelter Eintrag: $_->{id} - " . ($_->{displayName}{text} // 'unbekannt');
                next;
            }

            # Speichern – flache Kopie, um Referenzprobleme zu vermeiden
            $seen{ $_->{id} } = { %$_ };
        }

        Debug "Verarbeitete Datei: $_";
    }

    # Werte aus Hash extrahieren
    my @merged_places = values %seen;

    # JSON schreiben
    FileWrite("$path/merged.json",
        JSON->new->utf8->pretty->encode({ places => \@merged_places })
    );

    # Debug-Ausgabe
    Debug "Gesamt-Einträge gelesen: $total";
    Debug "Einzigartige Einträge: " . scalar(@merged_places);
    Debug "Doppelte Einträge: $dupes";
    Debug "IDs der doppelten Einträge: " . join(", ", @dupe_ids) if $dupes;

    # Datei per scp kopieren
    return system("scp $path/merged.json telekom-hosting:/home/www/public_html/assets/src/locations.json");
}

Fertig, bin ich mit dem Code noch nicht, das unless mag ich noch ausbauen und mit encode_json beschäftigen, was damit der Vorteil ist und ob man auf eval dann verzichten kann. Der Zähler der doppelten Einträge ist auch noch um 1 falsch. Das ist aber ein anderes Thema, es funzt erstmal wie es gedacht war.

Gruß Thomas

TomLee

Ok, man muss nur mal den Kasper machen, um auf andere Gedanken zu kommen.

Wsl. ist es so, dass das holen der Daten, ein ganz anderes Thema ist, wie die spätere Verarbeitung.

Das bekomm ich auch ohne Hilfe hin.

TomLee

#2
Ich habs mit Unterstützung jetzt so am laufen:

package main;

use strict;
use warnings;
use Readonly;
use JSON;
use English qw( -no_match_vars );

our $VERSION = '0.01';

# Logging-Level als unveränderliche Konstanten
Readonly my $LOG_INFO => 3;

# Initialisierung
sub googlelocations_Initialize {
    my $hash = shift // return;
    return;
}

# zentraler Speicher für eindeutige Places, Key = place id
my %all_places_map;

# -------------------------
# Callback für Google Places (nur sammeln)
# -------------------------
sub google_places_callback {
    my $param = shift;
    my $err   = shift;
    my $data  = shift;

    if ($err) {
        Log3 undef, $LOG_INFO, 'Google Places ERROR: ' . $err;
        return;
    }

    if ( !$data ) {
        return;
    }

    my $decoded;
    if ( !eval { $decoded = JSON->new->decode($data); 1 } ) {
        return Log3 undef, $LOG_INFO,
          'Google Places JSON decoding error: ' . $EVAL_ERROR;
    }

    if ( exists $decoded->{places} && ref( $decoded->{places} ) eq 'ARRAY' ) {
        for my $place ( @{ $decoded->{places} } ) {
            if ( $place->{id} ) {
                $all_places_map{ $place->{id} } = $place;
            }
        }
    }

    Log3 undef, $LOG_INFO,
        'Google Places Ergebnisse aktualisiert, total '
      . scalar( keys %all_places_map )
      . ' eindeutige Einträge';
    return;
}

# -------------------------
# Nach allen Requests: Datei schreiben und hochladen
# -------------------------
sub finalize_google_places {
    my $file = '/path/to/local/googleplaces.json';
    my $json_text;

    if (
        !eval {
            $json_text =
              JSON->new->encode( { places => [ values %all_places_map ] } );
            1;
        }
      )
    {
        return Log3 undef, $LOG_INFO,
          'Google Places JSON encoding error: ' . $EVAL_ERROR;
    }

    my $ret = FileWrite( $file, $json_text );
    if ($ret) {
        Log3 undef, $LOG_INFO,
          "Google Places ERROR: FileWrite fehlgeschlagen für $file: $ret";
        return;
    }

    Log3 undef, $LOG_INFO, 'Google Places main.json erfolgreich erstellt';

    my $remote_path = '/remote/path/locations.json';
    my $scp_ret     = system( 'scp', $file, "remote-host:$remote_path" );

    Log3 undef, $LOG_INFO,
      'Google Places locations.json auf Server hochgeladen';
    return;
}

# -------------------------
# Request-Liste (Koordinaten und Orte anonymisiert)
# -------------------------
my @placesRequests = (
    { type => "nearby", included => [ "restaurant", "bar" ], lat => 0, lon => 0, radius => 1000 },
    { type => "nearby", included => [ "bakery", "pharmacy" ], lat => 0, lon => 0, radius => 1000 },
    { type => "nearby", included => ["store"], excluded => ["gas_station"], lat => 0, lon => 0, radius => 500 },
    { type => "text", query => "minigolf umgebung" },
    { type => "text", query => "Highlights" },
);

# -------------------------
# Dispatcher für Requests
# -------------------------
sub do_places_request {
    my $hash = shift;

    my $i   = $hash->{helper}{idx};
    my $req = $placesRequests[$i];

    if ( !$req ) {
        finalize_google_places();
        return;
    }

    my ($url, $body);

    if ( $req->{type} eq 'text' ) {
        $url  = 'https://places.googleapis.com/v1/places:searchText';
        $body = { textQuery => $req->{query}, languageCode => 'de' };
    }
    else {
        $url  = 'https://places.googleapis.com/v1/places:searchNearby';
        $body = {
            includedTypes       => $req->{included},
            locationRestriction => {
                circle => {
                    center => { latitude => $req->{lat}, longitude => $req->{lon} },
                    radius => $req->{radius},
                },
            },
            maxResultCount => 20,
            rankPreference => 'POPULARITY',
            languageCode   => 'de',
        };
        if ( defined $req->{excluded} ) {
            $body->{excludedTypes} = $req->{excluded};
        }
    }

    my $json_body;
    if ( !eval { $json_body = JSON->new->encode($body); 1 } ) {
        return Log3 $hash->{NAME}, 1,
            'Google Places JSON encoding error: ' . $EVAL_ERROR;
    }

    HttpUtils_NonblockingGet(
        {
            url     => $url,
            method  => 'POST',
            timeout => 15,
            header  => "Content-Type: application/json\r\n"
              . "X-Goog-Api-Key: API_KEY\r\n"
              . "X-Goog-FieldMask: places.id,places.displayName,places.types,places.websiteUri,places.rating,places.location",
            data     => $json_body,
            hash     => $hash,
            callback => \&google_places_callback,
        }
    );

    $hash->{helper}{idx}++;
    InternalTimer( gettimeofday() + 1, 'do_places_request', $hash );
    return;
}

# -------------------------
# Startet alle Requests
# -------------------------
sub start_google_places {
    my $hash = shift;
    $hash->{helper}{idx} = 0;
    %all_places_map = ();         # vorher leeren
    do_places_request($hash);
    return;
}
1;


Wenn jemand etwas sehen sollte, was man besser machen kann, hab ich nix dagegen das er es kund tut.

Beta-User

Zum Umgang mit Jason siehe https://forum.fhem.de/index.php?msg=1227235

Und ein explizites "use ..." schadet vermutlich auch nicht 😉.
Server: HP-elitedesk@Debian 13, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

TomLee


Beta-User

Zitat von: TomLee am 08 Dezember 2025, 18:57:55Habs ergänzt, danke für den Hieb.
... aber nur in der Minimal-Version...

Das nicht-umcodierende "new" gefällt nicht?
Server: HP-elitedesk@Debian 13, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

TomLee

Muss ich nochmal lesen, später, jetzt geht nicht mehr, mir raucht der Kopf trotz großer Hilfe.

TomLee

Hab mich nochmal mit beschäftigt. Das nimmt ja nie ein Ende wenn man weiter liest 🙈

Soll es jetzt Unicode (JSON->new->encode) oder besser utf-8 (JSON->new->utf8->encode) sein? Ich schlaf besser nochmal drüber...

Beta-User

Zitat von: TomLee am 08 Dezember 2025, 23:55:19Soll es jetzt Unicode (JSON->new->encode) oder besser utf-8 (JSON->new->utf8->encode) sein? Ich schlaf besser nochmal drüber...
Nun ja, die Kurzform ist eher:

Wenn man decode_json() verwendet, ist das imo die Kurzform von "JSON->new->utf8->decode()". Das geht davon aus, dass sich die Gegenstelle an die vereinbarten Standards hält und tatsächlich UTF8-encodierte Daten liefert. Das ist nur leider nicht immer der Fall, und FHEM kann mindestens in den Fällen dann besser mit dem umgehen, was "JSON->new->decode()" ermittelt hat. Für "normgerechte" Daten macht die nicht-transformierende Variante keinen Unterschied ;) .

Fürs Einpacken verwende ich zwischenzeitlich in der Mehrzahl der Fälle Bordmittel, also "toJSON()" aus fhem.pl. Das macht es _nicht_ normgerecht, was bisher aber nur dann zu Problemen geführt hat, wenn Zahlen und boolsche Werte im Spiel waren (und die Gegenstelle dann auch noch geprüft hat)...
Server: HP-elitedesk@Debian 13, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors