scraping - scrapy python español
¿Cómo obtener las URL de falla de scrapy? (7)
Scrapy ignora 404 de manera predeterminada y no analiza. Para manejar el error 404 haz esto. Esto es muy fácil, si está recibiendo el código de error 404 en respuesta, puede manejar esto de una manera muy fácil ..... en la configuración de escritura
HTTPERROR_ALLOWED_CODES = [404,403]
y luego maneje el código de estado de respuesta en su función de análisis.
def parse(self,response):
if response.status == 404:
#your action on error
en la configuración y obtener respuesta en la función de análisis
¡Soy un novato de scrapy y es increíble el framework de crawler que he conocido!
En mi proyecto, envié más de 90,000 solicitudes, pero algunas fallaron. Configuré el nivel de registro para que sea INFO, y solo puedo ver algunas estadísticas pero no detalles.
2012-12-05 21:03:04+0800 [pd_spider] INFO: Dumping spider stats:
{''downloader/exception_count'': 1,
''downloader/exception_type_count/twisted.internet.error.ConnectionDone'': 1,
''downloader/request_bytes'': 46282582,
''downloader/request_count'': 92383,
''downloader/request_method_count/GET'': 92383,
''downloader/response_bytes'': 123766459,
''downloader/response_count'': 92382,
''downloader/response_status_count/200'': 92382,
''finish_reason'': ''finished'',
''finish_time'': datetime.datetime(2012, 12, 5, 13, 3, 4, 836000),
''item_scraped_count'': 46191,
''request_depth_max'': 1,
''scheduler/memory_enqueued'': 92383,
''start_time'': datetime.datetime(2012, 12, 5, 12, 23, 25, 427000)}
¿Hay alguna manera de obtener un informe más detallado? Por ejemplo, muestre esas URL fallidas. ¡Gracias!
Sí, esto es posible
Agregué una lista de failed_urls a mi clase spider y anexé URLs si el estado de la respuesta era 404 (esto tendrá que extenderse para cubrir otros estados de error).
Luego agregué un identificador que une la lista en una sola cadena y la agrego a las estadísticas cuando la araña está cerrada.
En función de sus comentarios, es posible realizar un seguimiento de los errores de Twisted.
from scrapy.spider import BaseSpider
from scrapy.xlib.pydispatch import dispatcher
from scrapy import signals
class MySpider(BaseSpider):
handle_httpstatus_list = [404]
name = "myspider"
allowed_domains = ["example.com"]
start_urls = [
''http://www.example.com/thisurlexists.html'',
''http://www.example.com/thisurldoesnotexist.html'',
''http://www.example.com/neitherdoesthisone.html''
]
def __init__(self, category=None):
self.failed_urls = []
def parse(self, response):
if response.status == 404:
self.crawler.stats.inc_value(''failed_url_count'')
self.failed_urls.append(response.url)
def handle_spider_closed(spider, reason):
self.crawler.stats.set_value(''failed_urls'', '',''.join(spider.failed_urls))
def process_exception(self, response, exception, spider):
ex_class = "%s.%s" % (exception.__class__.__module__, exception.__class__.__name__)
self.crawler.stats.inc_value(''downloader/exception_count'', spider=spider)
self.crawler.stats.inc_value(''downloader/exception_type_count/%s'' % ex_class, spider=spider)
dispatcher.connect(handle_spider_closed, signals.spider_closed)
Salida (las estadísticas downloader / exception_count * solo aparecerán si se lanzan excepciones, las simulé al intentar ejecutar la araña después de que apagué el adaptador inalámbrico):
2012-12-10 11:15:26+0000 [myspider] INFO: Dumping Scrapy stats:
{''downloader/exception_count'': 15,
''downloader/exception_type_count/twisted.internet.error.DNSLookupError'': 15,
''downloader/request_bytes'': 717,
''downloader/request_count'': 3,
''downloader/request_method_count/GET'': 3,
''downloader/response_bytes'': 15209,
''downloader/response_count'': 3,
''downloader/response_status_count/200'': 1,
''downloader/response_status_count/404'': 2,
''failed_url_count'': 2,
''failed_urls'': ''http://www.example.com/thisurldoesnotexist.html, http://www.example.com/neitherdoesthisone.html''
''finish_reason'': ''finished'',
''finish_time'': datetime.datetime(2012, 12, 10, 11, 15, 26, 874000),
''log_count/DEBUG'': 9,
''log_count/ERROR'': 2,
''log_count/INFO'': 4,
''response_received_count'': 3,
''scheduler/dequeued'': 3,
''scheduler/dequeued/memory'': 3,
''scheduler/enqueued'': 3,
''scheduler/enqueued/memory'': 3,
''spider_exceptions/NameError'': 2,
''start_time'': datetime.datetime(2012, 12, 10, 11, 15, 26, 560000)}
Esta es una actualización de esta pregunta. Me encontré con un problema similar y necesitaba usar las señales de scrapy para llamar a una función en mi pipeline. He editado el código de @ Talvalin, pero quería hacer una respuesta para mayor claridad.
Básicamente, debe agregarse a sí mismo como argumento para handle_spider_closed. También debe llamar al despachador en init para que pueda pasar la instancia de spider (self) al método de gestión.
from scrapy.spider import Spider
from scrapy.xlib.pydispatch import dispatcher
from scrapy import signals
class MySpider(Spider):
handle_httpstatus_list = [404]
name = "myspider"
allowed_domains = ["example.com"]
start_urls = [
''http://www.example.com/thisurlexists.html'',
''http://www.example.com/thisurldoesnotexist.html'',
''http://www.example.com/neitherdoesthisone.html''
]
def __init__(self, category=None):
self.failed_urls = []
# the dispatcher is now called in init
dispatcher.connect(self.handle_spider_closed,signals.spider_closed)
def parse(self, response):
if response.status == 404:
self.crawler.stats.inc_value(''failed_url_count'')
self.failed_urls.append(response.url)
def handle_spider_closed(self, spider, reason): # added self
self.crawler.stats.set_value(''failed_urls'','',''.join(spider.failed_urls))
def process_exception(self, response, exception, spider):
ex_class = "%s.%s" % (exception.__class__.__module__, exception.__class__.__name__)
self.crawler.stats.inc_value(''downloader/exception_count'', spider=spider)
self.crawler.stats.inc_value(''downloader/exception_type_count/%s'' % ex_class, spider=spider)
Espero que esto ayude a cualquiera con el mismo problema en el futuro.
Aquí hay otro ejemplo de cómo manejar y recolectar errores 404 (verificando las páginas de ayuda de github):
from scrapy.selector import HtmlXPathSelector
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.item import Item, Field
class GitHubLinkItem(Item):
url = Field()
referer = Field()
status = Field()
class GithubHelpSpider(CrawlSpider):
name = "github_help"
allowed_domains = ["help.github.com"]
start_urls = ["https://help.github.com", ]
handle_httpstatus_list = [404]
rules = (Rule(SgmlLinkExtractor(), callback=''parse_item'', follow=True),)
def parse_item(self, response):
if response.status == 404:
item = GitHubLinkItem()
item[''url''] = response.url
item[''referer''] = response.request.headers.get(''Referer'')
item[''status''] = response.status
return item
Simplemente ejecute scrapy runspider
con -o output.json
y vea la lista de elementos en el archivo output.json
.
A partir de scrapy 0.24.6, el método sugerido por alecxe no detectará errores con las URL de inicio. Para registrar los errores con las URL de inicio, debe anular parse_start_urls
. Adaptando la respuesta de alexce para este propósito, obtendrías:
from scrapy.selector import HtmlXPathSelector
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.item import Item, Field
class GitHubLinkItem(Item):
url = Field()
referer = Field()
status = Field()
class GithubHelpSpider(CrawlSpider):
name = "github_help"
allowed_domains = ["help.github.com"]
start_urls = ["https://help.github.com", ]
handle_httpstatus_list = [404]
rules = (Rule(SgmlLinkExtractor(), callback=''parse_item'', follow=True),)
def parse_start_url(self, response):
return self.handle_response(response)
def parse_item(self, response):
return self.handle_response(response)
def handle_response(self, response):
if response.status == 404:
item = GitHubLinkItem()
item[''url''] = response.url
item[''referer''] = response.request.headers.get(''Referer'')
item[''status''] = response.status
return item
Las respuestas de @Talvalin y @alecxe me ayudaron mucho, pero no parecen capturar los eventos de descarga que no generan un objeto de respuesta (por ejemplo, twisted.internet.error.TimeoutError
y twisted.web.http.PotentialDataLoss
) . Estos errores aparecen en el volcado de estadísticas al final de la ejecución, pero sin meta información.
Como descubrí aquí , los errores son rastreados por el middleware Stats.py , capturado en el método process_exception
clase DownloaderStats
, y específicamente en la variable ex_class
, que incrementa cada tipo de error según sea necesario, y luego vuelca los recuentos al final de la carrera.
Para hacer coincidir dichos errores con la información del objeto de solicitud correspondiente, puede agregar metainformación a cada solicitud (a través de request.meta
) y luego colocarla en el método process_exception
de Stats.py:
self.stats.set_value(''downloader/my_errs/%s'' % request.meta, ex_class)
Eso generará una cadena única para cada error de este tipo. Puede guardar el Stats.py
modificado como Mystats.py
, agregarlo al middleware (con la precedencia correcta) y deshabilitar el Stats.py
regular:
DOWNLOADER_MIDDLEWARES = {
''myproject.mystats.MyDownloaderStats'': 850,
''scrapy.downloadermiddleware.stats.DownloaderStats'': None,
}
La salida al final de la ejecución se ve así (aquí usando metainformación donde las urls / solicitudes están mapeadas a una meta basada en enteros de groupID / memberID, como ''0/14''
):
{''downloader/exception_count'': 3,
''downloader/exception_type_count/twisted.web.http.PotentialDataLoss'': 3,
''downloader/my_errs/0/1'': ''twisted.web.http.PotentialDataLoss'',
''downloader/my_errs/0/38'': ''twisted.web.http.PotentialDataLoss'',
''downloader/my_errs/0/86'': ''twisted.web.http.PotentialDataLoss'',
''downloader/request_bytes'': 47583,
''downloader/request_count'': 133,
''downloader/request_method_count/GET'': 133,
''downloader/response_bytes'': 3416996,
''downloader/response_count'': 130,
''downloader/response_status_count/200'': 95,
''downloader/response_status_count/301'': 24,
''downloader/response_status_count/302'': 8,
''downloader/response_status_count/500'': 3,
''finish_reason'': ''finished''....}
Esta respuesta se refiere a errores no basados en descarga.
Además de algunas de estas respuestas, si desea realizar un seguimiento de los errores de Twisted, eche un vistazo al uso del parámetro errback
del objeto Request, en el que puede establecer que se llame a una función de devolución de llamada con la Falla trenzada en una falla de solicitud. Además de la url, este método puede permitirle rastrear el tipo de falla.
A continuación, puede registrar las URL utilizando: failure.request.url
(donde failure
el objeto Twisted Failure
pasado a errback
).
# these would be in a Spider
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, callback=self.parse,
errback=self.handle_error)
def handle_error(self, failure):
url = failure.request.url
logging.error(''Failure type: %s, URL: %s'', failure.type,
url)
Los documentos de Scrapy ofrecen un ejemplo completo de cómo se puede hacer esto, excepto que las llamadas al registrador de Scrapy están ahora depreciadas , así que he adaptado mi ejemplo para usar el registro integrado de Python):
https://doc.scrapy.org/en/latest/topics/request-response.html#topics-request-response-ref-errbacks