В первых двух статьях мы изучили теоретические знания 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
На фотографиях в сочетании с нашим наблюдением за страницей указателя мы видим, что слева, например, классифицируются китайские певцы, европейские и американские певцы, а 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
Инструменты разработчика, щелкните правой кнопкой мыши метку, как показано ниже:
Непосредственно разбираем код стартовой страницы:
# 获得所有歌手的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=
Запрос содержит информацию о комментарии. Посчитайте горячие комментарии на странице сравнения, они полностью совпадают. Давайте посмотрим на картинку ниже:
На изображении выше красная рамка обрамленаForm Data
данные, да, этоPost
Запрашивать информацию. Затем мы должны построить их в словарь с помощьюpost
просить. Мы смотрим на картинкуreferer
url, да, идентификатор за 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
. Это означает, что если мы запрашиваем одни и те же данные, мы выполняем операцию обновления, а если мы не находим те же данные, мы выполняем операцию вставки. Это эквивалентно тому, что мы сделали вставку в базу данных и одновременно выполнили операцию дедупликации.
Наконец
Итак, наш сканер завершен, давайте закончим код и запустим его.
адрес проекта
Спасибо за чтение
Искренняя признательность, рука оставляет аромат ПожертвоватьWeChat Pay
Alipay
- Автор этой статьи:Ю Кун
- Ссылка на эту статью: YushenWeb.com/2017/07/Пак Ючон…
- Уведомление об авторских правах:Все статьи в этом блоге, если не указано иное, используютCC BY-NC-SA 3.0соглашение. Пожалуйста, укажите источник!