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;

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;

    # Fehlerprüfung
    if ($err) {
        Log3 undef, $LOG_INFO, "Google Places ERROR: $err";
        return;
    }

    if ( !$data ) {
        return;
    }

# JSON parsen mit eval
    my $decoded;
    if ( !eval { $decoded = decode_json($data); 1 } ) {
        return Log3(undef, $LOG_INFO, "Google Places JSON decoding error: $@");
    }

    # Places sammeln, Duplikate über place id verhindern
    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 = '/opt/fhem/www/googlelocations/main.json';
    my $json_text;

    # encode_json in eval packen
    if ( !eval { $json_text = encode_json({ places => [ values %all_places_map ] }); 1 } ) {
        return Log3(undef, $LOG_INFO, "Google Places JSON encoding error: $@");
    }

    # Datei schreiben
    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";

    # Upload auf Remote-Server
    my $remote_path = "/home/www/public_html/assets/src/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 (anonymisiert)
# -------------------------
my @placesRequests = (
    {
        type     => "nearby",
        included => [ "restaurant", "bar" ],
        lat      => 00.000000,
        lon      => 00.000000,
        radius   => 1500
    },
    {
        type     => "nearby",
        included => [
            "ice_cream_shop", "bakery", "post_office", "gas_station",
            "pharmacy",       "bank"
        ],
        lat    => 00.000000,
        lon    => 00.000000,
        radius => 1500
    },
    {
        type     => "nearby",
        included => [ "store", "discount_store", "florist" ],
        excluded => [
            "gas_station",      "bakery",
            "ice_cream_shop",   "dessert_shop",
            "auto_parts_store", "restaurant",
            "car_dealer",       "electrician",
            "pharmacy"
        ],
        lat    => 00.000000,
        lon    => 00.000000,
        radius => 1000
    },
    {
        type     => "nearby",
        included => ["gym"],
        excluded => ["sports_club"],
        lat      => 00.000000,
        lon      => 00.000000,
        radius   => 4300
    },
    { type => "text", query => "minigolf in der umgebung" },
    {
        type     => "nearby",
        included => ["sports_club"],
        lat      => 00.000000,
        lon      => 00.000000,
        radius   => 1800
    },
    {
        type     => "nearby",
        included => ["water_park"],
        excluded => ["amusement_center"],
        lat      => 00.000000,
        lon      => 00.000000,
        radius   => 50000
    },
    {
        type     => "nearby",
        included => ["public_bath"],
        excluded => ["water_park"],
        lat      => 00.000000,
        lon      => 00.000000,
        radius   => 25000
    },
    {
        type     => "nearby",
        included => ["amusement_park"],
        excluded => [ "water_park", "public_bath", "zoo", "wildlife_park" ],
        lat      => 00.000000,
        lon      => 00.000000,
        radius   => 50000
    },
    { type => "text", query => "Highlights der Region" },
    {
        type     => "nearby",
        included => ["electric_vehicle_charging_station"],
        lat      => 00.000000,
        lon      => 00.000000,
        radius   => 3000
    },
    {
        type     => "nearby",
        included => ["public_bath"],
        lat      => 00.000000,
        lon      => 00.000000,
        radius   => 1000
    },
    {
        type     => "nearby",
        included => ["zoo"],
        lat      => 00.000000,
        lon      => 00.000000,
        radius   => 25000
    },
);

# -------------------------
# 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};
        }
    }

    HttpUtils_NonblockingGet(
        {
            url     => $url,
            method  => 'POST',
            timeout => 15,
            header  => "Content-Type: application/json\r\n"
              . "X-Goog-Api-Key: YOUR_API_KEY_HERE\r\n"
              . "X-Goog-FieldMask: places.id,places.displayName,places.types,places.websiteUri,places.rating,places.delivery,places.location",
            data     => encode_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 = ();

    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.