Сканер Python (13): Scrapy фактически захватывает облачную музыку NetEase

база данных Python рептилия Scrapy

В первых двух статьях мы изучили теоретические знания Scrapy, поэтому мы не можем быть Чжао Куо, говорящим о солдатах на бумаге. Практика — единственный критерий проверки истины. В этой статье мы собираем всю музыку и музыкальные обзоры NetEase Cloud Music.

Сайт анализа

Открываем браузер и заходим на веб-страницу NetEase Cloud Music. Если мы хотим получить всю музыку, у нас должна быть запись для получения всех музыкальных данных.

Затем, наблюдая за навигацией по странице, мы можем получить только всех певцов. Но поскольку на странице сведений об исполнителе нет ссылки на всю музыку, мы можем получить только все альбомы. Получите всю музыку со всех альбомов.

поисковый процесс

Используя страницу певца в качестве индексной страницы, захватите всех певцов;
Захватите все альбомы всех певцов;
Соберите всю музыку по всем альбомам;
Проанализируйте Ajax всей музыки и получите все горячие обзоры;
Сохраните название музыки, исполнителя, альбом, горячий комментарий, автора горячего комментария, номер горячего комментария в базе данных.

Начинать

Создать проект

scrapy startproject 163music

Создайте файл сканера (можно создать из командной строки):

# spiders/spider.py
from scrapy import Spider
class MusicSpider(Spider):
    name = "music"
    allowed_domains = ["163.com"]
    base_url = 'https://music.163.com'

Определить имя данных

Сначала мы записываем данные для сохранения в файл элемента.Хотя этот шаг не обязательно записывать первым, мы можем следить за процессом.

#items.py
import scrapy
class MusicItem(scrapy.Item):
    # define the fields for your item here like:
    # 我们保存歌曲的id
    id = scrapy.Field()
    artist = scrapy.Field()
    album = scrapy.Field()
    music = scrapy.Field()
    comments = scrapy.Field()

Анализировать индексные страницы

Наша индексная страница — это страница певцов по адресу:https://music.163.com/#/discover/artist/cat?id=1001&initial=65

image

На фотографиях в сочетании с нашим наблюдением за страницей указателя мы видим, что слева, например, классифицируются китайские певцы, европейские и американские певцы, а ABCDE под певцами также является классификацией по имени.

Его можно найти, перейдя по ссылке,id- значение левой классификации,initialзначение ссылки ABCDE.

Мы можем обнаружить, что каждая ссылка в ABCDE начинается с 65 и доходит до 90 плюс 0 для «других» ссылок. Такое правило можно легко реализовать с помощью кода. И номер классификации певца слева относительно сложно реализовать его правила с помощью кода. Просто его количество не так много, мы можем написать по одному, чтобы сохранить коллекцию. Мы записываем эти два параметра в класс рептилий.

class MusicSpider(Spider):
    name = "music"
    allowed_domains = ["163.com"]
    base_url = 'https://music.163.com'
    ids = ['1001','1002','1003','2001','2002','2003','6001','6002','6003','7001','7002','7003','4001','4002','4003']
    initials = [i for i in range(65, 91)]+[0]

стартовый URL

Очевидно, что страницы исполнителей имеют разные категории, и все стартовые страницы не могут быть одной.url, поэтому мы должны переписатьstart_requests. Это нужно для создания всех страниц категорий певцов.

def start_requests(self):
    for id in self.ids:
  for initial in self.initials:
      url = '{url}/discover/artist/cat?id={id}&initial={initial}'.format(url=self.base_url,id=id,initial=initial)
    yield Request(url, callback=self.parse_index)

Логика этого шага по-прежнему очень ясна, циклически перебирая каждыйid, в цикле каждыйinitial, пропустите их через.formatкомпозиция методаurl. затем используйтеyieldсинтаксический сахар, будетurlОбратный вызов функции разбора страницы индекса. Я считаю, что у всех не возникнет проблем с этим шагом после того, как они разберутся с первыми двумя теориями.

тогда мыparse_index()распечатать функциюResponse:

def parse_index(self, response):
    print(response.text)

Консоль запускает сканер:scrapy crawl music

так какscrapyне поддерживаетсяldeработает, поэтому, если мы должны думать, например.pycharmДля запуска нам нужно написать работающую программу:

# 163music/entrypoint.py
# 注意这个文件在项目的根目录,也就是scrapy.cfg文件所在
# 这里的music就是爬虫的名字
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'music'])

теперь мыpycharmЗапуск этого файла эквивалентен запуску поискового робота.

Операция выполнена успешно, но мы не получаем нужных данных. Что тут происходит?
Если вы не забудете использоватьRequestsКогда библиотека запрашивает, мы иногда добавляем в запрос некоторые заголовки запроса, затемscrapyкуда мы его добавим.

Ответ прост, вsettings.pyв файле.

Добавить настройки заголовка запроса

нам надоsettingsОтменить в файлеDEFAULT_REQUEST_HEADERSобратите внимание, потому чтоscrapyПо умолчанию нам не нужны заголовки запросов. Добавляем в него головной запрос NetEase Cloud, который является данными в наших инструментах разработчика:

DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Encoding': 'gzip, deflate, sdch',
    'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Cookie':'_ntes_nuid=5e2135ea19041c08d61bddbb9009de63; _ntes_nnid=a387121ca9ed891dca82492f6c088c57,1483420952257; __utma=187553192.690483437.1489583101.1489583101.1489583101.1; __utmz=187553192.1489583101.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __oc_uuid=ff821060-097f-11e7-8c2a-73421a9a1bc4; mail_psc_fingerprint=032ad52396a72877e07f21386dee35a2; NTES_CMT_USER_INFO=106964635%7C%E6%9C%89%E6%80%81%E5%BA%A6%E7%BD%91%E5%8F%8B06o2qr%7Chttps%3A%2F%2Fsimg.ws.126.net%2Fe%2Fimg5.cache.netease.com%2Ftie%2Fimages%2Fyun%2Fphoto_default_62.png.39x39.100.jpg%7Cfalse%7CbTE1MTUyMzQ3Mjc3QDE2My5jb20%3D; usertrack=c+5+hlkgTIMgjwa+EDUGAg==; _ga=GA1.2.690483437.1489583101; Province=025; City=05278; NTES_PASSPORT=aXWcpL4bYTLQnXY4eO888VlwXt.v922HPG1pBkj.vkeDwsISwc4gjpib7gtylUsoCy.yIGuJPZg7Uq2lTWqIo3A5ddE7eIf5DP_mjdHrg7ky2KFIZHP60ge8g; P_INFO=m15152347277@163.com|1500267468|1|blog|11&10|jis&1499527300&mail163#jis&320800#10#0#0|151277&1|study&blog&photo|15152347277@163.com; UM_distinctid=15d4ee58fc9483-032aae6568b355-333f5902-100200-15d4ee58fca912; NTES_SESS=35juNvuVAClEtPfwjy5rP5GVXVpRFMmwg2ItfudhfLmyGTk4G2l_fIFHi_xsOJTWQrUJvW3JwsMFyepEs0SR6z1_QnKjbQFaesBY9ABy0TVFP_KIiXNgb89wCGe.3_hmKR90f2ybdvNPWqPX8_YesVlIQrWdw5Nfg6KF0EcoVXO3DgV09cJHAeiE_; S_INFO=1500623480|1|0&80##|m15152347277; ANTICSRF=dd45f2a4489d303de869d820a0dadf05; playerid=64643457; JSESSIONID-WYYY=oR0Q0Ce%2Bhldid%2FFtfsiobsg%5Cecyra1qnHBuFFPNBUW%2BbZ3%5C2uq5%2Fqz4VrhRll0%5CaVCfY%2Fg0%2BC47vS%5Cv6rsyuD76tlqWN%2BUryVxph9fZeCmVIDtu5so7vdcdp%2B92hI3A0R5Zm%2Besa5l3ND%5Cz59WOYTY%2FCUjG%2B8gFSGVyzTpMquPQIxyIM%3A1500647790286; _iuqxldmzr_=32; MUSIC_U=f5333454d16d0f0ca5e59b3a82afaabcb107f5e73a4504bae87278f38158d65dbef309e3badc0bfac257abd5a88c5d62dc7e2cf554b1b3fc233a987fb3c42671e386323209b86ec1bf122d59fa1ed6a2; __remember_me=true; __csrf=5cd5b19efc6ea479e298487216162acf; __utma=94650624.776578804.1489210725.1500604214.1500644866.50; __utmb=94650624.28.10.1500644866; __utmc=94650624; __utmz=94650624.1499960824.48.42.utmcsr=yukunweb.com|utmccn=(referral)|utmcmd=referral|utmcct=/412.html',
    'DNT': '1',
    'Host': 'music.163.com',
    'Pragma': 'no-cache',
    'Referer': 'http://music.163.com/',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
}

Обратите внимание, что для извлечения песни из NetEase Cloud Music требуется вход в систему для получения данных.cookiesВот и все.

Теперь запустите сканер.Если он работает успешно, вы сможете увидеть распечатанные данные. Это показывает, что наша программа верна.

Напишите функцию парсинга стартовой страницы

Этот шаг заключается в использовании нашего селектора для извлечения информации, мы открываем инструменты разработчика, все, что нам нужно, это певец.aна этикеткеhrefИнформация. для еще не использованногоxpathиcssСелектор можно использоватьChromeИнструменты разработчика, щелкните правой кнопкой мыши метку, как показано ниже:

image

Непосредственно разбираем код стартовой страницы:

# 获得所有歌手的url
def parse_index(self, response):
    artists = response.xpath('//*[@id="m-artist-box"]/li/div/a/@href').extract()
    for artist in artists:
  artist_url = self.base_url + '/artist' + '/album?' + artist[8:]
  yield Request(artist_url, callback=self.parse_artist)

Страница сведений об альбоме певцаurlПримеры:https://music.163.com/#/artist/album?id=6452

Мы проанализировалиhrefзначение после объединения его на полную страницу сведений об альбоме певцаurl. Затем вернитесь к следующей функции синтаксического анализа.

Извлечь все URL альбомов

Этот шаг такой же, как и предыдущий, потому что этот шаг не сложный, но я повторю его. Над кодом:

# 获得所有歌手专辑的url
def parse_artist(self, response):
    albums = response.xpath('//*[@id="m-song-module"]/li/div/a[@class="msk"]/@href').extract()
    for album in albums:
  album_url = self.base_url + album
  yield Request(album_url, callback=self.parse_album)

Извлечь все песни

Этот шаг немного отличается, потому что если мы извлечем музыкуurl, тогда нам нужна музыкаidвurlсередина. Если мы напрямуюURLПосле обратного вызова функции, которая анализирует музыкальную страницу, мы не можем получить это позже.idиз. Вы можете сами наблюдать за страницей, чтобы определить этот шаг.

Так что нам нужно не толькоurlПерезвоните на следующую функцию разбора, но и поставьте музыкуidпередается следующей функции. Тогда вам должно быть интересно, почему бы и нетidсохранить прямо вitemШерстяная ткань.

Это потому, что структура данных, которая нам нужна, будет такой:

{'id':123456,'music':'晴天','artist':'周杰伦','album':'叶美惠','comments':[{'comment_author':'小明','comment_content':'我爱你','comment_like':'123456'},{...},{}...]}

Если мы сохраним музыку сейчасid, то мы не уверены, может ли соответствовать следующая информация. Итак, как я могу передать данные следующей функции?

scrapyпредоставил мнеmetaПараметры используются для сохранения наших данных в функцию, давайте посмотрим на код:

# 获得所有专辑音乐的url
def parse_album(self, response):
    musics = response.xpath('//ul[@class="f-hide"]/li/a/@href').extract()
    for music in musics:
  music_id = music[9:]
  music_url = self.base_url + music
  yield Request(music_url, meta={'id': music_id}, callback=self.parse_music)

вот так мы совмещаемURLПерешли к аналитической функции, а также к музыкеidпередается следующей функции.

Извлечение музыкальной информации, анализ комментариев Ajax

Для извлечения музыкальной информации страницы вы можете использовать селектор для ее извлечения.Трудность заключается в том, что область комментариев отсутствует в полученном нами исходном коде. Если вы сомневаетесь, вы можете распечатать исходный код некоторых страниц сведений о музыке. Так где же информация из комментариев?Я полагаю,что все начинают сомневаться,так ли это.Ajaxзагружен.

Чтобы проверить это сомнение, мы нажимаем на страницу в области комментариев и видим, что мы достигли второй страницы браузера.urlНет никаких изменений. На данный момент вы можете в основном знать, чтоajaxстраница загружена.

Мы сказали раньшеAjaxСпособ обработки запроса, здесь повторяться не будем. ОткрытымChromeинструменты разработчика, нажмитеNetworkпомеченXHRОбновите страницу, в это время будет несколько запросов. Мы щелкали один за другим, чтобы увидеть содержание их ответов, и обнаружили, чтоR_SO_4_186016?csrf_token=Запрос содержит информацию о комментарии. Посчитайте горячие комментарии на странице сравнения, они полностью совпадают. Давайте посмотрим на картинку ниже:

image

image

На изображении выше красная рамка обрамленаForm Dataданные, да, этоPostЗапрашивать информацию. Затем мы должны построить их в словарь с помощьюpostпросить. Мы смотрим на картинкуrefererurl, да, идентификатор за URL-адресом — это идентификатор песни. Удобно ли нам передавать id песни в предыдущей функции.

Нам нужно добавить каждый музыкальный запрос в заголовок предыдущего запроса.refererпараметр.

DEFAULT_REQUEST_HEADERS['Referer'] = self.base_url + '/playlist?id=' + str(music_id)

будетForm DataВсе в порядке с построением словаря, построениемAjaxпроситьurlэтоR_SO_4_музыка позадиid. тогда нет проблемscrapyкак пользоватьсяPostзапрос.

Ответ скрапFormRequestметод, нам нужно импортировать его, а затем использовать иRequestТаким же образом нам также нужно передать всю музыкальную информацию, извлекаемую этой функцией, в следующую функцию, извлекающую горячие обзоры, а затем передать все данные вместе вitem.

код показывает, как показано ниже:

# 获得音乐信息
def parse_music(self, response):
    music_id = response.meta['id']
    music = response.xpath('//div[@class="tit"]/em[@class="f-ff2"]/text()').extract_first()
    artist = response.xpath('//div[@class="cnt"]/p[1]/span/a/text()').extract_first()
    album = response.xpath('//div[@class="cnt"]/p[2]/a/text()').extract_first()
    data = {
  'csrf_token': '',
  'params': 'Ak2s0LoP1GRJYqE3XxJUZVYK9uPEXSTttmAS+8uVLnYRoUt/Xgqdrt/13nr6OYhi75QSTlQ9FcZaWElIwE+oz9qXAu87t2DHj6Auu+2yBJDr+arG+irBbjIvKJGfjgBac+kSm2ePwf4rfuHSKVgQu1cYMdqFVnB+ojBsWopHcexbvLylDIMPulPljAWK6MR8',
  'encSecKey': '8c85d1b6f53bfebaf5258d171f3526c06980cbcaf490d759eac82145ee27198297c152dd95e7ea0f08cfb7281588cdab305946e01b9d84f0b49700f9c2eb6eeced8624b16ce378bccd24341b1b5ad3d84ebd707dbbd18a4f01c2a007cd47de32f28ca395c9715afa134ed9ee321caa7f28ec82b94307d75144f6b5b134a9ce1a'
    }
    DEFAULT_REQUEST_HEADERS['Referer'] = self.base_url + '/playlist?id=' + str(music_id)
    music_comment = 'http://music.163.com/weapi/v1/resource/comments/R_SO_4_' + str(music_id)
    yield FormRequest(music_comment, meta={'id':music_id,'music':music,'artist':artist,'album':album}, \
            callback=self.parse_comment, formdata=data)

Извлеките информацию о горячих отзывах и передайте ее элементу

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

itemСловарь работает так же, как и словарь, мы можем сохранять их так же, как данные словаря. Но не глупо ли шаг за шагом писать словарь с таким количеством данных? Есть ли более простой способ. На этот раз встроенныйevalУдобный метод, я не буду объяснять его здесь, он очень прост в использовании, он будет динамически получать каждый ключ нашего словаря и сохранять его для нас. Давайте посмотрим на код:

# 获得所有音乐的热评数据
import json
def parse_comment(self, response):
    id = response.meta['id']
    music = response.meta['music']
    artist = response.meta['artist']
    album = response.meta['album']
    result = json.loads(response.text)
    comments = []
    if 'hotComments' in result.keys():
  for comment in result.get('hotComments'):
      hotcomment_author = comment['user']['nickname']
      hotcomment = comment['content']
      hotcomment_like = comment['likedCount']
      # 这里我们将评论的作者头像也保存,如果大家喜欢这个项目,我后面可以做个web端的展现
      hotcomment_avatar = comment['user']['avatarUrl']
      data = {
    'nickname': hotcomment_author,
    'content': hotcomment,
    'likedcount': hotcomment_like,
    'avatarurl': hotcomment_avatar
      }
      comments.append(data)
    item = MusicItem()
    # 由于eval方法不稳定,具体的可以自己搜索,我们过滤一下错误
    for field in item.fields:
  try:
      item[field] = eval(field)
  except:
      print('Field is not defined', field)
    yield item

Наконец, мы передаем данные вItem.

Обработка данных в конвейере

существуетPipelineНа самом деле, здесь мы не имеем никакого отношения к данным, здесь нам нужно сохранить данные в базе данных.

нам нужно создатьmongodbсвоего рода. затем вsettingsгенерал-лейтенантITEM_PIPELINESключ к тому, что мы создалиmongdbКласс, поскольку нам не нужно вносить изменения в данные, можно перезаписать их напрямую. Для облегчения управления и ясности общей структуры нам также необходимоsettingsНастройте информацию о нашей базе данных в формате . Конкретный код выглядит следующим образом:

ITEM_PIPELINES = {
   'music163.pipelines.MongoPipeline': 300,
}
# 添加数据库信息
MONGO_URI = 'localhost'
MONGO_DB = 'music163'

Далее нужно написать нашMongodbкласс. Во-первых, нам нужно передать в этот класс два параметра, то есть мы находимся передsettingsфайловая база данныхuriи имя базы данных, мы назначаем им назначение:

class MongoPipeline(object):
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
接下来我们定义一个from_crawler类方法,这个方法就相当于将这个类的两个参数通过crawler对象从settings中拿到这两个参数(数据库uri和名称)。
class MongoPipeline(object):
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DB')
        )
    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]
    def close_spider(self, spider):
        self.client.close()

здесь@classmethodдекораторpythonОдин из наиболее часто используемых методов, вы можете обратиться к соответствующей информации, чтобы понять его для конкретных операций.

Назадopen_spider()иclose_spider()Метод на самом деле является переопределенным методом класса, что означает, что мы запускаем сканер и вызываемopen_spider()метод, который вызывается при закрытии краулераclose_spider()метод. К ним добавляем операции запуска БД и закрытия БД.

Последний метод является наиболее важным, т.process_item()метод заключается вitemработать. Мы здесь в основном для выполнения операции вставки в базу данных.

Сначала нам нужноitems.pyдобавить файлtable_name = 'music'атрибут, эквивалентный имени таблицы базы данных. Это облегчает нам передачу этого свойства вprocess_item()метод, нам нужно вызвать базу данныхupdateметод:

def process_item(self, item, spider):
    self.db[item.table_name].update({'id': item.get('id')}, {'$set': dict(item)}, True)
    return item

Этот метод имеет три параметра, первый параметр передается в поле запроса к базе данных, мы используем музыкуidчтобы узнать.

Второй параметр нашitemданные, мы преобразуем их в словарную форму.

Третий параметр имеет решающее значение, мы передаемTrue. Это означает, что если мы запрашиваем одни и те же данные, мы выполняем операцию обновления, а если мы не находим те же данные, мы выполняем операцию вставки. Это эквивалентно тому, что мы сделали вставку в базу данных и одновременно выполнили операцию дедупликации.

Наконец

Итак, наш сканер завершен, давайте закончим код и запустим его.

адрес проекта

github

Спасибо за чтение

Искренняя признательность, рука оставляет аромат Пожертвовать俞坤 WeChat Pay

WeChat Pay

俞坤 Alipay

Alipay

  • Автор этой статьи:Ю Кун
  • Ссылка на эту статью: YushenWeb.com/2017/07/Пак Ючон…
  • Уведомление об авторских правах:Все статьи в этом блоге, если не указано иное, используютCC BY-NC-SA 3.0соглашение. Пожалуйста, укажите источник!