Creando una araña scrapy genérica

Mi pregunta es realmente cómo hacer lo mismo que una pregunta anterior, pero en Scrapy 0.14.

Usando una araña Scrapy para varios sitios web

Básicamente, tengo una GUI que toma parámetros como dominio, palabras clave, nombres de tags, etc. y quiero crear una araña genérica para rastrear esos dominios para esas palabras clave en esas tags. He leído cosas conflictivas, utilizando versiones anteriores de scrapy, ya sea anulando la clase del administrador de arañas o creando dinámicamente una araña. ¿Qué método se prefiere y cómo implemento e invoco la solución adecuada? Gracias por adelantado.

Aquí está el código que quiero hacer genérico. También utiliza BeautifulSoup. Lo emparejé así que, con suerte, no eliminé nada crucial para entenderlo.

class MySpider(CrawlSpider): name = 'MySpider' allowed_domains = ['somedomain.com', 'sub.somedomain.com'] start_urls = ['http://www.somedomain.com'] rules = ( Rule(SgmlLinkExtractor(allow=('/pages/', ), deny=('', ))), Rule(SgmlLinkExtractor(allow=('/2012/03/')), callback='parse_item'), ) def parse_item(self, response): contentTags = [] soup = BeautifulSoup(response.body) contentTags = soup.findAll('p', itemprop="myProp") for contentTag in contentTags: matchedResult = re.search('Keyword1|Keyword2', contentTag.text) if matchedResult: print('URL Found: ' + response.url) pass 

Podría crear una araña de tiempo de ejecución que es evaluada por el intérprete. Esta pieza de código podría ser evaluada en tiempo de ejecución así:

 a = open("test.py") from compiler import compile d = compile(a.read(), 'spider.py', 'exec') eval(d) MySpider  print MySpider.start_urls ['http://www.somedomain.com'] 

Utilizo el enfoque de Scrapy Extensions para extender la clase Spider a una clase llamada Masterspider que incluye un analizador genérico.

A continuación se muestra la versión muy “corta” de mi analizador genérico extendido. Tenga en cuenta que necesitará implementar un renderizador con un motor de Javascript (como Selenium o BeautifulSoup) tan pronto como comience a trabajar en las páginas usando AJAX. Y una gran cantidad de código adicional para administrar las diferencias entre los sitios (rechazo basado en el título de la columna, manejar la URL relativa o larga, administrar diferentes tipos de contenedores de datos, etc.).

Lo que está interfiriendo con el enfoque de Scrapy Extension es que aún puede anular el método del analizador genérico si algo no encaja pero nunca tuve que hacerlo. La clase Masterspider verifica si se han creado algunos métodos (por ejemplo, parser_start, next_url_parser …) bajo la clase de araña específica del sitio para permitir la administración de especificaciones: enviar un formulario, construir la solicitud next_url a partir de los elementos de la página, etc.

Como estoy raspando sitios muy diferentes, siempre hay aspectos específicos que administrar. Es por eso que prefiero mantener una clase para cada sitio raspado para poder escribir algunos métodos específicos para manejarlo (preprocesamiento / postproceso excepto PipeLines, generadores de solicitudes …).

masterspider / sitespider / settings.py

 EXTENSIONS = { 'masterspider.masterspider.MasterSpider': 500 } 

masterspider / masterspdier / masterspider.py

 # -*- coding: utf8 -*- from scrapy.spider import Spider from scrapy.selector import Selector from scrapy.http import Request from sitespider.items import genspiderItem class MasterSpider(Spider): def start_requests(self): if hasattr(self,'parse_start'): # First page requiring a specific parser fcallback = self.parse_start else: fcallback = self.parse return [ Request(self.spd['start_url'], callback=fcallback, meta={'itemfields': {}}) ] def parse(self, response): sel = Selector(response) lines = sel.xpath(self.spd['xlines']) # ... for line in lines: item = genspiderItem(response.meta['itemfields']) # ... # Get request_url of detailed page and scrap basic item info # ... yield Request(request_url, callback=self.parse_item, meta={'item':item, 'itemfields':response.meta['itemfields']}) for next_url in sel.xpath(self.spd['xnext_url']).extract(): if hasattr(self,'next_url_parser'): # Need to process the next page URL before? yield self.next_url_parser(next_url, response) else: yield Request( request_url, callback=self.parse, meta=response.meta) def parse_item(self, response): sel = Selector(response) item = response.meta['item'] for itemname, xitemname in self.spd['x_ondetailpage'].iteritems(): item[itemname] = "\n".join(sel.xpath(xitemname).extract()) return item 

masterspider / sitespider / spiders / somesite_spider.py

 # -*- coding: utf8 -*- from scrapy.spider import Spider from scrapy.selector import Selector from scrapy.http import Request from sitespider.items import genspiderItem from masterspider.masterspider import MasterSpider class targetsiteSpider(MasterSpider): name = "targetsite" allowed_domains = ["www.targetsite.com"] spd = { 'start_url' : "http://www.targetsite.com/startpage", # Start page 'xlines' : "//td[something...]", 'xnext_url' : "//a[contains(@href,'something?page=')]/@href", # Next pages 'x_ondetailpage' : { "itemprop123" : u"id('someid')//text()" } } # def next_url_parser(self, next_url, response): # OPTIONAL next_url regexp pre-processor # ... 

En lugar de tener el name las variables, el allowed_domains , el start_urls y las rules adjuntas a la clase, debe escribir un MySpider.__init__ , llamar a CrawlSpider.__init__ desde que pase los argumentos necesarios, y configurar el name , el allowed_domains , etc. por objeto. MyProp y las palabras clave también deben configurarse dentro de su __init__ . Así que al final deberías tener algo como abajo. No es necesario que agregue un name a los argumentos, ya name BaseSpider establece este BaseSpider desde kwargs :

 class MySpider(CrawlSpider): def __init__(self, allowed_domains=[], start_urls=[], rules=[], findtag='', finditemprop='', keywords='', **kwargs): CrawlSpider.__init__(self, **kwargs) self.allowed_domains = allowed_domains self.start_urls = start_urls self.rules = rules self.findtag = findtag self.finditemprop = finditemprop self.keywords = keywords def parse_item(self, response): contentTags = [] soup = BeautifulSoup(response.body) contentTags = soup.findAll(self.findtag, itemprop=self.finditemprop) for contentTag in contentTags: matchedResult = re.search(self.keywords, contentTag.text) if matchedResult: print('URL Found: ' + response.url) 

No estoy seguro de cuál es el camino preferido, pero le diré lo que he hecho en el pasado. No estoy seguro de que esta sea la mejor (o correcta) forma de hacerlo y me interesaría saber lo que piensan otras personas.

Por lo general, solo CrawlSpider clase principal ( CrawlSpider ) y o bien paso los argumentos y luego inicializo la clase principal a través de super(MySpider, self).__init__() desde dentro de mi propia función init o obtengo esos datos de una base de datos donde ha guardado una lista de enlaces que deben adjuntarse a start_urls anteriormente.

En cuanto a rastrear dominios específicos pasados ​​como argumentos, simplemente Spider.__init__ :

 class MySpider(scrapy.Spider): """ This spider will try to crawl whatever is passed in `start_urls` which should be a comma-separated string of fully qualified URIs. Example: start_urls=http://localhost,http://example.com """ def __init__(self, name=None, **kwargs): if 'start_urls' in kwargs: self.start_urls = kwargs.pop('start_urls').split(',') super(Spider, self).__init__(name, **kwargs)