Rufen Sie das Python-Skript per PowerShell auf und übergeben Sie PSObject. Geben Sie die analysierten Daten zurück
Einige Hintergrundinformationen: Derzeit frage ich 4Mio-Zeilen (mit 50 Spalten) von einem MS SQL-Server mit dbatools in ein PSObject (in Batch 10.000 Zeilen pro Abfrage), verarbeite die Daten mit PowerShell (viele RegEx-Inhalte) und schreibe zurück in a MariaDb mit SimplySql . Im Durchschnitt bekomme ich ca. 150 Zeilen / Sek. Musste eine Menge Tricks (Net's Stringbuilder etc.) für diese Leistung verwenden, es ist nicht so schlimm imho
Als neue Anforderung möchte ich die Sprache einiger Textzellen erkennen und muss persönliche Daten (Name & Adresse) entfernen. Zu diesem Zweck habe ich einige gute Python- Bibliotheken ( spacy und pycld2 ) gefunden. Ich habe Tests mit pycld2 gemacht - ziemlich gute Erkennung.
Vereinfachter Code zur Verdeutlichung (Hinweis: Ich bin ein Python-Noob):
#get data from MS SQL
$data = Invoke-DbaQuery -SqlInstance $Connection -Query $Query -As PSObject -QueryTimeout 1800 for ($i=0;$i -lt $data.length;$i++){ #do a lot of other stuff here #... #finally make lang detection if ($LangDetect.IsPresent){
$strLang = $tCaseDescription -replace "([^\p{L}\p{N}_\.\s]|`t|`n|`r)+",""
$arg = "import pycld2 as cld2; isReliable, textBytesFound, details = cld2.detect('" + $strLang + "', isPlainText = True, bestEffort = True);print(details[0][1])"
$tCaseLang = & $Env:Programfiles\Python39\python.exe -c $arg } else { $tCaseLang = ''
}
}
#write to MariaDB
Invoke-SqlUpdate -ConnectionName $ConnectionName -Query $Query
Dieser Python-Aufruf funktioniert jedes Mal, zerstört jedoch die Leistung (12 Zeilen / Sek.) Durch den Schleifenaufruf und den Import von pycld2 lib jedes Mal. Das ist also eine lahme Lösung :) Wie oben erwähnt, möchte ich außerdem Spacy verwenden, bei dem einige weitere Spalten analysiert werden müssen, um die persönlichen Daten zu entfernen.
Ich bin mir nicht sicher, ob ich die Stimmung habe, den gesamten PS-Parser in Python umzuwandeln: |
Ich glaube, eine bessere Lösung könnte darin bestehen, das gesamte PSObject von PowerShell an Python zu übergeben (bevor die PS-Schleife beginnt) und es sowie PSObject zurückzugeben - nachdem es in Python verarbeitet wurde -, aber ich weiß nicht, wie ich es kann Realisieren Sie dies mit der Python / Python-Funktion.
Was wäre Ihr Ansatz / Vorschläge, irgendwelche anderen Ideen? Vielen Dank :)
Antworten
Das folgende vereinfachte Beispiel zeigt, wie Sie mehrere [pscustomobject]
( [psobject]
) Instanzen von PowerShell an ein Python-Skript übergeben können ( -c
in diesem Fall als Zeichenfolge übergeben ):
durch Verwendung von JSON als Serialisierungsformat über ConvertTo-Json...
... und diesen JSON über die Pipeline übergeben , die Python über stdin (Standardeingabe) lesen kann .
Wichtig :
Zeichenkodierung :
PowerShell verwendet die in der
$OutputEncoding
Voreinstellungsvariablen angegebene Codierung beim Senden von Daten an externe Programme (z. B. Python). In PowerShell [Core] v6 + wird standardmäßig Stücklistenloses UTF-8 verwendet , in Windows PowerShell jedoch leider ASCII (!) .So wie PowerShell Sie auf das Senden von Text an ein externes Programm beschränkt, interpretiert es auch immer das, was es empfängt, als Text, und zwar basierend auf der darin gespeicherten Codierung
[Console]::OutputEncoding
. Leider verwenden beide PowerShell-Editionen zum jetzigen Zeitpunkt standardmäßig die OEM- Codepage des Systems .Sowohl Sende- und Empfang (BOM-less) UTF-8 in den beiden Powershell - Ausgaben , (vorübergehend) Satz
$OutputEncoding
und[Console]::OutputEncoding
wie folgt dar :
$OutputEncoding = [Console]::OutputEncoding = [System.Text.Utf8Encoding]::new($false)
Wenn Sie möchten, dass Ihr Python-Skript auch Objekte ausgibt , sollten Sie erneut JSON verwenden , mit dem Sie in der PowerShell Objekte analysieren können ConvertFrom-Json.
# Sample input objects.
$data = [pscustomobject] @{ one = 1; two = 2 }, [pscustomobject] @{ one = 10; two = 20 } # Convert to JSON and pipe to Python. ConvertTo-Json $data | python -c @'
import sys, json
# Parse the JSON passed via stdin into a list of dictionaries.
dicts = json.load(sys.stdin)
# Sample processing: print the 'one' entry of each dict.
for dict in dicts:
print(dict['one'])
'@
Wenn es sich bei den zu übergebenden Daten um eine Sammlung einzeiliger Zeichenfolgen handelt , benötigen Sie JSON nicht:
$data = 'foo', 'bar', 'baz' $data | python -c @'
import sys
# Sample processing: print each stdin input line enclosed in [...]
for line in sys.stdin:
print('[' + line.rstrip('\r\n') + ']')
'@
Basierend auf der Antwort von @ mklement0 möchte ich die fertige und getestete Lösung mit der Rückgabe des JSON von Python an Powershell unter Berücksichtigung der korrekten Zeichenkodierung teilen. Ich habe es bereits mit 100.000 Zeilen auf einem Stapel versucht - keine Probleme, fehlerfrei und superschnell :)
#get data from MS SQL
$query = -join@( 'SELECT `Id`, `CaseSubject`, `CaseDescription`, `AccountCountry`, `CaseLang` ' 'FROM `db`.`table_global` ' 'ORDER BY `Id` DESC, `Id` ASC ' 'LIMIT 10000;' ) $data = Invoke-DbaQuery -SqlInstance $Connection -Query $Query -As PSObject -QueryTimeout 1800
$arg = @' import pycld2 as cld2 import simplejson as json import sys, re, logging def main(): #toggle the logging level to stderr # https://stackoverflow.com/a/6579522/14226613 -> https://docs.python.org/3/library/logging.html#logging.debug logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) logging.info('->Encoding Python: ' + str(sys.stdin.encoding)) # consideration of correct character encoding -> https://stackoverflow.com/a/30107752/14226613 # Parse the JSON passed via stdin into a list of dictionaries -> https://stackoverflow.com/a/65051178/14226613 cases = json.load(sys.stdin, 'utf-8') # Sample processing: print the 'one' entry of each dict. # https://regex101.com/r/bymIQS/1 regex = re.compile(r'(?=[^\w\s]).|[\r\n]|\'|\"|\\') # hash table with Country vs Language for 'boosting' the language detection, if pycld2 is not sure lang_country = {'Albania' : 'ALBANIAN', 'Algeria' : 'ARABIC', 'Argentina' : 'SPANISH', 'Armenia' : 'ARMENIAN', 'Austria' : 'GERMAN', 'Azerbaijan' : 'AZERBAIJANI', 'Bangladesh' : 'BENGALI', 'Belgium' : 'DUTCH', 'Benin' : 'FRENCH', 'Bolivia, Plurinational State of' : 'SPANISH', 'Bosnia and Herzegovina' : 'BOSNIAN', 'Brazil' : 'PORTUGUESE', 'Bulgaria' : 'BULGARIAN', 'Chile' : 'SPANISH', 'China' : 'Chinese', 'Colombia' : 'SPANISH', 'Costa Rica' : 'SPANISH', 'Croatia' : 'CROATIAN', 'Czech Republic' : 'CZECH', 'Denmark' : 'DANISH', 'Ecuador' : 'SPANISH', 'Egypt' : 'ARABIC', 'El Salvador' : 'SPANISH', 'Finland' : 'FINNISH', 'France' : 'FRENCH', 'Germany' : 'GERMAN', 'Greece' : 'GREEK', 'Greenland' : 'GREENLANDIC', 'Hungary' : 'HUNGARIAN', 'Iceland' : 'ICELANDIC', 'India' : 'HINDI', 'Iran' : 'PERSIAN', 'Iraq' : 'ARABIC', 'Ireland' : 'ENGLISH', 'Israel' : 'HEBREW', 'Italy' : 'ITALIAN', 'Japan' : 'Japanese', 'Kosovo' : 'ALBANIAN', 'Kuwait' : 'ARABIC', 'Mexico' : 'SPANISH', 'Monaco' : 'FRENCH', 'Morocco' : 'ARABIC', 'Netherlands' : 'DUTCH', 'New Zealand' : 'ENGLISH', 'Norway' : 'NORWEGIAN', 'Panama' : 'SPANISH', 'Paraguay' : 'SPANISH', 'Peru' : 'SPANISH', 'Poland' : 'POLISH', 'Portugal' : 'PORTUGUESE', 'Qatar' : 'ARABIC', 'Romania' : 'ROMANIAN', 'Russia' : 'RUSSIAN', 'San Marino' : 'ITALIAN', 'Saudi Arabia' : 'ARABIC', 'Serbia' : 'SERBIAN', 'Slovakia' : 'SLOVAK', 'Slovenia' : 'SLOVENIAN', 'South Africa' : 'AFRIKAANS', 'South Korea' : 'Korean', 'Spain' : 'SPANISH', 'Sweden' : 'SWEDISH', 'Switzerland' : 'GERMAN', 'Thailand' : 'THAI', 'Tunisia' : 'ARABIC', 'Turkey' : 'TURKISH', 'Ukraine' : 'UKRAINIAN', 'United Arab Emirates' : 'ARABIC', 'United Kingdom' : 'ENGLISH', 'United States' : 'ENGLISH', 'Uruguay' : 'SPANISH', 'Uzbekistan' : 'UZBEK', 'Venezuela' : 'SPANISH'} for case in cases: #concatenate two fiels and clean them a bitfield, so that we not get any faults due line brakes etc. tCaseDescription = regex.sub('', (case['CaseSubject'] + ' ' + case['CaseDescription'])) tCaseAccCountry = case['AccountCountry'] if tCaseAccCountry in lang_country: language = lang_country[tCaseAccCountry] isReliable, textBytesFound, details = cld2.detect(tCaseDescription, isPlainText = True, bestEffort = True, hintLanguage = language) else: isReliable, textBytesFound, details = cld2.detect(tCaseDescription, isPlainText = True, bestEffort = True) #Take Value case['CaseLang'] = details[0][0] #logging.info('->Python processing CaseID: ' + str(case['Id']) + ' / Detected Language: ' + str(case['CaseLang'])) #encode to JSON retVal = json.dumps(cases, 'utf-8') return retVal if __name__ == '__main__': retVal = main() sys.stdout.write(str(retVal)) '@ $dataJson = ConvertTo-Json $data $data = ($dataJson | python -X utf8 -c $arg) | ConvertFrom-Json
foreach($case in $data) {
$tCaseSubject = $case.CaseSubject -replace "\\", "\\" -replace "'", "\'"
$tCaseDescription = $case.CaseDescription -replace "\\", "\\" -replace "'", "\'"
$tCaseLang = $case.CaseLang.substring(0,1).toupper() + $case.CaseLang.substring(1).tolower() $tCaseId = $case.Id $qUpdate = -join @(
"UPDATE db.table_global SET CaseSubject=`'$tCaseSubject`', " "CaseDescription=`'$tCaseDescription`', "
"CaseLang=`'$tCaseLang`' " "WHERE Id=$tCaseId;"
)
try{
$result = Invoke-SqlUpdate -ConnectionName 'maria' -Query $qUpdate
} catch {
Write-Host -Foreground Red -Background Black ("result: " + $result + ' / No. ' + $i)
#break
}
}
Close-SqlConnection -ConnectionName 'maria'
Bitte entschuldigen Sie die unglückliche Hervorhebung der Syntax. Der Skriptblock enthält SQL, Powershell und Python. 🙄