如何在调用item_scraped scrapy信号后启动新请求?

时间:2015-10-15 13:21:48

标签: python-2.7 phantomjs scrapy-spider

我需要使用Scrapy(http://example.com/itemview)从网站中删除每个项目的数据。我有一个itemID列表,我需要在example.com中的表单中传递它。 每个项目都没有网址更改。因此对于我的蜘蛛中的每个请求,url将始终是相同的。但内容会有所不同。

  

我不会想要处理每个请求的for循环。所以我按照下面提到的步骤。

  • 使用上面的url启动了蜘蛛
  • 添加了item_scraped和spider_closed信号
  • 通过了几个函数
  • 将抓取的数据传递给管道
  • trigerred item_scraped signal

此后它会自动调用spider_closed信号。但我希望继续上述步骤,直到完成总项目ID。

class ExampleSpider(scrapy.Spider):
  name = "example"
  allowed_domains = ["example.com"]
  itemIDs = [11111,22222,33333]
  current_item_num = 0

  def __init__(self, itemids=None, *args, **kwargs):
    super(ExampleSpider, self).__init__(*args, **kwargs)
    dispatcher.connect(self.item_scraped, signals.item_scraped)
    dispatcher.connect(self.spider_closed, signals.spider_closed)

  def spider_closed(self, spider):
     self.driver.quit()

  def start_requests(self):
     request = self.make_requests_from_url('http://example.com/itemview')
     yield request

 def parse(self,response):
     self.driver = webdriver.PhantomJS()
     self.driver.get(response.url)
     first_data = self.driver.find_element_by_xpath('//div[@id="itemview"]').text.strip()
     yield Request(response.url,meta={'first_data':first_data},callback=self.processDetails,dont_filter=True)

 def processDetails(self,response):
     itemID = self.itemIDs[self.current_item_num]
     ..form submission with the current itemID goes here...
     ...the content of the page is updated with the given itemID...
     yield Request(response.url,meta={'first_data':response.meta['first_data']},callback=self.processData,dont_filter=True)

def processData(self,response):
    ...some more scraping goes here...
   item = ExamplecrawlerItem()
   item['first_data'] = response.meta['first_data']
   yield item

def item_scraped(self,item,response,spider):
   self.current_item_num += 1
   #i need to call the processDetails function here for the next itemID 
   #and the process needs to contine till the itemID finishes
   self.parse(response)

我的piepline:

class ExampleDBPipeline(object):
  def process_item(self, item, spider):
     MYCOLLECTION.insert(dict(item))
     return

1 个答案:

答案 0 :(得分:0)

我希望我有一个优雅的解决方案。但相反,它是一种调用底层类的hackish方式。

                self.crawler.engine.slot.scheduler.enqueue_request(scrapy.Request(url,self.yourCallBack))

但是,您可以在生成项目并将其回调到self.processDetails后生成请求。只需将其添加到您的processData函数:

   yield item
   self.counter += 1
   yield scrapy.Request(response.url,callback=self.processDetails,dont_filter=True, meta = {"your":"Dictionary"}

此外,PhantomJS可以很好用,让你的生活轻松,但它比常规连接慢。如果可能的话,找到json数据的请求,或者在没有JS的情况下使页面不可解析的任何内容。为此,打开chrome,右键单击,单击inspect,转到网络选项卡,然后在表单中输入ID,然后查看XHR或JS选项卡,查找包含所需数据或下一个URL的JSON。大多数情况下,通过添加ID会有一些url,如果你能找到它,你可以连接你的url并直接调用它而不需要JS渲染的成本。有时它是随机的,或者没有,但我已经取得了相当大的成功。然后,您也可以使用它同时产生许多请求,而不必担心phantomJS会一次尝试做两件事或者必须初始化它的许多实例。你可以使用标签,但这很痛苦。

另外,我会使用您的ID队列来确保线程安全。否则,你可以让processDetails在同一个ID上调用两次,但是在程序的逻辑中,一切似乎都是线性的,这意味着你没有使用Scrapy的并发功能,你的程序会变得更慢。要使用队列添加:

import Queue
#go inside class definition and add
itemIDQueue = Queue.Queue()
#within __init__ add
[self.itemIDQueue.put(ID) for ID in self.itemID]
#within processDetails replace  itemID = self.itemIDs[self.current_item_num] with
itemID = self.itemIDQueue.get()

然后没有必要增加计数器,你的程序是线程安全的。