Python Svenska Tecken, (hjälp en nybörjare)

Trädvy Permalänk
Medlem
Registrerad
Dec 2013

Python Svenska Tecken, (hjälp en nybörjare)

Hej!

Jag har stött på ett problem i Python där jag inte kan läsa in svenska tecken.
Använder Python 3.5 och PyCharm 2016.2.3.
Något saknas i min kod antar jag ? Jag har förvisso lagt till "encoding=utf-8", borde de inte bli svenska tecken då ?

Min kod: (webscraping, följer en tutorial)

import urllib.request import re try: url = 'http://www.sweclockers.com/' resp = urllib.request.urlopen(url) respData = resp.read() saveFile = open('data.txt', 'w', encoding='utf-8') saveFile.write(str(respData)) saveFile.close() except Exception as e: print(str(e))

Trädvy Permalänk
Medlem
Registrerad
Jul 2010

Prova att lägga detta högst upp i din källkod:

#!/usr/bin/env python # -*- coding: utf-8 -*-

Trädvy Permalänk
Forumledare
Registrerad
Okt 2002
Skrivet av n413:

import urllib.request import re try: url = 'http://www.sweclockers.com/' resp = urllib.request.urlopen(url) respData = resp.read() saveFile = open('data.txt', 'w', encoding='utf-8') saveFile.write(str(respData)) saveFile.close() except Exception as e: print(str(e))

Din respData kommer som en byte-ström, dvs råa "ettor och nollor" som nu visserligen råkar representera tecken, men vilka tecken det faktiskt representerar är inte klart förrän man applicerar en teckenkodning på den ström man har fått. SweClockers sänder ut UTF-8, så du skulle kunna omvandla din byte-ström till ett internt strängobjekt genom att avkoda strömmen som just UTF-8:

response = urllib.request.urlopen(url) raw_response_data = resp.read() # Detta är ett `bytes`-objekt response_data = raw_response_data.decode('utf-8') # Detta är nu ett strängobjekt

Ett strängobjekt i Python 3 är i praktiken teckenkodningsoberoende när det väl har skapats. Den behöver inte längre veta om att den kom från UTF-8 eller någon annan teckenkodning — den vet nu vilka faktiska tecken som den ska representera (i praktiken lagras den i en modern CPython-tolk som den mest kompakta representationen av UCS-4, UCS-2 och Latin-1 som klarar av innehållet, men det är en implementationsdetalj).

Detta teckenkodningsagnostiska objekt kan du nu sedan hantera transparent i olika sammanhang. Om du vill skriva ut strängen till en terminal så kommer den först fråga terminalen "Vilken teckenkodning gillar du?", koda strömmen enligt denna teckenkodning och skicka ut motsvarande bitström. Vill du skriva till en fil så kommer Python ta reda på vilken teckenkodning du valt att öppna filen med och på samma sätt koda strängen till motsvarande bitström och banka ner den på disk.

Så, ditt response_data-objekt är efter avkodning med UTF-8 nu ett strängobjekt. Om du skapar en fil och väljer teckenkodningen att vara UTF-8 så kommer Python ta strängobjektet, koda det enligt UTF-8-reglerna och skriva det till filen:

#!/usr/bin/env python3 import re import urllib.request URL = 'http://www.sweclockers.com/' FILENAME = 'data.txt' def main(): response = urllib.request.urlopen(URL) raw_response_data = response.read() response_data = raw_response_data.decode('utf-8') with open(FILENAME, 'w', encoding='utf-8') as f: f.write(response_data) if __name__ == '__main__': main()

Varför denna "round trip" till strängobjektet med avkodning/kodning när SweClockers skickar ut UTF-8 direkt? Tja, om det inte finns några prestandamässiga problem så är det ofta en bra reflex att omvandla alla inkommande strängar till ett "korrekt" strängobjekt som man utan större fara kan skicka runt i sin kod utan att behöva minnas vilken teckenkodning som gällde. Tumregeln är att avkoda inkommande data tidigt, och koda det så nära utmatning som möjligt.

Vill man utnyttja att man vet att dokumentets teckenkodning är UTF-8 och man vill banka den direkt till disk så kan man öppna filen i binärläge och skriva bytes-objektet rakt ned till filen:

#!/usr/bin/env python3 import re import urllib.request URL = 'http://www.sweclockers.com/' FILENAME = 'data.txt' def main(): response = urllib.request.urlopen(URL) response_data = response.read() with open(FILENAME, 'wb') as f: f.write(response_data) if __name__ == '__main__': main()

Med det sagt så är det generellt enklare att behandla strängar som strängar snarare än byteströmmar, om inte annat för sin egen sinnesfrids skull.

Jag tog mig friheten att skriva om koden en smula. I stället för att lägga generell kod på den globala nivån så deklarerade jag en main()-funktion som jag kallar på genom besvärjelsen if __name__ == '__main__':, osv. Detta är en konvention som du kommer se mer eller mindre överallt i Python-världen. Anledningen har att göra med import av moduler kontra att köra dem som egna skript, samt att undvika att förorena den globala namnrymden. Det tar inte många knapptryckningar att fixa, undviker problem med odeklarerade funktioner pga deras ordning i filen, med mera, så det är bra att vänja sig vid. Se What does `if __name__ == “__main__”:` do? [StackOverflow].

Jag använde även en kontexthanterare ("context manager") för open-anropet genom nyckelordet with. Detta har följden att filen alltid hanteras korrekt på så sätt att den stängs automatiskt när den trillar ur scope, i stället för att du måste ta ansvar för att explicit kalla close() (i praktiken kallas detta ändå när filen trillar ur scope i "vanliga" Python-tolkar, men det är inte garanterat, och framför allt är det inte garanterat när det händer). Mindre kod att skriva, mer robust kod.

Att lära sig:

  • Vad är ett bytes-objekt? Vad är ett str-objekt?

  • Vad innebär det att avkoda ("decode") något? Vad innebär det att koda ("encode") något? Vilka operationer applicerar på bitströmmar respektive strängar?

  • Vad är en kontexthanterare? Varför vill man använda dessa?

Sista punkten är lite överkurs, men det kan vara trevligt att ha sett det någon gång, då du definitivt kommer se with fler gånger om du fortsätter med Python.

Skrivet av Formel117:

Prova att lägga detta högst upp i din källkod:

#!/usr/bin/env python # -*- coding: utf-8 -*-

Detta anger tolken till Python 2 enligt en vanlig konvention på *nix-system, samt deklarerar att Python-filens teckenkodning är UTF-8. Det rör inte den data som tas emot eller skrivs till fil.

Om filen börjar köras med Python 2 kommer det också bli problem med urllib-importen (motsvarigheten till urllib.request.open är i Python 2 snarare urllib2.urlopen) samt att open i Python 2 inte tar någon encoding-parameter.

Stavning.

Nu med kortare användarnamn, men fortfarande bedövande långa inlägg.

Trädvy Permalänk
Medlem
Registrerad
Dec 2013

@phz: Tack för ett utomordentlig svar!!! Bästa svar jag läst på länge

Jag ska göra min läxa och läsa på om punkterna du listat. Tack! Dock lyckades jag faktiskt tidigare hitta lösningen på just de problemet efter letande (2-3 kvällar) på diverse ställen/tutorials etc, slutligen hittade jag svaret i Python library. Inkommande ström behövde tillägget decode('utf-8). Det tar så väldans tid att lära sig programmera på egen hand.
Jag upplever det oerhört svårt att kliva över den där tröskeln innan man kan börja göra något vettigt på egen hand, utan att hela tiden fastna på nya saker

Nu har jag problemet igen. Tidigare problemet var enklare att förstå, det här problemet känns mer ologiskt.

Jag hämtar nu data från dagens industri istället och försöker läsa in lite statistik, det går ju sådär...

Problemet/ frågan: Hur kommer det sig att stat_1 respektive stat_2 ger så olika output när dess regular expression är desamma vid ett tillfälle i loopen. (stat_1 är"hårdkodat"/statiskt det andra, stat_2, hämtar sitt "regex" från listan "namnbanker")

import re import urllib.request URL = 'http://trader.di.se/index.php/quote/maklarstat/1061816' FILENAME = 'data.txt' def main(): response = urllib.request.urlopen(URL) raw_response_data = response.read() response_data = raw_response_data.decode('utf-8') with open(FILENAME, 'w', encoding='utf-8') as f: f.write(response_data) namnBanker = re.findall(r'18px" style="text-align:left">(.*)</td>', str(response_data)) taBortradtecken = re.split(r'\n|\t', str(response_data)) i = 0 for namn in range(0, 2): regex = str("r'") + str(namnBanker[i]) + str("(.*?)d>'") pat = re.compile(regex, re.MULTILINE) stat_1 = re.findall(r'Svenska Handelsbanken AB(.*?)d>(.*?)class', str(taBortradtecken)) stat_2 = re.findall(pat, str(taBortradtecken)) print("\n--------------------------------------------------\n") print("stat 1: " + str(stat_1)) print("stat 2 : " + str(stat_2)) i += 1 if __name__ == '__main__': main()

PS. dessutom verkar "flaggan" re.MULTILINE totalt verkningslöst, så därför skapade jag ytterligare en sträng "taBortradtecken" (dvs \n och \s) för att kunna använda ett regular expression som löper över flera rader.

Trädvy Permalänk
Forumledare
Registrerad
Okt 2002
Skrivet av n413:

Det tar så väldans tid att lära sig programmera på egen hand.
Jag upplever det oerhört svårt att kliva över den där tröskeln innan man kan börja göra något vettigt på egen hand, utan att hela tiden fastna på nya saker

Det är bara att köra på, men det är nog viktigt att låta inlärningen ta sin tid så att man verkligen förstår varje problem man stöter på, så att man samlar byggstenar för att ständigt förbättra sin förståelse. Stressar man för mycket för att "bara få något gjort" så kommer man efter projektet ha lärt sig mycket mindre än om man tagit sig tid, och därmed lättare fastna i samma mönster i nästa projekt, och i nästa, … .

Skrivet av n413:

Hur kommer det sig att stat_1 respektive stat_2 ger så olika output när dess regular expression är desamma vid ett tillfälle i loopen. (stat_1 är"hårdkodat"/statiskt det andra, stat_2, hämtar sitt "regex" från listan "namnbanker")

regex = str("r'") + str(namnBanker[i]) + str("(.*?)d>'") pat = re.compile(regex, re.MULTILINE) stat_1 = re.findall(r'Svenska Handelsbanken AB(.*?)d>(.*?)class', str(taBortradtecken)) stat_2 = re.findall(pat, str(taBortradtecken))

Uttrycken är inte lika. Prefixet r till en sträng som avgränsas med exempelvis apostrofer är ett kompilatordirektiv som säger till att det i strängen som följer inte ska tolkas några escape-sekvenser, men strängen i sig kommer i ett sådant fall inte innehålla r''.

När du konstruerar ditt regex för stat_2 så konstruerar du strängen (för banknamn "ABG Sundal Collier Norge ASA"):

r'ABG Sundal Collier Norge ASA(.*?)d>'

men mönstret för ditt stat_1 är strängen:

ABG Sundal Collier Norge ASA(.*?)d>

vilket är vad du vill ha. I andra fallet betyder prefixet r i koden bara att du inte vill att Python skulle tolka backslash-sekvenser i strängen som escape-tecken (detta är vanligt (och starkt förespråkat) att alltid göra för bland annat mönster till reguljära uttryck).

Se 2.4.1. Lexical analysis → String and Bytes literals i dokumentationen.

Citat:

PS. dessutom verkar "flaggan" re.MULTILINE totalt verkningslöst, så därför skapade jag ytterligare en sträng "taBortradtecken" (dvs \n och \s) för att kunna använda ett regular expression som löper över flera rader.

re.MULTILINE aktiverar sökning över flera rader, men ditt metatecken . ("punkt") matchar fortfarande bara "vanliga" tecken, och vandrar inte över en radbrytning. Ett sätt att kunna göra vad du verkar försöka göra är att även sätta flaggan re.DOTALL för att låta punkten även matcha radbörjan/radslut.

Du kombinerar multipla flaggor till re-modulen genom "bitvis OR"-operatorn, vilket i Python är | ("pipe"). Exempel:

result = re.search(pattern, haystack, re.MULTILINE|re.DOTALL)

Se

i manualen.

Nu med kortare användarnamn, men fortfarande bedövande långa inlägg.