Первый Паук Scrapy - PullRequest
0 голосов
/ 07 мая 2019

Я пытаюсь создать Scrapy Spider для анализа исполнителя и отслеживания информации из SoundCloud.

Используя инструменты разработчика в FireFox, я определил, что можно выполнить вызов API, который возвращает объект JSON, которыйпреобразует в словарь Python.Для этого вызова API требуется идентификатор исполнителя, и, насколько я могу судить, эти идентификаторы были автоматически увеличены.Это означает, что мне не нужно сканировать сайт, и я могу просто иметь список начальных URL-адресов, которые делают начальный вызов API, а затем анализировать страницы, которые следуют из этого.Я считаю, что это должно сделать меня более дружелюбным к сайту?

Из полученного ответа можно получить URL артиста, а посещение и анализ этого URL даст больше информации о художнике

ОтURL артистов, мы можем посетить их треки и почистить список треков вместе с атрибутами треков.

Я думаю, что у меня возникли проблемы из-за непонимания структуры Scrapy ... Если я прямо вставлю вURL художников - start_urls Scrapy передает объект scrapy.http.response.html.HtmlResponse в parse_artist.Это позволяет мне извлекать данные, которые мне нужны (я не включил весь код для разбора страницы, чтобы сделать фрагмент кода короче).Однако, если я передам тот же объект той же функции из функции parse_api_call, это приведет к ошибке ...

Я не могу понять, почему это так, и любая помощь будет принята.

Примечание: Первоначальный вызов API захватывает дорожки от исполнителя, и смещение и предел можно изменить, а функцию вызывать рекурсивно для сбора дорожек.Это, однако, оказалось ненадежным, и даже если это не приводит к ошибке, которая завершает программу, он не получает полный список треков от исполнителя.

Вот текущий код:

"""
Scrapes SoundCloud websites for artists and tracks
"""

import json
import scrapy
from ..items import TrackItem, ArtistItem
from scrapy.spiders.crawl import CrawlSpider, Rule
from scrapy.linkextractors.lxmlhtml import LxmlLinkExtractor


class SoundCloudBot(scrapy.Spider):

    name = 'soundcloudBot'
    allowed_domains = ['soundcloud.com']
    start_urls = [
        'https://api-v2.soundcloud.com/users/7436630/tracks?offset=0&limit=20&client_id=Q11Oe0rIPEuxvMeMbdXV7qaowYzlaESv&app_version=1556892058&app_locale=en',
        'https://api-v2.soundcloud.com/users/4803918/tracks?offset=0&limit=20&client_id=Q11Oe0rIPEuxvMeMbdXV7qaowYzlaESv&app_version=1556892058&app_locale=en',
        'https://api-v2.soundcloud.com/users/17364233/tracks?offset=0&limit=20&client_id=Q11Oe0rIPEuxvMeMbdXV7qaowYzlaESv&app_version=1556892058&app_locale=en',
        'https://api-v2.soundcloud.com/users/19697240/tracks?offset=0&limit=20&client_id=Q11Oe0rIPEuxvMeMbdXV7qaowYzlaESv&app_version=1556892058&app_locale=en',
        'https://api-v2.soundcloud.com/users/5949564/tracks?offset=0&limit=20&client_id=Q11Oe0rIPEuxvMeMbdXV7qaowYzlaESv&app_version=1556892058&app_locale=en'
    ]

    # This is added for testing purposes. When these links are added directly to the 
    # start_urls the code runs as expected, when these links are extracted using parse_api_call
    # is when problems arise

    # start_urls.extend([
    #     'https://soundcloud.com/futureisnow',
    #     'https://soundcloud.com/bigsean-1',
    #     'https://soundcloud.com/defjam',
    #     'https://soundcloud.com/ymcmbofficial',
    #     'https://soundcloud.com/walefolarin',
    #     # 'https://soundcloud.com/futureisnow/tracks',
    #     # 'https://soundcloud.com/bigsean-1/tracks',
    #     # 'https://soundcloud.com/defjam/tracks',
    #     # 'https://soundcloud.com/ymcmbofficial/tracks',
    #     # 'https://soundcloud.com/walefolarin/tracks'
    # ])

    def parse(self, response):
        url = response.url

        if url[:35] == 'https://api-v2.soundcloud.com/users':
            self.parse_api_call(response)
        # 'https://soundcloud.com/{artist}' 
        elif url.replace('https://soundcloud.com', '').count('/') == 1: # One starting forward slash for artist folder
            self.parse_artist(response)
        # 'https://soundcloud.com/{artist}/{track}' 
        elif url.replace('https://soundcloud.com', '').count('/') == 2 and url[-6:] == 'tracks':
            self.parse_tracks(response)

    def parse_api_call(self, response):
        data = json.loads(response.text)
        artistItem = ArtistItem()

        first_track = data['collection'][0]
        artist_info = first_track.get('user')

        artist_id   = artist_info.get('id')
        artist_url  = artist_info.get('permalink_url')
        artist_name = artist_info.get('username')

        artistItem['artist_id'] = artist_id  
        artistItem['username']  = artist_name
        artistItem['url']       = artist_url

        artist_response = scrapy.http.response.html.HtmlResponse(artist_url)
        self.parse_artist(artist_response)

        # Once the pipelines are written this will be changed to yeild 
        return artistItem


    def parse_artist(self, response):
        # This prints out <class 'scrapy.http.response.html.HtmlResponse'>
        # It doesn't matter if start_urls get extend with artists' URLS or not
        print(type(response))

        data = response.css('script::text').extract()

        # This prints out a full HTML response if the function is called directly 
        # With scrapy, or an empty list if called from parse_api_call
        print(data)

        track_response = scrapy.http.response.html.HtmlResponse(f'{response.url}/tracks')
        self.parse_tracks(track_response)


    def parse_tracks(self, response):
        pass

1 Ответ

2 голосов
/ 07 мая 2019

Вы должны использовать

Request(url) 

чтобы получить данные из нового URL. Но вы не можете выполнить ее как обычную функцию и получить результат сразу. Вы должны использовать return Request() или yield Request(), и scrapy помещает их в очередь, чтобы получить данные позже.

Получив данные, он использует метод parse() для анализа данных из ответа. Но вы можете установить собственный метод в запросе

Request(url, self.parse_artist)

Но в parse_artist() у вас не будет доступа к данным, которые вы получили в предыдущей функции, поэтому вы должны отправить их в запросе, используя meta - т.е.

Request(artistItem['url'], self.parse_artist, meta={'item': artistItem})

Полный рабочий код. Вы можете поместить все в один файл и запустить его без создания проекта.

Это также сохраняет результат в output.csv

import scrapy
from scrapy.http import Request
import json

class MySpider(scrapy.Spider):

    name = 'myspider'

    allowed_domains = ['soundcloud.com']

    start_urls = [
        'https://api-v2.soundcloud.com/users/7436630/tracks?offset=0&limit=20&client_id=Q11Oe0rIPEuxvMeMbdXV7qaowYzlaESv&app_version=1556892058&app_locale=en',
        'https://api-v2.soundcloud.com/users/4803918/tracks?offset=0&limit=20&client_id=Q11Oe0rIPEuxvMeMbdXV7qaowYzlaESv&app_version=1556892058&app_locale=en',
        'https://api-v2.soundcloud.com/users/17364233/tracks?offset=0&limit=20&client_id=Q11Oe0rIPEuxvMeMbdXV7qaowYzlaESv&app_version=1556892058&app_locale=en',
        'https://api-v2.soundcloud.com/users/19697240/tracks?offset=0&limit=20&client_id=Q11Oe0rIPEuxvMeMbdXV7qaowYzlaESv&app_version=1556892058&app_locale=en',
        'https://api-v2.soundcloud.com/users/5949564/tracks?offset=0&limit=20&client_id=Q11Oe0rIPEuxvMeMbdXV7qaowYzlaESv&app_version=1556892058&app_locale=en'
    ]

    def parse(self, response):

        data = json.loads(response.text)

        if len(data['collection']) > 0:
            artist_info = data['collection'][0]['user']

            artistItem = {
                'artist_id': artist_info.get('id'),
                'username': artist_info.get('username'),
                'url':  artist_info.get('permalink_url'),
            }

            print('>>>', artistItem['url'])
            # make requests to url artistItem['url'],
            # parse response in parse_artist,
            # send artistItem to parse_artist
            return Request(artistItem['url'], self.parse_artist, meta={'item': artistItem})
        else:
            print("ERROR: no collections in data")


    def parse_artist(self, response):

        artistItem = response.meta['item']

        data = response.css('script::text').extract()

        # add data to artistItem
        #print(data)
        artistItem['new data'] =  'some new data'

        #print('>>>', response.urljoin('tracks'))
        print('>>>', response.url + '/tracks')
        # make requests to url artistItem['url'],
        # parse response in parse_tracks,
        # send artistItem to parse_tracks
        return Request(response.url + '/tracks', self.parse_tracks, meta={'item': artistItem})


    def parse_tracks(self, response):
        artistItem = response.meta['item']

        artistItem['tracks'] =  'some tracks'

        # send to CSV file
        return artistItem


#------------------------------------------------------------------------------
# run it without creating project
#------------------------------------------------------------------------------

from scrapy.crawler import CrawlerProcess

c = CrawlerProcess({
    'USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0',
    # save in file as CSV, JSON or XML
    'FEED_FORMAT': 'csv',     # csv, json, xml
    'FEED_URI': 'output.csv', #
})
c.crawl(MySpider)
c.start()

ouput.csv

artist_id,username,url,new data,tracks
17364233,Def Jam Recordings,https://soundcloud.com/defjam,some new data,some tracks
4803918,Big Sean,https://soundcloud.com/bigsean-1,some new data,some tracks
19697240,YMCMB-Official,https://soundcloud.com/ymcmbofficial,some new data,some tracks
5949564,WALE,https://soundcloud.com/walefolarin,some new data,some tracks
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...