python爬取b站追番信息_Scrapy实战B站番剧信息爬取

Part 0 安装

采用Anaconda版本的Python可以直接使用conda install -c scrapinghub scrapy进行安装,采用pip install Scrapy有的环境需要其他的依赖,可能会报错。

Part 1 信息获取

打开番剧索引链接https://www.bilibili.com/anime/index/

F12打开浏览器控制台,inspect in,点到对应的番剧发现信息列表如图,虽然点右键可以复制Xpath,不过这样获得的Xpath经常在Scrapy里面无法获取。这里我们手工来填,就根据属性 class="bangumi-item"就行。(在使用Xpath helper输入Xpath查询之前是不带xh-highlight的,这里是因为插件高亮显示的原因)

获取结果如下:

由于这里是按追番人数分的,切换到按更新时间和评分分信息又会不一样。再细分一下,各个信息Xpath如下。是否会员观看就没管了…也差不多。

标题 //*[@class="bangumi-item"]//*[@class="bangumi-title"]

人数 //*[@class="bangumi-item"]//*[@class="shadow"]

集数 //*[@class="bangumi-item"]//*[@class="pub-info"]

命令行测试

这里是在浏览器里面获得的,再来测试一下Scrapy里面能否成功获取。

开cmd输入scrapy shell "https://www.bilibili.com/anime/index",命令行方式测试一下是否能正常获取。

需要注意有的时候因为反爬虫机制不能正确返回Response,查询一下帮助scrapy shell -h,加上-s USER_AGENT='Mozilla/5.0'就可以更改对应的设置,即

scrapy shell "https://www.bilibili.com/anime/index" -s USER_AGENT='Mozilla/5.0'

In [1]: response.xpath('//*[@class="bangumi-item"]//*[@class="bangumi-title"]').extract()

Out[1]: []

获取失败了?再输入view(response),在浏览器里面看一下返回的结果是怎样的。结果发现弹出一个:“没有找到这样的番剧”。是哪里出错了?输入response.text查看一下源代码,发现里面并没有出现具体信息,所以光用这个网址来获得信息是不行的。

API获取

F12里面Network抓包看一下,为了找出是在哪里出现了具体信息,我们需要在Response里面批量搜索,比如追番人数391.9。按照网上的方法,点右键先把抓下来的包存成har,然后搜索391.9

出现在4527行

它的request请求格式出现在与之最近的4124行

https://bangumi.bilibili.com/media/web_api/search/result?season_version=-1&area=-1&is_finish=-1&copyright=-1&season_status=-1&season_month=-1&pub_date=-1&style_id=-1&order=3&st=1&sort=0&page=1&season_type=1

网址扔到浏览器里面看一下,到了最后还是成了API的形式…

API格式分析

几个关键的如下:

sort 0降序排列 1升序排列

order 3 追番人数排列 0更新时间 4最高评分 2播放数量 5开播时间

page 控制返回的Index

pagesize 20为默认,和网页上的一致 不过最多也就25

剩下的属性和网页右侧的筛选栏一致,大概也能猜出来了。

番剧详细信息获取与调试

需要注意有的番剧有付费信息,有的没有;有的人数不够,没有评分信息。比如下面这个就没有badge badge_type等信息

{'cover': 'http://i0.hdslb.com/bfs/bangumi/152c536f8ecaad8f3d7d568d33da81c963a4a722.png',

'index_show': '全12话',

'is_finish': 1,

'link': 'https://www.bilibili.com/bangumi/play/ss23850',

'media_id': 78352,

'order': {'follow': '202.4万人追番',

'play': '4704.6万次播放',

'pub_date': 1522944000,

'pub_real_time': 1522944000,

'renewal_time': 1532966400,

'score': '9.6分',

'type': 'follow'},

'season_id': 23850,

'title': '超能力女儿'}

不过对详细信息来说就不再是API形式返回的了,用我们最开头失败了的Xpath方法来获取。方法还是差不多,我就直接列结果了。

Tags //*[@class="media-tag"]/text()

简介 //*[@name="description"]/attribute::content

有一点比较奇怪,在网页里面审查元素,Staff表 //*[@class="mic-evaluate"] 第一个元素为声优表 第二个为编剧等Staff表

然而,Scrapy命令行里面测试一下

In [5]: response.xpath('//*[@class="media-tag"]/text()').extract()

Out[5]: ['搞笑', '战斗', '日常', '声控', '漫改']

Tags 简介是没问题的,Staff就获取不到了。response.text查看一下,果然Scrapy和浏览器打开的不一样,发现CV表和Staff表在一大串json里面(执行text=response.xpath('//script')[4].extract()得到),太多了就不贴了,非常详细,还包括评论、每集的标题等等,用正则表达式提出来好了。

In [66]: actor_p=re.compile('actors":(.*?),')

In [67]: re.findall(actor_p,text)

Out[67]: ['"扎克:冈本信彦\\n蕾:千菅春香\\n丹尼:樱井孝宏\\n艾迪:藤原夏海\\n凯西:伊濑茉莉也"']

In [71]: ratings_count_p=re.compile('count":(.*?),')

In [72]: re.findall(ratings_count_p,text)

Out[72]: ['20853']

n [73]: staff_p=re.compile('staff":(.*?),')

In [74]: re.findall(staff_p,text)

Out[74]: ['"原作:真田まこと\\n监督:铃木健太郎\\n系列构成:藤冈美畅\\n角色设计&总作画监督:松元美季\\n美术监督:魏斯曼(スタジオちゅーりっぷ)\\n色彩设计:田边香奈\\n摄影监督:高桥昭裕\\n编集:近藤勇二(Real-T)\\n音响监督:岩浪美和\\n 音响效果:小山恭正\\n音乐:ノイジークローク\\n音乐制作:Lantis\\n动画制作:J.C.STAFF\\n制作:「杀戮天使」制作委员会"']

Part 2 爬虫编写

本次编写一个仅用于分析文本数据、不下载番剧封面图片的爬虫。

命令行下输入scrapy startproject bilibili,Pycharm新建Project,打开该目录。

items.py定义要爬的字段,我自己定义的列在文末。

spider文件夹下新建一个bilibili_spider.py,用来定义具体的行为。比较麻烦的是API每页包含20个子页面,API中还有这20个番剧的信息,并且需要根据API来判断是否把所有番剧爬完了。

这些爬取行为的问题可以参考如下文章:

Scrapy框架之带有分页的详情页面抓取

Scrapy研究探索(五)——自动多网页爬取(抓取某人博客所有文章)

Scrapy中的scrapy.Spider.parse()如何被调用?

如何获取http://a.com中的url,同时也获取http://a.com页面中的数据?

可以直接在parse方法中将request和item一起“返回”,并不需要再指定一个parse_item例如:

def parse(self, response):

#do something

yield scrapy.Request(url, callback=self.parse)

#item[key] = value

yield item

如果想使用-o out.csv输出,需要注意设定编码,在设置settings.py中添加一行

FEED_EXPORT_ENCODING = 'utf-8'

然后启动爬虫scrapy crawl bilibili -o out.csv,虽然用Excel打开仍然是乱码,但是记事本打开就是正常的了。这是因为Excel是ANSI编码,记事本另存为该编码就好。

最终效果如图,后续就可以对分数、CV、类型等等进行分析了。

items.py

# -*- coding: utf-8 -*-

# Define here the models for your scraped items

#

# See documentation in:

# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy

class BilibiliItem(scrapy.Item):

# define the fields for your item here like:

badge= scrapy.Field()

badge_type= scrapy.Field()

is_finish= scrapy.Field()

media_id= scrapy.Field()

index_show= scrapy.Field()

follow= scrapy.Field()

play= scrapy.Field()

pub_date= scrapy.Field()

pub_real_time= scrapy.Field()

renewal_time= scrapy.Field()

score= scrapy.Field()

season_id= scrapy.Field()

title = scrapy.Field()

tags= scrapy.Field()

brief= scrapy.Field()

cv= scrapy.Field()

staff= scrapy.Field()

count= scrapy.Field()

pass

bilibili_spider.py

import scrapy

import logging

from scrapy import Request

from bilibili.items import BilibiliItem

import re

import json

class MySpider(scrapy.Spider):

name = 'bilibili'

allowed_domains = ['bilibili.com']

url_head = 'https://bangumi.bilibili.com/media/web_api/search/result?season_version=-1&area=-1&is_finish=-1&copyright=-1&season_status=-1&season_month=-1&pub_date=-1&style_id=-1&order=3&st=1&sort=0&season_type=1'

start_urls = [url_head+"&page=1"]

# 先处理列表中的番剧信息

def parse(self, response):

self.log('Main page %s' % response.url,level=logging.INFO)

data=json.loads(response.text)

next_index=int(response.url[response.url.rfind("=")-len(response.url)+1:])+1

if(len(data['result']['data'])>0):

# 发出Request 处理下一个网址

next_url = self.url_head+"&page="+str(next_index)

yield Request(next_url, callback=self.parse)

medias=data['result']['data']

for m in medias:

media_id=m['media_id']

detail_url='https://www.bilibili.com/bangumi/media/md'+str(media_id)

yield Request(detail_url,callback=self.parse_detail,meta=m)

# 再处理每个番剧的详细信息

def parse_detail(self, response):

item = BilibiliItem()

item_brief_list=['badge','badge_type','is_finish','media_id','index_show','season_id','title']

item_order_list=['follow','play','pub_date','pub_real_time','renewal_time','score']

m=response.meta

for key in item_brief_list:

if (key in m):

item[key]=m[key]

else:

item[key]=""

for key in item_order_list:

if (key in m['order']):

item[key]=m['order'][key]

else:

item[key]=""

tags=response.xpath('//*[@class="media-tag"]/text()').extract()

tags_string=''

for t in tags:

tags_string=tags_string+" "+t

item['tags']=tags_string

item['brief'] = response.xpath('//*[@name="description"]/attribute::content').extract()

detail_text = response.xpath('//script')[4].extract()

actor_p = re.compile('actors":(.*?),')

ratings_count_p = re.compile('count":(.*?),')

staff_p = re.compile('staff":(.*?),')

item['cv'] = re.findall(actor_p,detail_text)[0]

item['staff'] = re.findall(staff_p,detail_text)[0]

count_list=re.findall(ratings_count_p,detail_text)

if(len(count_list)>0):

item['count'] = count_list[0]

else:

item['count']=0

# self.log(item)

return item

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值