W tym wpisie pokażę w jaki sposób zaimplementować w języku C#, mechanizm wyszukiwania danych podmiotu gospodarczego w rejestrze GUS, na podstawie numeru NIP lub REGON.

Aktualizacja 2015-03-24

Informacje na temat nowej wyszukiwarki GUS

Komunikacja

GUS nie udostępnia usługi typu WebService, dzięki któremu można by w łatwy sposób przeszukać rejestr podmiotów. Pozostaje więc zasymulowanie pracy przeglądarki aby uzyskać dostęp do tych danych. Należy przeanalizować co wysyła przeglądarka (np. za pomocą programu Fiddler) i odtworzyć dokładnie ten proces, poprzez ustawienie odpowiednich cookies oraz pól formularza.

Etapy

Parametry wyszukiwania przesyłane są za pomocą formularza, czyli żądaniem typu POST. Dodatkowym utrudnieniem jest weryfikacja każdego żądania poprzez wprowadzenie tzw. captchy.

W ogólności, proces wyszukiwania można podzielić na trzy etapy:

  1. Pobranie captchy oraz cookies
  2. Wysłanie kodu weryfikacyjnego oraz parametrów wyszukiwania
  3. Parsowanie odpowiedzi HTML

Implementacja

Nie będę przedstawiać pełnej implementacji klasy, lecz najistotniejsze szczegóły. Jeśli potrzebujesz kompleksowe i działające rozwiązanie – napisz do mnie.

Inicjalizacja

Do pobrania/wysłania danych wykorzystamy klasę HttpWebRequest. Na poczatku zdefiniujmy metodę zwracającą instancję tej klasy z prawidłowo przygotowanymi nagłówkami:

private static string GUSSearchURL = "http://stat.gov.pl/regon/";
private CookieContainer cookies = new CookieContainer();
private HttpWebRequest createHttpRequest(string url)
{
    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
    request.Referer = "http://www.stat.gov.pl/regon/";
    request.ServicePoint.Expect100Continue = false;
    request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130224 Firefox/21.0";
    request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
    request.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
    request.Headers.Add("Accept-Language", "pl-PL,pl;q=0.8,en-US;q=0.6,en;q=0.4");
    request.CookieContainer = this.cookies;
    request.Timeout = 5000;
    return request;
}

Instancja klasy CookieCointainer będzie przechowywać ciasteczka dla wszystkich realizowanych żądań.

Pobranie captchy

Captcha jest dostępna pod adresem http://www.stat.gov.pl/regon/Captcha.jpg?(1-999). Należy wylosować liczbę z tego przedziału i przesłać żądanie GET na ten adres. W odpowiedzi otrzymamy treść typu Image/Jpeg, którą można wyświetlić użytkownikowi (lub wykorzystać system łamania captcha).

public System.Drawing.Image DownloadCaptcha()
{
    Random r = new Random();
    int captchaId = r.Next(1, 999);
    HttpWebRequest request = this.createHttpRequest(String.Format("http://www.stat.gov.pl/regon/Captcha.jpg?{0}", captchaId));
    request.Method = "GET";
 
    HttpWebResponse response = null;
    try
    {
        response = (HttpWebResponse)request.GetResponse();
    }
    catch (Exception ex)
    {
        return null;
    }
 
    System.Drawing.Image captcha = System.Drawing.Image.FromStream(response.GetResponseStream());
    return captcha;
}

Odesłanie captchy

Gdy już mamy kod weryfikacyjny pobranej captchy, można wysłać żądanie POST z parametrami wyszukiwania oraz kodem.

Na początku zdefiniujemy funkcję, która uzupełni dodatkowe ciasteczka, które są wysyłane w finalnym żądaniu.

private void addCookiesToSendRequest(string captcha, string criteria, string value)
{
    Dictionary<string, string> cookieHeaders = new Dictionary<string, string>()
    {
      {"lastCritIx", "1"}, {"isAmbiguousAnswer", "false"}, {"toGenerNewVerifCode", "false"}, {"cCodeHistory", ""},
      {"openingPageType", ""},  {"focusedObjIdGlobal", "verifCodeTF"}, {"focusedObjCursorPos", "5"}, {"browser", "FF-21.0"}, {"testCookie", ""}
    };
 
    cookieHeaders.ToList().ForEach(x => this.cookies.Add(new Cookie(x.Key, x.Value, "/regon/", "www.stat.gov.pl")));
    this.cookies.Add(new Cookie("lastCCode", captcha, "/regon/", "www.stat.gov.pl"));
    this.cookies.Add(new Cookie(criteria, value, "/regon/", "www.stat.gov.pl"));
}

oraz typ wyliczeniowy, do określenia rodzaju wyszukiwania (po numerze NIP bądź REGON):

public enum SearchType { NIP, REGON };

Poniżej funkcja wysyłająca finalne żądanie do systemu GUS. Należy spreparować pola formularza, ciasteczka oraz wysłać żądanie typu POST.

public void ConfirmCaptcha(string captcha, SearchType searchType, string value)
{
    string searchField = null;
    string criteriaType = null;
    if (searchType == SearchType.NIP)
    {
        searchField = "1nip";
        criteriaType = "criterion1TF";
    }
    else
    {
        searchField = "0regon";
        criteriaType = "criterion0TF";
    }
 
    this.addCookiesToSendRequest(captcha, criteriaType, value);
 
    HttpWebRequest request = this.createHttpRequest(GUSSearchURL);
    request.Method = "POST";
    request.ContentType = "application/x-www-form-urlencoded";
 
    string postFields = String.Format("queryTypeRBSet={0}00={2}11={2}&verifCodeTF={1}",
    searchField, captcha, value);
    byte[] postData = ASCIIEncoding.ASCII.GetBytes(postFields);
    request.ContentLength = postData.Length;
    Stream stream = request.GetRequestStream();
    stream.Write(postData, 0, postData.Length);
    stream.Close();
 
    HttpWebResponse response = null;
    try
    {
        response = (HttpWebResponse)request.GetResponse();
    }
    catch (Exception ex)
    {
        return;
    }
    string htmlContent = null;
    using (StreamReader streamReader = new StreamReader(response.GetResponseStream()))
    {
        htmlContent = streamReader.ReadToEnd();
    }
}

Parsowanie danych

Po odesłaniu captchy, odpowiedź należy ręcznie przeparsować.

Możliwe są następujące rodzaje odpowiedzi:

  • Na danym numerze NIP jest kilka podmiotów i należy doprecyzować podmiot po REGON
  • Numer NIP/REGON jest nieprawidłowy
  • Numer nie został odnaleziony
  • Niepoprawny kod weryfikacyjny
  • Wpis został odnaleziony (sukces)

Parsowanie danych jest dosyć proste. Kod HTML można załadować do np. HTMLDocument i wyszukać w DOM odpowiednie tabelki i wiersze z danymi.

Aktualizacja 2014-04-27

Od teraz odpowiedź serwera jest kompresowana GZipem. Wystarczy w instancji klasy HttpWebRequest ustawić właściwość AutomaticDecompression na DecompressionMethods.GZip. Koniecznie należy też podać adres bez WWW czyli http://stat.gov.pl/regon jako URL serwera.


Podobne artykuły

Komentarze

25 odpowiedzi na “GUS – wyszukiwarka podmiotów w C#”

  1. Andrzej napisał(a):

    Piękne dzięki, zaoszczędziłem sporo czasu.
    Pozdrawiam

  2. Michał napisał(a):

    Ekstra! Wszystko działa jak należy po przeparsowaniu. Wielkie dzięki za udostępnienie tego artykułu, bo sam to bym chyba miliard lat to robił :)
    Pozdrawiam!

  3. Piotr napisał(a):

    od kliku dni nie działa ten mechanizm. Albo awaria albo coś zmienili u siebie na stronie. Przychodzą w odpowiedzi jakieś krzaki.

  4. Bartek napisał(a):

    Cześć, czy ten mechanizm nadal działa? Walczyłem dzisiaj cały dzień z tym problemem ale niestety bezskutecznie. Za każdym razem otrzymuję komunikat "Twoja przeglądarka ma wyłączoną obsługę mechanizmu cookies.". Ciasteczka są dodane do CookieContainer, w debugu je widać ale po wysłaniu requesta confirmCaptcha za każdym razem pojawia się ten błąd. Będę wdzięczny za jakiekolwiek wskazówki.

    • Tomasz Maciejewski Tomasz Maciejewski napisał(a):

      Tak, mechanizm działa – przed chwilą sprawdziłem. Sprawdź programem Fiddler, jakie ciasteczka są ustawiane i porównaj z tym co masz w programie.

  5. PO napisał(a):

    Wydaje mi się, że może być problem z query stringiem który jest przekazywany w POST. W przeglądarce budowany qs wygląda trochę inaczej…

  6. Tomasz Maciejewski Tomasz Maciejewski napisał(a):

    Jeśli zrobicie wszystko tak jak jest napisane w artykule to musi działać. Sprawdziłem ten kod i jest OK. Nie zapomnijcie o kompresji oraz adresie bez WWW (http://stat.gov.pl/regon/)

  7. PO napisał(a):

    Mam jeszcze pytanie odnośnie parametrów:

    string captcha, SearchType searchType, string value

    captcha – tekst z obrazka

    value – nip/regon który sprawdzamy?

     

    Dzięki wielkie za pomoc :)

  8. Zenek napisał(a):

    Super, dzięki.
    Wszystko działa.

  9. Piotr napisał(a):

    NIP działa super a REGON nie. Nie rozumiem dlaczego

    • Tomasz Maciejewski Tomasz Maciejewski napisał(a):

      Należy porównać za pomocą programu Fiddler, jakie ciasteczka ustawia apka a jakie standardowy interfejs na WWW. W razie dalszych problemów pisz na PW.

    • PZ napisał(a):

      Hej :) natknąłem się na ten sam problem!

      W miejscu, gdzie w funkcji wysyłającej żądanie POSTem ustawiasz postFields musisz zmienić na:
      queryTypeRBSet={0}00={2}11={2}&verifCodeTF={1}

      regon jest zczytywany po pierwszym parametrze ({1}00={2}), który w przypadku oryginalnego kodu jest pusty!

      Testowane, działa, pozdrawiam

    • Tomasz Maciejewski Tomasz Maciejewski napisał(a):

      Dzięki za zwrócenie uwagi :)

  10. Piotr napisał(a):

    Dziwne, mam ten sam problem co Bartek. Pokazuje mi błąd: “Twoja przeglądarka ma wyłączoną obsługę mechanizmu cookies”. Porównywałem cookies Fiddlerem i niestety nie wiem co może być nie tak.

    • Piotr napisał(a):

      Już wszystko w porządku. Musiałem dodać cookie, które otrzymałem z captcha’y do requestu który pobiera już odpowiednie dane na podstawie nip/regon i captcha

  11. Piotr napisał(a):

    Cześć,
    Od niedawna na stronie http://stat.gov.pl/regon/ można zauważyć komunikat, że powstała nowa wyszukiwarka podmiotów, a stara nie jest już aktualizowana (przestali aktualizować dane w dniu 2014-11-07). W nowej wersji zapewne pozmieniały się główne mechanizmy pobierania ciasteczek, captcha etc. Z różnic widać gołym okiem, że wystarczy raz na początku sesji wpisać kod, a później wyszukiwać dowolną ilość podmiotów, jest też multiwyszukiwanie czego nie było w wersji poprzedniej. Czy ktoś z Was próbował się do nowej wersji jakoś dobrać z poziomu .net?

  12. Dawid napisał(a):

    No właśnie ostatnio zmienili interfejs. Mam nadzieję, ze uda się Tobie zająć tematem. Z tego co można z komunikatu wyczytać dane są nie aktualne w starej wyszukiwarce ;(

  13. Tomasz napisał(a):

    Teraz używają JSONa

  14. Michał Bryłka napisał(a):

    opracowałem porządny parser na podstawie tego co mi zwraca ten “cudowny” web service.

    Też tak macie że jak szukacie po NIPie to wszystko jest ok ale przy szukaniu po REGONie nagle odpowiedź nie zawiera NIPa a za to powtarza REGON ?

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *