Podsumowanie<\/a><\/li><\/ol><\/nav><\/div>\n\n\n\nCo to jest web crawler?<\/h2>\n\n\n\n Web crawler to program, kt\u00f3ry przegl\u0105da Internet w poszukiwaniu okre\u015blonych przez jego tw\u00f3rc\u0119 tre\u015bci. Wachlarz zastosowa\u0144 tego typu aplikacji jest bardzo szeroki. Od automatyzacji, po analiz\u0119 i prowadzenie statystyk. Wyobra\u017a sobie, ze chcesz napisa\u0107 program, kt\u00f3ry b\u0119dzie przeszukiwa\u0142 r\u00f3\u017cne portale z og\u0142oszeniami o prac\u0119, a nast\u0119pnie b\u0119dzie przedstawia\u0142 Ci wy\u0142\u0105cznie przefiltrowane propozycje. Albo b\u0119dzie oblicza\u0142 b\u0119dzie prowadzi\u0142 statystyki SEO dla wybranych s\u0142\u00f3w kluczowych. Mo\u017cesz zaprogramowa\u0107 go tak, \u017ce b\u0119dzie wchodzi\u0142 na r\u00f3\u017cne strony Internetowe, a nast\u0119pnie b\u0119dzie pobiera\u0142 informacje o d\u0142ugo\u015bci tekst\u00f3w, czy nasyceniu strony s\u0142owami kluczowymi. A mo\u017ce chcesz go wykorzystywa\u0107 researchu? Zastosowa\u0144 jest na prawd\u0119 wiele.<\/p>\n\n\n\n
Niezb\u0119dne biblioteki<\/h2>\n\n\n\n Do napisania prostego web crawlera nie potrzeba nam wiele. Request <\/em>jest dost\u0119pny od razu po instalacji Pythona. Konieczne b\u0119dzie wy\u0142\u0105cznie zainstalowanie Beatifullsoup4.<\/p>\n\n\n\npip3 install bs4<\/code><\/pre>\n\n\n\nZasada dzia\u0142ania b\u0119dzie dosy\u0107 prosta. Za pomoc\u0105 request b\u0119dziemy wykonywa\u0107 zapytania do serwera i pobiera\u0107 dane, a za pomoc\u0105 BS4 b\u0119dziemy nawigowa\u0107 po drzewie DOM. W poni\u017cszym przyk\u0142adzie pomijam na razie wykonywanie requesta. Po prostu pokazuj\u0119 Ci jak dzia\u0142a BS4.<\/p>\n\n\n\n
from bs4 import BeautifulSoup\n\nsoup = BeautifulSoup(\"<p>I am learning <span>BeautifulSoup<\/span><\/p>\", features=\"lxml\")\nfound = soup.find('span')\nprint(found)\n<\/code><\/pre>\n\n\n\nPowy\u017cszy przyk\u0142ad zwr\u00f3ci nam <span>. U\u017cy\u0142em tak\u017ce flagi features, poniewa\u017c w przeciwnym wypadku Python zwr\u00f3ci\u0142by ostrze\u017cenie, o u\u017cyciu domy\u015blnego analizatora – lxml. Teraz mo\u017cemy przyst\u0105pi\u0107 do tworzenia naszego crawlera.<\/p>\n\n\n\n
Om\u00f3wienie naszego projektu<\/h2>\n\n\n\n Nasz prosty web crawler b\u0119dzie pobiera\u0142 oferty pracy z pracuj.pl, a dok\u0142adniej oferty pracy z Jav\u0105 w Lublinie. Zobaczmy jak wygl\u0105da struktura naszego adresu url:<\/p>\n\n\n\n
https://it.pracuj.pl\/praca\/java;kw\/lublin;wp?rd=30<\/code><\/pre>\n\n\n\nOd tego adresu rozpoczniemy nasz request. Nast\u0119pnie nasz crawler przejdzie przez wszystkie podstrony, a zebrane dane b\u0119dziemy mogli zapisa\u0107, albo wy\u015bwietli\u0107. My\u015bl\u0119, \u017ce b\u0119dzie to ca\u0142kiem niez\u0142e wprowadzenie do pisania web crawler\u00f3w w Python.<\/p>\n\n\n\n
Web crawler w praktyce<\/h2>\n\n\n\nPobieramy oferty pracy z pierwszej strony<\/h3>\n\n\n\n Pisanie naszego web crawlera zaczniemy od zaimportowania niezb\u0119dnych bibliotek i wykonania requesta. Zauwa\u017c, \u017ce w naszym przyk\u0142adzie wy\u015bwietlam dane z requesta dwukrotnie. Samo page wy\u015bwietli nam jedynie status strony. W naszym przypadku b\u0119dzie to status 200. Nam jednam zale\u017cy na tym, \u017ceby dosta\u0107 si\u0119 do kodu html strony. Musimy wi\u0119c u\u017cy\u0107 page.text<\/strong>.<\/p>\n\n\n\nimport requests\nfrom bs4 import BeautifulSoup\n\nurl = 'https:\/\/it.pracuj.pl\/praca\/java;kw\/lublin;wp?rd=30'\npage = requests.get(url)\nprint(page) #zwraca status - w naszym przypadku 200\nprint(page.text) #zwraca kod html<\/code><\/pre>\n\n\n\nNie jeste\u015bmy w stanie jednak w ten spos\u00f3b porusza\u0107 si\u0119 po drzewie DOM, ani wydobywa\u0107 wa\u017cnych dla nas tre\u015bci w wygodny spos\u00f3b. Do tego u\u017cyjemy biblioteki BeautifulSoup. Na pocz\u0105tku spr\u00f3bujmy zwr\u00f3ci\u0107 tre\u015b\u0107 wszystkich nag\u0142\u00f3wk\u00f3w h2 na tej stronie<\/p>\n\n\n\n
soup = BeautifulSoup(page.text, 'html.parser')\ntitles = soup.find_all('h2', {'data-test': 'offer-title'})\nfor title in titles:\n print(title.getText())<\/code><\/pre>\n\n\n\nWydaje mi si\u0119, \u017ce wskazany kod jest dosy\u0107 prosty. Najpierw u\u017cywamy parsera html na naszym kodzie html, a nast\u0119pny u\u017cywamy metody find_all, \u017ceby pobra\u0107 wszystkie nag\u0142\u00f3wki h2, kt\u00f3re posiadaj\u0105 data-test r\u00f3wne offer-title<\/strong>, kt\u00f3re przez t\u0119 metod\u0119 s\u0105 zwracane w formie tablicy. Za pomoc\u0105 metody getText(), wy\u015bwietlamy tre\u015b\u0107 naszych nag\u0142\u00f3wk\u00f3w.<\/p>\n\n\n\nRozwi\u0105zanie problemu paginacji<\/h3>\n\n\n\n Pojawia si\u0119 jednak inny problem – strona wykorzystuje paginacj\u0119, a my chcemy pobra\u0107 dane o wszystkich ofertach. Zastan\u00f3wmy si\u0119 jakie mamy opcje.<\/p>\n\n\n\n
\nMo\u017cemy wy\u015bwietli\u0107 wszystkie oferty na jednej stronie. Zwr\u00f3\u0107 uwag\u0119 na adres url. Domy\u015blnie zmienna rd jest r\u00f3wna 30. Zwi\u0119kszaj\u0105c t\u0119 liczb\u0119, np. do 100 jeste\u015bmy w stanie wy\u015bwietli\u0107 wszystkie oferty na jednej stronie.<\/li>\n\n\n\n Po doj\u015bciu do ostatnie strony znika button nast\u0119pna. Mo\u017cemy wi\u0119c po wy\u015bwietleniu wszystkich ofert z danej strony sprawdza\u0107, czy ten button znajduje si\u0119 na danej stronie. Je\u015bli tak, przechodzimy na kolejn\u0105 stron\u0119.<\/li>\n\n\n\n Mo\u017cemy ju\u017c po uruchomieniu pierwszej strony sprawdzi\u0107 ile mamy stron, a nast\u0119pnie wykona\u0144 tak\u0105 ilo\u015b\u0107 iteracji.<\/li>\n<\/ol>\n\n\n\nPierwsz\u0105 opcj\u0119 sobie odpu\u015bcimy, poniewa\u017c niczego si\u0119 dzi\u0119ki niej nie nauczycie. Zanijmy od opcji ostatniej. Najpierw poka\u017c\u0119 Ci kod, a p\u00f3\u017aniej wyt\u0142umacz\u0119 jak dzia\u0142a.<\/p>\n\n\n\n
def is_pagination_button(tag):\n return tag.has_attr('data-test') and tag['data-test'].startswith('bottom-pagination-button-page-')\n\npagination_buttons = soup.find_all(is_pagination_button)\nprint(len(pagination_buttons))<\/code><\/pre>\n\n\n\nW tym przypadku skorzysta\u0142em z tego, \u017ce buttony paginacji zawieraj\u0105 data-test = bottom-pagination-button-page-x, gdzie x, to oczywi\u015bcie numer strony. Szukam wi\u0119c tag\u00f3w, kt\u00f3re zawieraj\u0105 data-test, i jest warto\u015b\u0107 zaczyna si\u0119 od bottom-pagination-button-page-<\/strong>.<\/p>\n\n\n\n
\n\t\t\t\n\t\t\t\t \n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div>\n\n\nWeb crawler w Pythonie – ca\u0142y kod<\/h3>\n\n\n\nimport requests\nfrom bs4 import BeautifulSoup\n\ndef run(page):\n url = 'https:\/\/it.pracuj.pl\/praca\/java;kw\/lublin;wp?rd=30&pn=' + str(page)\n page = requests.get(url)\n soup = BeautifulSoup(page.text, 'html.parser')\n return soup\n\ndef is_pagination_button(tag):\n return tag.has_attr('data-test') and tag['data-test'].startswith('bottom-pagination-button-page-')\n\npagination_buttons = run(1).find_all(is_pagination_button)\npages = len(pagination_buttons)\n\nfor i in range(1, pages):\n titles = run(i).find_all('h2', {'data-test': 'offer-title'})\n\n for title in titles:\n print(title.getText())\n<\/code><\/pre>\n\n\n\nZauwa\u017c w jak niewielu liniach uda\u0142o nam si\u0119 napisa\u0107 dzia\u0142aj\u0105cy webrawler, kt\u00f3ry na prawd\u0119 mo\u017ce by\u0107 u\u017cyteczny! Sam skrypt oczywi\u015bcie nie jest idealny, ale idealnie pokazuje jak mo\u017cna napisa\u0107 crawler, kt\u00f3ry b\u0119dzie pobiera\u0142 te dane na kt\u00f3rych Ci zale\u017cy.<\/p>\n\n\n\n
Oczywi\u015bcie powy\u017cszy przyk\u0142ad w \u017caden spos\u00f3b nie wyczerpa\u0142 tematu BS4, ani metod wyszukiwania element\u00f3w na stronie. Cz\u0119sto b\u0119dziemy wyszukiwa\u0107 elementy po ichj klasie, alb identyfikatorze. Dlatego postaram si\u0119 w tym miejscu nieco poszerzy\u0107 ten temat. Za\u0142\u00f3\u017cmy, \u017ce chcemy wyszuka\u0107 element po jego id. Nic prostszego. Wystarczy u\u017cy\u0107:<\/p>\n\n\n\n
soup.find_all(id='my-testing-id')<\/code><\/pre>\n\n\n\nOczywi\u015bcie uzyskamy w ten spos\u00f3b list\u0119 element\u00f3w. Mo\u017cemy wi\u0119c j\u0105 wy\u015bwietli\u0107 w p\u0119tli, jak to robili\u015bmy w poprzednim przyk\u0142adzie, albo wy\u015bwietli\u0107 konkretny odwo\u0142uj\u0105c si\u0119 do jego klucza.<\/p>\n\n\n\n
soup.find_all(id='my-testing-id')[0].getText()<\/code><\/pre>\n\n\n\nW podobny spos\u00f3b mo\u017cemy odwo\u0142a\u0107 si\u0119 do elementu o okre\u015blonym id<\/p>\n\n\n\n
soup.find_all(class_='my-testing-class')[0].getText()<\/code><\/pre>\n\n\n\nNale\u017cy tylko zwr\u00f3ci\u0107 uwag\u0119 na podkre\u015blnik po class. Bardzo cz\u0119sto jednak pisz\u0105c web crawler b\u0119dziesz musia\u0142 porusza\u0107 si\u0119 bardziej swobodnie po drzewie DOM. W ko\u0144cu cz\u0119sto zdarzaj\u0105 si\u0119 sytuacje, \u017ce nie chcemy pobra\u0107 wszystkich element\u00f3w danej klasy, ale tylko te, kt\u00f3re znajduj\u0105 si\u0119, np. w okre\u015blonym kontenerze. BeatifulSoup udost\u0119pnia metod\u0119 select, kt\u00f3ra w\u0142a\u015bnie do tego s\u0142u\u017cy.<\/p>\n\n\n\n
soup.select('body .hello-world')<\/code><\/pre>\n\n\n\nPo prostu zawsze musisz my\u015ble\u0107 o tym, czy nasz crawler nie pobierze danych na kt\u00f3rych nam w danej chwili nie zale\u017cy.<\/p>\n\n\n\n
Pobieranie atrybut\u00f3w<\/h3>\n\n\n\n Do tej porty pobierali\u015bmy wy\u0142\u0105cznie tekst z danego elementu, ale czasami potrzebujemy pobra\u0107 inne informacje, jak adres linku, b\u0105d\u017a inne atrybuty. Mo\u017cemy to zrobi\u0107 w banalnie prosty spos\u00f3b:<\/p>\n\n\n\n
a['href']\ninput['name']<\/code><\/pre>\n\n\n\nPrzydatna strategia<\/h2>\n\n\n\n Tworz\u0105c w\u0142asne web crawlery pewnie mo\u017ce si\u0119 okaza\u0107, \u017ce potrzebujesz mie\u0107 wi\u0119ksz\u0105 swobod\u0119 w przeskakiwaniu pomi\u0119dzy stronami. Nasz przyk\u0142adowy crawler by\u0142 dosy\u0107 prosty, ale co w sytuacji, kiedy chcieliby\u015bmy wej\u015b\u0107 we wszystkie og\u0142oszenia i pobra\u0107 z nich odpowiednie dane?<\/p>\n\n\n\n
Strategia jest dosy\u0107 prosta. W pierwszym kroku przechodzimy kolejno po wszystkich stronach, tak jak robili\u015bmy to w g\u0142\u00f3wnym przyk\u0142adzie, ale nasz web crawler zamiast wy\u015bwietla\u0107 text, b\u0119dzie zapisywa\u0142 w tablicy adresy url wszystkich og\u0142osze\u0144. Maj\u0105c ju\u017c w tablicy wszystkie potrzebne nam adresy url, mo\u017cemy kolejno po nich przej\u015b\u0107 i wyci\u0105gn\u0105\u0107 te dane o kt\u00f3re nam chodzi. Jest to bardzo popularna metoda, kt\u00f3ra na pewno Ci si\u0119 przyda.<\/p>\n\n\n
\n
\n\t\t\t\n\t\t\t\t \n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div>\n\n\nOgraniczenia tej metody<\/h2>\n\n\n\n Podstawowym ograniczeniem, z kt\u00f3rym pewnie cz\u0119sto si\u0119 spotkasz s\u0105 strony renderowane przez JavaScript. We wspomnianej metodzie pobieramy kod html, a nast\u0119pnie parsujemy go za pomoc\u0105 BeatifulSoup. Je\u017celi strona zosta\u0142a stworzona, np. w React, albo Angularze – ta metoda nie zadzia\u0142a. W takich sytuacjach mo\u017cemy skorzysta\u0107 chocia\u017cby z Selenium. Istniej\u0105 te\u017c metody na blokowanie crawlownia stron. Wtedy pojawia si\u0119 pro\u015bba o potwierdzenie, \u017ce jeste\u015b cz\u0142owiekiem. Na to te\u017c s\u0105 sposoby, ale raczej wtedy nie korzysta si\u0119 z tej metody. Jest ona jednak dosy\u0107 prosta, szybka i sprawdza si\u0119 przy wi\u0119kszo\u015bci stron.<\/p>\n\n\n\n
Dalsza rozbudowa skryptu<\/h2>\n\n\n\n Jak wspomnia\u0142em wcze\u015bniej – skrypt jest raczej szkicem, kt\u00f3ry pokazuje jak mo\u017ce tworzy\u0107 web crawlery za pomoc\u0105 request i BeatifulSoup. na pewno wypada\u0142oby doda\u0107 obs\u0142ug\u0119 wyj\u0105tk\u00f3w, pomy\u015ble\u0107 nad zapisem naszych wynik\u00f3w do bazy danych – mo\u017cesz skorzysta\u0107 chocia\u017cby z sqlite, albo mo\u017cesz wy\u015bwietli\u0107 wyniki w nieco bardziej atrakcyjnej formie. Mo\u017ce warto te\u017c przefiltrowa\u0107 wyniki? Cz\u0119sto dostaniesz og\u0142oszenia pracy na stanowiska inne ni\u017c Java Developer (niestety pracuj.pl tak dzia\u0142a). Mo\u017cesz usun\u0105\u0107 wyniki, kt\u00f3re zawieraj\u0105 w tytule inne j\u0119zyki programowania. A co w sytuacji, kiedy strona w og\u00f3le nie posiada paginacji? Wtedy nie wykona si\u0119 p\u0119tla, a zwi\u0105zku z tym, \u017cadne dane nie zostan\u0105 pobrane. Koniecznie trzeba o to zadba\u0107! Jak widzisz, masz sporo mo\u017cliwo\u015bci dalszej rozbudowy tego skryptu.<\/p>\n\n\n\n
Podsumowanie<\/h2>\n\n\n\n Przedstawiona metoda jest jedn\u0105 z kilku, kt\u00f3re s\u0105 u\u017cywane do tworzenia web crawler\u00f3w. Do bardziej zaawansowanych rzeczy \u015bwietnie sprawdza si\u0119 Scrapy, kt\u00f3ry jest frameworkiem i posiada wiele zaawansowanych funkcji. Mamy te\u017c oczywi\u015bcie Selenium, kt\u00f3re emuluje dzia\u0142anie przegl\u0105darki internetowej. Jest to \u015bwietna biblioteka do automatyzowania pewnych czynno\u015bci na stronach www, albo do pobierania okre\u015blonych tre\u015bci te\u017c si\u0119 nadaje. Niew\u0105tpliw\u0105 zalet\u0105 jest to, \u017ce mo\u017ce bez problemu dzia\u0142a\u0107 na stronach, kt\u00f3re zosta\u0142y stworzone, np., w oparciu o React, Vue, czy innego Angulara. Ale o tych metodach mo\u017ce powiemy sobie innym razem.<\/p>\n","protected":false},"excerpt":{"rendered":"
Python nadaje si\u0119 \u015bwietnie po pisania web crawler\u00f3w. Jest to nie tylko bajecznie proste, ale tak\u017ce niezwykle u\u017cyteczne. Co to jest web crawler? Web crawler to program, kt\u00f3ry przegl\u0105da Internet w poszukiwaniu okre\u015blonych przez jego tw\u00f3rc\u0119 tre\u015bci. Wachlarz zastosowa\u0144 tego typu aplikacji jest bardzo szeroki. Od automatyzacji, po analiz\u0119 i prowadzenie statystyk. Wyobra\u017a sobie, ze […]<\/p>\n","protected":false},"author":1,"featured_media":335,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-327","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-programowanie"],"_links":{"self":[{"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/posts\/327","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/comments?post=327"}],"version-history":[{"count":18,"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/posts\/327\/revisions"}],"predecessor-version":[{"id":368,"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/posts\/327\/revisions\/368"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/media\/335"}],"wp:attachment":[{"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/media?parent=327"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/categories?post=327"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/webowyswiat.pl\/wp-json\/wp\/v2\/tags?post=327"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}