Wrzasq.pl

Allegro WebAPI tutorial vol. 3

Thursday, 25 September 2008, 00:13

Część I

Dziękuję za odzew

Miło wiedzieć, że mój opis Allegro WebAPI na coś się komuś przydał i momo, że moja strona w głównym założeniu nie ma być blogiem, to uważem, że naprawdę brakuje materiałów opisujących tą usługę. Ponieważ po poprzednich dwóch częściach dostałem kilka maili z proźbami o pomoc i objaśnienia sądzę, że dobrze będzie to zrobić publicznie, bo innym też się może przydać, a jak już pisałem w sieci ze świecą szukać jakiejkolwiek pomocy.

Dwa problemy, o które zagadnął mnie 333kylix999 to wyszukiwanie z użyciem doSearch() i pobieranie listy kategorii.

Synchronizacja kategorii

Jako iż ten problem jest dużo łatwiejszy do omówienia to pójdzie na początek. Przedstawię jedynie sposób ściągania listy kategorii - ich zapis do bazy danych to raczej kwestia indywidualna i nie powinno to zresztą sprawić żadnego kłopotu gdy już będziemy mieli dane. Do pobierania kategorii służy wywołanie doGetCatsData() oraz jego inna wersja - doGetCatsDataLimit() (ponieważ kategorii jest bardzo dużo nieraz może się okazać, że trzeba je pobierać częściami). Należy pamiętać, że każdy serwis/kraj ma inne kategorie, dlatego jako parametry do wspomnianych wywołań przekazujemy kraj, który nas interesuje, swój klucz WebAPI, oraz klucz wersji danego serwisu. W odpowiedzi dostajemy listę kategorii w postacii tablicy obiektów. Należy pamiętać tutaj o wspomnianym w poprzednich częściach mankamencie WebAPI - nazewnictwie pól, których to nazwy zawierają myślnik (traktowany przez języki programowania jako minus). Kategorie oczywiście są zagnieżdżane, a nasza lista, którą otrzymamy posiada informacje o kategoriach nadrzędnych. Przyda się więc wygenerować drzewo kategorii. Na początku przedstawie klasę w której będę zamykał informacje dotyczące danej kategorii, oraz ich listy:

/**
 * @link https://wrzasq.pl/
 * @author Wrzasq <wrzasq@gmail.com>
 * @copyright 2008 (C) by Wrzasq
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class WebAPICategory
{
/**
 * ID kategorii.
 * 
 * @var int
 */
    protected $id;

/**
 * Nazwa kategorii.
 * 
 * @var string
 */
    protected $name;

/**
 * Kategorie wewnątrz tej kategorii.
 * 
 * @var array
 */
    protected $children = array();

/**
 * Tworzy nową kategorię.
 * 
 * @param int $id ID kategorii.
 * @param string $name Nazwa kategorii.
 */
    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

/**
 * Zwraca ID kategorii.
 * 
 * @return int ID.
 */
    public function getId()
    {
        return $this->id;
    }

/**
 * Zwraca nazwę kategorii.
 * 
 * @return string Nazwa.
 */
    public function getName()
    {
        return $this->name;
    }

/**
 * Dodaje podkategorię.
 * 
 * @param WebAPICategory $child Podkategoria.
 */
    public function appendChild(WebAPICategory $child)
    {
        $this->children[] = $child;
    }

/**
 * Zwraca listę podkategorii.
 * 
 * @return array Podkategorie.
 */
    public function getChildren()
    {
        return $this->children;
    }
}

/**
 * @link https://wrzasq.pl/
 * @author Wrzasq <wrzasq@gmail.com>
 * @copyright 2008 (C) by Wrzasq
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class WebAPICategories
{
/**
 * Lista kategorii.
 * 
 * @var array
 */
    protected static $categories = array();

/**
 * Dodaje kategorię do listy.
 * 
 * @param WebAPICategory $category Kategoria do dodania.
 */
    public static function addCategory(WebAPICategory $category)
    {
        self::$categories[ $category->getId() ] = $category;
    }

/**
 * Zwraca kategorię o podanym ID.
 * 
 * @param int $id ID kategorii.
 * @return WebAPICategory Żądana kategoria.
 * @todo Jest to tylko poglądowy przykład i zakładam, że w WebAPI kategorie są posortowane, jednak w biznesowych implementacjach potrzebne będzie sprawdzanie, czy żądana kategoria istnieje.
 */
    public static function getCategory($id)
    {
        return self::$categories[$id];
    }
}

// dodaje pierwszą - główną kategorię
WebAPICategories::addCategory( new WebAPICategory(0, '') );

Teraz wystarczy wczytać listę kategorii, którą otrzymamy w odpowiedzi od Allegro:

    $categories = $client->doGetCatsData(WebAPISoapClient::COUNTRY_PL, $version['ver-key'], $config['apiKey']);

    foreach($categories['cats-list'] as $entry)
    {
        // tworzymy nasz wrapper na dane o kategorii
        $category = new WebAPICategory($entry->{'cat-id'}, $entry->{'cat-name'});

        // pobieramy kategorię nadrzędną z listy
        $parent = WebAPICategories::getCategory($entry->{'cat-parent'});

        // dowiązujemy podkategorię
        $parent->appendChild($category);

        // dodajemy nową kategorię do listy
        WebAPICategories::addCategory($category);
    }

Obiekt kategorii przesyłany w odpowiedzi posiada też pole zaznaczające jego kolejność w liście podkategorii $category->{'cat-position'} jednak do samego mechanizmu drzewa kategorii nie jest to niezbędne, a jego obsługa również nie powinna być zbyt trudna, więc nie chcę tutaj dodawać zbędnych rzeczy. Tak przetworzoną listę kategorii można łatwo przejrzeć zaczynając od kategorii głównej, czyli od WebAPICategories::getCategory(0):

    // wypisujemy rekurencyjnie nasze drzewo kategorii

    function printTree(WebAPICategory $category, $prefix = '')
    {
        foreach( $category->getChildren() as $child)
        {
            echo $prefix, $child->getId(), ': ', $child->getName(), "\n";
            printTree($child, $prefix . '    ');
        }
    }

    printTree( WebAPICategories::getCategory(0) );

Oprócz tego należy pamiętać, że wiele kategorii posiada dodatkowe pola - dlatego jeśli zależy nam na pełnej obsłudze opcji kategorii warto zaznajomić się również z wywołaniem doGetSellFormFieldsExt(), które zwraca listę pól formularza wraz z informacjami o kategorii, której się tyczą.

Wyszukiwarka Allegro

Drugim problemem, który miałem omówić jest wyszukiwanie. Do tego służy wywołanie doSearch(). Jest ono dość proste w działaniu - wysyłamy obiekt formularza z wartościami pól poszczególnych kryteriów, a otrzymujemy listę pasujących aukcji. Aby wywołać doSearch() musimy się najpierw zalogować. Dodatkowo należy pamiętać, że rezultaty wyszukiwania są zwracane po 50 rekordów i nie mamy możliwości zmian wielkości tej tablicy. Obiekt formularza składa się z następujących pól:

search-string (tekstowe)
Wyszukiwana fraza.
search-options (liczbowe)
Opcje wyszukiwania (domyślna wartośc 0).
search-order (liczbowe)
Kryterium sortowania (domyślna wartość 1).
search-order-type (liczbowe)
Kierunek sortowania - 0 rosnąco, 1 malejąco (domyślna wartość 0).
search-country (liczbowe)
Kraj z którego wyniki mają być uwzględnione (domyślna wartość 0).
search-category (liczbowe)
ID kategorii w której chcemy wyszukiwać.
search-offset (liczbowe)
Strona wyników (wybiera winyki z zakresu strona * 50 do (strona + 1) * 50 - 1).
search-city (tekstowe)
Miasto w którym chcemy szukać.
search-state (liczbowe)
Numer województwa.
search-price-from (zmiennoprzecinkowe)
Dolny zakres ceny.
search-price-to (zmiennoprzecinkowe)
Górny zakres ceny.

Do opcji po raz kolejny przyda nam się kilka nowych wartości stałych w naszej klasie - po pierwsze opcje wyszukiwania (jak zwykle wypadkową flag otrzymujemy przez ich binarne sumowanie):

Flaga 1
Wyszukiwanie któregokolwiek z wyrazów szukanej frazy (domyślnie wyszukiwane są wszystkie wyrazy).
Flaga 2
Wyszukiwanie także w opisach przedmiotów (domyślnie tylko w nazwach aukcji).
Flaga 4
Tylko nowe aukcje.
Flaga 8
Tylko aukcje typu "Kup Teraz".
Flaga 16
Uwzględnianie także już zakończonych aukcji.
Flaga 32
Wyszukiwanie w mieście użytkownika aktualnie zalogowanego (właściciela klucza sesji).
Flaga 64
Wyszukiwanie w województwie użytkownika aktualnie zalogowanego (właściciela klucza sesji).

Oraz numery pól do sortowania według:

1
Czasu zakończenia aukcji.
2
Ilości ofert.
4
Ceny.
8
Nazwy.

Tak więc obecnie klasa główna przedstawia się tak:

/**
 * @see http://www.php.net/manual/en/book.soap.php
 * @link https://wrzasq.pl/
 * @author Wrzasq <wrzasq@gmail.com>
 * @copyright 2008 (C) by Wrzasq
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class WebAPISoapClient extends SoapClient
{
/**
 * Kraj - Polska.
 */
    const COUNTRY_PL = 1;
/**
 * Logowanie do serwisu testwebapi.pl.
 */
    const COUNTRY_TESTWEBAPI = 228;

/**
 * Zapytanie o wersję Allegro WebAPI.
 */
    const QUERY_ALLEGROWEBAPI = 1;

/**
 * Czas trwania - 3 dni.
 */
    const LIFETIME_3DAYS = 0;
/**
 * Czas trwania - 5 dni.
 */
    const LIFETIME_5DAYS = 1;
/**
 * Czas trwania - 7 dni.
 */
    const LIFETIME_7DAYS = 2;
/**
 * Czas trwania - 10 dni.
 */
    const LIFETIME_10DAYS = 3;
/**
 * Czas trwania - 14 dni.
 */
    const LIFETIME_14DAYS = 4;

/**
 * Koszty pokrywa sprzedający.
 */
    const TRANSPORT_COST_SELLER = 0;
/**
 * Koszty pokrywa kupujący.
 */
    const TRANSPORT_COST_BUYER = 1;

/**
 * Przesyłka pocztowa (polecona/paczka).
 */
    const TRANSPORT_OPTION_POST = 1;
/**
 * Przesyłka pocztowa priorytetowa (polecona/paczka).
 */
    const TRANSPORT_OPTION_POSTPRIORITY = 2;
/**
 * Przesyłka kurierska.
 */
    const TRANSPORT_OPTION_COURIER = 4;
/**
 * Odbiór osobisty.
 */
    const TRANSPORT_OPTION_PERSONAL = 8;
/**
 * Inne.
 */
    const TRANSPORT_OPTION_OTHER = 16;
/**
 * Zgadzam się na przesłanie przedmiotu za granicę.
 */
    const TRANSPORT_OPTION_ABROAD = 32;

/**
 * Przedpłata.
 */
    const PAYMENT_OPTION_PREPAID = 1;
/**
 * Pobranie.
 */
    const PAYMENT_OPTION_POSTPAID = 2;
/**
 * Płatności Allegro.
 */
    const PAYMENT_OPTION_ALLEGRO = 4;
/**
 * Bezpieczne Płatności Allegro z Escrow.
 */
    const PAYMENT_OPTION_ESCROW = 8;
/**
 * Bezpieczne Płatności Allegro z Escrow.
 */
    const PAYMENT_OPTION_OTHER = 16;

/**
 * Pogrubienie.
 */
    const OPTION_BOLD = 1;
/**
 * Miniaturka.
 */
    const OPTION_THUMB = 2;
/**
 * Podświetlenie.
 */
    const OPTION_HIGHLIGHT = 4;
/**
 * Wyróżnienie.
 */
    const OPTION_PREMIUM = 8;
/**
 * Strona kategorii.
 */
    const OPTION_CATEGORY = 16;
/**
 * Strona główna.
 */
    const OPTION_HOME = 32;

/**
 * Wyszykiwanie któregokolwiek wyrazu.
 */
    const SEARCH_OPTION_ANY = 1;
/**
 * Wyszukuj w opisach.
 */
    const SEARCH_OPTION_DESCRIPTION = 2;
/**
 * Tylko nowe aukcje.
 */
    const SEARCH_OPTION_NEWONLY = 4;
/**
 * Tylko aukcje "Kup Teraz".
 */
    const SEARCH_OPTION_BUYNOWONLY = 8;
/**
 * Uwzględniaj także zakończone aukcje.
 */
    const SEARCH_OPTION_CLOSEDTOO = 16;
/**
 * Wyszukuj aukcji z miasta użytkownika.
 */
    const SEARCH_OPTION_SESSIONCITY = 32;
/**
 * Wyszukuj aukcji z województwa użytkownika.
 */
    const SEARCH_OPTION_SESSIONSTATE = 64;

/**
 * Sortowanie według daty zakończenia.
 */
    const ORDERBY_ENDING = 1;
/**
 * Sortowanie według ilości ofert.
 */
    const ORDERBY_BIDS = 2;
/**
 * Sortowanie według ceny.
 */
    const ORDERBY_PRICE = 4;
/**
 * Sortowanie według nazwy.
 */
    const ORDERBY_NAME = 8;

/**
 * Sortowanie rosnąco.
 */
    const ORDER_ASC = 0;
/**
 * Sortowanie malejąco.
 */
    const ORDER_DESC = 1;

/**
 * Automatycznie tworzy klienta dla interfejsu Allegro WebAPI.
 */
    public function __construct()
    {
        parent::__construct('http://webapi.allegro.pl/uploader.php?wsdl');
    }

/**
 * Redukuje obraz do wielkości nadającej się do przesyłu.
 * 
 * @param string $url URL obrazka (lokalne, albo sieciowe).
 * @return string Binarna zawartość obrazka w formacie JPEG.
 */
    public static function resize($url)
    {
        $image = file_get_contents($url);

        // właśnie tutaj używamy Base64 ręcznie, ale nigdzie indziej!
        while( strlen( base64_encode($image) ) > 200000)
        {
            $temp = imagecreatefromstring($image);
            $x = ceil(0.9 * imagesx($temp) );
            $y = ceil(0.9 * imagesy($temp) );

            $image = imagecreatetruecolor($x, $y);
            imagecopyresized($image, $temp, 0, 0, 0, 0, $x, $y, imagesx($temp), imagesy($temp) );

            imagejpeg($image, 'temp.jpg', 75);
            $image = file_get_contents('temp.jpg');
            unlink('temp.jpg');
        }

        return $image;
    }
}

Odpowiedź na nasze zapytanie zawierać będzie trzy pola:

search-count (liczbowe)
Ilość znalezionych przedmiotów (wszystkich, nie tylko na aktualnej stronie).
search-count-featured (liczbowe)
Ilość przedmiotów wyróżnionych (przedmioty wyróżnione są zwracane przed pozostałymi).
search-array (tablicowe)
To pole zawiera tablicę obiektów znalezionych aukcji.

Teraz wystarczy stworzyć obiekt formularza, przypisać wartości jego polom i wyślać wywołaniem doSearch(). Przedtem oczywiście się zalogować. Używając przykładu z części pierwszej nasz kod wyglądałby mniej więcej tak (przykładowo szukamy aukcji zawietających wyrazy GeForce lub Radeon w naszym mieście do 300 PLN, wyniki posortujemy według cen):

    // właściwe logowanie do serwisu
    $session = $client->doLogin($config['login'], $config['password'], $country, $config['apiKey'], $version['ver-key']);

    // generujemy formularz
    $form = new StdClass();
    $form->{'search-string'} = 'GeForce Radeon';
    $form->{'search-options'} = WebAPISoapClient::SEARCH_OPTION_ANY | WebAPISoapClient::SEARCH_OPTION_SESSIONCITY;
    $form->{'search-order'} = WebAPISoapClient::ORDERBY_PRICE;
    $form->{'search-order-type'} = WebAPISoapClient::ORDER_ASC;
    $form->{'search-country'} = 0;
    // 0 oznacza, że wyszukujemy w całym serwisie - ID kategorii możemy pobrać z poprzedniego omówienia problemu importu kategorii
    $form->{'search-category'} = 0;
    $form->{'search-city'} = '';
    $form->{'search-state'} = 0;
    $form->{'search-price-from'} = 1;
    $form->{'search-price-to'} = 300;

    // strona wyników liczona od 0
    $form->{'search-offset'} = 0;

    echo '<table>
    <thead>
        <tr>
            <td>ID</td>
            <td>Nazwa</td>
            <td>Cena</td>
        </tr>
    </thead>
    <tbody>';

    // listujemy wszystkie wyniki
    do
    {
        // wysyłamy formularz
        $search = $client->doSearch($session['session-handle-part'], $form);
        $form->{'search-offset'}++;

        // listujemy aktualną stronę
        foreach($search['search-array'] as $auction)
        {
            echo '        <tr>
            <td>', $auction->{'s-it-id'}, '</td>
            <td><a href="http://allegro.pl/item', $auction->{'s-it-id'}, '.html">';

            // sprawdza, czy aukcja ma miniaturkę do wyświetlenia
            if( !empty($auction->{'s-it-thumb-url'}) )
            {
                echo '<img src="', $auction->{'s-it-thumb-url'}, '" alt="thumbnail" />';
            }

            echo htmlspecialchars($auction->{'s-it-name'}), '</a></td>
            <td>';

            // cena aukcyjna
            if($auction->{'s-it-price'} > 0)
            {
                echo htmlspecialchars($auction->{'s-it-price'}, 2, ',', ' ');
            }
            // cena "Kup Teraz"
            else
            {
                echo htmlspecialchars($auction->{'s-it-buy-now-price'}, 2, ',', ' ');
            }

            echo '</td>
        </tr>';
        }
    }
    // sprawdzamy czy są następne strony
    while( $form->{'search-offset'} * 50 < $search['search-count']);

    echo '    </tbody>
</table>';

Dokładny opis struktury obiektu pojedynczej aukcji znajduje się tutaj. Nie sposób opisać w prostym przykładzie wszystkie jego pola, a na szczęście ich zastosowanie jest raczej dośc oczywiste.

Część V
Część VI

Tags: , , , , ,