2017-05-12 19 views
4

Tor를 사용하는 스크레이퍼에서 작업하고 있습니다.이 예제에서는 단순화 된 버전 인 https://github.com/khpeek/scraper-compose입니다. 나는 Scrapy extension으로 활성화 한 settings.py에서Tor의 스템 콘트롤러에서 "제어 소켓 (소켓 클로즈) : 빈 소켓 콘텐트"수신 중 오류가 발생했습니다.

import scrapy 
from tutorial.items import QuoteItem 

class QuotesSpider(scrapy.Spider): 
    name = "quotes" 
    start_urls = ['http://quotes.toscrape.com/page/{n}/'.format(n=n) for n in range(1, 3)] 

    custom_settings = { 
         'TOR_RENEW_IDENTITY_ENABLED': True, 
         'TOR_ITEMS_TO_SCRAPE_PER_IDENTITY': 5 
         } 

    download_delay = 2 # Wait 2 seconds (actually a random time between 1 and 3 seconds) between downloading pages 


    def parse(self, response): 
     for quote in response.css('div.quote'): 
      item = QuoteItem() 
      item['text'] = quote.css('span.text::text').extract_first() 
      item['author'] = quote.css('small.author::text').extract_first() 
      item['tags'] = quote.css('div.tags a.tag::text').extract() 
      yield item 

, : quotes_spider.py 정의는 Scrapy Tutorial에 따라 매우 간단한 일이다

. 
├── docker-compose.yml 
├── privoxy 
│   ├── config 
│   └── Dockerfile 
├── scraper 
│   ├── Dockerfile 
│   ├── requirements.txt 
│   ├── tutorial 
│   │   ├── scrapy.cfg 
│   │   └── tutorial 
│   │    ├── extensions.py 
│   │    ├── __init__.py 
│   │    ├── items.py 
│   │    ├── middlewares.py 
│   │    ├── pipelines.py 
│   │    ├── settings.py 
│   │    ├── spiders 
│   │    │   ├── __init__.py 
│   │    │   └── quotes_spider.py 
│   │    └── tor_controller.py 
│   └── wait-for 
│    └── wait-for 
└── tor 
    ├── Dockerfile 
    └── torrc 

거미 :이 프로젝트는 다음 (간체) 구조를 가지고 extensions.py

입니다 라인

EXTENSIONS = { 
    'tutorial.extensions.TorRenewIdentity': 1, 
} 

로그에 따라 성공적으로 IP 주소를 변경하고 긁어 계속 : I 작동 확장으로 대형, docker-compose up 다음 docker-compose build를 사용하여 크롤링을 시작하면

import logging 
import random 
from scrapy import signals 
from scrapy.exceptions import NotConfigured 

import tutorial.tor_controller as tor_controller 

logger = logging.getLogger(__name__) 

class TorRenewIdentity(object): 

    def __init__(self, crawler, item_count): 
     self.crawler = crawler 
     self.item_count = self.randomize(item_count) # Randomize the item count to confound traffic analysis 
     self._item_count = item_count     # Also remember the given item count for future randomizations 
     self.items_scraped = 0 

     # Connect the extension object to signals 
     self.crawler.signals.connect(self.item_scraped, signal=signals.item_scraped) 

    @staticmethod 
    def randomize(item_count, min_factor=0.5, max_factor=1.5): 
     '''Randomize the number of items scraped before changing identity. (A similar technique is applied to Scrapy's DOWNLOAD_DELAY setting).''' 
     randomized_item_count = random.randint(int(min_factor*item_count), int(max_factor*item_count)) 
     logger.info("The crawler will scrape the following (randomized) number of items before changing identity (again): {}".format(randomized_item_count)) 
     return randomized_item_count 

    @classmethod 
    def from_crawler(cls, crawler): 
     if not crawler.settings.getbool('TOR_RENEW_IDENTITY_ENABLED'): 
      raise NotConfigured 

     item_count = crawler.settings.getint('TOR_ITEMS_TO_SCRAPE_PER_IDENTITY', 50) 

     return cls(crawler=crawler, item_count=item_count)   # Instantiate the extension object 

    def item_scraped(self, item, spider): 
     '''When item_count items are scraped, pause the engine and change IP address.''' 
     self.items_scraped += 1 
     if self.items_scraped == self.item_count: 
      logger.info("Scraped {item_count} items. Pausing engine while changing identity...".format(item_count=self.item_count)) 

      self.crawler.engine.pause() 

      tor_controller.change_identity()      # Change IP address (cf. https://stem.torproject.org/faq.html#how-do-i-request-a-new-identity-from-tor) 
      self.items_scraped = 0         # Reset the counter 
      self.item_count = self.randomize(self._item_count)  # Generate a new random number of items to scrape before changing identity again 

      self.crawler.engine.unpause() 

tor_controller.py

import logging 
import sys 
import socket 
import time 
import requests 
import stem 
import stem.control 

# Tor settings 
TOR_ADDRESS = socket.gethostbyname("tor")   # The Docker-Compose service in which this code is running should be linked to the "tor" service. 
TOR_CONTROL_PORT = 9051   # This is configured in /etc/tor/torrc by the line "ControlPort 9051" (or by launching Tor with "tor -controlport 9051") 
TOR_PASSWORD = "foo"   # The Tor password is written in the docker-compose.yml file. (It is passed as a build argument to the 'tor' service). 

# Privoxy settings 
PRIVOXY_ADDRESS = "privoxy"  # This assumes this code is running in a Docker-Compose service linked to the "privoxy" service 
PRIVOXY_PORT = 8118    # This is determined by the "listen-address" in Privoxy's "config" file 
HTTP_PROXY = 'http://{address}:{port}'.format(address=PRIVOXY_ADDRESS, port=PRIVOXY_PORT) 

logger = logging.getLogger(__name__) 


class TorController(object): 
    def __init__(self): 
     self.controller = stem.control.Controller.from_port(address=TOR_ADDRESS, port=TOR_CONTROL_PORT) 
     self.controller.authenticate(password=TOR_PASSWORD) 
     self.session = requests.Session() 
     self.session.proxies = {'http': HTTP_PROXY} 

    def request_ip_change(self): 
     self.controller.signal(stem.Signal.NEWNYM) 

    def get_ip(self): 
     '''Check what the current IP address is (as seen by IPEcho).''' 
     return self.session.get('http://ipecho.net/plain').text 

    def change_ip(self): 
     '''Signal a change of IP address and wait for confirmation from IPEcho.net''' 
     current_ip = self.get_ip() 
     logger.debug("Initializing change of identity from the current IP address, {current_ip}".format(current_ip=current_ip)) 
     self.request_ip_change() 
     while True: 
      new_ip = self.get_ip() 
      if new_ip == current_ip: 
       logger.debug("The IP address is still the same. Waiting for 1 second before checking again...") 
       time.sleep(1) 
      else: 
       break 
     logger.debug("The IP address has been changed from {old_ip} to {new_ip}".format(old_ip=current_ip, new_ip=new_ip)) 
     return new_ip 

    def __enter__(self): 
     return self 

    def __exit__(self, *args): 
     self.controller.close() 


def change_identity(): 
    with TorController() as tor_controller: 
     tor_controller.change_ip() 

입니다. 날 irks 무엇

그러나, 나는 그런

scraper_1 | 2017-05-12 16:35:06 [stem] INFO: Error while receiving a control message (SocketClosed): empty socket content 

과 같은 오류 메시지가 이러한 오류의 원인이 무엇

scraper_1 | 2017-05-12 16:35:06 [stem] INFO: Error while receiving a control message (SocketClosed): received exception "peek of closed file" 

다음을 참조 기간 동안 엔진이 일시 정지 된 것입니다? 그들은 레벨이 INFO이므로 어쩌면 무시할 수 있습니까? (나는 Stem의 소스 코드를 https://gitweb.torproject.org/stem.git/으로 보았지만 지금까지 무슨 일이 일어나고 있는지에 대한 핸들을 얻을 수 없었다.)

답변

1

귀하의 문제와 관련하여 귀하가 도달 한 결론이나 결론을 알 수 없습니다.

실제로 내가 한 것과 같은 로그 메시지가 나타납니다. My Scrapy 프로젝트는 훌륭하게 수행되었으며, Tor와 Privoxy를 사용하는 IP 회전도 성공적으로 수행되었습니다. 나는 방금 로그 INFO : [stem ] Error while receiving a control message (SocketClosed): empty socket content을 얻고 있었다. 그리고 그것은 나에서 도대체 괴롭혔다.

나는 그것을 파헤치는 원인을 알아 내기 위해 시간을 들였고 무시해도 괜찮은지 (결국은 오류 메시지가 아닌 정보 메시지 일 뿐이다) 보았다.

결론은 무엇이 원인인지 모르겠지만 무시할 정도로 안전하다고 느꼈습니다.

로그에 따르면 소켓 내용 (실제로 소켓 연결과 관련된 관련 정보가 들어있는 control_file 줄기)은 비어 있습니다. control_file이 비어 있으면, 소켓 연결을 종료하도록 트리거합니다 (파이썬 소켓 문서에 따라). 소켓 연결을 닫으려면 control_file이 비어있는 원인이 무엇인지 모르겠습니다. 그러나 소켓 연결이 실제로 닫히면 소켓 연결이 성공적으로 열리는 것처럼 보입니다. 내 치료의 크롤링 작업과 IP 순환이 잘 작동하기 때문입니다. 비록 내가 그 진정한 원인을 찾을 수는 없지만, 나는 단지 몇 가지 원인을 가정 할 수있다. (1) Tor 네트워크가 불안정하다. (2) 코드가 controller.signal(Signal.NEWNYM) 일 때, 소켓은 일시적으로 닫히고 다시 기다린다. 내가 지금 생각할 수없는 이유.