>> wybierz styl >> es :: ns :: bs

Weblog Tomasza Przechlewskiego [Zdjęcie T. Przechlewskiego] [[Ikona]]


scrum
random image [Photo gallery]
Zestawienie tagów
1-wire | 18b20 | 1wire | 2140 | 3rz | alsamixer | amazon | anniversary | antypis | apache | api | arm | armenia | astronomy | asus | atom.xml | awk | aws | bakłażan | balcerowicz | balta | bash | berlin | bibtex | bieszczady | biznes | blogger | blogging | blosxom | borne-sulinowo | breugel | bt747 | budapeszt | canon | cedewu | chello | chown | chujowetaśmy | cmentarz | contour | cron | css | csv | curl | d54250wykh | debian | dejavu | dhcp | dht22 | dia | docbook | dom | ds18b20 | dyndns | dynia | ebay | economy | ekonomia | elka | elm | emacs | emacs23 | english | ess | eu | excel | exif | exiftool | f11 | fc | fc11 | fc15 | fc5 | fc8 | fedora | fedora21 | fenix | ffmpeg | finepix | firefox | flickr | fontforge | fontspec | fonty | fop | foto | france | francja | fripp | fuczki | fuji | fuse | gammu | garmin | gawk | gazwyb | gdańsk | gdynia | gender | geo | georgia | gft | git | github | gmail | gnokii | gnus | google | googlecl | googleearth | googlemaps | gphoto | gphoto2 | gps | gpsbabel | gpsphoto | gpx | gpx-viewer | greasemonkey | gruzja | grzyby | haldaemon | handbrake | historia | history | hitler | holocaust | holokaust | hpmini | humour | iblue747 | ical | iiyama | ikea | imap | inkscape | inne | internet | j10i2 | javascript | jhead | k800i | kajak | kamera | kleinertest | kml | kmobiletools | knuth | kod | kolibki | komorowski | konwersja | krutynia | kuchnia | kurski | latex | latex2rtf | latex3 | lcd | legend | lenny | lesund | lewactwo | liberation | linux | lisp | lisrel | litwa | logika | ltr | lubowla | lwp | m2wś | mapsource | marvell | math | mathjax | mazury | mbank | mediolan | mencoder | mh17 | michalak | microsoft | monitor | mp4box | mplayer | ms | msc | msw | mtkbabel | museum | muzyka | mymaps | mysql | nanopi | natbib | navin | neo | neopi | netbook | niemcy | niemieckie zbrodnie | nikon | nmea | nowazelandia | nuc | nxml | oauth | oauth2 | obituary | okular | olympus | ooffice | ooxml | opera | otf | otftotfm | other | overclocking | panoramio | pdf | pdfpages | pdftex | pdftk | perl | photo | photography | picasa | picasaweb | pim | pine | pit | plotly | pls | plugin | po | politics | polityka | polsat | postęp | powerpoint | prelink | problem | propaganda | pstoedit | putin | python | r | radio | random | raspberry pi | refugees | relaxng | ridley | router | rower | rowery | rpi | rsync | rtf | ruby | rugby | russia | rwc | rwc2007 | rwc2011 | rzym | samba | sem | sheevaplug | sienkiewicz | signature | sks | skype | skytraq | smoleńsk | sqlite | srtm | ssl | statistics | stats | statystyka | stix | suwałki | svg | svn | swornegacie | szwajcaria | słowacja | tbilisi | terrorism | tex | texgyre | texlive | thunderbird | tomato | tourism | tramp | trang | truetype | ttf | turystyka | tusk | tv | tv5monde | twitter | typetools | ubuntu | uchodźcy | udev | umap | unix | upc | updmap | ups | utf8 | varia | video | vienna | virb edit | vostro | wammu | wdc | wdfs | webcam | webdav | wh2080 | wiedeń | wikicommons | wilno | windows | windows8 | wine | wioślarstwo | word | wordpress | wrt54gl | ws1080 | wtyczka | ww2 | www | wybory | wybory2015 | włochy | węgry | xemex | xetex | xft | xhtml | xine | xml | xmllint | xsd | xslt | xvidtune | youtube | yum | zakopane | zakupy | zdf | łeba | świdnica
Pobrania via google: [[Ikona]]
Archiwum
Inne blogi
N. Walsh | Morten H. Frederiksen | B. Clementson | prawo.vagla.pl | F. Hecker | M. Olson | J. Tennison | J. Clark | M. Nottingham | M. Shuttleworth | T. Isakowicz-Zalewski | J. Anglim | José A. Ortega Ruiz Modern Perl
Inne tematyczne
Ashwin Amanna | wiesia.nets.pl | Wojt | rwm.org.pl | DataBlog | Revolutions | Learning R | A. Gelman | C. Nel | J. Vogelgesang | ubl.xml.org/ | J.D. Long |
O stronie
wykorzystywany jest blosxom plus następujące wtyczki: tagging, flatarchives, rss10, lastbuilddatexhtmlmime. Niektóre musiałem dopasować nieco do swoich potrzeb. Więcej o blosxom jest tutaj
Subskrypcja
RSS 1.0
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

url | Tue, 01/12/2015 08:16 | tagi: , ,
Korzystanie z GoogleMaps API z użyciem Google::GeoCoder::Smart

Geokodowanie to zamiana adresu lub nazwy miejsca na parę współrzędnych. Perlowy moduł Google::GeoCoder::Smart wykorzystany w poniższym skrypcie używa geolokalizatora Google'a:

#!/usr/bin/perl
use Google::GeoCoder::Smart;
$geo = Google::GeoCoder::Smart->new();

$location = $ARGV[0];

my $coords = addr2coords( $location );

## ## ## ## ## ##
sub addr2coords {
 my $a = shift ; ## address for example "Sopot,Polska"
 my $r = shift || 'n'; ## flag--order of coordinates lat/lng or lng/lat
 my ($lat, $lng) ;

 ## ## consult cache first ; $GeoCodeCache is a global hash ## ##
 if (exists $GeoCodeCache{"$a"} ) { ($lat,$lng) = split (" ", $GeoCodeCache{"$a"} );  }
 else {

   my ($resultnum, $error, @results, $returncontent) = $geo->geocode("address" => "$a");
   $resultnum--;

   if ($resultnum > 0) { print STDERR "** Location $a occured more than once! **" }

   if ( $error eq 'OK' ) {
      for $num(0 .. $resultnum) {
      $lat = $results[$num]{geometry}{location}{lat};
      $lng = $results[$num]{geometry}{location}{lng};
      ##print "*** LAT/LNG:$lat $lng ERROR: $error RES: $resultNo ***\n";
     }
   } else { print STDERR "** Location $a not found! due to $error **"  }
 }

 $GeoCodeCache{"$a"} = "$lat $lng"; ## store in cache
 ##sleep $SLEEP_TIME;

 if ($r eq 'r' ) { return "$lng,$lat"; } # KML order lng/lat
 else { return "$lat $lng"; ## GPX order lat/lng }
}

Jest limit 2500 żądań/dzień (24 godziny, przy czym nie jest dokładnie opisane kiedy następuje `reset' licznika, tj. rozpoczyna się następna doba). Jeżeli się przekroczy limit to:

perl ./coordinates.pl Wrocław
** Location Wrocław not found! due to OVER_QUERY_LIMIT **** 

Ponieważ w bibliotekach Perla jest wszystko są także moduły Geo::Coder::ManyGeo::Coder::Multiple, który potrafią korzystać z wielu Geokoderów na raz (Google, Yahoo, Bing), zwiększając w ten sposób dzienny limit. Nie używałem...

Dopisane 29 stycznia 2012: W sieci via Google można znaleźć informacje, że reset ma miejsce ,,at midnight 12:00 PST.'' Ale w tym przypadku coś nie bardzo się zgadza, bo exact midnight PST byłoby o 9:00 rano (8:00 GMT), a blokę na mój IP zdjęli około 16.00. (A kwota wyczerpała się o jakieś 18--19 dnia poprzedniego--dokładnie nie pamiętam.)

url | Sun, 29/01/2012 09:15 | tagi: , ,

W książce Flickr Hacks Paula Bauscha i Jima Bumgardnera do manipulowania danymi w formacie XML przesyłanymi z flickr.com używa się XML::Simple. Jakoś XML::Simple nie wydaje mi się specjalnie simple w tym wypadku. Zdecydowanie wolę XML::LibXML, który pozwala przede wszystkim na dotarcie do poszczególnych fragmentów dokumentów XML w prosty sposób bez używania karkołomnych referencji. Przykładowo poniższy skrypt (fragment) wykonuje zapytanie flickr.photos.search [najistotniejszą częścią skryptu -- zaznaczoną odmiennym kolorem tła -- jest pętla do { ... }]:

use XML::LibXML;
use LWP::Simple;
use Getopt::Long;

require 'login2flickr.rc';

our $api_key;
our $my_flickr_id;

my $tags_txt;
my $show_help;
my ($user_id, $all_users, $report_views);
my @Photos;

GetOptions( "all" => \$all_users,
     "user=s" => \$user_id, "views"  => \$report_views,
     "tags=s" => \$tags_txt);

unless ( $user_id ) {  $user_id = $my_flickr_id; }
if ( $all_users ) { $user_id = ''; } ## search all photos

my $parser = XML::LibXML->new;
my $pageno;
my $method = 'flickr.photos.search';
my $max_per_page = 500;
my ($total_pages, $current_page);

my $search_query =  "&extras=geo,views"; # http://www.flickr.com/services/api/flickr.photos.search.html
if ( $user_id ) { $search_query .=  "&user_id=$user_id";  }
if ( $tags_txt ) { $search_query .=  "&tags=$tags_txt" }


do {
   $pageno++;

   my $resp = get ( "http://www.flickr.com/services/rest/?" .
       "method=$method&api_key=$api_key&per_page=$max_per_page&page=$pageno" . 
       $search_query );

   ## sprawdź czy jest OK:
   my $photos = $parser->parse_string($resp); 
   unless ( $photos->getElementsByTagName('rsp' )->[0]->getAttribute( 'stat' ) eq 'ok') { 
        die "Problems with retriving photo list!\n" }

   push @Photos, $photos->getElementsByTagName('photo' );

   $total_pages = $photos->getElementsByTagName('photos' )->[0]->getAttribute('pages' );
   $current_page = $photos->getElementsByTagName('photos' )->[0]->getAttribute('page' );

   # Dla dużych zbiorów zdjęć może długo trwać, więc wypisz że coś robisz:
   printf STDERR " *** Page %d  of %d retreived...\n", $current_page, $total_pages;

} while ( $current_page lt; $total_pages  );

## Lista @Photos zawiera wszystkie zdjęcia...
## ... dalsza część skryptu ...

Pętla jest niezbędna ponieważ flickr API organicza wielkość zwracanego dokumentu do maksymalnie 500 zdjęć. Jeżeli zdjęć jest więcej, to zapytanie należy zadać wielokrotnie, zmieniając wartość parametru page. Przy czym liczbę wszystkich stron określa wartość atrybutu pages.

Po ściągnięciu dokumentu skrypt sprawdza czy wszystko jest OK. Jeżeli tak, to za pomocą getElementsByTagName pobiera wszystkie elementy photo i dodaje je do listy @Photos. Teraz pobierane są wartości atrybutów pages/page w celu ustalenia czy wszystko już ściągnięto. Jeżeli pages < page to pętla działa dalej...

Zwracam uwagę, że od jakiegoś czasu zapytanie może zawierać parametr extras, dzięki któremu zwracany dokument XML zawiera dodatkowe informacje, takie jak: tagi, współrzędne geograficzne, liczbę odsłon, itd... Jest to opisane w API na flickr.com a także tutaj. W wyżej przedstawionym skrypcie deklaracja:

my $search_query =  "&extras=geo,views";

Oznacza, że do standardowych opisów zdjęć będą dodane także współrzędne geograficzne (oczywiście tylko wtedy gdy zdjęcia będą nimi oznaczone) oraz liczba odsłon. BTW dodanie extras=views powoduje, że kiedyś niemożliwy do wykonania poprzez API problem ustalenia ile razy zdjęcie było oglądane teraz jest jak najbardziej wykonalny. Zatem skrypty -- opisane w Nowa usługa na flickr.com oraz Czy flickr umie liczyć -- lepsze rozwiązanie -- są już niepotrzebne.

Kompletny skrypt pobierający zdjęcia z flickr.com i wypisujący plik GPX dla tych, które zawierają współrzędne geograficzne jest tutaj. Ale uwaga: próba wykonania zestawienia wszystkich wież ciśnień z flickr.com:

flickr_photo_gsearch.pl -a -t 'wieżaciśnień,wasserturm,watertower' > wasserturme-alles.xml

Skończyła się pobraniem ponad 23 tysięcy zdjęć (z tego około 6 tys. było geokodowane). Plik GPX pn. wasserturme-alles.xml miał 1,8 Mb i za nic nie szło tego wyświetlić w przeglądarce. Za duże... Mniejsze pliki są ok: kaplice [mapa ] albo moje wieże ciśnień [mapa ].

url | Sun, 14/09/2008 10:10 | tagi: , , ,
Ebay REST API

`Na szybko' zrobiłem trzy skrypty wykorzystujące Ebay REST API. Skrypty mają ułatwić wyszukiwanie oraz archiwizowanie interesujących mnie aukcji. Próbowałem wykorzystać do ww. celu gotowe narzędzie ale jakoś nic interesującego nie znalazłem. Bibioteka Net::eBay jakaś taka słabo udokumentowana i nieporęczna mi się wydała podobnie jak WWW::Search::Ebay. (Por. skrypt opisany np. tutaj.)

Najbardziej skomplikowany z moich skryptów wykorzystuje metodę FindItemsAdvanced do wyszukania aukcji. Metoda FindItemsAdvanced ma parametr MaxEntries określający liczbę zwracanych aukcji. Parametr ten ma domyślną wartość 20 a maksymalną akceptowaną wartością jest 200 (jeżeli podamy 0 to metoda zwróci liczbę pasujących do zapytania aukcji). Jeżeli aukcji jest więcej niż wynosi wartość MaxEntries, to aby ściągnąć kolejne aukcje metodę FindItemsAdvanced trzeba wykonać wielokrotnie, podając odpowiednią wartość parametru PageNumber. Przykładowo jeżeli aukcji jest 121, a wartość MaxEntries wynosi 40, to trzeba FindItemsAdvanced wykonać 4 razy.

Parametr ItemSort ustala porządek sortowania. Wartość StartDate tego parametru powoduje, że aukcje są posortowane według daty wstawienia na ebay (daty/czasy są definiowane w GMT oczywiście).

W związku z takim działaniem metody FindItemsAdvanced skrypt ebay_rest_search.pl najpierw ustala liczbę pasujących aukcji, a następnie pobiera kolejno strony w pętli do momentu aż znajdzie aukcję już pobraną (aukcje są przechowywane w haszu %IdxLog, który jest przechowywany na dysku przy wykorzystaniu funkcji store/retrieve modułu Storable). Nowe aukcje są dodawane do hasza %IdxLog:

## Uproszczony fragment skryptu:
while ( ($current_page >= 0) && ($current_page <= $last_page ) ) {
  $current_page++;
  $xml = make_call('search_text' => $search_text, 'per_page' => $MAX_PER_PAGE,
     'site_id' => "$this_site_id", 'page' => $current_page);

  my $xm = $xmlp->XMLin($xml,  ForceArray => 0);
  my $list = $xm ->{'SearchResult'}->{ItemArray}->{Item} ;

  foreach $item ( @{$list} ) { 
    $item_id = $item->{'ItemID'};

    if ( exists $IdxLog{ $item_id } ) {## zakończ 
      $current_page = -99; last ;  }
    else { $new_items++;
      $IdxLog{ $item_id } = [ $item->{'Title'}, $item->{'StartTime'}, $item->{'EndTime'},
          $item->{'GalleryURL'}, $item->{'ListingStatus'}, 
	  $item->{'ViewItemURLForNaturalSearch'}, $day_added ];
    }
  }
}

Metoda XMLin jest z modułu XML::Simple. Moduł ten definiuje prosty interfejs do dokumentów XML. Po wykonaniu XMLin, zawartość dokumentu jest dostępna w postaci elementów zagnieżdżonych list/haszy. Można oczywiście analizować dokument XML w inny sposób, nie upieram się że XML::Simple jest najlepszym pomysłem.

Uwaga: metoda FindItemsAdvanced nie umożliwia przeszukania całej bazy eBay (odpowiednik wyszukiwanie zaawansowane->preferowana lokalizacja->cały świat). Parametr siteid określa, który serwis eBay ma być przeszukany. Jeżeli podamy id=0 to zapytanie dotyczyć będzie ebay.com, a jeżeli id=212, to ebay.com.pl, itd. Aby przeszukać cały ebay to trzeba wykonać FindItemsAdvanced wielokrotnie.

Następnie hasz %IdxLog jest przeglądany w pętli celem archiwizacji aukcji, które się zakończyły:

for $id (sort { $IdxLog{$a}[2] cmp $IdxLog{$b}[2] } keys %IdxLog) {
   $end_time = $IdxLog{$id}[2]; # drugi element listy to czas zakończenia
   $time_to_end = $now_in_seconds - ebaydate2seconds( $end_time );

   if ($time_to_end > 0 && $status =~ /Active/) {# Aukcja zakończona
      $result = `ebay_rest_item.pl -i $id`; ## ściągnij innym skryptem

      if ($? > 0 ) { warn "Problems backuping $id\n"; } else {
	$IdxLog{$id}[4] = 'Completed'; # zmień status na zakończoną
	$completed_items++; }
   }
}

Zmienna $now_in_seconds, to liczba sekund od epoch a funkcja ebaydate2seconds zmienia napis zawierający czas w formacie zwracanym w dokumencie XML na liczbę sekund od epoch. Jeżeli różnica czasu bieżącego a czasu zakończenia aukcji wskazuje, ze aukcja się zakończyła jest ona ściągana za pomocą skryptu ebay_rest_item:

#!/usr/bin/perl
use LWP::Simple;
use Getopt::Long;

my $verbose = 1;
use lib "$ENV{HOME}/.ebay"; ## configuration files

GetOptions('item=s' => \$ebay_item_id, 'help' => \$print_help, );
unless ($ebay_item_id) { die "Usage: $0 -i <item_id>\n"; }

our $netebay_rc;
require("netebay.rc");

my $url = "http://open.api.ebay.com/shopping?callname=GetSingleItem" .
  "&responseencoding=XML" .
  "&appid=$EBayRC{p_appid}" .
  "&siteid=0" .
  "&version=525" .
  "&ItemID=$ebay_item_id" .
  "&IncludeSelector=Description,Details";

my $xml = get $url ;

## In a scalar context m//g iterates through the string, returning true each time
## it matches. If you modify the string in any way, the match position is reset.
my %Pics;
while ($xml =~ /<PictureURL>([^<>]+)<\/PictureURL>/mg) { $Pics{$1} = 1; }

my $pic, $ori_pic;

for $pic  (keys %Pics ) { 
  $ori_pic = $pic; $ori_pic =~ s/\?/\\\?/g; # zmieniamy meta znaki: ?
  $pic =~ s/\?[^\?]+$//g;
  my ($content_type, $document_length, $modified_time, $expires, $server) = head($pic);

  $pic_no++;

  if ( $content_type =~ /image/ ) {  $content_type =~ m/\/(.+)$/;
    $local_file = "${ebay_item_id}_${pic_no}.$1"; } 
  else { $local_file = "${ebay_item_id}_${pic_no}.JPG?"; } ## problems with content_type

  warn "Storing $pic in  $ARCH_DIR/$local_file ... \n";
  getstore($pic, "$ARCH_DIR/$local_file");

  ## zapisz lokalną nazwę jako atrybut
  ## Uwaga na metaznaki (? już jest obezwładniony):
  $xml =~ s/<PictureURL>$ori_pic/<PictureURL local='$local_file'>$ori_pic/;

}

open (LOG, ">$ARCH_DIR/$ebay_item_id.xml" ) || 
     die " ** cannot open $ARCH_DIR/$ebay_item_id.xml **";

print LOG "$xml\n";

Ten skrypt zapisuje po prostu dokument XML zwrócony przez metodę GetSingleItem. Argumentem skryptu jest id aukcji. Jedyne utrudnienie związane jest z pobraniem zdjęć. Adresy URL do zdjęć są zdefiniowane wewnątrz elementów PictureURL. Aby uprościć skrypt elementy te są wyszukiwane przy pomocy prostego wyrażenia regularnego. Konstrukcja:

while ($napis =~ /wzorzec/mg) { ## $1 zawiera dopasowany do wzorca napis  }

Będzie dopasowywać wzorzec do $napisu iteracyjnie. W każdej iteracji zmienna $1 będzie zawierała kolejny napis pasujący do wzorca. Aby powyższe działało prawidłowo $napis nie może być modyfikowany wewnątrz pętli (i nie jest). Adresy URL są składowane w haszu %Pics.

Teraz hasz %Pics jest przeglądany w pętli. Zdjęcia są ściągane a dodatkowo dokument XML jest modyfikowany -- też za pomocą wyrażeń regularnych -- w taki sposób, że do elementu dodawana jest informacja o nazwie lokalnej zdjęcia. Nazwa lokalna jest tworzona automatycznie jako: ${ebay_item_id}_${pic_no}.$1, gdzie ${pic_no} jest kolejnym numerem zdjęcia dla aukcji o numerze ${ebay_item_id}.

Ostatni skrypt jest najprostszy i drukuje fragment drzewa kategorii eBay (konkretnie podkategorie dla kategorii, której id podano jako argument skryptu). Korzeń drzewa ma id równe -1.

#!/usr/bin/perl
use LWP::Simple;
use Getopt::Long;

use lib "$ENV{HOME}/.ebay"; ## configuration files
use $ebay_site_id = 'US'; ## 0 is US ebay

GetOptions( 'site=s' => \$ebay_site_id, 'category=i' => \$ebay_cat_id,
    'help' => \$print_help, );
unless ($ebay_cat_id) {$ebay_cat_id = -1 } ## top-level

our $netebay_rc;
require("netebay.rc");

my $site_id_code = $Sites_Ids{$ebay_site_id};

my $url  = "http://open.api.ebay.com/Shopping?callname=GetCategoryInfo" .
   "&appid=$EBayRC{p_appid}" .
   "&version=533" .
   "&siteid=$site_id_code" .
   "&CategoryID=$ebay_cat_id" .
   "&IncludeSelector=ChildCategories";

print (get $url) . "\n" ;

Ten skrypt przyda się do udoskonalenia działania ebay_rest_search, tak żeby przeszukiwania dotyczyły tylko wybranych kategorii. Niestety eBay nie ma jednolitej hierarchii kategorii. Ten sam przedmiot może być różnie klasyfikowany.

No i tyle. Pozostaje umieścić ebay_rest_search w crontabie. Skrypty są tutaj.

url | Sun, 22/06/2008 16:12 | tagi: , , , ,
Nowa usługa na flickr.com

Niedawno (13 grudnia 2007) Flickr uruchomił nowy serwis pn. stats! Użytkownicy serwisu z wykupioną usługą pro (ca 25 USD, czyli niedużo -- jak to się czasy zmieniły BTW) mają dostęp do różnych statystyk dotyczących swoich zdjęć. Informacje o usłudze zobaczyłem dzisiaj. Żeby z niej skorzystać trzeba się zasubskrybować -- czytaj nacisnąć duży guzik ze stosownym napisem. Wówczas ukazuje się napis, że OK, ale statystyki będą jutro. Grozę potęguje animacja gościa z pneumatycznym młotem. Widocznie jestem jednym z pierwszych bo były od razu -- wystarczyło odświeżyć stronę.

[[screen dump]]
[[screen dump]]
[[screen dump]]

A więc pro-user dostaje takie informacje jak: liczba odsłon dla każdego zdjęcia oraz statystyki adresów referer w podziale na ,,wczoraj'' oraz ,,ogółem''. Ponadto wykres liniowy liczby odsłon zawierający dzienne dane z ostatniego miesiąca oraz zestawienie tabelaryczne zawierające podsumowania liczby odsłon (wczoraj, ostatni tydzień, ostatni miesiąc oraz ogółem) dla stron głównych (photostream), zbiorów (sets), kolekcji (collections) i stron pojedynczych zdjęć.

Statystki ,,oglądnięć'' dostępne ze strony http://www.flickr.com/photos/tprzechlewski/stats/allphotos/ (Nie ma co klikać, więc i linka nie ma -- strona dostępna po zalogowaniu:-) są kompletne, tj. dla każdego zdjęcia dostępna jest liczba odsłon (z wczoraj, z ostatniego tygodnia oraz ogółem).

W pierwszej chwili pomyślałem, że nowa usługa renders obsolete jak mawiają Anglicy moje skrypty, które z takim zapałem ostatnio udoskonalałem. Ale niekoniecznie! Dane ze strony stats! primo nie są trwałe (niektóre tak -- ale nie wszystkie, np. dzienne statystyki oglądalności dostępne są tylko dla ,,wczoraj''), secundo żeby były trwałe trzeba je ściągać na swój komputer na przykład codziennie. No i tu się przyda programik flickr_store_views.pl, który potrafi się zalogować na flickr.com, pobrać stronę i zapisać co trzeba na dysk. Zamiast ściągać 170 stron i z nich wyciągać potrzebne informacje wystarczy teraz ściągnąć kilka stron spod adresu tprzechlewski/stats/allphotos/yesterday/pageliczba robiąc to metodycznie, czyli codziennie.

Jeszcze link z forum z informacjami nt. omawianej wyżej usługi. Your stats will eventually be a rolling 28 day window, czyli dostępne będą tylko dane z ostatniego miesiąca. Zwracam też uwagę na komentarz dot. tłumaczenia słowa interestingness na język hiszpański jako interesidad. Że niby takie słowo nie istnieje. Faktycznie nie ma w słowniku a google znajduje raptem 4 strony z interesidad (w tym powyższa). Ale jak przetłumaczyć interestingness (za WordNet: the power of attracting or holding one's interest), np. na j. polski? Moc przyciągania?

Dopisane 13 maja 2008: Skrypt flick-store-views.pl przestał działać. Coś zostało zmienione w procedurze logowania. Nie chce mi się tego poprawiać...

url | Fri, 14/12/2007 17:42 | tagi: , , ,
Dodawanie zdjęć na flickr.com w trybie My-flickr-upld

Znacząco poprawiłem swój tryb do dodawania zdjęć na flikr.com. Teraz wszystko się dzieje wewnątrz Emacsa łącznie z uruchomieniem wysyłania plików na serwer. W starej wersji pliki konfiguracyjne czytał Emacsowy moduł xml.el co było może i eleganckie ale odbywało się przeraźliwie wolno. W nowej wersji plik konfiguracyjny jest plikiem lispowym wygenerowanym skryptem Perla z plików XML. Jak to działa opisałem na oddzielnej stronie. Jedna sprawa jest tajemnicza:

#!/usr/bin/perl -w
require 'login2flickr.rc';
require 'flickr_utils.rc';

my @tmpx = get_sets_ids();
my @tmpy = get_pools_ids();

W plikach login2flickr.rc oraz flickr_utils.rc są zdefiniowane procedury, który czytają plik z dysku i zwracają zmienne. W szczególności flickr_utils.rc zawiera dwie prawie identyczne procedury (get_sets_ids oraz get_pools_ids), czytające różne pliki konfiguracyjne. Kurcze... na jednym komputerze perl zwraca błąd: Undefined subroutine &main::get_pools_ids called at... a na drugim działa. Ten sam perl, ta sama wersja FC5, jedna procedura z pliku dołączanego poleceniem require jest zdefiniowana druga nie... Wystarczy zmienić kolejność poleceń require żeby powyższe działało w obu systemach. Nic mi do głowy nie przychodzi...

url | Tue, 11/12/2007 20:58 | tagi: , , , , ,
Czy flickr umie liczyć -- lepsze rozwiązanie

Wymyśliłem lepsze rozwiązanie problemu opisanego tutaj. Ściągane strony są obrabiane ,,w locie'' a w pliku na dysku są zapisywane tylko informacje o liczbie odsłon. Mówiąc konkretniej korzystam z metod store/retrive modułu Storable, zapisując/czytając hasz postaci $PhotoLog{data}{photoid}= views. Generowaniem podsumowań zajmuje się inny skrypt, który wypisuje wyniki w postaci dobrze sformatowanego fragmentu dokumentu HTML (,,opakowanego'' wewnątrz elementu <div>). Ten fragment następnie można wstawić w odpowiednie miejsce strony HTML. Wreszcie ostatni skrypt tworzy wykres liniowy (przy wykorzystaniu modułu GD::Graph) liczby odsłon oraz liczby odsłoniętych zdjęć. Trzy skrypty można połączyć w całość:

#!/bin/bash
perl flick-store-views.pl
if [ "$?" -ne "0" ] ; then echo "** Problems ..." ; exit 1 ; fi
perl flick-report-views.pl -lang=pl -max=25 > 00-pl.phtml && \
perl flick-graph-views.pl
if [ "$?" -ne "0" ] ; then echo "** Problems ..." ; exit 1 ; fi
echo "OK"

Jakoś tak... Przykład wykorzystania jest na tych stronach [1] [2] [3]. Skrypty można pobrać stąd (flick-store-views.pl, flick-report-views.pl oraz flick-graph-views.pl.) Powyższe można nawet wsadzić do crona. Skrypty wykorzystują m.in. moduły GD::Graph::lines oraz Storable. Ten pierwszy musiałem doinstalować do mojej FC5. Z tym był zresztą pewien kłopot ponieważ yum nie zadziałał--nie wiem czemu. Ostatecznie sprawę rozwiązało ,,ręczne'' ściągnięcie perl-GDGraph-1.4307-1.fc5.noarch.rpm, perl-GDTextUtil-0.86-7.fc5.noarch.rpmperl-GD-2.35-1.fc5.i386.rpm ze strony rpm.pbone.net.

Przy okazji dowiedziałem się jak ,,porządnie'' wycentrować tabelę w oknie przeglądarki:

<table style='margin-left: auto; margin-right: auto;' ...
url | Tue, 27/11/2007 16:41 | tagi: , , , ,
Czy flickr umie liczyć -- rozwiązanie

Łączną liczbę odsłon dla wszystkich zdjęć -- por. poprzedni wpis na ten temat -- można ustalić programistycznie ściągając wszystkie strony albumu (mają one URLe kończące się na pageliczba, tj. page1, page2, page3, itp.). Pod miniaturą każdej fotografii jest odpowiednia informacja. Zwykłe LWP::Simple w zupełności by do tego wystarczył:

use LWP::Simple;
my $max_page = $ARGV[0] || 1; # nie podano ile -- ściągnij pierwszą
$urlbase = "http://www.flickr.com/photos/tprzechlewski/page";

for ($p=1; $p =< $max_page; $p++ ) {
   $content = get($urlbase . $p );
   die "Couldn't get it!" unless defined $content;
   print $content;  }

Niech powyższy kod zapisano w ftotal.pl. Teraz można by np. ściągnąć wszystkie strony podając ftotal.pl 161 > ftotal.log a następnie wyłuskać odpowiednie informacje z pliku ftotal.log innym skryptem.

Ale... Ale jest jeden problem. Flickr słusznie nie liczy odsłon (zalogowanego) właściciela albumu. Skrypt nie autoryzuje dostępu i działa jako ,,osoba trzecia'' więc jednocześnie sztucznie nabija statystykę. Jeżeli skrypt byłby uruchamiany cyklicznie zmieniłby znacząco statystykę odsłon głównych stron w albumie. Przykładowo w moim przypadku byłoby to dodanie 161 odsłon, bo tyle liczy -- obecnie -- stron głównych mój album. Można by machnąć ręką, ale z drugiej strony jakby udało się skrypt zalogować...

Logowanie do www.flickr.com jest jednak cokolwiek skomplikowane. Kombinowałem na różne sposoby szukając w google albo gotowców albo podpowiedzi. Pierwszym ,,podejrzanym'' był moduł WWW::Mechanize:

#!/usr/bin/perl
use WWW::Mechanize;

my $mech = WWW::Mechanize->new( autocheck => 1 );

$mech->credentials( 'login' => 'password' );
$mech->get( 'http://www.flickr.com/photos/tprzechlewski/page1/' );
print $mech->content();

Nie działa... Próbowałem, też WWW::Mechanize::Shell, opisany przykładowo w tekście Michaela Schilli Simple Data Scraper (tutaj jest polskie tłumaczenie). BTW były pewne kłopoty z jego zainstalowaniem, ponieważ make test kończy się błędem... Szukając wskazówek do rozwiązania mojego problemu znalazłem także potencjalnie przydatny tekst pt. Secure Web site access with Perl. Podsumowując WWW::Mechanize okazał się strzałem w płot, ale może się przyda do czegoś innego...

Rozwiązanie znalazłem -- jak to często bywa -- trochę przypadkowo. Punktem wyjścia były skrypty ze strony: coder.home.cosmic-cow.net (ich kopie umieściłem tutaj). Następnie posługując się wtyczką do Firefoxa pn. Live HTTP headers ustaliłem co i gdzie trzeba zmienić. Skrypt wykorzystuje moduły HTTP::Request::CommonLWP::UserAgent, koncepcyjnie jest mało skomplikowany ale dość rozwlekły bo liczy ca 100 wierszy. Nie będę go więc cytował, jest dostępny tutaj. Nie do końca jestem też pewny czy wszystkie wywołania GET są potrzebne, ale nie mam czasu/wiedzy tego optymalizować, ważne że działa, tj. udaje zalogowanego użytkownika serwisu flickr.

Skrypt działa w ten sposób, że ściąga n stron ,,głównych'' z mojego konta flickr (te URLe przypominam kończą się na pageliczba). Liczbę n, podaną jako parametr wywołania skryptu, ustalam ,,empirycznie'' konsultując się z flickr.com (ewentualnie w wersji gold-extended skryptu można by to zautomatyzować):

perl flick-total-views.pl 161 > flick-total-views.log &&  \
  perl -h flick-aggr-totals.pl flick-total-views.log > stats.html

Skrypt flick-aggr-totals.pl zlicza co trzeba parsując flick-total-views.log. Na razie pomija pliki, które nie były wcale oglądane (tj. 0 views), ale to łatwo poprawić/zmienić, bo kod HTML generowany przez www.flickr/ nie jest specjalnie zaplątany. Wynik pierwszego zastosowanie ww. skryptów zamieściłem na mojej stronie.

url | Sun, 18/11/2007 11:47 | tagi: , , , ,
Czy flickr umie liczyć?

Flickr wyświetla liczbę że tak powiem odsłon pod każdym zdjęciem, zbiorczo dla każdego zbioru oraz łącznie dla całego albumu (zakreślone na czerwono na rysunkach poniżej). Wydawać by się mogło, że np. sumując odsłony dla wszystkich zdjęć otrzyma się łączną liczbę odsłon w albumie, to znaczy ile razy oglądano nasze zdjęcia. Już na pierwszy rzut oka widać, że tak nie jest. Po prostu nic nie jest sumowane a każdy licznik ,,liczy'' swoją stronę. Odzielnie jest sumowana ,,strona główna'', oddzielnie każda strona dla pojedynczego zdjęcia i oddzielnie strona główna każdego zbioru.

[[screen dump]] [[screen dump]] [[screen dump]]

A jak obliczyć łączną liczbę odsłon dla wszystkich zdjęć? Wydawałoby się, że to pryszcz, bo flickr słynny jest ze swojego API. Akurat tego jednak nie da się ustalić -- nie ma takiej metody. Wprawdzie flickr.activity.userPhotos. zwraca m.in. liczbę wyświetleń każdej pojedynczej strony, ale tylko dla stron na których coś się stało: dodano komentarz, ktoś dodał taga albo dodał zdjęcie do swoich ulubionych. Do tego maksymalnie można ściągnąć 50 zdjęć na raz (maksymalna wartość per_page), parametr timeframe może przyjąć maksymalnie wartość jednego miesiąca (30d, większe wartości są ignorowane) a metodę można uruchomić powtórnie nie częściej niż co godzinę (czyli co godzinę można ściągnąć jedną stronę). Poddałem się...

Nie ustaliłem wprawdzie ile było odsłon zdjęć w moim albumie ale eksperymentując z API flickera odkryłem przynajmniej jak można obejść się bez perlowego pakietu Flickr-API (ale nie bez Perla). Otóż wystarczą moduły LWP::Simple oraz Digest::MD5:

Niektóre metody nie wymagają uwierzytelnienia. Ich wywołanie jest szczególnie proste -- nie jest potrzebny nawet moduł Digest::MD5 -- i sprowadza się do konstruowania adresów URL według następującego schematu (znak \ na końcu oznacza kontynuację wiersza):

http://www.flickr.com/services/rest/?method=metoda&parametr1=wartość1\
  &parametr2=wartość2...

W metodach, które uwierzytelnienia wymagają sprawa się komplikuje. Trzeba podać api_key, auth_token oraz secret (poniżej nazwany shared_secret) opisane tutaj i/lub w dokumentacji modułu Flickr-Upload. Najpierw należy zbudować napis według schematu:

secretapi_keyapi_keyauth_tokenauth_tokenmethodmethodarg1wart1arg2wart2 ... 

Następnie utworzyć jego skrót za pomocą funkcji MD5. W przypadku Perla może to wyglądać jak na poniższym przykładzie (metoda flickr.activity.userPhotos ma argumenty page, per_page oraz timeframe). Obliczony skrót dodajemy jako ostatnią część adresu URL:

Digest::MD5 qw(md5_hex);
my $method = 'flickr.activity.userPhotos';

 ## ...
 ## skrót MD5:
my $api_sig = md5_hex( "${shared_secret}api_key${api_key}auth_token${auth_token}method${method}" .
   "page${page}" . "per_page${per_page}" . "timeframe${timeframe}" ) ;

my $url = "http://www.flickr.com/services/rest/?method=$method" .  
   "&api_key=$api_key" . "&auth_token=$auth_token" . 
   "&page=$page" . "&per_page=$per_page" .
   "&timeframe=$timeframe" . "&api_sig=$api_sig" ; ## wstaw skrót tutaj

print $url;

Przy okazji -- jak to często bywa -- znalazłem ciekawą stronę dotyczącą języka Perl. Jest też na ww. stronie opis pakietu Flickr-Upload, z którego też korzystam. Norman Walsh zaimplementował nawet API flickra w XSLT -- ciekawe ale przydatność taka sobie.

url | Wed, 14/11/2007 08:28 | tagi: , , , ,