Serwis demotywatory.pl jest znany zapewne każdemu internaucie. Gromadzi on obrazki wrzucane przez użytkowników z ironicznymi podpisami. W godzinach szczytu ciężko jest jednak przeglądać stronę główną. Nic nie stoi na przeszkodzie, aby wszystkie te obrazki pobrać na nasz lokalny dysk.
Pierwszą rzeczą, którą musimy zrobić to oszacować ilość demotywatorów na głównej. Biorąc pod uwagę, że na jednej podstronie jest ich wyświetlanych 10, a podstron z nimi w dniu 5 lutego 2011 jest 1953 możemy założyć, że do pobrania mamy niecałe 20 000 obrazków.
Nie jest to spora liczba. Do osiągnięcia spokojnie w 2-3 godziny działania skryptu na średniej klasy łączu internetowym.
Jeśli chodzi o wybór języka, zdecydowałem się skorzystać z PHP+MySQL oraz biblioteki cURL.
Ok, mając przygotowane założenia można przystąpić do analizy strony. Przeciętny demot jest wyświetlany w następujący sposób (załamałem linie, aby było czytelniej):
[codesyntax lang=”html4strict”]
<img src="http://statichg.demotywatory.pl/uploads/201102/1296841564_by_Kavarito_500.jpg" class="demot" alt="Najprostsze wynalazki - To one tak bardzo ułatwiają nam codzienne życie " height="5620" width="500px">
[/codesyntax]
Korzystając z wyrażeń regularnych wystarczy użyć poniższego zapisu, aby wyciągnąć wszystkie 10 ścieżek do obrazków.
[codesyntax lang=”php”]
$wzorzec = '#img src="(.*)" class="demot"#';
[/codesyntax]
Rozbijmy nasze zadanie na dwa skrypty. Jeden, który pobierze i zapisze wszystkie adresy obrazków w bazie danych i drugi, który będzie je pobierał.
Uważam, że będzie to najbardziej optymalne. Raz, że będziemy mieć adresy demotów pod ręką, a dwa, zapisanie adresów obrazków potrwa krócej, niżeli samych obrazków. Dzięki tej technice przy odrobinie szczęścia unikniemy sytuacji, kiedy to w połowie pobierania danych, admin doda nowy obrazek, w efekcie czego całość ulegnie przesunięciu, a my pominiemy jakiś plik.
Do pobrania danych musimy sprawdzić, która z podstron jest ostatnią, na chwilę obecną jest to 1953. Dzięki wszechmocnemu adminowi, strony numerowane są w zmiennej GET – demotywatory.pl/page/ID, co znacząco ułatwia nam pracę – pętla for z dekrementacją.
Kod będzie wyglądał tak jak ten poniżej, pomijając jeden szczegół 😉
[codesyntax lang=”php”]
<?php //MySQL mysql_connect("localhost", "login", "haslo") or die("Error 1"); mysql_select_db("baza") or die("Error 2"); //OPTIONS if(empty($argv[1])) { die('[!]Usage: $ php '.$argv[0].' <ostatnia strona> '); } //START $a = array(); $curl = curl_init(); for($i=$argv[1]; $i>=0; --$i) { //od podanej strony do pierwszej curl_setopt_array( $curl, array( CURLOPT_URL => 'http://www.demotywatory.pl/page/'.$i, CURLOPT_RETURNTRANSFER => true, CURLOPT_COOKIEFILE => '/tmp/dd.cookie', CURLOPT_COOKIEJAR => '/tmp/dd.cookie', CURLOPT_USERAGENT => 'Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13' ) ); //kod HTML strony zapisujemy do zmiennej $wynik $wynik = curl_exec($curl); #echo $wynik; //luskamy co potrzeba $wzorzec = '#img src="(.*)" class="demot"#'; preg_match_all($wzorzec, $wynik, $a[$i]); #print_r($a[$i][1]); //sciezki do obrazkow #print_r($a); //wszystko for($j=9; $j>=0; --$j) { //10 obrazkow na stronie $adres = $a[$i][1][$j]; mysql_query("INSERT INTO demoty (adres) VALUES ('$adres');"); } echo $i." - Transfer completed "; } curl_close($curl); ?>
[/codesyntax]
Nasza baza danych powinna przy dobrych wiatrach urosnąć do rozmiarów rzędu 20 000 rekordów w niecałe 15 minut.
Teraz tylko odczytanie wszystkich rekordów jeden po drugim i zapisywanie ścieżek do katalogu.
[codesyntax lang=”php”]
<?php mysql_connect("localhost", "login", "haslo") or die("Error 1"); mysql_select_db("baza") or die("Error 2"); function save_image($img, $fullpath) { //ta funkcja byla znaleziona gdzies w sieci $ch = curl_init($img); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_BINARYTRANSFER,1); $rawdata = curl_exec($ch); curl_close($ch); if(file_exists($fullpath)){ unlink($fullpath); } $fp = fopen($fullpath,'x'); fwrite($fp, $rawdata); fclose($fp); } $zapytanie = mysql_query("SELECT * FROM demoty ORDER BY id ASC"); while($zapytanie && $rekord = mysql_fetch_assoc($zapytanie)) { $id = $rekord['id']; $adres = $rekord['adres']; save_image($adres, "demoty/$id.jpg"); echo $id." - Download copleted "; } ?>
[/codesyntax]
Ten etap powinien zająć już nieco więcej czasu, jak wspomniano na początku, 2-3 godzinki.
No i cóż, to wszystko. Warto dodać, że otwarty katalog z ~20 000 zdjęciami (~1 GB) jest w stanie skutecznie przymulić system. Niegłupim pomysłem przez to wydaje się posegregowanie obrazków po 1000 w podkatalogach 😉
Ostatnio się bawiłam w przymulanie systemu. Może ci to pokażę.
Można prościej, czytelniej i nie w pehapie:
[code]
#!/usr/bin/env ruby
require 'rubygems’
require 'open-uri’
require 'nokogiri’
require 'fileutils’
DIR=”/home/arachnist/demotywatory”
(62..1988).each { |page|
puts „Page: #{page}”
html = Nokogiri::HTML(open(„http://demotywatory.pl/page/#{page}”))
html.xpath(„//img[@class=’demot’]/@src”).each { |attr|
if attr.value =~ /[a-z]$/ then
uri = attr.value.sub(„_500”, „”)
puts uri.sub(/.*//, „”)
i = open(uri.gsub(” „, „%20”)).read
o = File.new(„#{DIR}/#{uri.sub(/.*//, „”)}”, „w”)
o.puts i
o.close
end
}
}
[/code]
Robiłem coś podobnego swego czasu, ale problem „zbyt wielu zdjęć w jednym katalogu” można rozwiązać bardzo prosto, zrób foldery o stałej szerokości nazwy, u mnie to było 8 cyfr (a wystarczą 3 cyfry) i rób katalog na podstawie int(page/10). Daje Ci to 100obrazków na jeden folder.
Zawsze lepiej stosować pętlę while, niż for.
Ze względu na wydajność
Przepraszam, ale o jakiej wydajności tutaj mówimy? :>