2012-12-05 10 views
33

Jestem początkującym scrapy i to niesamowite ramy robota, które znam!Jak uzyskać adresy URL niepowodzenia skrobania?

W moim projekcie, wysłałem ponad 90 000 wniosków, ale niektóre z nich nie powiodło się. Ustawiam poziom rejestrowania na INFO, i widzę tylko statystyki, ale nie szczegóły.

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)} 

Czy istnieje sposób na uzyskanie bardziej szczegółowego raportu? Na przykład pokaż te nieudane adresy URL. Dzięki!

Odpowiedz

42

Tak, jest to możliwe.

Dodałem listę failed_urls do mojej klasy pająka i dołączonych do niej adresów URL, jeśli status odpowiedzi wyniósł 404 (należy ją rozszerzyć, aby uwzględnić inne stany błędów).

Potem dodaje uchwyt, który łączy się z listy w jeden łańcuch i dodać ją do statystyk, gdy pająk jest zamknięta.

Na podstawie twoich komentarzy możliwe jest śledzenie błędów skręconych.

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) 

wyjściowe (downloader/exception_count * statystyki pojawi się tylko wtedy, gdy faktycznie są wyjątki wyrzucane - I symulowane przez nich próby uruchomienia pająka po ja wyłączony mój bezprzewodowy adapter):

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)} 
+0

To już nie działa. 'exceptionions.NameError: nazwa globalna 'self' is not defined' error occurred. 'BaseSpider' to teraz' Spider' http://doc.scrapy.org/en/0.24/news.html?highlight = basespider # id2 https://github.com/scrapy/dirbot/blob/master/dirbot/spiders/dmoz.py, ale nie mogę znaleźć poprawki, aby Twój kod działał jeszcze @Talvalin. – Mikeumus

15

Oto kolejny przykład, jak obsługiwać i zebrać 404 błędy (strony sprawdzanie github pomoc):

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 

Wystarczy uruchomić scrapy runspider z -o output.json i zobaczyć lista elementów w pliku output.json.

10

Odpowiedzi z @Talvalin i @alecxe pomógł mi wiele, ale nie wydaje się, aby uchwycić zdarzenia downloader, które nie generują obiekt odpowiedzi (na przykład twisted.internet.error.TimeoutError i twisted.web.http.PotentialDataLoss). Błędy te pojawiają się w zrzucie statystyk na końcu przebiegu, ale bez żadnych informacji meta.

Jak dowiedział here, błędy są śledzone przez Stats.py middleware, ujęte w klasie DownloaderStatsprocess_exception metody, a konkretnie w zmiennej ex_class, które przyrostach wpisać w razie potrzeby każdy błąd, a następnie zrzuca liczniki u koniec biegu.

Aby dopasować takie błędy z informacjami z odpowiedniego obiektu żądania, można dodać meta informacji na każde żądanie (poprzez request.meta), a następnie wyciągnij ją do metody process_exception z Stats.py:

self.stats.set_value('downloader/my_errs/%s' % request.meta, ex_class) 

które będą generować unikalny ciąg znaków dla każdego błędu tego rodzaju.Można zapisać zmienione Stats.py jako Mystats.py, dodać go do warstwy pośredniej (z prawej pierwszeństwa) i wyłączyć regularne Stats.py:

DOWNLOADER_MIDDLEWARES = { 
    'myproject.mystats.MyDownloaderStats': 850, 
    'scrapy.downloadermiddleware.stats.DownloaderStats': None, 
    } 

Sygnał wyjściowy na końcu biegu wygląda tak (tu za pomocą meta informacje gdzie Adresy URL/wnioski są odwzorowywane na Integer oparte meta str z GroupID/memberID, jak '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'....} 

This answer oferty z błędami nie opartych downloader.

+0

Dokładnie tego, czego szukam. Myślę, że Scrapy powinien dodać tę funkcję, aby zapewnić wygodny dostęp do informacji o błędach, takich jak adres URL. – wlnirvana

+1

Użyj 'scrapy.downloadermiddlewares.stats' zamiast przestarzałego w najnowszej (1.0.5) wersji' scrapy.contrib.downloadermiddleware.stats'' –

+0

@ElRuso dzięki - zaktualizowałem odpowiedź – bahmait

6

Od zera 0.24.6, metoda sugerowana przez alecxe nie będzie łapała błędów z początkowymi adresami URL. Aby rejestrować błędy z początkowymi adresami URL, musisz zastąpić parse_start_urls. Dostosowując odpowiedź alexce na ten cel, otrzymasz:

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 
5

To jest aktualizacja tego pytania. Wpadłem na podobny problem i potrzebowałem użyć sygnałów scrapy, aby wywołać funkcję w moim potoku. Dokonałem edycji kodu @ Talvalin, ale chciałem odpowiedzieć tylko dla większej przejrzystości.

Zasadniczo powinieneś dodać siebie jako argument dla handle_spider_closed. Powinieneś również wywołać funkcję dispatcher w init, abyś mógł przekazać instancję pająka (self) do metody obsługi.

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) 

Mam nadzieję, że to pomoże ktoś z tym samym problemem w przyszłości.

8

Scrapy zignoruj ​​404 domyślnie i nie parsuj. Aby obsłużyć błąd 404, zrób to. Jest to bardzo łatwe, jeśli otrzymujesz kod błędu 404 w odpowiedzi, można obsługiwać ten jest bardzo łatwy sposób ..... w Ustawienia napisać

HTTPERROR_ALLOWED_CODES = [404,403] 

a następnie obsługiwać kod statusu odpowiedzi na swoje funkcja parsowania.

def parse(self,response): 
    if response.status == 404: 
     #your action on error 

w ustawieniach i uzyskać odpowiedź w funkcji parse

0

Ponadto niektóre z tych odpowiedzi, jeśli chcesz śledzić błędy skręcony, chciałbym przyjrzeć się używając errback parametr żądanie obiektu, na którym można ustawić funkcję zwrotną, która będzie wywoływana za pomocą Twisted Failure po błędzie żądania. Oprócz adresu URL ta metoda umożliwia śledzenie typu awarii.

Następnie można zalogować się za pomocą adresów URL: failure.request.url (gdzie failure jest Twisted Failure obiekt przeszedł do 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) 

docs Scrapy pełnemu przykład tego, jak można to zrobić, poza tym, że połączenia z rejestratorem Scrapy są teraz depreciated, więc ja dostosowany moim przykładem używać Pythona zbudowany w logging):

https://doc.scrapy.org/en/latest/topics/request-response.html#topics-request-response-ref-errbacks