python - start - Creando una araña scrapy genérica
scrapy spider (5)
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 etiquetas, etc. y quiero crear una araña genérica para rastrear esos dominios para esas palabras clave en esas etiquetas. 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
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)
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.
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
<class ''__main__.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
# ...