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.


Strona Internetowa

Potrzebujesz Å‚adnej strony internetowej? Zobacz demo na: tej stronie strona internetowa


Podobne artykuły

Komentarze

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

  1. Andrzej pisze:

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

  2. Michał pisze:

    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 pisze:

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

  4. Bartek pisze:

    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 pisze:

      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 pisze:

    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 pisze:

    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 pisze:

    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 pisze:

    Super, dzięki.
    Wszystko działa.

  9. Piotr pisze:

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

    • Tomasz Maciejewski Tomasz Maciejewski pisze:

      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 pisze:

      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 pisze:

      Dzięki za zwrócenie uwagi :)

  10. Piotr pisze:

    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 pisze:

      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 pisze:

    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?

    • Tomasz Maciejewski Tomasz Maciejewski pisze:

      W wolnej chwili spróbuję się tym zająć

    • Piotr pisze:

      Cześć,

      Czy jest szansa żeby coś z tym tematem zrobić?

    • Tomasz Maciejewski Tomasz Maciejewski pisze:

      Zawsze można odezwać się na priv i ustalić szczegóły.

  12. Dawid pisze:

    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 pisze:

    Teraz używają JSONa

  14. Michał Bryłka pisze:

    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 e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *