Podstawy obsługi Web API Allegro.pl - Web Services i moduł suds-jurko w Pythonie

Podstawy obsługi Web API Allegro.pl - Web Services i moduł suds-jurko w Pythonie

Dzisiaj o tym jak ożenić Pythona z Web API udostępnionym przez Allegro.pl. Jest to pierwszy, wprowadzający do tematu wpis z nadchodzącej serii. Na wstępie chciałbym zaznaczyć, że nie będę wgłębiał się w teorię, terminologię ani zasady działania Web Services, czy SOAP, WSDL, XML, Łosie, Jelenie, Sarny, Dziki, Kuny. Wymienione elementy postaram się ominąć jak najszerszym łukiem i tylko niezbędne pobieżnie nakreślić.

Ktoś może z oburzeniem spytać: “To jak to tak, niby wpis o tym jak używać Web Services a nic nie będziesz tłumaczył?!”, już odpowiadam: “Tak, w sensie, że nie będę tłumaczył”. Skupie się jedynie na praktyce, ponieważ cały temat Web Services jest tak obszerny, że aż się odechciewa pisać ten wpis. No, ale nie przejmuj się czytelniku! Zapewniam, że do korzystania z zasobów Web API serwisu Allegro.pl nie potrzebujesz tej wiedzy.

Tych, którzy jednak liczyli na jakieś wyjaśnienia jak Web API u Allegro działa, kieruje do dokumentacji, gdzie jest to całkiem nieźle wyjaśnione (więc po co powtarzać): WebAPI w pigułce - Oficjalna dokumentacja

Ten wpis poświęcę na omówienie samej komunikacji z Web Serwisem, o tym jak to zrobić przy użyciu Python i biblioteki suds-jurko (fork oryginalnej biblioteki suds). Jak zwykle będą przykłady banalne i “po najmniejszej linii oporu”, bez przejmowania się o wydajność i bezpieczeństwo, dlatego jeżeli piszesz “dla siebie” - to spoko, ale jak chcesz komuś opchnąć jakiś skrypt - to poczekaj do następnej części gdzie będzie o REST API Allegro (bezpieczniejsze logowanie).

Uzyskanie klucza Web API.

Update 16-07-2018!

Zanim zaczniemy, musimy uzyskać klucz Web API z panelu naszego konta na Allegro.pl. Służy on do identyfikacji podczas komunikacji, ale nie jest to uwierzytelnienie (od tego będzie id sesji).

Logujemy się na nasze konto w Allegro.pl, następnie przechodzimy do zakładki Moje konto, tam na dole na ekranie głównym odnajdujemy sekcję WebAPI, a w niej odnośnik Informacje i ustawienia lub od razu do panelu Zarządzanie aplikacjami Allegro

Moje allegro

Dla osób z tekstowstrętem, cały proces w obrazkach.

Proces rejestracji aplikacji

Dla jasności, dane wygenerowane można zamiennie używać dla REST API jak i Web API, w tym przypadku naszym kluczem Web API będzie “Client ID / klucz WebAPI”.

Klucze Web API sprzed 24.05.2018

Uwaga! Jeżeli generowałeś dane dostępowe przed 24 maja 2018 roku, zamiast Client ID posiadasz klucz WebAPI. Znajdziesz go pod tym samym adresem, pod którym są pozostałe dane dostępowe. Klucz WebAPI jest przyporządkowany do danego konta. Na jednym koncie może być wygenerowany tylko jeden klucz.

Jeżeli więc wcześniej (przed 24 maja 2018) generowałeś klucz WebAPI, to jest on nadal dostępny na samym dole panelu Zarządzanie aplikacjami Allegro, możesz go użyć lub ten nowo wygenerowany.

Obsługa protokołu SOAP w Python

SOAP - czyli protokół komunikacji naszego zainteresowania, w uproszczeniu polega na wysyłaniu i odbieraniu żądań w postaci składni XML.

Potrzebny będzie tytułowy moduł suds, ale jest on przestarzały i od dawna nieaktualizowany, więc proponuję trochę bardziej aktualny fork tego modułu o nazwie suds-jurko. Jest jeszcze biblioteka Zeep, ale nie miałem z nią przyjemności, dlatego użyję sprawdzonej metody.

from suds.client import Client

WSDL - jest to język pliku który definiuje dany Web Service, z niego dowiadujemy się co ma on nam do zaoferowania (zasoby), jakie typy obsługuje, stuktury danych wymienianych, no ogólnie taka w uproszczeniu dokumentacja w XML z której się dowiadujemy co on zaś i jak z nim gadać.

Musimy jakoś przyswoić i przerobić WSDL, żeby wiedzieć jak z danym Web Service gadać. Moduł suds ma na to prosty sposób, importujemy klasę Client z jego zasobów a następnie inicjujemy ją, przekazując w jej parametrze URL do WSDL danego Web Api, tutaj wiadomo, chodzi nam o te dla Allegro, czyli:

https://webapi.allegro.pl/service.php?wsdl

Możecie zrobić mały test, żeby bardziej zrozumieć czym WSDL jest, wystarczy wklepać powyższy URL jako adres do przeglądarki. Wyświetlić się powinna struktura w formie XML, można rozejrzeć się i popatrzeć co jest w środku.

client = Client('https://webapi.allegro.pl/service.php?wsdl')

Mamy instancję klienta, który będzie się komunikował z Web API. Aby przesłać jakieś żądanie, musimy odwołać się do odpowiedniego zasobu w serwisie. Spokojnie, to nic trudnego, wszystkie zasoby opisane są w oficjalnej dokumentacji. Odwołajmy się więc do zasobu doGetSystemTime, którego opis to:

“Metoda pozwala na pobranie aktualnego (dla danego kraju) czasu z serwera Allegro”.

Metoda przyjmuje parametry wejściowe, które muszą odpowiadać temu, co spodziewa się otrzymać (zgodny typ, wymagane pola itd.). W tym przypadku, z dokumentacji (ale również z pliku WSDL) możemy poznać co konkretnie musimy dołączyć:

countryId | int | wymagany | Identyfikator kraju (listę identyfikatorów krajów uzyskać można za pomocą metody doGetCountries).
webapiKey | string | wymagany | Klucz WebAPI użytkownika.

Przykład wywołania:

response = client.service.doGetSystemTime(webapiKey='<nasz_klucz_webapi>', countryId=1)

Wynik:

>>> print(response)
1269807050

Teraz coś, co zwróci nam trochę bardziej rozbudowaną odpowiedź (uzyskanie klucza wersji API dla Polski, przyda się w dalszej części):

response = client.service.doQueryAllSysStatus(webapiKey='<nasz_klucz_webapi>', countryId=1)

Wynik:

>>> print(response)
(ArrayOfSysstatustype){
   item[] = 
      (SysStatusType){
         countryId = 1
         programVersion = "1.0"
         catsVersion = "1.6.41"
         apiVersion = "1.0"
         attribVersion = "1.0"
         formSellVersion = "1.14.65"
         siteVersion = "1.0"
         verKey = 1520603076
      },
 }

Jak widzimy, zwrócona struktura przypomina trochę znany nam typ dict. Niestety, nie jest to dict, musimy się trochę wysilić aby wyłuskać elementy które nas interesują. Dodam tylko, że zwrócone dane nie powinny być dla nas zaskoczeniem, ponieważ jak wcześniej wspominałem, wszystkie typy, struktury danych (przyjmowanych i zwracanych) są zdefiniowane w pliku WSDL (jak i w dokumentacji).

Widzimy że typy są określone w nawiasach, również jest hierarchiczna struktura danych. Klient wstępnie zadbał o przekonwertowanie danych, dla naszej wygody. Na górze (w hierarchi) jest lista item[] a w niej zawarte dane. Spróbujmy ją wyświetlić.

>>> print(response.item)
[(SysStatusType){
   countryId = 1
   programVersion = "1.0"
   catsVersion = "1.6.41"
   apiVersion = "1.0"
   attribVersion = "1.0"
   formSellVersion = "1.14.65"
   siteVersion = "1.0"
   verKey = 1520603076
 }]

Ok, działa, możemy więc w łatwy sposób uzyskać dane ze struktury. Wejdźmy jeszcze głębiej.

>>> print(response.item[0].verKey)
1520603076

Skoro item[] to lista - to aby odwołać się do jej zawartości, podajemy index obiektu który nas interesuje. W tym przypadku jest tylko jeden, więc ma index o numerze 0. Dalej, z tego obiektu wyciągamy verKey.

Uwierzytelnienie - czyli logowanie

Uzyskaliśmy fundamentalną wiedzę jak porozumiewać się z Web API, możemy więc przejść do meritum, czyli jak uwierzytelnić nasze poczynania, a co za tym idzie uzyskać dostęp do zasobów, które tego wymagają. Musimy uzyskać sessionId (znany również jako sessionHandler).

UWAGA! Jest to bardzo niebezpieczna metoda! Przekazujemy dane do NASZEGO KONTA na Allegro w formie plain text!

auth = client.service.doLogin(
    userLogin='<login>',
    userHashPassword='<haslo>', 
    countryCode=1,
    webapiKey='<nasz_klucz_webapi>', 
    localVersion='<uzyskany_wczesniej_verKey>'
)
>>> print(auth.sessionHandlePart)
22eb99326c6be29aa16d07d622bcfbcbee94ad54846f2f4e03

To jest nasz identyfikator sesji, który przekazywać będziemy w parametrach sessionId lub sessionHandler dla zasobów wymagających uwierzytelnienia.

UWAGA! Jest inny zasób, który jest tak samo niebezpieczny, ale zapewnia “ochronę” naszego hasła, ponieważ je hashuje. W przypadku wykradnięcia hasha, będzie można przy jego użyciu się zalogować przez Web API, ale chroni to ludzi, którzy mają wszędzie to samo hasło (chociaż też nie koniecznie - ale nie o tym teraz).

import hashlib
import base64

auth = client.service.doLoginEnc(
    userLogin='<login>',
    userHashPassword=base64.b64encode(hashlib.sha256('<haslo>'.encode('utf-8')).digest()).decode('utf-8'), 
    countryCode=1,
    webapiKey='<nasz_klucz_webapi>', 
    localVersion='<uzyskany_wczesniej_verKey>'
)

Cały przykładowy kod

from suds.client import Client
import hashlib
import base64

client = Client('https://webapi.allegro.pl/service.php?wsdl')

response = client.service.doQueryAllSysStatus(webapiKey='<nasz_klucz_webapi>', countryId=1)

version_key = response.item[0].verKey

auth = client.service.doLoginEnc(
    userLogin='<login>',
    userHashPassword=base64.b64encode(hashlib.sha256('<haslo>'.encode('utf-8')).digest()).decode('utf-8'), 
    countryCode=1,
    webapiKey='<nasz_klucz_webapi>', 
    localVersion='<uzyskany_wczesniej_verKey>'
)

Oczywiście, nie oznacza to, że samo użycie tych metod i przekazaniu przez internet hasła i loginu w plain text, spowoduje automatyczne przejęcie ich przez osoby niepowołane, ale jest takowe ryzyko. Mimo użycia protokołu HTTPS, czyli połączenia szyfrowanego, musimy być ostrożni. Najlepiej, gdy używamy tej metody, zmienić hasło na unikalne (nie powtarzające się nigdzie indziej, np. do skrzynki e-mail, ftp, panelu sklepu internetowego) i zmienić je od razu po skończonej pracy takiego skryptu. Jeżeli chcemy wykorzystać skrypt, np. do automatyzacji i będzie on umieszczony na zewnętrznym serwerze, odpalany przez CRON, lub w inny bezobsługowy i powtarzający się sposób, proponuję skorzystać z możliwości uzyskania token przez najnowsze REST API Allegro i przekazania go do zasobu doLoginWithAccessToken. Cały proces logowania z użyciem REST API, oraz jak obsługiwać 2 API na raz w jednym skrypcie opiszę już wkrótce w kolejnych wpisach.

UPDATE! Bezpieczniejsze logowanie do WebAPI

Ważna informacja, która ratuje nas z opresji, którą było super głupie logowanie się do WebAPI przy użyciu hasła i loginu do konta allegro (w dodatku w plain text)! Co więcej, zespół Allegro wprowadził dwustopniowe logowanie do naszego konta w serwisie Allegro.pl! Jest to niezwykle dobra informacja, dla ludzi którzy cenią sobie wyłączność na dostęp do swojego konta. Co z WebAPI? Również sytuacja się poprawiła! Wprowadzono hasła do aplikacji, które chronią nas na wypadek wycieku hasła używanego do uwierzytelnienia naszych skryptów w WebAPI (bo jest różne od tego do konta).

Co więcej, gdy ustawimy dwustopniowe logowanie do naszego konta, musimy użyć zdefiniowanego hasła do aplikacji - nie będzie możliwości zalogowania się przy użyciu danych do konta.

Moje allegro > Bezpieczeństwo > Hasła do aplikacji

To na szybko, jak w bezpieczny sposób uzyskać sessionId używając hasła aplikacji.

from suds.client import Client
import hashlib
import base64

client = Client('https://webapi.allegro.pl/service.php?wsdl')

response = client.service.doQueryAllSysStatus(webapiKey='<nasz_klucz_webapi>', countryId=1)

version_key = response.item[0].verKey

sha256_application_password = hashlib.sha256('<haslo_do_aplikacji>'.encode('utf-8')).digest()

auth = client.service.doLoginEnc(
    userLogin='<login>',
    userHashPassword=base64.b64encode(sha256_application_password).decode('utf-8'), 
    countryCode=1,
    webapiKey='<nasz_klucz_webapi>', 
    localVersion=version_key
)

session = auth.sessionHandlePart

Niestety, nie zmienia to faktu, że nadal będę musiał napisać o tym jak żonglować dwoma API na raz, bo tak jak zdejmuje to ciężar autoryzacji w WebAPI przez REST API, to i tak nadal potrzebujemy REST API do procesu wystawiania aukcji (co zmniejsza jeszcze bardziej jego użyteczność na chwilę obecną).

Paweł Chaniewski

Paweł Chaniewski
"Im mniej nużącej pracy manualnej tym lepiej, zwłaszcza kiedy musimy sami prowadzić sklep internetowy". Autor bloga cwsi.pl o tematyce automatyzacji w dziedzinie e-commerce. Entuzjasta języków skryptowych (szczególnie Python).

Google Apps Script - SOAP Client - Allegro WebAPI

Zaawansowane użycie Google Apps Script, czyli klient dla Web Service Allegro. Czytaj dalej