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
    剩下的属性和网页右侧的筛选栏一致,大概也能猜出来了。


番剧详细信息获取与调试

网址详细信息在
https://www.bilibili.com/bangumi/media/md102392
这里的102392就是上一步返回的media_id


需要注意有的番剧有付费信息,有的没有;有的人数不够,没有评分信息。比如下面这个就没有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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • scrapy学习笔记(有示例版) 我的博客 scrapy学习笔记1.使用scrapy1.1创建工程1.2创建爬虫模...
    陈思煜阅读 12,670评论 4 46
  • 首先理解为什么要快速阅读?一本书所消耗的成本,由金钱成本、机会成本、时间成本所构成,所以快速有质量的阅读,降低阅...
    keely陈阅读 279评论 0 0
  • 因为要学习日语的关系,也因为日剧确实很好看的原因。最近看了《东爱》,这是一部1991年拍摄的电影。好老啊!但是真的...
    wokenshin阅读 1,545评论 0 0
  • “李嫣,你抓住我呀,”齐林伸手去抓,女孩的笑容逐渐消失在自己的眼前。“不,不要......”齐林惊醒了,他起身抓起...
    蓝色妖姬hui阅读 775评论 8 8
  • 打卡第17天 姓名:Matty 部门:业务部 组别:待定 【知~学习感悟] 读《道盛和夫自传》第二章 爱护地球 稻...
    黄科进阅读 73评论 0 1