poniedziałek, 30 listopada 2015

Pobieranie twitów za pomocą Perla i API Twittera

Poniższy skrypt Perlowy służy do pobierania najnowszych twitów (Tweets) użytkowników identyfikowanych poprzez ich screen_name. Twity są dopisywane do bazy, która jednocześnie pełni rolę pliku konfiguracyjnego. Przykładowo, aby twity użytkownika maly_wacek były dodane do bazy należy wpisać do niej wpis (w dowolnym miejscu, dla porządku najlepiej na początku):

INIT;maly_wacek;;INIT

Ściśle rzecz biorąc po pierwszym dodaniu do bazy, powyższy wpis jest już niepotrzebny, ale też nie przeszkadza. Baza jest zapisywana w taki sposób, że najnowszy tweet każdego użytkownika jest na końcu, zatem po przeczytaniu pliku, w wyniku przypisania $Users{$tmp[1]} = $tmp[0] (por. poniżej), hash %Users zawiera wszystkich użytkowników oraz id_str ich ostatnio pobranego twita. Zapewne niespecjalnie optymalny sposób archiwizacji, ale prosty i działa:

#!/usr/bin/perl
use Net::Twitter;

# Z UTF8 w Perlu jest zawsze problem:
use open ":encoding(utf8)";
use open IN => ":encoding(utf8)", OUT => ":utf8";

my $timelineBase = "timelines.log";

if ( -f "$timelineBase" ) {

   open (BASE,  $timelineBase) ||
      die "Cannot open: $timelineBase";

   while (<BASE>) { chomp();
      @tmp = split /;/, $_;
      $Users{$tmp[1]} = $tmp[0]; # last id_str
   }
}

close (BASE) ;

## ###  ####

open (BASE,  ">>$timelineBase") ;

my $nt = Net::Twitter->new(legacy => 0);

my $nt = Net::Twitter->new(
   traits   => [qw/API::RESTv1_1/],
   consumer_key        => "######",
   consumer_secret     => "######",
   access_token        => "######",
   access_token_secret => "######", );

foreach $user ( keys %Users ) {
   my @message ; my $screen_name = $user ;
   my $result ;

   if ( $Users{$user} eq 'INIT' ) {
     ## max ile się da, wg dokumentacji 3200
     $result = $nt->user_timeline({
       screen_name => $screen_name, count=> '3200' })
   }
   else {
     $result = $nt->user_timeline({
       screen_name => $screen_name, 
         since_id => $Users{$user}, });
   }

   foreach my $tweet ( @{$result} ) {
      $text_ = $tweet->{text} ;
      $text_ =~ s/;/\,/g; $text_ =~  s/\n/ /g;
      $date_ = $tweet->{created_at} ;
      push ( @message, $tweet->{id_str} .  ";" \
         . "$screen_name;$date_;$text_" );
   }

   ## Drukuj posortowane:
   my $tweetsC;
   foreach my $tweet ( sort (@message) ) {
      $tweetsC++ ; print BASE $tweet . "\n"; }
   if ( $tweetsC > 0 ) {
       print STDERR "fetched $tweetsC for $screen_name\n"; }
}

close (BASE)

Uwaga: poprzez API można pobrać twity użytkowników, którzy zablokowali nam możliwość oglądania ich konta (inna sprawa po co oglądać takiego palanta).

Utworzenie aplikacji na apps.twitter.com

Należy się zalogować na stronie apps.twitter.com/. Kliknąć Create New App.

Wybrać Name (np. tprzechlewski.app), Description, Website i Callback URL.

Wybrać Keys and Access Tokens i pobrać wartości: Consumer Key oraz Consumer Secret.

Przewinąć zawartość strony i wybrać Create my access token. Zostaną wygenerowane Access Token oraz Access Token Secret, które także należy pobrać.

Na potrzeby wyżej opisanego skryptu to wystarczy. Pobrane wartości wstawiamy w miejsca oznaczone jako ######

Instalowanie Net::Twitter

Na jednym z moich komputerów ciągle działa dość archaiczna wersja Debiana Lenny:

$ cat /proc/version
Linux version 2.6.32-5-kirkwood (Debian 2.6.32-30)

$ cat /etc/issue
Debian GNU/Linux 5.0 \n \l

$ perl --version
This is perl, v5.10.0 built for arm-linux-gnueabi-thread-multi
Copyright 1987-2007, Larry Wall

Z poważnym obawami, że się uda spróbowałem:

cpan> install Net::Twitter
Strange distribution name

Pomaga (por. tutaj):

cpan> install IO::AIO 

Potem:

cpan> install YAML
cpan> install Net::Twitter

Ściąga się milion pakietów. Przy testowaniu Net-HTTP-6.09 system zawisł na etapie t/http-nb.t (pomogło Ctr-C), ale finał był pomyślny, tj. Net::Twitter został zaistalowany.

Mój inny system jest już nowszy a instalacja Net::Twitter bezproblemowa:

$ cat /etc/issue
Fedora release 21 (Twenty One)
  
$ perl --version
This is perl 5, version 18, subversion 4 (v5.18.4) built for x86_64-linux-thread-multi
(with 25 registered patches, see perl -V for more detail)
Copyright 1987-2013, Larry Wall

$ yum install perl-Net-Twitter

Automatyzacja

Teraz wystarczy umieścić w crontab na przykład taki wpis:

# 48 min po północy codziennie
48 0 * * * /home/tomek/bin/twitter.sh 

Co zawiera twitter.sh jest oczywiste

Protokoły wyborcze do pobrania

Jak ktoś jest zainteresowany, to pobrane ze strony PKW protokoły wyborcze, są dostępne tutaj. Na razie są protokoły z wyborów parlamentarnych 2015 r. oraz (słynnych) wyborów samorządowych 2014 r. -- odpowiednio 224 i 553 Mb (po spakowaniu).

sobota, 21 listopada 2015

Rozkład komisji obwodowych według liczby oddanych głosów

Rozkład komisji obwodowych według liczby oddanych głosów (na podstawie szczegółowych wyników wyborów do Sejmu RP, pobranych ze strony PKW -- por. Web scrapping protokołów wyborczych ze strony PKW):

komisje <- read.csv("komisje_glosy_razem.csv", sep = ';',  header=T, na.string="NA");
str(komisje);

hist(komisje$glosy, breaks=seq(0, 3200, by=25), col="orange",
     freq=TRUE,main="Komisje wg liczby oddanych głosów",
     xlab="# głosów",ylab="# komisji (N = 27859)" )

mtext(text="https://github.com/hrpunio/Data/tree/master/sejm", 4, cex=0.7)
text(3200,100, "Me = 495\nQ1 = 265\nQ3 = 782...", 2, cex=0.7,  adj=c(0,0));

fivenum(komisje$glosy);

quantile(komisje$glosy, c(.10));
quantile(komisje$glosy, c(.05));
quantile(komisje$glosy, c(.90));

czwartek, 12 listopada 2015

Wiek posłów ósmej kadencji Sejmu RP

Na stronie www.sejm.gov.pl już dziś pojawiły się strony o nowowybranych posłach 8 kadencji. Strony można ściągnąć na przykład takim oto prostym skryptem basha:

#!/bin/bash
# Przykładowy URL: http://www.sejm.gov.pl/Sejm8.nsf/posel.xsp?id=002&type=A
padtowidth=3
for ((i=1;i<=460;i++)) ; do
  ## parametr id w URLu ma wartość 001--460
  ## za pomocą printf/tricku z padtowidth dodajemy wiodące zera:
  POSEL=`printf "%0*d\n" $padtowidth $i`
  wget 'http://www.sejm.gov.pl/Sejm8.nsf/posel.xsp?id='$POSEL'&type=A'\
     -O $POSEL.html
done

Na stronach na razie jest niewiele informacji, ale jest data urodzenia, liczba zdobytych głosów oraz okręg wyborczy z którego poseł został wybrany. Za pomocą prostych skryptów Perla można wydłubać te dane, dodać informacje o wieku/płci i zapisać w pliku CSV:

imnz;rokur;wiek;klub;miejsce;okreg;glosy;plec
Adam Abramowicz;1961-03-10;54;PiS;NA;7 Chełm;10500;M
Andrzej Adamczyk;1959-01-04;56;PiS;NA;13 Kraków;18514;M
...

Jak wygląda struktura wiekowa w poszczególnych klubach? (na poniższym wydruku symbole x.1, x.2, x.3, x.4 oraz x.5, to odpowiednio: wartość minimalna, pierwszy kwartyl, mediana, trzeci kwartyl oraz wartość maksymalna)

p <- read.csv("Sejm_8_u.csv", sep = ';',  header=T, na.string="NA");
boxplot (wiek ~ klub, p, xlab="Klub", ylab="Wiek", col='yellow')

aggregate (p$wiek, list(Klub = p$klub), fivenum)
aggregate (p$wiek, list(Klub = p$klub), na.rm=TRUE, mean)

A jak wyglądała średnia wieku w poszczególnych kadencjach Sejmu?

p <- read.csv("Sejm1-8.csv", sep = ';',  header=T, na.string="NA");
boxplot (wiek ~ kadencja, p, xlab = "Kadencja", ylab = "Wiek", col='yellow')

aggregate (p$wiek, list(Kadencja = p$kadencja), fivenum)

 Kadencja  x.1  x.2  x.3  x.4  x.5
1     1991 22.0 37.0 43.0 49.0 70.0
2     1993 24.0 39.0 45.0 50.0 74.0
3     1997 23.0 40.5 46.0 51.0 72.0
4     2001 26.0 43.0 49.0 54.0 78.0
5     2005 23.0 41.0 47.0 53.0 67.0
6     2007 22.0 41.0 48.0 54.0 78.0
7     2011 22.0 42.0 50.0 56.0 73.0
8     2015 23.0 41.5 51.0 59.0 77.0

aggregate (p$wiek, list(Kadencja = p$kadencja), na.rm=TRUE, mean)

  Kadencja        x
1     1991 43.19438
2     1993 45.21535
3     1997 46.42500
4     2001 48.28221
5     2005 46.55230
6     2007 47.32948
7     2011 48.86739
8     2015 49.74783

Dane pobrane ze strony http://www.sejm.gov.pl/Sejm8.nsf/poslowie.xsp?type=A są dostępne tutaj.

piątek, 6 listopada 2015

Web scrapping protokołów wyborczych ze strony PKW

Ze strony PKW ściągnąłem szczegółowe wyniki wyborów do Sejmu RP. Szczegółowe w tym sensie, że pobrałem protokoły ze wszystkich 27859 komisji obwodowych. Takie protokoły są dostępne pod adresem:

http://parlament2015.pkw.gov.pl/321_protokol_komisji_obwodowej/IdKomisji

Identyfikatory obwodowych komisji da się pobrać ze strony PKW metodą ,,kolejnych przybliżeń'': najpierw okręgi, potem powiaty, potem gminy a na końcu w każdej gminie lista komisji obwodowych. Ponieważ otrzymałem tyle komisji obwodowych ile podaje PKW (por. tutaj), to zakładam że niczego nie pogubiłem.

Sprawdzenie danych zaczynamy od podsumowanie liczby głosów ważnych, które zostały oddane na kandydatów z każdego komitetu:

Komitet L.kandydatów L.głosów L.głosów* Różnica Okr19 Okr19* Różnica
KORWIN 899 722921 722999 78 21757 21767 10
KUKIZ 839 1338610 1339094 484 26546 26573 27
Kongres N. Prawicy 116 4852 4852 0 x x x
Razem 571 550343 550349 6 9469 9475 6
Samoobrona 119 4266 4266 0 x x x
BRAUN 202 13113 13113 0 x x x
JOW Bezpartyjni 138 15184 15656 472 x x x
Mniejszość Niemiecka 24 27530 27530 0 x x x
Obywatele do Parlamentu 40 1964 1964 0 266 266 0
Ruch Społeczny RP 59 3941 3941 0 186 186 0
STONOGA 299 42668 42731 63 x x x
Zjed. dla Śląska 42 18668 18668 0 x x x
PETRU 858 1155364 1155370 6 15942 15948 6
PiS 918 5711661 5711687 26 58317 58343 26
PO 914 3661455 3661474 19 32240 32259 19
PSL 916 779874 779875 1 796 797 1
ZLEW 905 1146837 1147102 265 7948 7956 8
* dane zagregowane ze strony PKW

Jak widać są rozbieżności (kolumny 3--5).

Po podliczeniu głosów w każdym obwodzie osobno okazuje się, że źródłem problemów jest m.in. okręg #19, w którym liczone są głosy za granicą (kolumna 6--8 w tabeli powyżej). W szczególności brak jest protokołu z komisji 97770 (baza Bagram/Afganistan, por. POLSKA-OKRĘGI-OKRĘG NR: 19-Zagranica-Zagranica) co być może wynika z konieczności zachowania tajemnicy wojskowej. Zakładając, że w Bagram PiS/PO/PSL/Petru/Razem dostały odpowiednio 26/19/1/6/6 głosów, to w przypadku 12 z 17 komitetów wynik się zgadza (problem stanowią KUKIZ, ZLEW, KORWIN, Bezpartyjni i Stonoga).

Drążąc temat wyliczyłem liczbę głosów dla komitetu JOW Bezpartyjni, który zarejestrował listy w 8 okręgach wyborczych:

Nr okręgu L.głosów L.głosów* Różnica
02 2068 2540 472
18 1045 1045 0
21 1772 1772 0
22 2289 2289 0
33 2344 2344 0
34 1426 1426 0
36 1973 1973 0
39 2267 2267 0
* dane zagregowane ze strony PKW

Zatem całe manko jest w okręgu #02. PKW podaje też stosowne dane w rozbiciu na powiaty (por. tutaj). Przykładowo dla powiatu ząbkowickiego (kod teryt 0224) JOW Bezpartyjni mieli otrzymać 237 głosów. W skład tego powiatu wchodzi 7 gmin, m.in. gmina Bardo (teryt 022401), w której to gminie JOW Bezpartyjni miał otrzymać 14 głosów (por. tutaj). Na terenie gminy Bardo działały 4 obwodowe komisje wyborcze (por. tutaj). W komisji #1 (Centrum Kultury i Promocji Bardo, ul. Kolejowa 12, 57-256 Bardo) na komitet JOW Bezpartyjni oddano 3 głosy. PKW udostępnia też szczegółowy protokół z tejże komisji (por. tutaj) -- wystarczy kliknąć w adres na stronie POLSKA-OKRĘGI-OKRĘG NR: 2-ząbkowicki-Bardo dla wyników komitetu JOW Bezpartyjni. No i na tym protokole każdy z kandydatów JOW Bezpartyjni ma w rubryce Liczba oddanych głosów 0 głosów.

Podsumowując powyższy dłuższy wywód: według protokołu JOW Bezpartyjni zdobył w tym obwodzie 0 głosów, ale według informacji zbiorczej na innej stronie 3 głosy.

Zatem się wyjaśniło że sumując informacje z protokołów komisji nie ma szans na otrzymanie poprawnego wyniku (ale to co mam jest wynikiem prawie dokładym--błąd jest niewielki). Pozostaje tajemnicą PKW dlaczego ich system działa tak pokracznie.

Dane pobrane ze strony http://parlament2015.pkw.gov.pl/ są dostępne tutaj. Pliki komisja_84873_protokol-0.png--komisja_84873_protokol-4.png to zrzuty ekranu ilustrujące ,,przypadek JOW Bezpartyjni w gminie Bardo'' opisany wyżej.

Wszelkie komentarze/uwagi/poprawki mile widziane:-)

Komisje z rekordowym poparciem wg komitetów

Komisje z rekordowym poparciem wg komitetów w wyborach do Sejmu (2015). Generalnie to są specyficzne komisje i/lub takie komisje, w których frekwencja była bardzo mała. Poniżej po jednym przykładzie dla 8 wybranych komitetów:

Komitet %głosów ważnych Ogółem*Adres Id komisji
KORWIN 50,0 14 Stowarzyszenie MONAR Ośrodek Leczenia... 102943
KUKIZ 82,86 35 DPS w Osinach 106282
KW Razem 40,00 5 NZO Dom Sue Ryder Bydgoszcz 86967
KWW Zbigniewa Stonogi 36,93 417 Świetlica Wiejska w Lubieszewie 102231
PETRU 50,00 2 Zespół Zakładów Opieki Zdrowotnej w Nowogrodźcu 83629
PO 100,00 1 Zakład Opieki Zdrowotnej Świnoujście 111436
PSL 100,00 1 Lokal Szpitala Pomocy Maltańskiej Oddział w Barczewie 107560
PiS 100,00 57 DPS w Kurozwękach 106804
ZLEW 61,29 31 DPS w Nakle/Notecią 86194
* głosów ogółem na wszystkich kandydatów.

Pełna lista dla 25 komisji z najwyższym poparcie dla każdego komitetu jest tutaj albo tutaj.

wtorek, 3 listopada 2015

Semper Fidelis

Poparcie dla trzech najpopularniejszych partii politycznych w komisjach zorganizowanych w zakładach karnych i aresztach śledczych (181 komisji). Procent głosów ważnych:

Województwo PO Kukiz PiS L. głosów
pomorskie 62.3 11.5 9.3 2175
dolnośląskie 55.6 13.2 10.9 2891
mazowieckie 55.5 13.1 9.5 3235
warmińsko-mazurskie 55.4 13.4 11.6 1550
lubuskie 54.4 12.5 10.7 894
śląskie 54.4 17.6 8.6 2869
zachodniopomorskie 54.2 13.2 10.9 1943
wielkopolskie 53.8 12.8 11.8 1790
kujawsko-pomorskie 51.4 13.8 11.3 1734
łódzkie 50.3 17.0 10.7 1522
opolskie 48.9 18.1 12.6 1492
małopolskie 47.2 18.2 14.0 1522
lubelskie 43.4 21.1 13.6 1364
podlaskie 45.9 19.3 12.3 826
świętokrzyskie 41.9 22.3 14.5 523
podkarpackie 40.1 18.8 15.6 959
POLSKA 52.5 15.3 11.2 27289

Na podstawie danych pobranych ze strony http://parlament2015.pkw.gov.pl/ metodą webscrappingu. Dane są dostępne tutaj.