Scrapy如何将多级页面爬取到一个item?

How to crawl multiple-level pages to one item in Scrapy?

我找到的所有关于 Scrapy 的例子都在谈论如何爬取单个页面,或者如何爬取多级页面,当每个最深的页面被保存为一个独立的 Item。但是我的情况有点复杂。

例如网站结构为:

A (List page of books)
--> B (Book summary page)
----> C (Book review pages)
----> D (Book download pages)

因此 Item 的定义如下所示:

class BookItem(scrapy.Item):
    name = scrapy.Field()
    type = scrapy.Field()
    introduction = scrapy.Field()
    resources = scrapy.Field() # To be a list of ResourceItem
    reviews = scrapy.Field() # To be a list of ReviewItem

# Download pages
class ResourceItem(scrapy.Item):
    title = scrapy.Field()
    createDate = scrapy.Field()
    author = scrapy.Field()
    link = scrapy.Field()


# Book reviews
class ReviewItem(scrapy.Item):
    title = scrapy.Field()
    createDate = scrapy.Field()
    author = scrapy.Field()
    content = scrapy.Field()

我怎样才能完成 BookItem 的所有字段?我 do 知道我可以写 4 个方法,比如 parse_A()parse_B()parse_C()parse_D(),Scrapy 允许它们通过在每个方法的末尾使用 yield scrapy.Request() 成为工作流程。

但是我应该return在最深的方法中,即parse_C()parse_D()

  1. 如果我return一个ResourceItemReviewItem,会直接保存
  2. 如果我return上面的方法BookItem,未完成的项目也会直接保存
  3. 如果我return a Request for parse_D() in parse_C(),同样不行,因为资源可能是空的(也就是说可能有是 B 页面上根本没有 C 的链接)。所以 parse_C() 不会被调用,留下 parse_D() 未被调用,最后 D 字段未填充。

您可以使用 meta 参数传递一些数据(参见 https://docs.scrapy.org/en/latest/topics/request-response.html)。

因此您可以在多个 requests/parse 函数中填充您的项目。

显示逻辑的快速示例:

def parse_summary(self, response):
  book_item = # scrape book item here
  reviews_url = # extract reviews url 
  resources_url = # extract resources url
  return scrapy.Request(reviews_url, callback=self.parse_reviews, meta={'item': book_item, 'resources_url': resources_url })

def parse_reviews(self, response):
  book_item = response.meta.get('item') # get item draft
  book_item.reviews = # extract reviews here
  resources_url = response.meta.get('resources_url')
  return scrapy.Request(resources_url, callback=self.parse_resources, meta={'item': book_item })

def parse_resources(self, response):
  book_item = response.meta.get('item') # get item draft
  book_item.ressources = # extract ressources here
  return book_item # once completed, return the item

希望你明白了(我对代码执行不是很有信心,只是没有测试就写下来了)。

我现在可以自己回答了

只需 yield None 省略 return 语句 parse_C()parse_D() 将解决问题。

一些解释

Scrapy 不会关闭蜘蛛 只是 因为回调之一 return 什么都没有,但要确保请求队列中也没有新的回调。

因此,由于 parse_B() 在完成产生子页面 C 和 D 的所有请求之前不会 return NoneItem,因此工作流不会被打扰了。