Zwei dict zusammenführen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Jyll
User
Beiträge: 26
Registriert: Montag 26. Juli 2021, 14:18

Hallo liebe Leute, ich möchte von eine Webseite mit beautifoulsoup die Daten ein (pro Table ein dict mit ca. 168 Datensätzen, Table1/dict1 = Länder, Table2/dict2 = Preise, 1xEur, 1xUSD) einlesen. Gelingt ja auch erstmal.
Nun möchte ich beide myDict (1 und 2) zusammenführen. Was ich auch mache, das gelingt mir nicht. Kann mir jemand bitte helfen. Bitte nicht meckern. Ich bin noch sehr neu und suche sehr viel selbst. Vielen Dank.

Code: Alles auswählen

from bs4 import BeautifulSoup
import requests
import csv

url = f'https://de.globalpetrolprices.com/gasoline_prices/'
url2 = f'https://www.finanzen.net/waehrungsrechner/us-dollar_euro'
output_filepath = '/home/Energiepreis.csv'

daten = requests.get(url)
soup = BeautifulSoup(daten.content, 'html.parser')
table = soup.find('div', attrs={'id': 'outsideLinks'})
table2 = soup.find('div', attrs={'id': 'graphic'})
daten2 = requests.get(url2)
soup2a = BeautifulSoup(daten2.content, 'html.parser')
soup2b = BeautifulSoup(daten2.text, 'html.parser')

kurs_name = soup2b.find_all('span', attrs={'id': 'currency-second-display'})
kurs_name2 = soup2a.find_all('span', attrs={'id': 'currency-second-display'})
kurs_zeile = soup2b.find('span', attrs={'id': 'currency-second-display'})
kurs = kurs_zeile.text.split(" ",4)
a = kurs[3].replace(',','.')
summe_1 = float(a)

with open(output_filepath, 'w', encoding='utf-8') as csv_schreib_datei:
    writer = csv.writer(csv_schreib_datei)
    writer.writerow(['Land','USD', 'EUR'])
    myDict0 = {}

    a = 1
    for i in table.find_all('a'):
        link_name = (i.get('href')).replace('gasoline_prices/','').replace('/','')
        list0 = [link_name]
        myDict1 = {a: link_name}
        print(myDict1)
        a = a + 1

    a = 1
    for x in table2.find_all('div', attrs={'style': 'position: absolute; top: 2px; left: 7px; height: 15px; color: #000000;'}):
        myDict2 = {}
        preis_name = (x.text)
        preis_name = str(preis_name)
        preis_eur = float(preis_name)/ float(a)
        list1=[str(preis_name),str(preis_eur)]
        #data1 = [str(link_name), str(preis_name), str(preis_eur)]
        myDict2 = {a: [str(preis_name),str(preis_eur)]}
        a = a + 1

def merge_two_dicts(x, y):
    z = myDict2.copy()  # start with keys and values of x
    z.update(myDict1)  # modifies z with keys and values of y
    return z

print(merge_two_dicts(myDict1, myDict2))



Sirius3
User
Beiträge: 18276
Registriert: Sonntag 21. Oktober 2012, 17:20

Benutze gleich von Anfang an sprechende Namen. `i` für ein HTML-Element? Oder `x`?
my-Präfixe sind eigentlich nie sinnvoll, weil sie keine Aussagekraft haben.
list0, list1, myDict1 und myDict2 sind jeweils Objekte mit nur einem Element, die in jedem Schleifendurchgang wieder überschrieben werden.
Dann haben die Wörterbücher als Schlüssel nur aufsteigende Nummern. Dafür sind Wörterbücher nicht gedacht.

Die Frage ist also, wie sehen die Daten aus, die Du mergen willst, und was soll das Ergebnis sein?
Jyll
User
Beiträge: 26
Registriert: Montag 26. Juli 2021, 14:18

@Sidrius, Danke schon mal. Also im Internet steht es so ('Land' ist eine html-table und 'USD' ist eine andere html-table):

Land: I USD:
Venezuela I 0.040
Iran I 0.060
usw.

Ich möchte es gerne in der csv-Datei so:
Land: I USD: I EUR
Venezuela I 0.040 I 0.034
Iran I 0.060 I 0.054
usw.

myDict1 nach meiner for schleife so: myDict1 = {1: Venezuela, 2: Iran, 3: usw.}
myDict2 nach meiner for-Schleife so: myDict2 = {1: ['0.040', '0.034'], 2: ['0.060', '0.054'}
mein Wunsch ist myDict3 = {1: ['Venezuele' , '0.040' , '0.034'], 2: ['Iran', '0.060', '0.054'], usw.}
und natürlich dann in der csv-Datei:
writer.writerows( ['Venezuele' , '0.040' , '0.034'], ['Iran', '0.060', '0.054'], usw. )

Ich hab wohl eine Denkblockade gerade.
myDict1 und 2 wird geändert. Habs jetzt nur schnell mal eben nochmal so hingeschrieben.

sorry.... ... vielleicht hab ich's jetzt doch verstanden, das was Du meintest
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Jyll: Wie Sirius3 schon schrieb sind Wörterbücher wo die Schlüssel einfach nur aufsteigende Zahlen sind, die falsche Datenstruktur. Sequenzuelle Daten gehören in eine Liste.

Du erstellst immer neue Wörterbücher und bindest die an den gleichen Namen, wodurch natürlich das Wörterbuch aus dem vorherigen Schleifendurchlauf weg ist. Du musst vor dem Datensammeln *leere* Wörterbücher, beziehungsweise Listen anlegen, und die dann *befüllen*, in dem Du neue Werte hinzufügst.

Die Namen sind teilweise wirklich schlecht. `daten` ist kein guter Name für ein `response`-Objekt. Dann `BeautifulSoup`-Objekte nicht nur nummerieren, sondern auch noch eine zweite Stufe mit `soup2a` und `soup2b` ist auch verirrend. Vor allem weil nicht so wirklich klar ist, warum Du aus der letztlich gleichen Quelle zwei `BeautifulSoup`-Objekte erstellst und beide abwechselnd benutzt. Bei den Abfragen ändert sich ja kein Zustand in diesem Objektbaum, es macht also keinen Sinn da zwei solche Objekte aufzubauen, statt einfach nur eines zu verwenden.

Man muss auch nicht zwanghaft Nummern an Namen hängen, wenn die Namen gar nicht gleichzeitig in Gebrauch sind. `daten` und `daten2` müssen nicht gleichzeitig existieren, also könnte man `daten2` auch einfach `daten` nennen. Beziehungsweise `response`, weil das jeweils besser zu dem Wert passt.

Die `soup`-Objekte müssen auch nicht parallel existieren.

Man sollte die Antwort vom Server prüfen ob die eine tatsächliche Antwort oder eine Fehlermelung enthält.

f-Zeichenkettenliterale machen nur Sinn, wenn man da auch wirklich einen Wert hineinformatiert.

Insgesamt sollte da mehr Ordung rein und klare einzelne im Code sichtbare und nachvollziehbare Schritte. Du öffnest schon die Ausgabedatei bevor Du überhaupt die Daten aus dem HTML geholt hast. Normalerweise macht man das schön eines nach dem anderen. Erst die Daten von der einen URL abfragen und aus dem HTML holen. Dann die Daten von der anderen URL abfragen und aus dem HTML holen.

Dann die Daten zusammenführen. Falls dabei noch keine passende Datenstruktur zum Speichern heraus kommt, die Daten in eine solche Struktur überführen.

Und dann erst die Ausgabedatei öffnen und die Daten speichern.

Jeden dieser Schritte am besten in einer eigenen Funktion, die man separat entwickeln und testen kann.

Codewiederholungen auch in eigene Funktionen herausziehen. HTML anfragen und ein `BeautifulSoup`-Objekt daraus machen, ist beispielsweise als Code fast identisch zweimal vorhanden.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Die Betreiber der ersten Website verkaufen übrigens sowohl die Daten als auch den Zugang zu einer API über die man die abfragen kann. Die mögen das ziemlich sicher nicht so gerne, wenn man die einfach kostenlos abgreift.

Eine passende Datensstruktur wäre jedenfalls eine Liste aus Listen mit einer Liste pro Tabellenzeile, wobei das erste Element der Ländername und das zweite der Dollarpreis als Zahl ist.

Die Listen lassen sich dann einfach um den Europreis erweitern, wenn man den Umrechnungskurs geholt hat.

Bei CSV-Dateien ist es wichtig das `newline`-Argument anzugeben.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import csv
from pathlib import Path

import requests
from bs4 import BeautifulSoup

PETROL_PRICES_URL = "https://de.globalpetrolprices.com/gasoline_prices/"
CURRENCY_CONVERTER_URL = (
    "https://www.finanzen.net/waehrungsrechner/us-dollar_euro"
)
OUTPUT_FILE_PATH = Path.home() / "Energiepreis.csv"


def get_soup(url):
    response = requests.get(url)
    response.raise_for_status()
    return BeautifulSoup(response.content, "html.parser")


def get_usd_eur_factor(url):
    text = get_soup(url).find("span", id="currency-first-display").text
    one, usd, equals, factor_text, eur = text.split()
    if (one, usd, equals, eur) != ("1", "USD", "=", "EUR"):
        raise ValueError(f"unexpeded text: {text!r}")

    return float(factor_text.replace(",", "."))


def iter_country_names_and_prices(url):
    soup = get_soup(url)

    country_nodes = soup("a", "graph_outside_link")
    dollar_per_litre_nodes = soup.select("#graphic > div > div > div")
    if len(country_nodes) != len(dollar_per_litre_nodes):
        raise ValueError(
            f"#countries ({len(country_nodes)})"
            f" ≠ #prices ({len(dollar_per_litre_nodes)})"
        )

    return zip(
        (node.text.strip() for node in country_nodes),
        (float(node.text) for node in dollar_per_litre_nodes),
    )


def main():
    usd_eur_factor = get_usd_eur_factor(CURRENCY_CONVERTER_URL)

    rows = (
        [country_name, dollar_price, dollar_price * usd_eur_factor]
        for country_name, dollar_price in iter_country_names_and_prices(
            PETROL_PRICES_URL
        )
    )
    with OUTPUT_FILE_PATH.open("w", encoding="utf-8", newline="") as file:
        writer = csv.writer(file)
        writer.writerow(["Land", "USD", "EUR"])
        writer.writerows(rows)


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
pillmuncher
User
Beiträge: 1530
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Wenn du etwas mit aufsteigenden Nummern in ein Dictionary packst, willst du eigentlich Listen verwenden. Und du musst dir auch klarmachen, was du mit "Dictionaries zusammenführen" meinst. Und das dann hinschreiben. Weder Dictionaries noch Listen haben eine eingebaute Methode guess_what_i_want_and_do_that().

Warum sind url und url2 f-Strings, also Format-Strings, wenn da gar nichts formatiert wird? Konstanten schreibt man übrigens KOMPLETT_GROSS.

Warum nummerierst du andauernd Variablennamen? Das ist doch total unübersichtlich. url sollte PRICES_URL und url2 sollte vermutlich EXCHANGE_RATE_URL heißen. soup, soup2, soup2a, soup2b, table und table2 - ich habe erst verstanden, was da drinsteht, nachdem ich den hanzen Code durchgearbeitet habe. Stell dir mal vor, dein Programm hat nicht 50 oder 100 Zeile sonder 50.000 oder 100.000. Gewöhn dir gleich an, vernünftige Namen zu verwenden. Es gilt der alte Aphorimus von Phil Karlton: "There are only two hard things in Computer Science: cache invalidation and naming things." Deswegen muss man an seinen Benennungen arbeiten und lernen, wie man es richtig macht. Siehe auch:
Bild

Warum wandelst du überall Strings in Strings um? Was soll das bringen? Meinst du, die werden dadurch noch mehr zu Strings?

Warum zerlegst du URLS von Hand, wenn es dazu urllib.parse in der Standard-Bibliothek gibt? Und da es sich um hier eine relative URL handelt, ist pathlib.Path sogar noch einfacher.

Geldbeträge sollten nicht durch floats repräsentiert werden, sondern entweder durch Decimals oder als Cent-Beträge in Integer. Siehe: https://0.30000000000000004.com

Warum dividierst du in deiner zweiten Schleife den Preis durch den aktuellen Index a? Mit dem du das weiter oben definierte a überschreibst, das den Kurs (als String) enthält? Vermutlich wolltest du durch summe_1 dividieren, was ein unglaublich wieder schlechter Name ist, schon allein weil es sich dabei gar nicht um eine Summe handelt. Warum nicht exchange_rate?

Da es keine Hausaufgabe zu sein scheint, hier dein Programm zum Lernen in sauber und getestet:

Code: Alles auswählen

import csv
import requests
from bs4 import BeautifulSoup
from decimal import Decimal, localcontext
from operator import itemgetter
from pathlib import Path


PRICES_URL = 'https://de.globalpetrolprices.com/gasoline_prices/'
EXCHANGE_RATE_URL = 'https://www.finanzen.net/waehrungsrechner/us-dollar_euro'
CSV_FILE_NAME = 'Energiepreis.csv'


def main():

    with localcontext() as ctx:
        # siehe https://docs.python.org/3/library/decimal.html
        ctx.prec = 4

        # Daten einlesen:
        prices_html = requests.get(PRICES_URL)
        exchange_rate_html = requests.get(EXCHANGE_RATE_URL)

        # Preisdaten finden:
        prices_soup = BeautifulSoup(prices_html.content, 'html.parser')
        countries = prices_soup.find('div', attrs={'id': 'outsideLinks'})
        prices = prices_soup.find('div', attrs={'id': 'graphic'})

        # Wechselkurs finden:
        exchange_rate_soup = BeautifulSoup(exchange_rate_html.text, 'html.parser')
        exchange_rate = Decimal(
            exchange_rate_soup
            .find('span', attrs={'id': 'currency-second-display'})
            .text
            .split(" ",4)[3]
            .replace(',','.')
        )

        country_names = []
        prices_usd_raw = []
        price_infos = []

        for a in countries.find_all('a'):
            # siehe https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parts
            country_names.append(Path(a.get('href')).parts[1])

        style = 'position: absolute; top: 2px; left: 7px; height: 15px; color: #000000;'
        for price_usd_raw in prices.find_all('div', attrs={'style': style}):
            prices_usd_raw.append(price_usd_raw.text)

        for country_name, price_usd_raw in zip(country_names, prices_usd_raw):
            price_usd = Decimal(price_usd_raw)
            price_eur = price_usd / exchange_rate
            price_infos.append({
                'Land': country_name,
                'USD': price_usd,
                'EUR': price_eur,
            })
        with open(CSV_FILE_NAME, 'a', encoding='utf-8') as csv_file:
            # siehe https://docs.python.org/3/library/csv.html#csv.DictWriter
            csv_writer = csv.DictWriter(csv_file, fieldnames=('Land', 'USD', 'EUR'))
            csv_writer.writeheader()
            csv_writer.writerows(sorted(price_infos, key=itemgetter('Land')))


if __name__ == '__main__':
    main()
Die Ausgabe-Datei ist jetzt sogar alphabetisch sortiert.

Ich erwarte natürlich, dass du jede Funktion etc. die du nicht kennst in der Dokimentation zur Standard-Bibliothel nachschlägst.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
pillmuncher
User
Beiträge: 1530
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

__blackjack__ hat geschrieben: Dienstag 12. Oktober 2021, 15:52 Die Betreiber der ersten Website verkaufen übrigens sowohl die Daten als auch den Zugang zu einer API über die man die abfragen kann. Die mögen das ziemlich sicher nicht so gerne, wenn man die einfach kostenlos abgreift.
Zitat:

Code: Alles auswählen

www.GlobalPetrolPrices.com is licensed under a
Creative Commons Attribution-NonCommercial-NoDerivs 3.0
Bei www.finanzen.net habe ich nicht nachgesehen, aber Wechselkurse kriegt man leicht auch woanders, wenn das ein Problem sein sollte.
In specifications, Murphy's Law supersedes Ohm's.
Jyll
User
Beiträge: 26
Registriert: Montag 26. Juli 2021, 14:18

@sirius, @__black__, @pilmuncher, so. Hab es jetzt hinbekommen. Es ging einfach darum, die For-Schleifen zu üben. Bin da wohl rückfällig geworden (VBA). Danke für die Hilfe. Ich hänge meinen neuen Code ab (writer) rein. Und: @__black__: war eine reine Übungssache für Listen und Schleifen. Suche mir eben ab und an paar Beispiele, um es zu verstehen. Rein private Natur. Für Euch siehst vielleicht noch nicht ausgereift aus. Ich bin schon mal etwas zufrieden. Danke für die Hilfe und viele Grüße
L = []
z = 1
for i in range(4):
for y in table.find_all('a'):
y = (y.get('href')).replace('gasoline_prices/','').replace('/','')
L.append(y)
z = z + 1
print(L)

M = []
N = []
z = 1
for i in range(4):
for x in table2.find_all('div', attrs={'style': 'position: absolute; top: 2px; left: 7px; height: 15px; color: #000000;'}):
x = str(x.text)
x_Eur = float(x)/ float(a)
y = str(x_Eur)
M.append(x)
N.append(str(x_Eur))
z = z + 1
print(M)
print(N)
print(int(z)-1)
print(list(L)[1])

O = []
z = 0
K = 168
for i in range(0,168):
for O in M:
O = [str(list(L)), str(list(M)),str(list(N))]
z = z + 1
print(O)
data = O
writer.writerows([data])

Sirius3
User
Beiträge: 18276
Registriert: Sonntag 21. Oktober 2012, 17:20

Neuer Code, immer noch sehr schlechte Variablennamen. Du tust Dir und uns keinen Gefallen, wenn Du so kryptisch programmierst.
Warum durchläufst Du alles 4 mal? z wird nie gebraucht.
In Variablen wird immer der korrekte Datentyp gespeichert, `a` sollte also bereits ein float sein, und nicht jedes mal umgewandelt werden. y wird im zweiten Schleifenblock gar nicht gebraucht, und in M und N sollten Zahlen stehen, keine Strings.
Woher kommt die Magische 168?
L, M und N sind Listen, warum machst Du Kopien von den Listen nur um auf ein Element zuzugreifen?
Warum wird O so oft überschrieben, wie es Elemente in M gibt?
Warum wird O in data umgenannt? Warum benutzt Du writerows, nur um eine 1-elementige Liste zu schreiben, die Du für den Aufruf extra erzeugst?

Code: Alles auswählen

countries = [
    anchor.get('href').replace('gasoline_prices/','').replace('/','')
    for anchor in table.find_all('a')
]

prices_us = []
prices_eur = []
for element in table2.find_all('div', attrs={'style': 'position: absolute; top: 2px; left: 7px; height: 15px; color: #000000;'}):
    price_us = float(element.text)
    price_eur = price_us / exchange_rate
    prices_us.append(price_us)
    prices_eur.append(price_eur)

writer.writerows(zip(countries, prices_us, prices_eur))
Antworten