使用 Scrapy 抓取多级数据,最佳方式

Scraping Multi level data using Scrapy, optimum way

我一直想知道使用 scrapy 废弃多级数据的最佳方法是什么 我会分四个阶段描述情况,

  1. 我正在遵循的当前架构来抓取此数据
  2. 基本代码结构
  3. 困难以及为什么我认为必须有更好的选择
  4. 我尝试存储数据并失败然后部分成功的格式

当前架构

  1. 数据结构

First page : List of Artist

Second page : List of Album for each Artist

Third Page : list of Songs for each Album

  1. 基本代码结构

class 音乐库(蜘蛛): 姓名 = 'MusicLibrary'

def parse(self, response):

    items = Discography()
    items['artists'] = []
    for artist in artists:
            item = Artist()
            item['albums'] = []
            item['artist_name'] = "name"
            items['artists'].append(item)
            album_page_url = "extract link to album and yield that page"
            yield Request(album_page_url,
                          callback=self.parse_album,
                          meta={'item': items,
                                'artist_name': item['artist_name']})

def parse_album(self, response):
    base_item = response.meta['item']
    artist_name = response.meta['artist_name']
    # this will search for the artist added in previous method and append album under that artist
    artist_index = self.get_artist_index(base_item['artists'], artist_name)
    albums = "some path selector"
    for album in albums:
        item = Album()
        item['songs'] = []
        item['album_name'] = "name"
        base_item['artists'][artist_index]['albums'].append(item)
        song_page_url = "extract link to song and yield that page"
        yield Request(song_page_url,
                      callback=self.parse_song_name,
                      meta={'item':  base_item,
                            "key": item['album_name'],
                            'artist_index': artist_index})

def parse_song_name(self, response):
    base_item = response.meta['item']
    album_name = response.meta['key']
    artist_index = response.meta["artist_index"]
    album_index = self.search(base_item['artists'][artist_index]['albums'], album_name)
    songs = "some path selector "

    for song in songs:
        item = Song()
        song_name = "song name"
        base_item['artists'][artist_index]['albums'][album_index]['songs'].append(item)
        # total_count (total songs to parse) = Main Artist page is having the list of total songs for each artist
        # current_count(currently parsed) = i will go to each artist->album->songs->[] and count the length

        # i will yield the base_item only when songs to scrape and song scraped count matches
        if current_count == total_count:
            yield base_item
  1. 困难以及为什么我认为必须有更好的选择

    • 目前,只有当所有页面和子页面都被抓取并且要抓取的歌曲和抓取的歌曲计数匹配时,我才会生成项目对象。
    • 但是请给出抓取的性质和抓取的数量...有些页面会给我代码而不是(200-状态正常)并且这些歌曲不会被抓取并且项目数量不匹配
    • 所以最后,即使 90% 的页面将被成功抓取并且计数不匹配,也不会产生任何内容并且所有 CPU 电源都将丢失..
  2. 我尝试存储数据并失败然后部分成功的格式

    • 我想要单行格式的每个项目对象的数据 即 artistName-Albumname-song name 因此,如果艺术家 A 有 1 张专辑 (aa) 和 8 首歌曲......将存储 8 个项目,每首歌曲有一个条目(项目)
    • 但是在当前格式下,当我尝试在上一个函数中每次都产生时 "parse_song_name" 它每次都会产生复杂的结构并且对象每次都是增量的...
    • 然后我认为在第一个 Discography->artist 然后是 Artist->albums 然后是 Albums->songs 中附加所有内容是问题,但是当我删除附加并尝试不附加时我只产生一个对象,它是最后一个不是全部..
    • 所以最后,如前所述开发了这个解决方法,但它并不是每次都有效(在没有 200 状态代码的情况下)
    • 当它工作时,在屈服之后,我写了一个管道,我再次解析这个 jSON 并将它存储在我最初想要的数据格式中(每首歌一行 -​​- 扁平结构)

任何人都可以建议我在这里做错了什么,或者当某些页面 return 非 200 代码时,我如何才能更有效地工作?

上面代码的问题是:

  1. Mutable object (list, dict) : 并且所有回调都在每个循环中更改同一个对象,因此...第一级和第二级数据在最后被覆盖第三个循环(mp3_son_url)...(这是我失败的尝试)

解决方案是使用简单的 copy.deepcopy 并在回调方法中从 response.meta 对象创建一个新对象而不更改 base_item对象

我有时间会尝试解释完整的答案..